Bonus — Stablecoin Economic Modeling
“Stablecoins are the highest-risk primitive in DeFi because the peg is a contract — not a Solidity contract, a social-economic contract — between code, capital, and incentives. The code can be flawless and the system can still die. Auditing a stablecoin means auditing the contract behind the contract: under which sequence of market states, behavioral responses, and oracle conditions does the peg cease to be true? If you cannot answer that question quantitatively, you cannot sign off on the audit.”
Tags: web3-security stablecoin defi cdp algorithmic peg depeg liquidation oracle economic-attack protocol-economic Learner: Past Tuan-08-DeFi-Security-AMM-Lending-Vault §5 (intro) → going deep on stablecoin design space Time: 5–7 days (4–6h/day) — denser than a normal bonus chapter; this is genuinely Week-08.5 Related: Tuan-08-DeFi-Security-AMM-Lending-Vault · Tuan-09-Oracle-MEV-Economic-Attack · Tuan-Bonus-Liquid-Staking-Restaking · Case-bZx-Price-Manipulation-2020 · Case-Euler-Finance-2023
1. Context & Why
1.1 Why a separate bonus chapter
Week 08 (Tuan-08-DeFi-Security-AMM-Lending-Vault) introduces stablecoins as one of four protocol families. That treatment is sufficient if you are auditing a consumer of a stablecoin (a lending market that lists DAI, a vault holding USDC, an AMM pool with USDT). It is not sufficient if you are auditing a stablecoin protocol itself, or a protocol whose risk is dominated by stablecoin exposure (most of DeFi, in 2026).
Three reasons stablecoin work deserves its own chapter:
- The bug class is economic, not lexical. Reentrancy you can find by reading code. A peg failure you can only find by writing a model and running it against shocks. The auditor who has only Slither, Echidna, and a checklist will never find the next UST. The one who has those plus a quantitative stress-test harness will.
- The blast radius is system-wide. A vulnerable lending protocol loses its own users. A vulnerable stablecoin takes down every protocol that holds it, every borrower who priced collateral in it, every CEX deposit denominated in it. The 2022 UST collapse vaporised ~$40B of value across LUNA, Anchor depositors, Curve LPs, and downstream lenders (Briola et al. 2022). The March 2023 USDC depeg pulled DAI, FRAX, and USDP off-peg within hours because the contagion paths were structural. (CoinDesk on Circle’s SVB exposure)
- The design space is alive. UST is gone but every cycle produces new algorithmic-adjacent designs (Ethena’s USDe + delta-neutral, Aave’s GHO, USDS / Sky, crvUSD’s LLAMMA, Liquity V2’s BOLD, M^0, f(x) Protocol, and a long tail of LRT-backed stables). Auditors will be asked to evaluate the next design — not just to grade Maker against Maker’s whitepaper.
1.2 The auditor’s framing
Strip away the marketing. Every stablecoin is the same machine:
peg_holds ⇔ ∀ market_states M:
redeem_path(1 unit, M) → 1 USD of value, with bounded latency,
AND arbitrage_loop_closes(M)
AND collateral_solvent(M)
A stablecoin is “stable” iff, for every market state the protocol can plausibly encounter, (a) there exists a redemption or arbitrage path that delivers ~$1 of value per unit and (b) that path closes economically (someone makes money taking the trade) and (c) the system as a whole remains solvent.
The job of the stablecoin auditor is to enumerate the market states M and find one where any of those three properties fails. The market states include:
- Sudden collateral price drops (10%, 30%, 50%, 80%).
- Sudden DEX liquidity reductions (50%, 90%).
- Oracle outage (5 min, 1 hour, indefinite).
- Frozen redemption path (off-chain custodian, PSM cap hit, gas spike).
- Coordinated holder exit (top-10 holders sell within an hour).
- Correlated collateral failure (multiple ilks correlated; LST de-pegging vs ETH).
- Governance action lag (parameter response slower than market).
- Reflexive feedback through dependent protocols (lending → liquidation → AMM dump → oracle update → more liquidations).
Each of these is a scenario to model, not just a sentence to write. §5 of this lesson is the modeling lab.
1.3 Learning goals
By the end of this lesson, you can:
- State the four-archetype taxonomy of stablecoins and, given an unfamiliar protocol, classify it with reasons.
- Reproduce the UST collapse mechanism mathematically and simulate it in code; explain exactly why the design failed.
- Audit a CDP stablecoin (Maker / LUSD / crvUSD class) for: oracle dependency, liquidation profitability, debt ceiling enforcement, redemption mechanics, governance latency.
- Audit a hybrid stablecoin (FRAX class) for: AMO authority, collateral marking, peg-defense liquidity.
- Audit a yield-bearing stablecoin (sDAI / sUSDe / USDS-savings class) for: rate-source integrity, share/asset rounding, redemption queue model.
- Write a Foundry simulation that stress-tests a CDP under a collateral price drop and identifies the insolvency threshold.
- Apply the §7 stablecoin audit checklist to a real protocol with named TVL on DeFiLlama.
1.4 Primary references
| Source | URL | Status |
|---|---|---|
| MakerDAO Technical Docs | https://docs.makerdao.com/ | Current; verify ilk-list and post-Sky rename [verify] |
| MakerDAO Liquidation 2.0 (Dog + Clipper) | https://docs.makerdao.com/smart-contract-modules/dog-and-clipper-detailed-documentation | Current |
| Maker Black Thursday 2020 post-mortem | https://blog.makerdao.com/recent-market-activity-and-next-steps/ · community recap | Historical; auction redesign was the response |
| Liquity V1 Whitepaper | https://docs.liquity.org/documentation/liquity-v1-whitepaper | Current; ETH-only, 110% MCR |
| Liquity V2 Docs | https://docs.liquity.org/v2-faq/general · https://docs.liquity.org/v2-overview/ | Current; BOLD stablecoin, multi-LST collateral, user-set interest rates [verify final mainnet params] |
| FRAX Docs — AMO Overview | https://docs.frax.finance/amo/overview | Current; CR transitioned toward 100% via FIP-188 (2023); verify present CR [verify] (CoinDesk on FIP-188) |
| crvUSD / LLAMMA Resources | https://resources.curve.finance/crvusd/loan-concepts/ · https://resources.curve.finance/crvusd/advanced-liquidation/ | Current; LLAMMA = soft liquidation via AMM bands |
| Briola, Vidal-Tomás, Wang, Aste — Anatomy of a Stablecoin’s Failure: Terra–Luna | https://arxiv.org/pdf/2207.13914 | Academic post-mortem on UST collapse |
| Federal Reserve — Lessons from the SVB Failure and Its Impact on Stablecoins | https://www.federalreserve.gov/econres/notes/feds-notes/in-the-shadow-of-bank-run-lessons-from-the-silicon-valley-bank-failure-and-its-impact-on-stablecoins-20251217.html | Current; official empirical post-mortem on USDC depeg |
| Chainalysis — Crypto Market Reaction to SVB and USDC Depeg | https://www.chainalysis.com/blog/crypto-market-usdc-silicon-valley-bank/ | Historical; on-chain flow data for March 2023 depeg |
| Paradigm — Stablecoin Design (Robert Leshner archive, Sam Kazemian interviews, Hayden Adams notes) | https://www.paradigm.xyz/writing (search “stablecoin”, “peg”) | Curated research |
| a16z crypto — Stablecoin Research | https://a16zcrypto.com/research/ (filter stablecoin) | Current |
| DeFiLlama — Stablecoins Dashboard | https://defillama.com/stablecoins | Live data; required for Lab 3 |
| Ethena Labs — USDe Whitepaper | https://ethena-labs.gitbook.io/ethena-labs | Current; delta-neutral synthetic dollar (perpetuals-funded) |
2. The Stablecoin Taxonomy
The four-archetype taxonomy is the first tool. Before you audit anything you classify it, because the audit checklist depends on the class. Misclassifying a hybrid as a CDP means you under-audit the algorithmic side; misclassifying a yield-bearing wrapper as the underlying means you miss the rate-source dependency.
2.1 The four archetypes (plus a fifth wrapper layer)
flowchart TB subgraph A[Fiat-Backed Centralized] USDC[USDC] USDT[USDT] PYUSD[PYUSD] USDP[Pax USD] end subgraph B[Crypto-CDP / Over-Collateralized] DAI[DAI / Sky USDS] LUSD[LUSD / BOLD] crvUSD[crvUSD] GHO[GHO] sUSD[sUSD] end subgraph C[Algorithmic-Hybrid Fractional] FRAX[FRAX] RAI[RAI] FEI[FEI - retired] end subgraph D[Pure Algorithmic] UST[UST - collapsed 2022] ESD[Empty Set Dollar - retired] Basis[Basis Cash - retired] end subgraph E[Synthetic / Delta-Neutral] USDe[USDe / sUSDe] USDx[USDx and family] end subgraph F[Yield-Bearing Wrappers] sDAI[sDAI] sUSDS[sUSDS] sUSDe[sUSDe] end A --> F B --> F C --> F E --> F style D fill:#ffcccc style E fill:#fff2cc style F fill:#cfe2f3
The auditor’s classification table:
| Archetype | Backing | Peg mechanism | Trust assumption | Failure mode | Examples |
|---|---|---|---|---|---|
| Fiat-backed centralized | Off-chain USD / Treasuries / cash equivalents | Issuer redemption at 1:1; primary market | Trust the issuer + the issuer’s bank + regulator | Custodial freeze, bank collapse, regulatory action, reserve mismatch | USDC, USDT, PYUSD, USDP |
| Crypto-CDP | On-chain collateral, over-collateralized (110–200%+) | Liquidations + arbitrage + redemption modules | Trust the oracle + the liquidator market + governance | Cascade liquidation, oracle manipulation, redemption-induced unwind, PSM concentration | DAI, USDS, LUSD, BOLD, crvUSD, GHO, sUSD |
| Algorithmic-hybrid (fractional) | Partial collateral + AMO yield + governance token | Mint/burn arbitrage + AMO operations + PSM | Trust collateral + algorithmic side under stress + AMO solvency | Reflexive collapse on AMO side, depeg via PSM exhaustion, governance-token death spiral | FRAX (pre-2023), RAI (with PID controller) |
| Pure algorithmic | No exogenous backing | Mint/burn against a sister token (LUNA) | Trust the demand side of the system | Death spiral as sister-token supply explodes | UST/Terra (collapsed), ESD, Basis Cash |
| Synthetic / delta-neutral | Crypto long + matching short on perps; net delta ≈ 0 | Funding rate captures yield, hedges price | Trust perp DEX solvency + funding sign + CEX custody if hedge is on CEX | Negative funding for prolonged period, perp DEX insurance failure, custodian compromise | USDe (Ethena) |
| Yield-bearing wrapper | Wraps any of the above with a yield-accruing rate (DSR, sSR, sUSDe yield) | ERC-4626 share-price math; underlying stable’s peg | Underlying peg + rate source integrity + wrapper math | Underlying peg failure ripples through; share-price manipulation in early life; rate source bug | sDAI, sUSDS, sUSDe |
Note on the “wrapper” row: a yield-bearing wrapper is not itself a stablecoin design — it’s an ERC-4626 vault over a stablecoin. The audit work is the wrapper’s accounting and rate-source, plus inheritance of the underlying’s stability risk. We treat wrappers in §6.
2.2 Why each class fails differently — the one-paragraph summary
- Fiat-backed centralized fails off-chain. The on-chain contract may be perfect; the bank holding the reserves can still collapse. The auditor’s deliverable here is mostly: trust assumptions, redemption flow, blacklist semantics, upgrade authority, mint/burn key custody. Reserves are a counsel-and-attestation question, not a Solidity question.
- Crypto-CDP fails under correlated collateral stress. Single-collateral CDPs (LUSD V1) are the simplest to model: one asset, one liquidation curve, one stability mechanism. Multi-collateral CDPs (DAI, Maker / Sky) add per-ilk parameter risk and cross-ilk contagion (if one ilk produces bad debt, the surplus buffer is shared). Liquidation failure (Black Thursday 2020) is the canonical stress test.
- Algorithmic-hybrid fails when reflexive feedback dominates. The system is stable in normal regimes because arbitrage closes profitably; it becomes unstable when the algorithmic side is called on at a scale that overwhelms collateral. FRAX surviving the March 2023 USDC depeg required Frax governance to pivot to 100% collateral, exactly because the algorithmic premise broke under stress.
- Pure algorithmic always fails eventually. We will model exactly why in §4.4. There is no published example of a pure algo stable that has survived a coordinated exit. UST, ESD, Basis Cash, ampleforth-as-pseudo-stable all collapsed or de-listed.
- Synthetic / delta-neutral fails when the hedge breaks. The system holds a long position (e.g., ETH or stETH) and an equal short position (perp short). If the short can’t be maintained (perp DEX insolvent, funding flips deeply negative for an extended period, custodian freezes the hedge collateral), the synthetic dollar is naked-long the volatile asset.
- Yield-bearing wrappers fail in the underlying, and they propagate that failure amplified because they are the form most often used as collateral in lending protocols. sDAI is more dangerous to a lending market than DAI because liquidations of sDAI must redeem to DAI first, adding a hop with rate risk.
2.3 The audit-class decision tree
flowchart TD start[Stablecoin under audit] --> q1{Has off-chain backing?} q1 -- "Yes, primarily fiat or Treasuries" --> fiat[Fiat-backed. Audit: redemption flow, blacklist, upgrade auth, attestation cadence] q1 -- "Partly, but mostly on-chain" --> q2{Pure algorithmic side?} q1 -- "No off-chain assets" --> q3{Backed by crypto > 100%?} q2 -- "Yes, partial collateral + algo arbitrage" --> hybrid[Hybrid. Audit: AMO authority, PSM concentration, CR mechanism] q2 -- "No, ~100% collateralized" --> q3 q3 -- "Yes, over-collateralized" --> q4{Single or multi collateral?} q3 -- "No, mint-burn against sister token" --> algo[Pure algorithmic. Default severity: critical. Almost certainly will depeg under stress] q3 -- "Hedged with derivatives short" --> synth[Synthetic. Audit: funding-rate path, hedge custody, insurance fund] q4 -- "Single" --> cdp_single[Single-collateral CDP — LUSD shape. Audit: stability pool, redemption, recovery mode] q4 -- "Multi" --> cdp_multi[Multi-collateral CDP — Maker shape. Audit: per-ilk params, cross-contagion, PSM, ESM] cdp_single --> wrap{Has yield wrapper?} cdp_multi --> wrap hybrid --> wrap fiat --> wrap synth --> wrap wrap -- "Yes" --> wrap_audit[Plus: 4626 share math, rate source, queue model, first-depositor risk] wrap -- "No" --> done[Audit scope is the stablecoin itself]
Run this decision tree on the first day of every stablecoin audit. The output determines your checklist.
3. Peg Mechanics — How a Stablecoin Actually Stays at $1
This is the section most auditors skip. They take “the peg holds” as axiomatic. The peg is not axiomatic; the peg is the output of a specific set of forces. You can only find peg-failure bugs if you can name those forces and reason about when they invert.
3.1 The four peg-defense mechanisms
Every stablecoin uses some combination of these four:
| Mechanism | How it works | Strong in | Weak in |
|---|---|---|---|
| Mint/burn arbitrage | Mint when price > 1 (buy market, redeem at $1) | All classes; trivially symmetric in centralized & CDP designs; asymmetric in algo | Asymmetric arbitrage paths (mint cheap, redemption blocked) |
| Liquidation incentives | When collateral coverage drops, liquidators buy collateral at discount; restores solvency | CDP designs | Liquidator capital constrained; cascade-induced spread blowout; oracle stale |
| Stability module (PSM) | Direct swap with another known stable (DAI ↔ USDC at 1:1) | Maker, FRAX | Concentrates risk in the counterparty stable (DAI’s USDC dependency); needs PSM liquidity |
| Treasury / market operations | Protocol holds reserve assets and actively defends the peg by buying/selling | Maker’s surplus buffer, FRAX AMOs, RAI’s PID | Front-runnable; political; usually slower than market |
A robust stablecoin uses more than one — DAI has all four. LUSD V1 has mint/burn + liquidation incentives + a unique redemption mechanism (anyone can redeem 1 LUSD for $1 of ETH at any time, paid for by the riskiest trove). UST had only mint/burn. Hence UST is the canonical death-spiral case.
3.2 The arbitrage closure condition
The only reason a stablecoin trades near $1 in normal markets is that arbitrageurs profit by pushing it there. The arbitrage closure condition has three parts:
- A path exists. There must be some sequence of operations a third party can execute that converts 1 unit of stable into something worth 1 into 1 unit of stable, in the reverse direction).
- The path is profitable. Net of fees, gas, slippage, time, and capital cost, the arbitrageur makes money.
- The path closes in time. The market doesn’t move adversely faster than the arbitrageur can complete the trade.
Test each archetype:
| Archetype | Path | Profitable? | Closes in time? |
|---|---|---|---|
| USDC trading at $0.95 | Buy 1 USDC for 1 | $0.05 minus fees | Only if Circle accepts redemption; in March 2023 redemption was frozen, the arb did not close |
| DAI trading at $1.02 | Deposit USDC into PSM, mint DAI 1:1, sell on Curve for $1.02 | $0.02 minus fees | Always, unless PSM debt ceiling is hit |
| LUSD trading at $0.98 | Buy 1 LUSD for 1 of ETH | $0.02 minus 0.5% redemption fee minus gas | Always (LUSD has unconditional 1:1 redemption against the riskiest trove) |
| UST trading at $0.95 | Buy 1 UST for 1 of LUNA, sell LUNA | Mechanism profitable | Closes only if LUNA market depth absorbs the mint without crashing LUNA |
| crvUSD trading at $1.01 | Mint crvUSD against collateral, sell on PegKeeper-balanced pool, repay later | Yes, marginal; PegKeepers also automate this | Generally yes; bounded by collateral availability |
The auditor’s reflex: for the protocol under audit, write down all peg arbitrage paths and ask, for each, “under what market state does this path stop being profitable, or stop existing?” The answers are your depeg-condition findings.
3.3 The Maker / Sky reference architecture (study this once, you can read any CDP)
flowchart TB User[User] -->|deposit ETH| Vault[CDP / Vat ilk] Vault -->|mint DAI up to liquidation ratio| DAI[DAI / USDS] Vault -->|owes stability fee| SF[Stability Fee accrues] SF --> Vow[Vow: Surplus Buffer] Vault -->|if oracle px drops, vault is unsafe| Dog[Dog] Dog --> Clipper[Clipper: Dutch auction] Clipper --> Keeper[Keeper buys collateral at decaying price] Vow -->|surplus > bump| Flap[Flap auction: burn MKR] Vow -->|deficit > sump| Flop[Flop auction: mint MKR, dilution] PSM[PSM: USDC ↔ DAI 1:1] --> DAI OSM[Oracle Security Module: 1-hour delay] --> Vault ESM[Emergency Shutdown Module] -.last resort.-> Vault
The pieces and what each contributes to peg:
Vat: the central accounting.Vat.urns[ilk][user]tracks each vault’s collateral (ink) and debt (art). The core invariant:art * rate <= ink * spot * 1/matfor every urn (sufficient collateralization). Bugs inVatare existential.Spotter: reads the oracle, multiplies bymat(liquidation ratio), writesspotto theVat. Spotter is what makes the OSM delay structurally enforceable.DogandClipper(Liquidation 2.0): when a vault is unsafe, theDogbarks the vault; theClipperopens a Dutch auction with starting pricetop = osm_price * buf(e.g., 1.30× current oracle) decaying via aCalccurve overtailseconds (e.g., exponential decay). Keepers calltaketo buy. The auction is atomic per call — no zero-bid pathology like the oldCat/Flipdesign after Black Thursday. (Liquidation 2.0 docs)Vow: the surplus / debt buffer. Stability fees feed it; auction shortfalls drain it. If surplus accumulates beyondbump,Flapburns MKR. If debt accumulates beyondsump,Flopmints MKR (dilutes holders).PSM: deposit USDC, mint DAI 1:1 (modulo small fee). The most effective peg-defense tool MakerDAO has, and the largest concentration risk: DAI’s USDC-dependence at peak was >50% via PSM.OSM(Oracle Security Module): a 1-hour delay between oracle observation andVatuse. Stops flash-loan oracle manipulation cold. Trade-off: in a fast crash, vault holders get a 1-hour grace, but the protocol gets a 1-hour disadvantage.ESM(Emergency Shutdown Module): MKR-staked emergency stop. When triggered, the system freezes; DAI holders can redeem against the residual collateral basket.
For each of these you should be able to recite, in audit: (a) who can call which function; (b) what invariant it preserves; (c) under which market state it breaks.
3.4 Liquity V1 / V2 reference — the radical-redemption design
Liquity V1 is the cleanest single-collateral CDP design ever shipped. It is worth memorising for contrast against Maker.
| Property | Liquity V1 | Maker / Sky |
|---|---|---|
| Collateral | ETH only | Multi-asset, parameterised per ilk |
| Minimum Collateral Ratio (MCR) | 110% (one of the lowest in DeFi) | 150%–170% per ilk typically |
| Interest rate | 0% (one-time borrow fee instead) | Stability fee, governance-set |
| Liquidation mechanism | Stability Pool absorbs liquidations atomically | Dutch auction (Clipper) |
| Redemption | Anyone can redeem LUSD for ETH at $1, against the riskiest trove | PSM (DAI ↔ USDC), no protocol-native ETH redemption |
| Governance | None (immutable contracts) | Active governance via MKR |
| Recovery Mode | At system TCR < 150%, MCR rises to 150%, liquidations expand | None (Maker uses Vow buffers) |
The Stability Pool is the design idea worth dwelling on: depositors stake LUSD in the pool; when a trove is liquidated, the pool’s LUSD is burned to cover the trove’s debt and the pool receives the trove’s collateral at a discount. Atomic, no auction race, no zero-bid pathology. The trade-off is that the pool must always have enough LUSD to absorb the next liquidation — if it runs dry, debt is redistributed to other trove holders (a socialised loss).
Liquity V2 (mainnet 2025) generalised this:
- Multiple collateral types (ETH + LSTs).
- User-set interest rates: each borrower picks their own rate; the system redeems against the lowest-rate troves first, creating market discipline that pushes rates toward the system equilibrium.
- Multi-collateral Stability Pools (one per market).
- BOLD as the V2 stablecoin; LUSD remains live for V1 traditionalists.
[verify final mainnet params at audit time] (Liquity V2 FAQ)
The audit angle on Liquity V2: the user-set interest rate is a new market-design primitive. The auditor’s question: under coordinated griefing (one big borrower sets a rate so low everyone redeems against him, but he can flip-flop the rate constantly), can the redemption mechanism be turned against itself? This is exactly the kind of finding that hides in economic-mechanism territory, not in Solidity.
3.5 crvUSD and LLAMMA — the soft liquidation idea
Curve’s crvUSD takes a different design path: instead of binary “safe / liquidate”, it uses soft liquidation via an AMM (the LLAMMA — Lending Liquidating AMM Algorithm).
Mechanism, in plain terms (Curve LLAMMA resources):
- Each loan deposits collateral into a set of N price bands, each band covering a small price range.
- As the collateral price falls into a band, that band’s collateral is gradually converted into crvUSD via the band’s AMM. When the price recovers, the AMM reverses the conversion.
- A loan is fully “soft liquidated” if the price falls below the lowest band; at that point all collateral has been converted to crvUSD. The loan is closeable but the borrower has not paid an auction premium — only the AMM’s slippage and trading fees.
- PegKeepers are autonomous arbitrage contracts that mint or burn crvUSD into Curve pools to bring price back to $1 when it drifts.
The audit angle on LLAMMA:
- Oracle path. LLAMMA uses an internal oracle (typically a TWAP / smoothed feed); a fast manipulation of the input oracle moves bands and triggers soft-liquidation. Verify oracle smoothing and TWAP windows.
- Band-cross accounting. The hardest math in LLAMMA is when the price crosses band boundaries with non-trivial trade size — the bands must conserve value across the transition. This is the precise place to put fuzz tests with invariants like “total value of (collateral + crvUSD) in all bands is non-decreasing modulo fees”.
- Loss-to-borrower vs auction-premium trade. Soft liquidation guarantees the borrower doesn’t get caught by a one-shot auction, but in oscillating markets the borrower bleeds value via repeated round-trips (sometimes called “the LLAMMA tax”). Documenting this in a finding is appropriate — it is design-by-intent but users may not understand it.
- PegKeeper governance and bounds. PegKeepers have limits on how much they can mint; under sustained depeg pressure, those limits are the binding constraint. Audit the limits.
3.6 FRAX, AMOs, and the hybrid story
FRAX historically operated at a Collateral Ratio (CR) less than 100% — for example, at CR = 85%, each FRAX was backed by 0.15 redeemable in FXS (the governance token, minted on demand at redemption time). The CR adjusted dynamically based on FRAX market price: if FRAX traded below $1 for an extended period, CR was raised (more collateral, less algorithmic); if it traded above, CR fell (more algorithmic, more capital-efficient).
The AMO (Algorithmic Market Operations Controller) is the mechanism that made the under-collateralized model viable: AMOs are autonomous contracts that deploy idle protocol-owned collateral into yield (Curve pools, lending protocols, Treasuries) so long as their actions don’t move the FRAX peg. They produce protocol revenue that buys back FXS, supports peg defense, and over time can increase CR organically.
The community voted (FIP-188, Feb 2023) to move toward 100% collateralisation, citing UST’s collapse and the political climate around partially-algorithmic stables. As of 2025 FRAX targets 100% CR while continuing to run AMOs as yield engines, and is positioning around the Fraxtal L2. (CoinDesk on FIP-188, FRAX AMO docs) [verify CR and AMO inventory at audit time]
The audit angle on FRAX-style designs:
- AMO authority. Who can deploy / withdraw via an AMO? Time-locked? Multisig? Single EOA? A single-EOA-controlled AMO is a multi-hundred-million-dollar honeypot.
- AMO mark-to-market. AMOs hold positions in Curve gauges, Aave deposits, Convex stakes. The protocol’s “100% collateralisation” claim requires marking these positions correctly. A bug that values an illiquid Curve gauge position at face means the protocol looks fully backed but isn’t.
- AMO unwind latency. If FRAX needs collateral to meet redemptions, AMO positions must unwind. Some positions (Curve gauge boosted with veCRV, locked Convex) cannot unwind quickly. Audit the worst-case unwind time vs the redemption pressure.
- PSM concentration. Like Maker’s, FRAX’s PSM with USDC is the principal peg-defense liquidity. USDC depeg = FRAX depeg. This is a structural finding for the user, not a code bug.
3.7 Synthetic / delta-neutral: Ethena USDe as the new design space
Ethena’s USDe takes a fundamentally different approach: hold ETH (or LSTs / stETH) as long collateral and open an equal-size short on perpetual futures (executed on Bybit, Binance, OKX-style venues via off-exchange settlement). The net delta is zero — the position doesn’t move with ETH price.
The yield comes from two sources:
- Funding rate: perp shorts collect funding from longs when funding is positive (the default in bull markets).
- Staking yield: the underlying ETH / stETH still earns staking yield.
When the user wraps USDe in sUSDe, they get the accrued yield as the share-price appreciates.
The auditor’s risk model for USDe (and any delta-neutral synthetic):
| Risk | What it is | Severity in stress |
|---|---|---|
| Funding rate flips negative | If perp funding goes negative for an extended period, shorts pay; yield turns to bleed | Real; happened in 2022 bear market. Mitigated by yield reserve fund. |
| Perp DEX insolvency | A CEX collapses with collateral (FTX 2022 shape) | Off-exchange settlement via custodian (Copper, Ceffu) mitigates but doesn’t eliminate |
| Custodian compromise | Off-exchange settlement custodian is compromised or freezes collateral | Black swan; insurance fund expected to absorb |
| Hedge slippage in fast moves | If ETH moves 20% in minutes, the short may be auto-deleveraged before USDe can rebalance | Real; insurance fund absorbs |
| Oracle / liquidation cascade on collateral | LST de-peg vs ETH affects long side | Real; bounded by collateral choice |
| Negative basis on synthetic | Spot-perp basis can go negative; net carry inverts | Real |
The audit angles are different from CDPs: less code-bug surface, more operational and market surface. The contract reads collateral attestations from the off-chain custodian, mints USDe against them; the bug class is closer to a bridge / oracle than to a Maker vault. Audit the attestation flow as if it were a bridge oracle.
4. Depeg Dynamics — How Stablecoins Actually Die
Now the part that you need to model, not just narrate.
4.1 The four shock types
Every depeg starts with one of four shocks:
| Shock | Mechanism | Example |
|---|---|---|
| Demand shock (sell-side) | Holders sell faster than arbitrage can buy back | USDC March 2023 |
| Supply shock (mint-side) | New units minted faster than market can absorb | UST May 2022 once burn-mint inverted; minor cases of unauthorized mint via bugs |
| Liquidation cascade | Falling collateral price triggers liquidations whose dumps push price further down | DAI March 2020 (Black Thursday); CDP under stress |
| Run on redemption | Holders all redeem at once; redemption path becomes capacity-constrained | DAI/USDC March 2023 (PSM redemption from DAI to USDC); centralised stables under regulator stress |
All real depegs are combinations. UST was demand-shock + supply-shock + reflexive loss of confidence in LUNA collateral. USDC March 2023 was demand-shock + redemption freeze (Friday/weekend SVB closure prevented Circle from honouring redemptions). DAI March 2020 was liquidation cascade plus zero-bid auction pathology.
4.2 Liquidation cascade — the mechanics
Consider a CDP system with N vaults at varying health factors. Assume oracle price drops by ΔP. The cascade:
- Some fraction of vaults become unsafe (
HF < 1). - Liquidators are triggered. Each liquidation sells collateral on the market (or, in Maker’s Clipper case, exposes collateral to buyers who themselves need to sell shortly).
- The collateral selling pushes spot price further down — call this the market impact of liquidations.
- The lower spot is propagated into the oracle (with some lag — Maker’s OSM 1-hour delay, Chainlink’s deviation threshold, etc.).
- More vaults become unsafe; goto step 1.
This is a positive-feedback loop. It terminates when either (a) all remaining vaults are deep above water; (b) the protocol runs out of liquidations to perform; or (c) the protocol becomes insolvent (collateral < debt across the system).
The auditor’s modeling question: at what collateral price drop ΔP does the system tip from “loss to some vaults” to “bad debt to the protocol”? This is solvable analytically for a single-asset CDP with closed-form distribution of vault health factors, and solvable numerically for any protocol. The Lab in §5 does it for a toy single-asset CDP.
4.3 Black Thursday (DAI, March 2020) — the canonical liquidation pathology
March 12–13, 2020: ETH dropped ~50% in 24 hours during the COVID market crash. Maker’s auction system (Cat/Flip at the time) suffered:
- Gas prices spiked to >200 gwei due to network congestion; many keepers’ transactions failed or timed out.
Flipauctions had no minimum bid, allowing a keeper to submitbid = 0and win if no one else bid.- A single keeper realised this and won ~$8.32M of ETH for zero DAI (community post-mortem).
- Maker accumulated ~$5.7M of bad debt; MKR was minted via
Flopauction to cover, diluting holders.
The lessons embedded in Liquidation 2.0 (Dog + Clipper):
- Atomic Dutch auction: settles in one transaction; no zero-bid window.
- Top-down price decay: starts above oracle (the
bufparameter); keeper can wait but price always finds a buyer eventually. - Per-keeper incentive (
chipandtip): explicit reward for triggering the auction, separate from auction profit, decouples the trigger from the bid. - Liquidation hop limit (
Dog.Hole): per-ilk and global caps on liquidation throughput per block; prevents network congestion from cascading.
This single redesign moved Maker from “vulnerable to a 50% drop” to “survived a 30% intraday drop in March 2023 without bad debt”. The general lesson for CDP auditors: the liquidation mechanism is the protocol’s heart; its failure mode under congestion is the protocol’s failure mode. Auditing a liquidation function without a high-gas / high-volatility scenario is malpractice.
4.4 The UST death spiral — the math
UST’s mechanism (simplified):
mint 1 UST ↔ burn $1 of LUNA at oracle price
burn 1 UST ↔ mint $1 of LUNA at oracle price
Equivalently, define S_U = UST supply, S_L = LUNA supply, P_L = LUNA price. A user can always convert: 1 UST → $1 of LUNA = 1 / P_L LUNA. So burning UST mints 1 / P_L LUNA per unit.
Suppose UST trades below $1 — say, at price p_U < 1. Arbitrage:
- Buy 1 UST in the market for
p_Udollars. - Burn the UST; receive
1 / P_LLUNA, worth $1. - Sell LUNA for $1.
- Profit:
1 - p_Uper UST burned.
This pushes UST price up (buying pressure) and LUNA price down (selling pressure). The mechanism is correct in form — arbitrage closes when UST returns to $1.
The death spiral. Let D be UST supply being burned (in $); D / P_L is the LUNA being minted; mass selling of LUNA pushes P_L down by some impact function f(D). As P_L drops:
LUNA needed to absorb $1 of UST burn = 1 / P_L → ∞ as P_L → 0
If users keep burning UST (because they want out at $1, and arbitrage still appears profitable), LUNA supply explodes hyperbolically while LUNA price collapses. The arb is “profitable” up to the moment LUNA market depth refuses to absorb the new supply — at which point burning UST mints LUNA the arbitrageur cannot sell, and the mechanism halts.
In May 2022, this took ~3 days. LUNA went from ~1 to ~40B of market cap was destroyed (Briola et al. 2022, arXiv).
The auditor’s takeaway, generalised: any stablecoin whose collateral or backing asset is itself created by the system has a reflexive failure mode. If you can mint backing by burning debt, the mechanism is unstable in the limit. Always ask: “what is the exogenous backing, and what is the market depth of that backing under stress?”
The Anchor 20% yield was the demand sink that masked the instability — UST had artificial demand because Anchor paid 20% APY on deposits, mostly subsidised by the Luna Foundation Guard and protocol-controlled reserves. When the subsidy looked at risk (or when speculators concentrated their sells), the demand sink dried up faster than the supply mechanism could catch up.
4.5 USDC March 2023 — when fiat-backed fails off-chain
Friday March 10, 2023: Silicon Valley Bank was placed into receivership by the California Department of Financial Protection and Innovation. Circle disclosed that ~$3.3B of USDC reserves (~8% of total backing) was held at SVB. Until the FDIC’s Sunday March 12 announcement that all depositors would be made whole, USDC’s reserves were materially uncertain. (Federal Reserve note, 2025)
Market response:
- USDC dropped to ~$0.87 by Saturday March 11 (CoinDesk timeline, Chainalysis on-chain analysis).
- DAI followed to ~$0.89 because of PSM concentration: arbitrageurs took DAI from the PSM to redeem against USDC, but no one wanted the USDC; the price equilibrated at the USDC price.
- FRAX (then at ~92% CR with USDC) followed similarly.
- Tether (USDT), which had no SVB exposure, rose briefly to $1.01 (flight to perceived safety).
- USDC’s primary market (Circle redemption) was effectively frozen because Friday banking hours closed and Circle could not move funds over the weekend.
Sunday March 12, the FDIC announced full coverage; USDC retraced to ~1 by Monday.
The lessons:
- Off-chain banking is a peg dependency. The on-chain contract was perfect. USDC didn’t depeg because of Solidity; it depegged because the redemption window closed.
- PSM concentration is contagion. DAI’s PSM made it functionally equivalent to USDC for the duration. The auditor finding “DAI is over 50% backed by USDC via PSM” is structurally a finding of Medium severity at minimum.
- Arbitrage closure requires the redemption path. USDC arbitrage was a paper trade for 48 hours — buy at 1 — but the redemption was frozen. Always ask: “is the redemption path always open, or is it conditional on off-chain liveness?”
- Time matters. The depeg lasted 48 hours; it became a non-event because the FDIC intervened. Without intervention, redemption flight would have continued. A protocol whose liquidations price USDC at “market” during those 48 hours would have under-collateralized loans en masse.
4.6 Other named events (auditor’s case-bibliography)
| Event | Date | Class | Cause | Outcome |
|---|---|---|---|---|
| Maker Black Thursday | Mar 2020 | CDP cascade | ETH 50% drop + zero-bid auction bug + gas congestion | $5.7M bad debt; auction redesign |
| UST collapse | May 2022 | Pure algo | Demand-sink withdrawal + arbitrage hyperbolic | ~$40B destroyed |
| Tether brief dip | Multiple | Fiat-backed | Reserve attestation concerns | Recovered within hours each time |
| USDC depeg | Mar 2023 | Fiat-backed | SVB closure froze redemption | Recovered after FDIC intervention; DAI/FRAX contagion |
| sUSD (Synthetix) depeg | Mar 2020, Mar 2023 | CDP with SNX backing | Correlated SNX collapse + thin liquidity | Recovered each time; SNX has high collateralization (~500%) |
| Tron-USDD depeg | Multiple | Hybrid → algo | Insufficient peg defense; reserve composition opaque | Has not recovered to par; trades persistently below $1 |
| MIM (Magic Internet Money) | 2022 | CDP | Collateral was UST (used as MIM collateral via Abracadabra) | Cascade following UST collapse |
| deUSD / lybra / other small algos | 2023–2024 | Various | Generally insufficient peg defense at scale | Most de-listed or remain micro-cap |
Every one of these is worth a paragraph in your audit notes. Patterns repeat.
5. Lab — Stress-Test a Stablecoin Yourself
Three labs. The first two are mandatory; the third is a real-world audit exercise. Total ~12–18 hours.
5.1 Lab structure
~/web3-sec-lab/bonus-stablecoin/
├── 01-cdp/ # Build & stress a single-collateral CDP
├── 02-ust-simulator/ # Model the UST collapse in code
└── 03-real-protocol-audit/ # Pick a stablecoin from DeFiLlama; write a risk report
5.2 Lab 1 — Build a single-collateral CDP and find its insolvency threshold
We build a minimal CDP system (single collateral = WETH, single stablecoin = USDk). Then we write a simulator that drops the WETH price across a range of values and identifies the price at which the protocol becomes insolvent (collateral value < total debt).
5.2.1 Setup
mkdir -p ~/web3-sec-lab/bonus-stablecoin/01-cdp && cd $_
forge init --no-commit single-cdp
cd single-cdp
forge install OpenZeppelin/openzeppelin-contracts --no-commit5.2.2 The CDP contract
// src/SimpleCDP.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @notice A minimal single-collateral CDP for stress-testing. Educational only.
contract USDk is ERC20 {
address public minter;
constructor() ERC20("USDk", "USDk") { minter = msg.sender; }
function mint(address to, uint256 amount) external {
require(msg.sender == minter, "only minter");
_mint(to, amount);
}
function burn(address from, uint256 amount) external {
require(msg.sender == minter, "only minter");
_burn(from, amount);
}
}
/// @notice Mock oracle. Owner can set price for stress testing.
contract MockOracle {
uint256 public price; // 8 decimals like Chainlink
address public owner;
constructor(uint256 _p) { price = _p; owner = msg.sender; }
function setPrice(uint256 _p) external {
require(msg.sender == owner, "only owner");
price = _p;
}
}
contract SimpleCDP {
// Configuration
uint256 public constant MCR = 150; // 150% minimum collateral ratio
uint256 public constant LIQ_BONUS = 105; // 5% liquidator bonus
uint256 public constant PRICE_DECIMALS = 1e8;
uint256 public constant DUST_LIMIT = 100e18; // 100 USDk minimum debt
// State
ERC20 public immutable weth; // collateral token
USDk public immutable usdk; // debt token
MockOracle public immutable oracle;
struct Vault {
uint256 collateral; // wei of WETH
uint256 debt; // wei of USDk
}
mapping(address => Vault) public vaults;
uint256 public totalDebt;
constructor(address _weth, address _usdk, address _oracle) {
weth = ERC20(_weth);
usdk = USDk(_usdk);
oracle = MockOracle(_oracle);
}
function _ethPriceUSD() internal view returns (uint256) {
return oracle.price(); // 8-decimal
}
/// @notice Healthy iff collateral value * 100 >= debt * MCR.
function isHealthy(address user) public view returns (bool) {
Vault memory v = vaults[user];
if (v.debt == 0) return true;
uint256 collateralUSD = (v.collateral * _ethPriceUSD()) / PRICE_DECIMALS;
return collateralUSD * 100 >= v.debt * MCR;
}
function deposit(uint256 amount) external {
weth.transferFrom(msg.sender, address(this), amount);
vaults[msg.sender].collateral += amount;
}
function borrow(uint256 amount) external {
require(amount > 0, "zero borrow");
Vault storage v = vaults[msg.sender];
v.debt += amount;
totalDebt += amount;
require(v.debt >= DUST_LIMIT, "below dust");
require(isHealthy(msg.sender), "would be unsafe");
usdk.mint(msg.sender, amount);
}
function repay(uint256 amount) external {
Vault storage v = vaults[msg.sender];
require(amount <= v.debt, "over-repay");
usdk.burn(msg.sender, amount);
v.debt -= amount;
totalDebt -= amount;
require(v.debt == 0 || v.debt >= DUST_LIMIT, "leaves dust");
}
function withdraw(uint256 amount) external {
Vault storage v = vaults[msg.sender];
require(amount <= v.collateral, "over-withdraw");
v.collateral -= amount;
require(isHealthy(msg.sender), "would be unsafe");
weth.transfer(msg.sender, amount);
}
/// @notice Liquidator pays debt in USDk, receives collateral worth debt*LIQ_BONUS/100.
/// @dev This is the simplest possible liquidation. In a real protocol you'd
/// have partial liquidations, auctions, etc.
function liquidate(address user) external {
require(!isHealthy(user), "healthy");
Vault storage v = vaults[user];
uint256 debt = v.debt;
// Collateral seized = debt * LIQ_BONUS / 100 / ETH price
uint256 collateralToSeize = (debt * LIQ_BONUS * PRICE_DECIMALS) / (100 * _ethPriceUSD());
uint256 actualSeize = collateralToSeize > v.collateral ? v.collateral : collateralToSeize;
// Burn liquidator's USDk equal to debt
usdk.burn(msg.sender, debt);
// Reduce vault state
v.collateral -= actualSeize;
v.debt = 0;
totalDebt -= debt;
// Transfer collateral to liquidator
weth.transfer(msg.sender, actualSeize);
// If collateral fully consumed but debt was higher → bad debt; in this
// educational version we just emit an event. Production: track explicitly.
if (collateralToSeize > v.collateral + actualSeize) {
// (bad debt left in protocol; not handled here)
}
}
}Notes already worth a finding in a real audit: dust on liquidation is not enforced, partial liquidation is missing, bad debt is silently absorbed (or rather, ignored). We will use this contract precisely because it’s flawed — to demonstrate the modeling workflow.
5.2.3 The stress-test simulator
// test/StressTest.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/SimpleCDP.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20 {
constructor() ERC20("WETH", "WETH") {}
function mint(address to, uint256 amount) external { _mint(to, amount); }
}
contract StressTest is Test {
WETH weth;
USDk usdk;
MockOracle oracle;
SimpleCDP cdp;
// Borrowers
address[20] borrowers;
function setUp() public {
weth = new WETH();
usdk = new USDk();
oracle = new MockOracle(2000e8); // ETH = $2000 initially
cdp = new SimpleCDP(address(weth), address(usdk), address(oracle));
usdk.transferOwnership; // skip ownership transfer; minter is test contract
// hack: minter is whoever deployed USDk (test contract); transfer minter authority
vm.store(address(usdk), bytes32(uint256(5)), bytes32(uint256(uint160(address(cdp)))));
// ^ implementation-dependent; check storage slot in your Foundry run
// 20 borrowers with varying collateralization
for (uint256 i = 0; i < 20; i++) {
address b = address(uint160(0x1000 + i));
borrowers[i] = b;
weth.mint(b, 10 ether);
vm.startPrank(b);
weth.approve(address(cdp), type(uint256).max);
cdp.deposit(10 ether);
// Borrow varying amounts: borrower i borrows (100 + i*50)% / MCR of their collateral
// i=0 → 100% / 1.5 = 66% LTV; i=19 → close to MCR limit
uint256 maxBorrowUSD = (10 ether * 2000e8 * 100) / (1e8 * cdp.MCR());
uint256 borrowUSD = (maxBorrowUSD * (50 + i * 2)) / 100;
// Convert from "USD units, 1e18 scale" — for the lab we pretend 1 USDk = $1
cdp.borrow(borrowUSD);
vm.stopPrank();
}
}
function test_insolvency_threshold() public {
// Drop ETH price in steps; check after each drop:
// - how many borrowers are liquidatable
// - what's the system collateral value vs system debt
// - is the system insolvent?
uint256[] memory pricePoints = new uint256[](20);
for (uint256 i = 0; i < 20; i++) {
pricePoints[i] = 2000e8 - (i * 100e8); // $2000 → $100
}
for (uint256 i = 0; i < pricePoints.length; i++) {
oracle.setPrice(pricePoints[i]);
uint256 unhealthy = 0;
uint256 totalCollateral = 0;
for (uint256 j = 0; j < 20; j++) {
if (!cdp.isHealthy(borrowers[j])) unhealthy++;
(uint256 c, ) = cdp.vaults(borrowers[j]);
totalCollateral += c;
}
uint256 collateralUSD = (totalCollateral * pricePoints[i]) / 1e8;
uint256 td = cdp.totalDebt();
bool insolvent = collateralUSD < td;
emit log_named_uint("price (USD)", pricePoints[i] / 1e8);
emit log_named_uint("unhealthy vaults", unhealthy);
emit log_named_uint("collateral USD", collateralUSD);
emit log_named_uint("total debt", td);
emit log_named_string("insolvent", insolvent ? "YES" : "no");
emit log("---");
if (insolvent) {
emit log_named_uint("INSOLVENCY AT PRICE", pricePoints[i] / 1e8);
return;
}
}
}
}Run:
forge test --match-test test_insolvency_threshold -vv5.2.4 Expected output and interpretation
You should see a table:
price (USD): 2000 unhealthy: 0 collateralUSD: 400000 debt: ~177000 insolvent: no
price (USD): 1900 unhealthy: 0 ...
...
price (USD): 1400 unhealthy: 5 ...
price (USD): 1000 unhealthy: 15 collateralUSD: 200000 debt: 177000 insolvent: no
price (USD): 800 unhealthy: 20 collateralUSD: 160000 debt: 177000 insolvent: YES
INSOLVENCY AT PRICE: 800
(Numbers depend on your borrower configuration; replicate the trend, not the exact figures.)
Interpretation:
- The system is healthy when collateral USD ≥ total debt; “unhealthy vaults” measures who would be liquidated, not insolvency. Liquidation transfers the unhealthy collateral to liquidators at a discount; the protocol’s solvency depends on whether total seized collateral still exceeds total debt.
- The system transitions from “loss to borrower” (vaults below MCR but above 100%) to “bad debt to protocol” when collateral USD < debt. In our 150% MCR design with up to ~75% LTV vaults, this happens around a 50–60% price drop (collateral worth less than debt).
- This is the toy version; real protocols add a stability fee accrual (compounds debt), a liquidation bonus (collateral leaves protocol faster than debt drops), and dust limits (un-liquidatable scraps left over).
Stretch tasks:
- Add a stability fee (e.g., 5% APY accrual on debt) and re-run. The insolvency threshold rises (debt grows over time).
- Add a liquidation bonus payout in collateral (already in the contract) and observe that protocol-side collateral drops faster than debt — making insolvency happen at higher prices than naive math suggests.
- Add a simulated liquidator who can’t always show up (e.g., 30% of liquidations succeed) and observe the cascade.
- Add a market impact function: each liquidation reduces oracle price by 0.5% (simulating that liquidators dump on the market). Observe the reflexive feedback.
The last stretch is the most important. In a stressed market liquidations cause further price drops; the “static price drop” analysis underestimates risk by 2–3x.
5.3 Lab 2 — UST collapse simulator
We model the UST mechanism: the mint/burn arbitrage against LUNA, with a finite-depth LUNA market.
# ust_simulator.py
# Run with: python3 ust_simulator.py
import math
def simulate_ust_collapse(
initial_ust_supply=18_000_000_000, # $18B at peak
initial_luna_supply=350_000_000, # ~350M LUNA pre-collapse
initial_luna_price=80.0, # ~$80
sell_pressure_pct_per_step=0.02, # 2% of remaining UST sold per step
luna_market_depth_usd=200_000_000, # depth that absorbs $X without major slippage
arb_aggression=0.5, # what fraction of UST below peg is arb'd per step
steps=300,
):
"""
Model: each step, some fraction of holders sell UST. Arbitrageurs buy
UST in the market and burn it for LUNA at the oracle price. The LUNA
is sold, which moves LUNA price down according to market impact.
"""
ust_supply = initial_ust_supply
luna_supply = initial_luna_supply
luna_price = initial_luna_price
ust_price = 1.0
history = []
for step in range(steps):
# 1) Sell pressure: some fraction of UST holders sell into market
sell_volume_usd = ust_supply * sell_pressure_pct_per_step
# naive: each $1 of sell pressure pushes UST price down by some elasticity
ust_price_impact = sell_volume_usd / (10 * luna_market_depth_usd)
ust_price = max(0.01, ust_price - ust_price_impact)
# 2) Arbitrage: arbs buy UST in market, burn for LUNA
if ust_price < 0.999:
arb_size_usd = ust_supply * sell_pressure_pct_per_step * arb_aggression
ust_burned = arb_size_usd / ust_price
luna_minted = (ust_burned * 1.0) / luna_price # burn 1 UST → $1 of LUNA
luna_supply += luna_minted
ust_supply -= ust_burned
# Arbs sell LUNA; market impact on LUNA price
luna_sell_usd = luna_minted * luna_price
luna_impact_pct = luna_sell_usd / luna_market_depth_usd
luna_price = luna_price * max(0.001, (1 - luna_impact_pct))
# Each UST burned also slightly pushes UST price up (less supply)
ust_price = min(1.0, ust_price + 0.1 * (ust_burned / ust_supply))
history.append({
"step": step,
"ust_supply": ust_supply,
"ust_price": ust_price,
"luna_supply": luna_supply,
"luna_price": luna_price,
})
if luna_price < 0.01 or ust_price < 0.05:
print(f"--- Collapse complete at step {step} ---")
break
return history
if __name__ == "__main__":
h = simulate_ust_collapse()
for i, snap in enumerate(h):
if i % 10 == 0 or i == len(h) - 1:
print(f"step={snap['step']:3} ust_supply=${snap['ust_supply']/1e9:6.2f}B "
f"ust_px=${snap['ust_price']:.4f} "
f"luna_supply={snap['luna_supply']/1e6:8.1f}M "
f"luna_px=${snap['luna_price']:6.4f}")Run:
python3 ust_simulator.pyYou should see, with default parameters:
- UST supply slowly declining (burns dominate); UST price oscillating then sinking.
- LUNA supply exploding (from 350M to billions then trillions).
- LUNA price collapsing toward zero hyperbolically.
- Termination when LUNA price hits ~$0.01.
Tasks:
- Tune
sell_pressure_pct_per_stepto see how fast collapse happens under various run scenarios. - Tune
luna_market_depth_usdupward — at what depth does the system not collapse? (Hint: in 2022 LUNA’s market depth was insufficient by orders of magnitude.) - Add a
protocol_buybackterm: each step the protocol can spend $X buying UST in market. Find the protocol-buyback rate at which the spiral is averted. This is what LFG’s BTC reserve attempted — and failed — to do. - Add
anchor_run: at step T, simulate the Anchor 20% yield protocol’s reserves running dry (a step-function drop in UST demand). Observe the impact. - Compare to a CDP-backed stable: replace the LUNA-mint with a “deposit collateral worth $X to mint X UST”, and observe that the death spiral does not occur — the supply-mint side is exogenously bounded.
The audit takeaway: in your audit report, this model becomes a quantitative finding. “Under sell pressure of >X% per period and LUNA-equivalent market depth of <Y, the system collapses in <Z steps” is dramatically more credible than “the algo design is fragile”. Auditors paid the top end of the market write the model.
5.4 Lab 3 — Real-protocol risk audit
Pick a stablecoin from DeFiLlama Stablecoins that is not USDC, USDT, or DAI. Suggested candidates (any era): GHO, crvUSD, BOLD, USDe, USDS, eUSD, sUSD. Pick one with >$50M circulating supply and accessible documentation.
Write a 1500–2500 word risk report addressing, for that protocol:
- Classification: which of the four (or five) archetypes from §2.1.
- Backing decomposition: by asset class, with %s. Identify the riskiest backing component and rationale.
- Peg-defense mechanisms: which of the four from §3.1; how each works in this protocol.
- Failure scenarios: write three scenarios — demand shock, collateral shock, redemption freeze — that would depeg this protocol. For each, name the specific parameter or path that would fail.
- Governance and emergency surface: who can change critical params? Time-locks? Multisig composition?
- Audit findings list: 5–10 items in the style of an audit report (Critical / High / Medium / Low), with rationale.
- One quantitative model: stress this protocol with a chosen shock and produce a numerical insolvency or depeg threshold.
This is the deliverable that an auditor would actually submit. Treat it as a writing exercise as much as an analytical one.
6. Yield-Bearing Wrappers — sDAI, sUSDe, sUSDS, and Composability Risk
A yield-bearing stablecoin wraps an underlying stable in an ERC-4626-style vault that accrues yield by a defined mechanism. This is the dominant pattern in 2024–2026 because it lets DeFi protocols hold a single “yield-bearing dollar” without needing to manage rate accrual themselves.
| Wrapper | Underlying | Yield source |
|---|---|---|
| sDAI | DAI | Dai Savings Rate (DSR), set by Maker governance |
| sUSDS | USDS | Sky Savings Rate, set by Sky governance |
| sUSDe | USDe | Funding rate + staking yield captured by Ethena |
| sFRAX | FRAX | Protocol fees + AMO yield |
| sFRXUSD | FRXUSD | Frax v3 successor wrapper |
6.1 The ERC-4626 share math
Recall from Tuan-07-Token-Standards-Integration-Risk (and Week 08 §4): an ERC-4626 vault holds underlying asset, mints shares to depositors, and tracks the share price via convertToAssets(shares) and convertToShares(assets).
The four rounding-direction rules:
| Function | Rounding | Why |
|---|---|---|
deposit(assets) returns shares | floor | User pays N assets, receives ≤ N×rate shares; vault rounds in its favor |
mint(shares) returns assets | ceil | User wants exactly N shares, pays ≥ N/rate assets; vault rounds in its favor |
withdraw(assets) returns shares | ceil | User wants exactly N assets, burns ≥ N×rate shares; vault rounds in its favor |
redeem(shares) returns assets | floor | User burns N shares, receives ≤ N/rate assets; vault rounds in its favor |
The pattern: rounding always favors the vault (existing depositors), never the depositor. An audit finding for any wrapper that rounds the other way.
6.2 The first-depositor / inflation attack on yield wrappers
A clean ERC-4626 wrapper has a critical edge case at zero TVL: if totalSupply == 0, the first depositor can be donation-attacked.
// Attacker: deposit 1 wei → receives 1 share.
// Attacker: donate 1,000 underlying directly to vault (transfer, not deposit).
// Now: 1 share = 1001 underlying.
// Victim deposits 999 → receives floor(999 * 1 / 1001) = 0 shares.
// Attacker withdraws their 1 share → receives all 1000 underlying.Mitigations (every modern wrapper uses one or more):
- Virtual shares: OpenZeppelin’s
ERC4626usesdecimalsOffsetto inflate the initial share supply; an attacker would need to donate10^offsetunderlying to skew the math meaningfully. - Permanent initial deposit: protocol or governance deposits a non-trivial amount and locks the resulting shares.
- Donation-blocking accounting: track
totalAssetsfrom internal state, notasset.balanceOf(address(this)).
Audit reflex: when reading any 4626 vault, the first thing you check is convertToShares(amount) and convertToAssets(shares) when totalSupply == 0. If those return naïve values (e.g., 1:1), the inflation attack is open.
6.3 Rate-source dependency
A wrapper’s share price depends on the wrapper’s yield-source claim. Three rate-source patterns:
- Periodic mint (DSR style): the protocol mints new underlying into the vault at the savings rate; share price tracks accumulated mint per share.
- Internal accumulator (sUSDe style): a
vestingmechanism unlocks yield linearly between epochs; the wrapper’stotalAssetsincreases over time without external mints. - External rebase (sFRAX-like): the wrapper holds a rebasing underlying and the rebase rate is the yield.
Audit angles:
- Rate source authority: who can change the rate? sUSDe’s yield comes from off-chain trading; the protocol publishes attestations. sDAI’s rate is on-chain DSR. The off-chain ones have more trust assumptions.
- Rate-source manipulation: can a rate jump be front-run? A naive vault that updates rate via
setRate(r)and immediately re-prices shares lets the caller MEV the difference. Mitigation: rate changes apply over time, not instantly. - Cross-protocol rate inheritance: a lending protocol that accepts sUSDe as collateral and uses its
convertToAssets()view as the price implicitly inherits Ethena’s funding-rate risk. The lending market may not be aware.
6.4 Composability — the audit chains across wrappers
A modern lending protocol might:
- Accept sUSDe as collateral, valued via
convertToAssets(). - sUSDe is backed by USDe, valued at 1:1 internally.
- USDe is backed by ETH + perp short, marked via custodian attestations.
- The custodian holds off-exchange collateral with Bybit / Binance / OKX-style venues.
Risks propagate downward through this chain. A custodian compromise affects USDe, which affects sUSDe’s redemption, which affects the lending market’s collateral value, which affects every lender’s solvency. Every wrapper is a dependency edge in your audit’s trust graph.
For each wrapper your audit’s protocol depends on, write:
- The underlying.
- The yield source and authority.
- The redemption path and its conditional liveness.
- The worst-case shock that breaks the share-price assumption.
This is mechanical work; do it for every wrapper, every audit.
7. The Stablecoin Audit Checklist
Apply this checklist to every stablecoin or stablecoin-adjacent protocol. Items are grouped by topic. Severity calibration assumes a stablecoin with >$10M circulating supply; smaller deployments may downgrade some items.
7.1 Classification & taxonomy
- Protocol’s archetype is documented (CDP / hybrid / pure algo / synthetic / fiat-backed); decision-tree result from §2.3 attached.
- All listed collateral types enumerated with %, liquidity profile, depeg history.
- Yield source (if any) documented with authority and rate-change mechanism.
- Cross-protocol dependencies graphed (oracles, AMMs, custodians, partner protocols).
7.2 Collateral and backing
- Backing ratio is documented and machine-readable (a contract view, not just a marketing page).
- For each collateral type: oracle source, fallback oracle, deviation thresholds, staleness checks.
- Per-collateral debt ceiling enforced in code (Maker
Vat.linestyle). - Per-collateral liquidation ratio enforced; no silent ratio change.
- For correlated collaterals: documented; correlation parameter (
eMode,pool-type) used to avoid contagion. - For off-chain backing: attestation cadence; on-chain proof-of-reserves; failure mode if attestation pauses.
- LST collateral has explicit handling for slashing, rebase semantics, and withdrawal-queue latency.
7.3 Oracle and price path
- Spot price for liquidation distinct from spot for borrowing (avoids self-feed manipulation).
- OSM / TWAP / smoothing for price-feed-to-state transition (1-hour Maker style or equivalent).
- Oracle staleness check with revert if last update older than X minutes.
- Fallback oracle wired and tested.
- No external view function is used as a price source for another protocol if that view is updatable mid-transaction (read-only reentrancy, Tuan-05-Vulnerability-Classes-Part-1 §2.2.4).
7.4 Mint / burn / supply
- All mint paths enumerated and gated by collateral check + access control.
- No path increases
totalSupplywithout a deposit (or its accounting equivalent). - No path decreases
totalSupplyoutside redemption / repayment. - PSM-style modules have explicit caps (
gem-equivalent debt ceilings). - Mint-rate limits per block / per epoch for protective throttling.
7.5 Liquidation
- Liquidation function exists, is permissionless, and remains profitable at current parameters.
- Partial liquidation supported; close-factor encoded.
- Dust limit on remaining debt after liquidation; no “uneconomical to liquidate” trove residue.
- Auction (if any) is atomic — no zero-bid window (Black Thursday lesson).
- Per-block / per-asset liquidation throughput cap (Maker
Dog.Hole). - Stability Pool–style fallback or socialized-loss accounting if liquidation auctions fail.
- Liquidator capital constraints modeled: would a generic liquidator find this profitable at 10× market gas? at 100×?
- Cross-collateral isolation: bad debt in one ilk does not seize collateral from another.
7.6 Redemption
- Redemption path defined; counterparty enumerated.
- Redemption fee accrues to protocol or LPs (not lost).
- Redemption rate-limit / cooldown / queue if applicable; behavior under maximum redemption pressure.
- Off-chain redemption dependencies (banking, custodian) documented and noted in trust assumptions.
7.7 Stability / yield modules
- PSM caps (debt ceiling, swap fees, both directions).
- AMO authority is time-locked or multisig; no single-EOA AMOs.
- AMO mark-to-market is correct under illiquid positions.
- Treasury / surplus buffer is sized vs historical drawdowns.
- Backstop mechanism (MKR-mint flop, FXS dilution, etc.) is documented; dilution math correct.
7.8 Governance and parameters
- Privileged setters enumerated; each protected by timelock (typically 24–48h minimum).
- Parameter bounds enforced on-chain (e.g., MCR cannot be set below 105%).
- Emergency Shutdown / pause is documented; authorisation is multi-party (not single EOA).
- Ramping mechanism for sensitive parameters (
Ain StableSwap; LTV in lending) to avoid step-shock. - Governance attack surface considered (flash-loan governance — see Tuan-08-DeFi-Security-AMM-Lending-Vault §3.7 and Beanstalk).
7.9 Stress and contagion
- §5.2-style stress test run for primary collateral: insolvency price threshold documented.
- Market-impact-on-liquidation modeled (reflexive feedback).
- PSM concentration risk documented (e.g., “X% of supply backed by USDC via PSM; USDC depeg = stablecoin depeg”).
- Cross-protocol exposure documented (lending markets, AMM pools, vault holdings of this stable).
7.10 Yield wrapper (if applicable)
- ERC-4626 rounding directions correct (4 rules).
- First-depositor inflation attack mitigated (virtual shares or permanent initial deposit).
- Rate-source authority and change cadence documented.
-
convertToAssets/convertToSharesare not external-reentrancy-readable mid-state-transition. - If wrapped underlying can depeg, the wrapper’s pricing handles it (rate manipulation, share price floor).
7.11 Off-chain / operational
- For fiat-backed: attestation firm, cadence, banking jurisdiction, regulatory posture.
- For synthetic: custodian compromise scenario; perp DEX insolvency scenario; funding-flip risk.
- Incident-response runbook: who can pause, who can trigger ESM, what does each step take in time.
- Bug bounty in place; severity matched to TVL.
8. Anti-patterns Catalog
Add to your master audit checklist (audit-checklist-master):
- Pure algorithmic supply mechanism with no exogenous backing. Default Critical. There is no published example of one surviving stress.
- Backing asset partly minted by the system itself (e.g., UST backed by LUNA which is freely mintable by burning UST). Reflexive instability — default High to Critical.
- Single oracle with no fallback for collateral pricing.
- Oracle read directly from a manipulable AMM (Uniswap V2 spot, V3 slot0 without TWAP, AMM
get_virtual_priceduring liquidity ops). - No price-update delay (OSM equivalent) between oracle and liquidation engine in a CDP. Flash-loan liquidations possible.
- Liquidation auction with no minimum bid / zero-bid window (Black Thursday pathology).
- Liquidation throughput unlimited per block — congestion-driven cascade can run unbounded.
- PSM with no debt ceiling — unbounded mint of stablecoin against a single off-chain asset.
- PSM concentration > 30% of stablecoin supply backed by one fiat-stable. Document as Medium-or-higher contagion finding.
- AMO controlled by single EOA or untimelocked multisig.
- AMO mark-to-market via Chainlink only, when AMO holds illiquid LP positions whose execution price differs from the Chainlink ETH/USD-style feed.
- Stability fee accrual not applied on every state-changing function — debt drift over time.
- Recovery Mode / ESM key in a single multisig without on-chain redundancy.
- Stablecoin used as own collateral in a lending market on the same protocol without isolation — Euler-style donation surface.
- ERC-4626 wrapper with naïve initial share price (1:1 at zero TVL) — inflation attack.
- ERC-4626 wrapper computing
totalAssets()viaasset.balanceOf(address(this))without donation guard. - Yield wrapper’s rate updateable in one tx without smoothing — MEV-able rate jump.
- No public dashboard / view for real-time backing composition.
- Redemption path requires off-chain liveness without documented contingency.
- Liquid Restaking Token (LRT) used as CDP collateral without modeling restaking slashing pass-through.
- Cross-chain stablecoin minted on multiple chains without unified supply accounting (Multichain / Wormhole-style risk).
- Borrow-side or collateral-side stablecoin is itself rebasing without explicit unit-economic-handling in the consumer.
9. Trade-offs and Open Debates
| Decision | Option A | Option B | Auditor’s view |
|---|---|---|---|
| Collateral diversity | Single asset (LUSD V1) | Multi-asset (Maker / Sky) | Single is simpler to audit and reason about; multi is more capital-efficient and resilient via diversification, but each new collateral type doubles audit surface. Default: insist on per-asset isolation modes for any new ilk. |
| Liquidation style | Auction (Maker Clipper) | Stability Pool (Liquity) | SP is more elegant and atomic; auction handles deeper liquidations and broader keeper participation. For a multi-billion stable, want both. |
| Liquidation incentive | Fixed bonus (5%) | Dynamic / curve-decay (Clipper) | Fixed is simpler; dynamic finds the marginal liquidator under stress. Prefer dynamic for any stable with >$100M circulating. |
| Soft liquidation | Off (binary) | On (crvUSD LLAMMA) | Soft reduces user-visible liquidation events but bleeds value in oscillating markets. Document the user-side trade-off in the audit. |
| PSM | Yes (Maker, FRAX) | No (LUSD, crvUSD relies on PegKeepers + redemption) | PSM is the most effective short-term defense but concentrates risk in the counterparty stable. Hybrid: cap PSM at 30% of supply. |
| Yield model | None (LUSD V1, BOLD-ish) | Native yield wrapper (sDAI, sUSDe) | Yield brings composability and demand; it also adds rate-source risk and a 4626 audit surface. Both reasonable; document trade-off. |
| Governance | None (LUSD V1 immutable) | Active (Maker / Sky MKR) | Immutable simplifies audit and removes governance attack surface but cannot respond to novel risk. Most protocols need governance; the audit task is to bound governance authority via timelocks and parameter caps. |
| Algorithmic side | Zero (post-FRAX FIP-188) | Partial (pre-2023 FRAX) | Post-UST, market and regulatory pressure favor zero. Hard to argue for an algo side in 2026 without strong novel mitigations. |
| Stablecoin pricing in a wrapper | 4626 convertToAssets | Oracle on the wrapper itself | The 4626 view inherits any underlying depeg silently — bad for collateral pricing. Use an oracle that prices the wrapper directly when used as collateral, even if it’s an internal one. |
10. Quiz (≥80% to advance)
-
Q: A protocol describes itself as “decentralised algorithmic stablecoin with partial collateralisation”. What is the highest-severity audit finding you can write before reading any code? A: The hybrid / algorithmic class has a structural reflexive failure mode (UST-style death spiral under demand shock) when the algorithmic side is called on at scale. Default severity: at least High, depending on what fraction is algorithmic and how the system handles sustained sub-peg pressure. The finding stands even with perfectly-audited Solidity.
-
Q: DAI traded at $0.89 during March 2023. Was this a code bug, an economic-design issue, or an off-chain failure? Severity in an audit? A: An economic-design issue rooted in PSM concentration: DAI was over 50% backed by USDC via the PSM, so a USDC depeg passed through directly. No code bug. Severity in an audit of DAI: Medium-to-High structural finding (PSM concentration risk). In an audit of a protocol holding DAI as collateral: a Medium finding that DAI’s pricing should account for its USDC inheritance under stress.
-
Q: A CDP protocol uses Chainlink ETH/USD for both the borrow-time and the liquidation-time price, with no OSM-style delay. What’s the specific attack? A: Flash-loan oracle manipulation: an attacker manipulates a thin AMM that contributes to the Chainlink aggregator (or, more directly, manipulates a less-defended price-feed if the protocol uses one) within one transaction; opens a position at the manipulated price; takes profit. Less directly: a flash-crash market move can liquidate vaults that would have been safe under a delayed price. The OSM (1-hour delay) defends against both. Finding: High severity in a CDP audit.
-
Q: Explain in 60 seconds why UST’s mechanism failed even though arbitrage “worked correctly”. A: UST → LUNA burn-mint was symmetric and mechanically sound. The failure was that LUNA was the only backing, and LUNA’s market depth was finite. As UST holders fled, arbitrage burned UST and minted LUNA; LUNA supply expanded hyperbolically while LUNA price collapsed. Each marginal $1 of UST burned required mining
1 / P_LLUNA — diverging asP_L → 0. The mechanism’s correctness in form became its destruction in scale. The Anchor 20% yield had masked the instability by manufacturing UST demand; when that subsidy looked at risk, the demand dried up and the spiral ran. -
Q: A new “Liquid Restaking Stable” launches: backed 100% by eETH (an ether.fi LRT), with 130% MCR. Top three audit findings? A: (1) Slashing pass-through risk: eETH’s value can be impaired by AVS slashing events not reflected in spot oracles instantly; 130% MCR may be insufficient for the worst-case slashing scenarios. (2) Withdrawal queue mismatch: liquidations need to convert eETH to ETH, which goes through ether.fi’s withdrawal queue (days to weeks under stress); liquidator capital constrained by queue. (3) Restaking dependency graph: backing depends on AVS solvency, validator integrity, and ether.fi governance — a multi-level trust stack. Each level needs explicit trust-assumption statement.
-
Q: An ERC-4626 wrapper for an off-chain-yield stablecoin computes share price via
totalAssets() = asset.balanceOf(address(this)). What’s the bug and how to fix? A: A direct token transfer to the vault inflatestotalAssets()without minting shares, donation-attacking the share price. Fix: tracktotalAssetsfrom internal accounting (storage variable updated ondeposit/withdraw/ yield accrual), not from token balance. Optionally also use virtual-shares (OZ’sdecimalsOffset) to make the initial-deposit attack uneconomical. -
Q: You’re auditing a fiat-backed stablecoin. What three off-chain questions go in the trust-assumptions section? A: (1) Where are reserves banked, in what jurisdictions, with what FDIC / regulatory backstop? (2) What is the redemption process — direct on-chain redemption to a verified user, or off-chain wire after KYC? — and what is its uptime guarantee (weekend, holiday, regulatory action)? (3) Who has mint / burn / blacklist authority on-chain, and is the key custody documented (HSM, MPC, multi-signature)?
-
Q: A CDP protocol’s liquidation function reverts when collateral price drops > 30% in a single block. Why might this exist, and is it good or bad? A: Existence rationale: protect against oracle-manipulation flash liquidations. Trade-off: in a real fast crash (Black Thursday, March 2020), legitimate liquidations are now blocked, leading to growing bad debt. Auditor verdict: the protection should be time-bounded (don’t liquidate for K blocks if drop > 30%, then resume) and combined with an OSM-style smoothed price, not a hard revert. A permanent revert is a likely-High finding.
-
Q: A stablecoin protocol claims “fully decentralised, no admin keys”. The audit reveals a
transferOwnershipfunction on the oracle contract is still callable. Severity? A: Critical. The oracle is the single most leveraged point in a CDP. Centralised ownership of the oracle = trivial protocol drain via setting prices to extreme values then opening or liquidating positions. Severity High-to-Critical depending on how the function is gated and what the timelock is, but a finding that contradicts the marketing claim is always at least Medium severity. -
Q: Compare LLAMMA’s “soft liquidation” to Maker’s Clipper Dutch auction. What user-side trade-off do borrowers make under LLAMMA that they don’t under Clipper? A: Under Clipper, a single liquidation event closes the loan with a one-shot collateral discount (~5–13%). The borrower loses a fixed bonus but doesn’t continue to bleed. Under LLAMMA, the position is continuously converted between collateral and stable across bands as the price oscillates; in a sideways-volatile market, the user can experience repeated round-trip losses (“LLAMMA tax”) that can compound. Trade-off: LLAMMA softens the binary-event risk at the cost of continuous-event risk, which is rationally better in trending markets and worse in choppy ones. Audit angle: surface this clearly in the user-facing risk documentation; flag if the protocol claims LLAMMA is “strictly safer” without nuance.
11. Deliverables
- Lab 1: SimpleCDP stress test run; insolvency threshold identified; results documented.
- Lab 1 stretch (≥2 of 4 stretch tasks): stability fee, liquidation bonus impact, sporadic liquidators, market-impact reflexive feedback.
- Lab 2: UST simulator run with default parameters showing death spiral; one parameter sweep showing the threshold at which the spiral averts (or never does).
- Lab 3: real-protocol risk report (1500–2500 words) with the §5.4 structure.
- Audit-checklist update: §7’s stablecoin checklist merged into your audit-checklist-master under a “stablecoin / monetary-protocol” section.
- Notes file: written walkthrough of how you would classify and risk-rank, in 60 seconds, an unfamiliar stablecoin pitched to you over a call.
12. Where this leads
This bonus chapter ends the core stablecoin material in the course. Cross-references that build on it:
- Tuan-09-Oracle-MEV-Economic-Attack — every stablecoin failure is downstream of an oracle event; this bonus relies on that week’s oracle taxonomy.
- Tuan-Bonus-Liquid-Staking-Restaking — LSTs and LRTs are increasingly common stablecoin collateral; the slashing-pass-through risks discussed there are inputs to your stablecoin audit.
- Case-Euler-Finance-2023 — the donation-attack lesson applies to stablecoin protocols with internal share accounting; the same shape appears in CDP “internal balance” primitives.
- Tuan-15-Audit-Methodology-Tooling (later) — the methodology section will revisit invariant testing with Foundry, Echidna, and Halmos; the stablecoin invariants from §7 of this chapter become input.
The shift in mindset, summarised one more time: stablecoin auditing is the discipline of modeling the contract behind the contract. The Solidity code is necessary but not sufficient. You audit the equations, the economic incentives, the off-chain dependencies, the reflexive feedback loops, the governance latency, the operational runbook — and only then the function bodies. An audit that finds only function-body bugs and misses a PSM concentration finding has failed the user, even if the function bodies are perfect.
The next bonus chapter you should read is Tuan-Bonus-Liquid-Staking-Restaking, which goes deep on the LST/LRT space that increasingly underpins both stablecoin collateral and stablecoin demand (via sUSDe-style synthetic dollars).
Last updated: 2026-05-16 See also: Roadmap · References · MOC-Web3-Security-Mastery · Tuan-08-DeFi-Security-AMM-Lending-Vault · Tuan-Bonus-Liquid-Staking-Restaking