Add CErc20RetireDelegate to retire frozen (!borrowRate) markets#1106
Open
rhlsthrm wants to merge 1 commit into
Open
Add CErc20RetireDelegate to retire frozen (!borrowRate) markets#1106rhlsthrm wants to merge 1 commit into
rhlsthrm wants to merge 1 commit into
Conversation
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.
❌ Deploy Preview for ionicv2 failed. Why did it fail? →
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds tooling to wind down a cToken market that is permanently frozen with a
!borrowRaterevert.When a market's IRM returns a per-block borrow rate above
borrowRateMaxMantissa(5e12) whilecash > totalFees,CTokenFirstExtension._accrueInterestHypotheticalreverts"!borrowRate"(CTokenFirstExtension.sol#L497).
Because every accruing entrypoint (mint/redeem/borrow/repay,
_setInterestRateModel,_withdrawAdminFees/_withdrawIonicFees) callsaccrueInterest()first, the market is fullyfrozen. The only non-accruing admin lever is
CErc20Delegator._setImplementationSafe, whose_becomeImplementationhook 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 zeroestotalReserves/totalAdminFees/totalIonicFees/totalBorrows, so utilization → 0, the ratedrops under the cap, and
accrueIntereststops 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) andguarded on
totalSupply == 0(refuses to run on markets with suppliers).RetireFrozenMarketTest.t.sol— Base fork test against the liveionmsUSDmarket; asserts thepre-fix
!borrowRaterevert, the sweep/zeroing, post-fixaccrueInterestsuccess, and unlisting.How the freeze happens
utilizationRate = borrows / (cash + borrows − reserves). WhentotalReserves + feesgrows to≈
cash + borrows, the denominator collapses → utilization explodes → rate ≫ cap → frozen.Verification
A chain-wide scan (via
accrueInterest()eth_call simulation) found exactly one currently-frozenmarket:
ionmsUSDon Base (0x5BE1Cb6CB3C9bfd16Db43ed4f6c081FA9783dd1C),totalSupply == 0.Scope / caveat
ionmsUSDis a dust market:32 msUSD ($32) cash, a ~0.07 msUSD borrow, no suppliers. For thatspecific market, a single
comptroller._unsupportMarket(...)(works while frozen, needs onlytotalSupply == 0) is sufficient and cheaper than deploying the delegate. This PR is thegeneral-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).