Skip to content

Add CErc20RetireDelegate to retire frozen (!borrowRate) markets#1106

Open
rhlsthrm wants to merge 1 commit into
developmentfrom
claude/retire-frozen-market
Open

Add CErc20RetireDelegate to retire frozen (!borrowRate) markets#1106
rhlsthrm wants to merge 1 commit into
developmentfrom
claude/retire-frozen-market

Conversation

@rhlsthrm

Copy link
Copy Markdown
Contributor

Summary

Adds tooling to wind down a cToken market that is permanently frozen with a !borrowRate revert.

When a market's IRM returns a per-block borrow rate above borrowRateMaxMantissa (5e12) while
cash > totalFees, CTokenFirstExtension._accrueInterestHypothetical reverts "!borrowRate"
(CTokenFirstExtension.sol#L497).
Because every accruing entrypoint (mint/redeem/borrow/repay, _setInterestRateModel,
_withdrawAdminFees/_withdrawIonicFees) calls accrueInterest() first, the market is fully
frozen. The only non-accruing admin lever is CErc20Delegator._setImplementationSafe, whose
_becomeImplementation hook runs during the upgrade.

Changes

  • CErc20RetireDelegate.sol — one-shot remediation delegate. _becomeImplementation(abi.encode(treasury))
    sweeps the underlying to a treasury (via inherited doTransferOut) and zeroes
    totalReserves/totalAdminFees/totalIonicFees/totalBorrows, so utilization → 0, the rate
    drops under the cap, and accrueInterest stops reverting. Must never be set as a latest delegate.
  • tasks/market/retire-frozen.ts (market:retire-frozen) — deploys + registers the delegate,
    _setImplementationSafe, then _unsupportMarket. Multisig-aware (prepareAndLogTransaction) and
    guarded on totalSupply == 0 (refuses to run on markets with suppliers).
  • RetireFrozenMarketTest.t.sol — Base fork test against the live ionmsUSD market; asserts the
    pre-fix !borrowRate revert, the sweep/zeroing, post-fix accrueInterest success, and unlisting.

How the freeze happens

utilizationRate = borrows / (cash + borrows − reserves). When totalReserves + fees grows to
cash + borrows, the denominator collapses → utilization explodes → rate ≫ cap → frozen.

Verification

TEST_RUN_CHAINID=8453 BASE_MAINNET_RPC_URL=... forge test --mc RetireFrozenMarketTest -vvv
# [PASS] testRetireFrozenIonmsUSD

A chain-wide scan (via accrueInterest() eth_call simulation) found exactly one currently-frozen
market: ionmsUSD on Base (0x5BE1Cb6CB3C9bfd16Db43ed4f6c081FA9783dd1C), totalSupply == 0.

Scope / caveat

ionmsUSD is a dust market: 32 msUSD ($32) cash, a ~0.07 msUSD borrow, no suppliers. For that
specific market, a single comptroller._unsupportMarket(...) (works while frozen, needs only
totalSupply == 0) is sufficient and cheaper than deploying the delegate. This PR is the
general-purpose instrument
for the same failure mode on a market with real value. Run the fork test
against current state immediately before any mainnet execution.

Follow-up (separate PR)

Protocol-level guard so markets can't freeze this way (non-accruing _reduceReserves, clamp-instead-of-revert,
or a reserve-overrun guard).

A cToken market whose IRM rate exceeds borrowRateMaxMantissa while
cash > totalFees reverts every accruing call with "!borrowRate",
freezing the market — even _setInterestRateModel and _withdraw*Fees
accrue first. The only non-accruing admin lever is _setImplementationSafe.

- CErc20RetireDelegate: one-shot delegate whose _becomeImplementation
  sweeps the underlying to a treasury and zeroes reserves/fees/borrows,
  unfreezing the market so it can be unlisted via _unsupportMarket.
- market:retire-frozen task to deploy/register/upgrade/unlist
  (multisig-aware), guarded on totalSupply == 0.
- RetireFrozenMarketTest fork test against the live Base ionmsUSD market.
@netlify

netlify Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploy Preview for ionicv2 failed. Why did it fail? →

Name Link
🔨 Latest commit be3d5dc
🔍 Latest deploy log https://app.netlify.com/projects/ionicv2/deploys/6a32ab6c52439000071596f2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant