Banked: Cycle 192 (Morpho Midnight saturated-target mining) Sources (cross-corroborated across 3 audits): - Spearbit 5.2.5 ("When lossIndex is at its max withdrawal would not be possible but other endpoints can be called"), 5.4.6 ("Loss index inflation"). - Blackthorn M-1 ("In lossIndex == type(uint128).max state a fresh lender principal can be slashed to zero on entry"), L-10 ("Side-effect of optimistic bad debt socialization"). - TrustSec M-2 PoC mechanism, TRST-R-11 ("Add a loss-factor safety margin to take()"). Severity: Medium (Blackthorn M-1, acknowledged-wontfix) plus reinforcing Low/Info across the others. Status: NOVEL bug-class for fixed-maturity zero-coupon lending. Not previously banked.
Midnight tracks per-obligation bad debt via a monotonic lossIndex (a.k.a. lossFactor). When a liquidation realizes bad debt, the index moves; each lender position stores the index value at entry (lastLossIndex/lastLossFactor), and the difference between the obligation's current index and the position's stored index determines how much of that position's credit is slashed. This is the same shape as Aave's variableBorrowIndex, a vault's lossPerShare, or any "socialize loss across all current holders" accumulator. Three distinct failure modes emerge, all specific to the design choice of an index-based, optimistically-socialized loss.
(Spearbit 5.2.5) When bad debt == total units, lossIndex saturates to type(uint128).max and totalUnits -> 0. The formula then perceives ALL credit (old or new) as 0, so withdraw reverts for any nonzero amount. But take(), repay, supplyCollateral, withdrawCollateral, liquidate remain callable. The obligation is economically dead but not gated. Repaid loan tokens (repaidUnits) sent into this state become permanently locked - usable by no one. Fix: block take() when index is saturated; emit a terminal event.
(Blackthorn M-1, the high-signal finding) Because the saturating liquidation does NOT emit any ObligationTerminated event and the SAME obligation id remains usable, a late lender who signs a buy offer after saturation has their fresh credit zeroed by the next _updatePosition - the saturated obligation-level loss index is applied to a position that did not exist when the loss occurred. Worse: collateral price can RECOVER after saturation, so the market looks usable again, luring victims. Full lender principal lost per victim entry. Root: index applied at position-update time without checking that the position post-dates the loss event, and no terminal-state gate on entry.
(Spearbit 5.4.6) Each bad-debt event increments the index. When totalUnits is small and min viable loan/collateral are both low and lltv ~ WAD (no buffer), an attacker can repeatedly open dust positions, self-liquidate them for bad debt, and inflate the index toward saturation at low cost (report estimates ~843 bad-debt liquidations at 10% of units to max it out). Honest lenders' position creation can be SANDWICHED between two inflation phases - they enter after most inflation has occurred but before final saturation, so they eat the slash. Combine with sub-pattern B for a targeted griefing/theft primitive. TrustSec TRST-R-11 reinforces: take() only rejects the EXACT terminal value, not the range where accounting is already "extremely imprecise."
take/deposit/supply). If entry is open while the index is saturated, fresh capital is at risk.lossPerShare / bad-debt index).For any "socialize loss via a monotonic index" design, the two load-bearing invariants are (1) a position must never absorb a loss that predates its own creation, and (2) the saturated/terminal state must be gated AND observable (terminal event). Both were missing or partial in Midnight. Cost-model the index-inflation attack explicitly under low-liquidity, near-WAD-LLTV configs - that is where it goes from theoretical to cheap.