← back to lessons

Pattern: Fixed-maturity boundary abuse + fungible-credit risk-transfer (zero-coupon lending economics breaks)

Banked: Cycle 192 (Morpho Midnight saturated-target mining) Sources: - Blackthorn M-2 ("take() does not control near-maturity fills, allowing unconsented seller debt and post-maturity collateral seizure"), L-4 ("LLTV_8 = WAD eliminates post-maturity liquidation incentive"), L-15 ("Incumbent lenders are involuntarily rolled from realized cash back into fresh borrower risk"). - Spearbit 5.2.2 (no input validation on offer.obligation.maturity; expired/dead obligations instantiable). - TrustSec SR-3 ("Protocol allows for LLTV=WAD with zero collateral buffer"), TRST-R-1 (post-maturity bad debt uses maxLif), TRST-L-1/L-7 (offer can't bind a taker; bundles lack deadlines). Severity: Medium (Blackthorn M-2) + Low/Systemic reinforcing. Status: NOVEL economics-of-fixed-maturity bug-class. Not previously banked.

This file captures TWO related but distinct novel patterns that are specific to zero-coupon, fixed-maturity, PT/YT-style credit/debt instruments.

Pattern 1: The maturity boundary (t == maturity) is a fill-vs-liquidatable seam

(Blackthorn M-2) take() validates block.timestamp <= offer.expiry but enforces NO minimum distance from offer.obligation.maturity. Meanwhile isLiquidatable() only treats a position as overdue once block.timestamp > obligation.maturity (STRICT >). So at EXACTLY maturity (expiry == maturity, the natural "valid until maturity" choice), a taker can: 1. Fill the maker's near-maturity SELL order at the exact maturity timestamp - sellerDebtIncrease = units - sellerCreditDecrease > 0 creates NEW seller debt; 2. The end-of-function solvency check passes because overdue liquidation needs strict > maturity; 3. One block later, time advances past maturity, the freshly-created seller debt is overdue and liquidatable for the post-maturity LIF bonus.

Result: the maker (seller) gets unconsented debt at the boundary and is immediately liquidated for a ~6.1% bonus (at LLTV 0.77, cursor 0.25). The continuous-fee variant: at maturity _updatePosition accrues the remaining fee, reducing credit below units, turning a credit EXIT into debt that is then seized. The boundary expiry == maturity is intuitive for users ("valid until maturity") and is precisely the dangerous value.

Pattern 2: LLTV == WAD destroys the liquidation incentive at/after maturity

(Blackthorn L-4, TrustSec SR-3) When a market allows the top LLTV tier LLTV_8 = WAD (1e18, zero collateral buffer), the max liquidation incentive factor collapses: maxLif = WAD.mulDivDown(WAD, WAD - cursor.mulDivDown(WAD - lltv, WAD)) == WAD for ALL cursor values, because WAD - lltv == 0. Post-maturity LIF ramps from WAD toward WAD - i.e. ZERO bonus. Liquidators receive only par collateral for par debt; after gas the EV is negative, so profit-seeking keepers skip these positions. Lenders become the only incentivized actor and must liquidate themselves. Example: $10M ETH/stETH at 2% depeg = $200K bad debt socialized instantly, $9.8M frozen with no profitable close path, liquidator EV -0.006 ETH. This is the inversion of the usual "high LLTV = more leverage" intuition: at LLTV == WAD the protocol's own post-maturity enforcement mechanism stops working.

Pattern 3: Fungible credit transfers risk to incumbent lenders (the deepest one)

(Blackthorn L-15) In classic bond logic, when a borrower sells a 100-face-value zero-coupon unit for 60, the 40 discount is the lender's risk premium for locking funds and bearing default risk - it belongs EXCLUSIVELY to whoever supplied the 60 now at risk. Midnight makes credits FUNGIBLE and treats all credit holders as first-come-first-served claimants on the obligation's withdrawable cash pool at FACE VALUE. Consequence: - T1: a borrower repays -> withdrawable cash = 100, Alice (incumbent lender) credit = 100. - T2: Bob buys 100 new units at a 60 discount (new borrower debt = 100), Bob net = -60. - T3: Bob immediately REDEEMS his 100 credit at par against the pooled withdrawable cash -> withdrawable = 0, Bob net = +40. Bob captured the 40 discount WITHOUT bearing any maturity/default risk - because he redeemed against cash that incumbent lenders' repayments funded. Alice's realized cash claim has been INVOLUNTARILY ROLLED back into exposure to the NEW borrower's debt, and she can no longer access her repaid funds (withdrawable drained). The discount no longer compensates the risk-bearer; risk silently transferred from the buyer to the incumbent lenders. Bob can even do this with no own funds via the onBuy callback (100 credited before the pull). Root: repayment cash entitlements are not separated from newly-minted credit; fungibility + face-value redemption + shared cash pool = the spread is capturable risk-free by a taker, the risk dumped on incumbents.

Why these are non-obvious

How to detect on future targets

  1. For any fixed-maturity instrument, separately test fills AT t == maturity, at t == maturity - 1, and at t == maturity + 1. Look for a state where a fill creates debt that is solvent at fill-time but overdue/liquidatable the next block. Check strict-vs-non-strict inequalities on the maturity comparison in fill vs liquidation paths.
  2. Compute the liquidation incentive factor (LIF/LIF-max) at every allowed LLTV tier. Find the tier where the bonus collapses to zero (typically LLTV == WAD / 100%). Confirm liquidators have negative EV there -> protocol enforcement broken.
  3. Model the cashflow of a fungible credit/zero-coupon unit: when a borrower repays into a SHARED pool and credits are FUNGIBLE + redeemable at FACE VALUE, check whether a new buyer can mint at a discount and immediately redeem at par against the pool, capturing the spread risk-free and rolling incumbent lenders back into fresh exposure. The tell is "repayment cash" and "new credit" sharing one withdrawable bucket with no per-position cash-claim tracking.
  4. Check maturity input validation: can an obligation be created already-expired / dead-on-arrival, or with an absurd maturity (Spearbit 5.2.2)? Dead markets that still accept collateral are footguns.
  5. Check that periphery/bundle close-position flows can actually bound RESULTING debt exposure (TrustSec L-6) and have deadlines (L-7) - near-maturity timing makes "I meant to exit flat" silently cross into debt.

Transfers to which protocol classes

Doctrine

Fixed-maturity zero-coupon instruments require modeling BOND ECONOMICS, not just solvency math: (a) the maturity timestamp is a state-transition seam - test fills and liquidations on both sides and at the exact boundary, watching strict/non-strict inequalities; (b) the liquidation incentive must stay positive across ALL allowed LLTV tiers or post-maturity enforcement silently dies; (c) fungible face-value credit + shared repayment-cash pool lets a risk-free taker capture the discount and dump default risk on incumbent lenders - the risk premium must stay attached to the party actually bearing maturity risk. These are the "the code is correct but the finance is wrong" findings that single-contract reviewers miss.

Generated 2026-07-02 13:15:05 UTC | auto-sync /15min