Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Register
  • Sign in
  • A Ahad Mobile
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • holatech
  • Ahad Mobile
  • Merge requests
  • !72

fix(be): stop a repay forgiveness sinking an undercapitalised lending pool

  • Review changes

  • Download
  • Patches
  • Plain diff
Merged Khanh Pham requested to merge fix/lending-usd-forgive-equity into develop Sep 18, 2026
  • Overview 0
  • Commits 2
  • Pipelines 1
  • Changes 25

Staging, 2026-09-17: the USD market went INSOLVENT and every later supply was refused with MARKET_INSOLVENT, which the app renders as "This market is frozen."

Cause: USD was opened with no house capital. A customer supplied 200, borrowed 105 and repaid 105 on max. The 0.001105947158663302 of accrued interest left over fell inside max_repay_residual_forgive and was written off by writeOffDust, which takes the residual off total_borrows with no money arriving to replace it. A forgiveness is spent protocol equity and nothing else, and the pool's entire buffer was 0.000165892074222496 -- its reserve cut. protocol_equity_free closed at -0.000940055084440806 and closeMarketIfInsolvent shut the market in the same transaction. USDC survived three write-offs over the same period only because an operator had injected 1 USDC of house capital into it.

Three changes, and the second is the one that actually closes the hole:

  • The USD thresholds. max_repay_residual_forgive goes to zero -- a residual of a cent or more is money the customer can pay and is not written off on their behalf -- and debt_dust goes to one transferable unit. Both are needed: zeroing the cap alone strands customers, because requestRepay floors a max to the market's decimals and refuses the resulting 0.00 with REPAY_EXCEEDS_DEBT, and its own comment names the debt-dust path as the way out.

  • An activation gate. A market must hold max(forgive cap, debt dust) of free equity before it may enter ACTIVE. No exemption for an empty book: CAPITAL_INJECTION has an empty row in the status matrix, so the house funds a FROZEN market and the real order is preflight, inject, activate. This is what would have refused USD on 2026-09-16.

  • A per-forgiveness check. BorrowService.equityAbsorbs re-asks the same question where the money actually leaves, so a pool that opened one forgiveness deep cannot pay for a second. It covers MAX_REPAY_RESIDUAL only; DUST must never be refused, or the sub-unit residual it exists to clear is stranded instead.

Settlement now reads max(quote snapshot, live settings) for both thresholds. The snapshot is a floor under what the customer was promised, not a ceiling over what they may receive: a threshold moved against them is held at the quote, one moved in their favour applies at once. Reading either alone was wrong in one direction -- live settings broke the promise requestRepay documents, and the snapshot alone made a repay quoted before this migration miss the raised dust floor and strand.

Known limit, deliberate: a pool that has already spent its buffer can still be pushed negative by an unrefusable DUST write-off. INSOLVENT is the correct verdict in that state rather than a defect. Holding the buffer across every equity-decreasing path needs a reserved-equity concept in EquityService and is left to its own change.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: fix/lending-usd-forgive-equity