Part 2 β Module 6: Stablecoins & CDPs
Difficulty: Advanced
Estimated reading time: ~35 minutes | Exercises: ~2-3 hours
π Table of Contents
The CDP Model and MakerDAO/Sky Architecture
Liquidations, PSM, and DAI Savings Rate
- Liquidation 2.0: Dutch Auctions
- Peg Stability Module (PSM)
- Dai Savings Rate (DSR)
- Read: Dog.sol and Clipper.sol
- Exercises
Build Exercise: Simplified CDP Engine
Stablecoin Landscape and Design Trade-offs
- Taxonomy of Stablecoins
- Liquity: A Different CDP Design
- The Algorithmic Stablecoin Failure Pattern
- Ethena (USDe): The Delta-Neutral Model
- crvUSD: Curveβs Soft-Liquidation Model
- The Fundamental Trilemma
- Common Mistakes
- Exercises
π‘ The CDP Model and MakerDAO/Sky Architecture
On the surface, a CDP (Collateralized Debt Position) looks like a lending protocol β deposit collateral, borrow an asset. But thereβs a fundamental difference: in a lending protocol, borrowers withdraw existing tokens from a pool that suppliers deposited. In a CDP system, the borrowed stablecoin is minted into existence when the user opens a position. There are no suppliers. The protocol is the issuer.
This changes everything about the design: thereβs no utilization rate (because thereβs no supply pool), no supplier interest rate, and the stability of the stablecoin depends entirely on the protocolβs ability to maintain the peg through mechanism design β collateral backing, liquidation efficiency, and monetary policy via the stability fee and savings rate.
MakerDAO (now rebranded to Sky Protocol) pioneered CDPs and remains the largest decentralized stablecoin issuer, with over $7.8 billion in DAI + USDS liabilities. Understanding its architecture gives you the template for how on-chain monetary systems work.
π‘ Concept: How CDPs Work
The core lifecycle:
- Open a Vault. User selects a collateral type (called an βilkβ β e.g., ETH-A, WBTC-B, USDC-A) and deposits collateral.
- Generate DAI/USDS. User mints stablecoins against the collateral, up to the maximum allowed by the collateral ratio (typically 150%+ for volatile assets). The stablecoins are newly minted β they didnβt exist before.
- Accrue stability fee. Interest accrues on the minted DAI, paid in DAI. This is the protocolβs revenue.
- Repay and close. User returns the minted DAI plus accrued stability fee. The returned DAI is burned (destroyed). User withdraws their collateral.
- Liquidation. If collateral value drops below the liquidation ratio, the Vault is liquidated via auction.
The critical insight: DAIβs value comes from the guarantee that every DAI in circulation is backed by more than $1 of collateral, and that the system can liquidate under-collateralized positions to maintain this backing.
π MakerDAO Contract Architecture
MakerDAOβs codebase (called βdssβ β Dai Stablecoin System) uses a unique naming convention inherited from formal verification traditions. The core contracts:
Vat β The core accounting engine. Stores all Vault state, DAI balances, and collateral balances. Every state-changing operation ultimately modifies the Vat. Think of it as the protocolβs ledger.
Vat stores:
Ilk (collateral type): Art (total debt), rate (stability fee accumulator), spot (price with safety margin), line (debt ceiling), dust (minimum debt)
Urn (individual vault): ink (locked collateral), art (normalized debt)
dai[address]: internal DAI balance
sin[address]: system debt (bad debt from liquidations)
Key Vat functions:
frob(ilk, u, v, w, dink, dart)β The fundamental Vault operation. Modifies collateral (dink) and debt (dart) simultaneously. This is how users deposit collateral and generate DAI.grab(ilk, u, v, w, dink, dart)β Seize collateral from a Vault (used in liquidation). Transfers collateral to the liquidation module and creates system debt (sin).fold(ilk, u, rate)β Update the stability fee accumulator for a collateral type. This is how interest accrues globally.heal(rad)β Cancel equal amounts of DAI and sin (system debt). Used after auctions recover DAI.
Normalized debt: The Vat stores art (normalized debt), not actual DAI owed. Actual debt = art Γ rate. The rate accumulator increases over time based on the stability fee. This is the same index pattern from Module 4 (lending), applied to stability fees instead of borrow rates.
π Deep Dive: MakerDAO Precision Scales (WAD / RAY / RAD)
MakerDAO uses three fixed-point precision scales throughout its codebase. Understanding these is essential for reading any dss code:
WAD = 10^18 (18 decimals) β used for token amounts
RAY = 10^27 (27 decimals) β used for rates and ratios
RAD = 10^45 (45 decimals) β used for internal DAI accounting (= WAD Γ RAY)
Why three scales?
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ink (collateral) = WAD e.g., 10.5 ETH = 10.5e18 β
β art (norm. debt) = WAD e.g., 5000 units = 5000e18 β
β rate (fee accum.) = RAY e.g., 1.05 = 1.05e27 β
β spot (price/LR) = RAY e.g., $1333 = 1333e27 β
β β
β Actual debt = art Γ rate = WAD Γ RAY = RAD (10^45) β
β Vault check = ink Γ spot vs art Γ rate β
β WAD Γ RAY WAD Γ RAY β
β = RAD = RAD β same scale! β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why RAY for rates? 18 decimals isnβt enough precision for per-second compounding. A 5% annual stability fee is ~1.0000000015 per second β you need 27 decimals to represent that accurately.
Why RAD? When you multiply a WAD amount by a RAY rate, you get a 45-decimal number. Rather than truncating, the Vat keeps the full precision for internal accounting. External DAI (the ERC-20) uses WAD.
π Deep Dive: Vault Safety Check β Step by Step
Letβs trace a real example to understand how the Vat checks if a vault is safe:
Scenario: User deposits 10 ETH, mints 15,000 DAI
ETH price: $2,000
Liquidation ratio: 150% (so LR = 1.5)
Step 1: Compute spot (price with safety margin baked in)
spot = oracle_price / liquidation_ratio
spot = $2,000 / 1.5 = $1,333.33
In RAY: 1333.33e27
Step 2: Store vault state
ink = 10e18 (10 ETH in WAD)
art = 15000e18 (15,000 normalized debt in WAD)
rate = 1.0e27 (fresh vault, no fees yet β 1.0 in RAY)
Step 3: Safety check β is ink Γ spot β₯ art Γ rate?
Left side: 10e18 Γ 1333.33e27 = 13,333.3e45 (RAD)
Right side: 15000e18 Γ 1.0e27 = 15,000e45 (RAD)
13,333 < 15,000 β β UNSAFE! Vault would be rejected.
The user can only mint up to:
max_art = ink Γ spot / rate = 10 Γ 1333.33 / 1.0 = 13,333 DAI
Step 4: After 1 year with 5% stability fee
rate increases: 1.0e27 β 1.05e27
Actual debt: art Γ rate = 15000 Γ 1.05 = 15,750 DAI
(User now owes 750 DAI more in stability fees)
Safety check with same ink and spot:
ink Γ spot = 13,333 (unchanged)
art Γ rate = 15000 Γ 1.05 = 15,750
Even more unsafe β liquidation trigger!
π Connection: This is the same index-based accounting from Module 4 (Aaveβs liquidity index, Compoundβs borrow index). The pattern: store a normalized amount, multiply by a growing rate to get the actual amount. No per-user updates needed β only the global rate changes.
Spot β The Oracle Security Module (OSM) interface. Computes the collateral price with the safety margin (liquidation ratio) baked in: spot = oracle_price / liquidation_ratio. The Vat uses this directly: a Vault is safe if ink Γ spot β₯ art Γ rate.
Jug β The stability fee module. Calls Vat.fold() to update the rate accumulator for each collateral type. The stability fee (an annual percentage) is converted to a per-second rate and compounds continuously.
Dai β The ERC-20 token contract for external DAI. Internal DAI in the Vat (dai[]) is not the same as the ERC-20 token. The DaiJoin adapter converts between them.
Join adapters β Bridge between external ERC-20 tokens and the Vatβs internal accounting:
GemJoinβ Locks collateral ERC-20 tokens and credits internalgembalance in the VatDaiJoinβ Converts internaldaibalance to/from the external DAI ERC-20 token
CDP Manager β A convenience layer that lets a single address own multiple Vaults via proxy contracts (UrnHandlers). Without it, one address can only have one Urn per Ilk.
π Intermediate Example: Simplified Vault Accounting
Before diving into the full Vat flow (which uses terse formal-verification naming), letβs see the same logic with readable names:
// The core CDP check, readable version:
function isSafe(address user) public view returns (bool) {
uint256 collateralValue = collateralAmount[user] * oraclePrice / PRECISION;
uint256 debtValue = normalizedDebt[user] * stabilityFeeRate / PRECISION;
uint256 minCollateral = debtValue * liquidationRatio / PRECISION;
return collateralValue >= minCollateral;
}
// The Vat does exactly this, but in one line:
// ink Γ spot β₯ art Γ rate
// where spot = oraclePrice / liquidationRatio (safety margin baked in)
This is the same check. The Vat just pre-computes spot = price / LR so the safety check is a single comparison. Once you see this, the Vat code becomes readable.
The Full Flow: Opening a Vault
- User calls
GemJoin.join()β transfers ETH (via WETH) to GemJoin, credits internalgembalance in Vat - User calls
CdpManager.frob()(orVat.frob()directly) β locksgemasink(collateral) and generatesart(normalized debt) - Vat verifies:
ink Γ spot β₯ art Γ rate(Vault is safe) and total debt β€ debt ceiling - Vat credits
daito the userβs internal balance - User calls
DaiJoin.exit()β converts internaldaito external DAI ERC-20 tokens
π» Quick Try:
On a mainnet fork, read MakerDAO state directly:
IVat vat = IVat(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);
(uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust) = vat.ilks("ETH-A");
// Art = total normalized debt for ETH-A vaults
// rate = stability fee accumulator (starts at 1.0 RAY, grows over time)
// spot = ETH price / liquidation ratio (in RAY)
// Actual total debt = Art * rate (in RAD)
Observe that rate is > 1.0e27 β that difference from 1.0 represents all accumulated stability fees since ETH-A was created. Every vaultβs actual debt is art Γ rate.
π Read: Vat.sol
Source: dss/src/vat.sol (github.com/sky-ecosystem/dss)
This is one of the most important contracts in DeFi. Focus on:
- The
frob()function β understand each check and state modification - How
spotencodes the liquidation ratio into the price - The authorization system (
wardsandcanmappings) - The
cage()function for Emergency Shutdown
The naming convention is terse (derived from formal specification): ilk = collateral type, urn = vault, ink = collateral amount, art = normalized debt, gem = unlocked collateral, dai = stablecoin balance, sin = system debt, tab = total debt for auction.
π How to Study the MakerDAO/dss Codebase
The dss codebase is one of DeFiβs most important β and one of the hardest to read due to its terse naming. Hereβs how to approach it:
-
Build a glossary first β Before reading any code, memorize the core terms:
ilk(collateral type),urn(vault),ink(locked collateral),art(normalized debt),gem(free collateral),dai(internal stablecoin),sin(bad debt),rad/ray/wad(precision scales: 45/27/18 decimals). Write these on a card and keep it visible while reading. -
Read
frob()line by line β This single function IS the CDP system. It modifies collateral (dink) and debt (dart) simultaneously. Trace eachrequirestatement: whatβs it checking? Map them to: vault safety check (ink Γ spot β₯ art Γ rate), debt ceiling check (Art Γ rate β€ line), dust check, and authorization. Understandingfrob()means understanding the entire protocol. -
Trace the authorization system β
wardsmapping controls admin access.canmapping controls who can modify whose vaults. Thewish()function checks both. This is unusual compared to OpenZeppelinβs AccessControl β understand howhope()andnope()grant/revoke per-user permissions. -
Read the Join adapters β
GemJoin.join()andDaiJoin.exit()are the bridges between external ERC-20 tokens and the Vatβs internal accounting. These are short (~30 lines each) and clarify how the internalgemanddaibalances relate to actual token balances. -
Study
grab()andheal()βgrab()is the forced version offrob()used during liquidation β it seizes collateral and createssin(system debt).heal()cancels equal amounts ofdaiandsin. Together, they form the liquidation and recovery cycle: grab creates bad debt, auctions recover DAI, heal cancels the bad debt.
Donβt get stuck on: The formal verification annotations in comments. The dss codebase was designed for formal verification (which is why the naming is so terse β it maps to mathematical specifications). You can ignore the verification proofs and focus on the logic.
π― Build Exercise: CDP Model and MakerDAO
Workspace: workspace/src/part2/module6/ β starter files: SimpleVat.sol, SimpleJug.sol, tests: SimpleVat.t.sol, SimpleJug.t.sol | Shared: VatMath.sol, SimpleStablecoin.sol, SimpleGemJoin.sol, SimpleDaiJoin.sol
Exercise 1: On a mainnet fork, trace a complete Vault lifecycle:
- Join WETH as collateral via GemJoin
- Open a Vault via CdpManager, lock collateral, generate DAI via frob
- Read the Vault state from the Vat (ink, art)
- Compute actual debt:
art Γ rate(fetch rate fromVat.ilks(ilk)) - Exit DAI via DaiJoin
- Verify you hold the expected DAI ERC-20 balance
Exercise 2: Read the Jug contract. Calculate the per-second rate for a 5% annual stability fee. Call Jug.drip() on a mainnet fork and verify the rate accumulator updates correctly. Compute how much more DAI a Vault owes after 1 year of accrued fees.
π Deep Dive: Stability Fee Per-Second Rate
A 5% annual fee needs to be converted to a per-second compound rate:
Annual rate: 1.05 (5%)
Seconds per year: 365.25 Γ 24 Γ 60 Γ 60 = 31,557,600
Per-second rate = 1.05 ^ (1 / 31,557,600)
β 1.000000001547125957...
In RAY (27 decimals): 1000000001547125957000000000
Verification: 1.000000001547125957 ^ 31,557,600 β 1.05 β
This is stored in Jug as the `duty` parameter per ilk.
Each time drip() is called:
rate_new = rate_old Γ (per_second_rate ^ seconds_elapsed)
π Connection: This is the same continuous compounding from Module 4 β Aave and Compound use the same per-second rate accumulator for borrow interest. The math is identical; only the context differs (stability fee vs borrow rate).
π Deep Dive: rpow() β Exponentiation by Squaring
The Jug needs to compute per_second_rate ^ seconds_elapsed. With seconds_elapsed potentially being millions (weeks between drip() calls), you canβt loop. MakerDAO uses exponentiation by squaring β an O(log n) algorithm:
Goal: compute base^n (in RAY precision)
Standard approach: base Γ base Γ base Γ ... (n multiplications) β O(n) β too expensive
Exponentiation by squaring: O(log n) multiplications
Key insight: x^10 = x^8 Γ x^2 (use binary representation of exponent)
Example: 1.000000001547^(604800) [1 week in seconds]
604800 in binary = 10010011101010000000
Step through each bit (right to left):
bit 0 (0): skip base = baseΒ²
bit 1 (0): skip base = baseΒ²
...
For each '1' bit: result *= current base
For each bit: base = base Γ base (square)
Total: ~20 multiplications instead of 604,800
// Simplified rpow (MakerDAO's actual implementation in jug.sol):
function rpow(uint256 x, uint256 n, uint256 base) internal pure returns (uint256 z) {
assembly {
z := base // result = 1.0 (in RAY)
for {} n {} {
if mod(n, 2) { // if lowest bit is 1
z := div(mul(z, x), base) // result *= x (RAY multiplication)
}
x := div(mul(x, x), base) // x = xΒ² (square the base)
n := div(n, 2) // shift exponent right
}
}
}
// Jug.drip() calls: rpow(duty, elapsed_seconds, RAY)
// where duty = per-second rate (e.g., 1.000000001547... in RAY)
Why assembly? Two reasons: (1) overflow checks β the intermediate mul(z, x) can overflow uint256, and the assembly version handles this via checked division, (2) gas efficiency β this is called frequently and the savings matter.
Where youβll see this: Every protocol that compounds per-second rates uses this pattern or a variation. Aaveβs MathUtils.calculateCompoundedInterest() uses a 3-term Taylor approximation instead (see Module 4) β faster but less precise for large exponents.
π Summary: CDP Model and MakerDAO
β Covered:
- CDP model: mint stablecoins against collateral (not lending from a pool)
- MakerDAO architecture: Vat (accounting), Jug (fees), Join adapters, CDP Manager
- Precision scales: WAD (18), RAY (27), RAD (45) and why each exists
- Vault safety check:
ink Γ spot β₯ art Γ ratewith step-by-step example - Normalized debt and rate accumulator pattern (same as Module 4 lending indexes)
- Code reading strategy for the terse dss codebase
Next: Liquidation 2.0 (Dutch auctions), PSM for peg stability, and DSR/SSR for DAI demand
π‘ Liquidations, PSM, and DAI Savings Rate
π» Quick Try:
Before studying the liquidation architecture, read live auction state on a mainnet fork:
IDog dog = IDog(0x135954d155898D42C90D2a57824C690e0c7BEf1B);
IClipper clipper = IClipper(0xc67963a226eddd77B91aD8c421630A1b0AdFF270); // ETH-A Clipper
// Check if there are any active auctions
uint256 count = clipper.count();
emit log_named_uint("Active ETH-A auctions", count);
// Read the circuit breaker state
(uint256 Art,,, uint256 line,) = IVat(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B).ilks("ETH-A");
(, uint256 chop, uint256 hole, uint256 dirt) = dog.ilks("ETH-A");
emit log_named_uint("Liquidation penalty (chop, RAY)", chop); // e.g., 1.13e27 = 13% penalty
emit log_named_uint("Per-ilk auction cap (hole, RAD)", hole);
emit log_named_uint("DAI currently in auctions (dirt, RAD)", dirt);
Even if count is 0 (no active auctions), youβll see the circuit breaker parameters β hole caps how much DAI can be raised simultaneously, preventing the cascade that caused Black Thursday.
π‘ Concept: Liquidation 2.0: Dutch Auctions
MakerDAOβs original liquidation system (Liquidation 1.2) used English auctions β participants bid DAI in increasing amounts, with capital locked for the duration. This was slow and capital-inefficient, and it catastrophically failed on βBlack Thursdayβ (March 12, 2020) when network congestion prevented liquidation bots from bidding, allowing attackers to win auctions for $0 and causing $8.3 million in bad debt.
π Deep Dive: Black Thursday Timeline (March 12, 2020)
Timeline (UTC):
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 06:00 ETH at ~$193. Markets stable. MakerDAO holds $700M+ in vaults. β
β β
β 08:00 COVID panic sell-off begins. ETH starts sliding. β
β β
β 10:00 ETH drops below $170. First liquidations trigger. β
β β Ethereum gas prices spike 10-20x (>200 gwei) β
β β
β 12:00 ETH hits $130. Massive cascade of vault liquidations. β
β β Network congestion: liquidation bots can't get txs mined β
β β Keeper bids fail with "out of gas" or stuck in mempool β
β β
β 13:00 KEY MOMENT: Auctions complete with ZERO bids. β
β β Attackers win collateral for 0 DAI β
β β Protocol takes 100% loss on those vaults β
β β English auction requires active bidders β none could bid β
β β
β 14:00 ETH bottoms near $88. Total of 1,200+ vaults liquidated. β
β $8.3 million in DAI left unbacked (bad debt). β
β β
β Post- MakerDAO auctions MKR tokens to cover the deficit. β
β crisis Governance votes for Liquidation 2.0 redesign. β
β English auctions β Dutch auctions (no bidders needed) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Root causes:
1. English auction REQUIRES competing bidders β no bidders = $0 wins
2. Network congestion prevented keeper bots from submitting bids
3. Capital lockup: bids locked DAI for auction duration β less liquidity
4. No circuit breakers: all liquidations fired simultaneously
Why this matters for protocol designers: Any mechanism that relies on external participants acting in real-time (bidding, liquidating, updating) can fail when the network is congested β which is exactly when these mechanisms are needed most. This is the βcoincidence of needsβ problem in DeFi crisis design.
Liquidation 2.0 replaced English auctions with Dutch auctions:
Dog β The liquidation trigger contract (replaces the old βCatβ). When a Vault is unsafe:
- Keeper calls
Dog.bark(ilk, urn, kpr) - Dog calls
Vat.grab()to seize the Vaultβs collateral and debt - Dog calls
Clipper.kick()to start a Dutch auction - Keeper receives a small incentive (
tip+chippercentage of the tab)
Clipper β The Dutch auction contract (one per collateral type). Each auction:
- Starts at a high price (oracle price Γ
bufmultiplier, e.g., 120% of oracle price) - Price decreases over time according to a price function (
Abacus) - Any participant can call
Clipper.take()at any time to buy collateral at the current price - Instant settlement β no capital lockup, no bidding rounds
Abacus β Price decrease functions. Two main types:
LinearDecreaseβ price drops linearly over timeStairstepExponentialDecreaseβ price drops in discrete steps (e.g., 1% every 90 seconds)
π Deep Dive: Dutch Auction Price Decrease
Price
β
β β Starting price = oracle Γ buf (e.g., 120% of oracle)
β \
β \ LinearDecrease: straight line to zero
β \
β \
β \ StairstepExponentialDecrease:
β ββββββ drops 1% every 90 seconds
β βββββ
β βββββ
β βββββ
β βββββ β "cusp" floor (e.g., 40%)
β Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· β tail (max duration)
ββββββββββββββββββββββββββββββββββββββββββ Time
0 5min 10min 15min 20min
Liquidator perspective:
- At t=0: price is ABOVE market β unprofitable, nobody buys
- Price falls... falls... falls...
- At some point: price = market price β breakeven
- Price keeps falling β increasingly profitable
- Rational liquidator buys when: auction_price Γ (1 - gas%) > market_price
- First buyer wins β no gas wars like in English auctions
Why Dutch auctions fix Black Thursday:
- No capital lockup β buy instantly, no bidding rounds
- Flash loan compatible β borrow DAI β buy collateral β sell collateral β repay
- Natural price discovery β the falling price finds the market clearing level
- MEV-compatible β composable with other DeFi operations (β Module 5 flash loans)
Circuit breakers:
tailβ maximum auction duration before reset requiredcuspβ minimum price (% of starting price) before reset requiredhole/Holeβ maximum total DAI being raised in auctions (per-ilk and global). Prevents runaway liquidation cascades.
The Dutch auction design fixes Black Thursdayβs problems: no capital lockup means participants can use flash loans, settlement is instant (composable with other DeFi operations), and the decreasing price naturally finds the market clearing level.
π Deep Dive: Dutch Auction Liquidation β Numeric Walkthrough
Setup:
Vault: 10 ETH collateral, 15,000 DAI debt (normalized art = 15,000, rate = 1.0)
ETH price drops from $2,000 β $1,800
Liquidation ratio: 150% β spot = $1,800 / 1.5 = $1,200 (RAY)
Safety check: ink Γ spot = 10 Γ $1,200 = $12,000 < art Γ rate = $15,000 β UNSAFE
Step 1: Keeper calls Dog.bark(ETH-A, vault_owner, keeper_address)
β Vat.grab() seizes collateral: 10 ETH moved to Clipper, 15,000 DAI of sin created
β tab (total to recover) = art Γ rate Γ chop = 15,000 Γ 1.0 Γ 1.13 = 16,950 DAI
(chop = 1.13 RAY = 13% liquidation penalty)
β Keeper receives: tip (flat, e.g., 300 DAI) + chip (% of tab, e.g., 0.1% = 16.95 DAI)
Step 2: Clipper.kick() starts Dutch auction
β Starting price (top) = oracle_price Γ buf = $1,800 Γ 1.20 = $2,160 per ETH
(buf = 1.20 = start 20% above oracle to ensure initial price is above market)
β lot = 10 ETH (collateral for sale)
β tab = 16,950 DAI (amount to recover)
Step 3: Price decreases over time (StairstepExponentialDecrease)
β t=0: price = $2,160 (above market β nobody buys)
β t=90s: price = $2,138 (1% drop per step)
β t=180s: price = $2,117
β t=270s: price = $2,096
β ...
β t=900s: price = $1,953 (still above market $1,800)
β t=1800s: price = $1,767 (now BELOW market β profitable!)
($2,160 Γ 0.99^20 = $2,160 Γ 0.8179 = $1,767)
Step 4: Liquidator calls Clipper.take() at t=1800s (price = $1,767)
β Liquidator offers: 16,950 DAI (the full tab)
β Collateral received: 16,950 / $1,767 = 9.59 ETH
β Remaining: 10 - 9.59 = 0.41 ETH returned to vault owner
Liquidator P&L (with flash loan from Balancer):
Received: 9.59 ETH
Sell on DEX at $1,800: 9.59 Γ $1,800 = $17,262
After 0.3% swap fee: $17,262 Γ 0.997 = $17,210
Repay flash loan: 16,950 DAI
Profit: $17,210 - $16,950 = $260
Gas: ~$10-30
Net: ~$230-250
Vault owner outcome:
Lost: 9.59 ETH ($17,262 at market price)
Recovered: 0.41 ETH ($738)
Penalty paid: 9.59 Γ $1,800 - 15,000 = $2,262 (~15.1% effective penalty)
β The 13% chop + buying below market price = higher effective cost
Protocol outcome:
Recovered: 16,950 DAI (covers 15,000 debt + 1,950 penalty)
The 1,950 DAI penalty goes to Vow (protocol surplus buffer)
sin (bad debt) cleared via Vat.heal()
Key insight: The liquidator doesnβt need to wait for the absolute best price β they just need auction_price < market_price - swap_fees - gas. The competition between liquidators (and MEV searchers) pushes the buy time earlier, reducing the vault ownerβs penalty. More competition = better outcomes for everyone except the liquidator margins.
π‘ Concept: Peg Stability Module (PSM)
The PSM allows 1:1 swaps between DAI and approved stablecoins (primarily USDC) with a small fee (typically 0%). It serves as the primary peg maintenance mechanism:
- If DAI > $1: Users swap USDC β DAI at 1:1, increasing DAI supply, pushing price down
- If DAI < $1: Users swap DAI β USDC at 1:1, decreasing DAI supply, pushing price up
The PSM is controversial because it makes DAI heavily dependent on USDC (a centralized stablecoin). At various points, over 50% of DAIβs backing has been USDC through the PSM. This tension β decentralization vs peg stability β is one of the fundamental challenges in stablecoin design.
Contract architecture: The PSM is essentially a special Vault type that accepts USDC (or other stablecoins) as collateral at a 100% collateral ratio and auto-generates DAI. The tin (fee in) and tout (fee out) parameters control the swap fees in each direction.
π‘ Concept: Dai Savings Rate (DSR)
The DSR lets DAI holders earn interest by locking DAI in the Pot contract. The interest comes from stability fees paid by Vault owners β itβs a mechanism to increase DAI demand (and thus support the peg) by making holding DAI attractive.
Pot contract: Users call Pot.join() to lock DAI and Pot.exit() to withdraw. Accumulated interest is tracked via a rate accumulator (same pattern as stability fees). The DSR is set by governance as a monetary policy tool.
Sky Savings Rate (SSR): The Sky rebrand introduced a parallel savings rate for USDS using an ERC-4626 vault (sUSDS). This is significant because ERC-4626 is the standard vault interface β meaning sUSDS is natively composable with any protocol that supports ERC-4626.
π‘ Concept: The Sky Rebrand: What Changed
In September 2024, MakerDAO rebranded to Sky Protocol. Key changes:
- DAI β USDS (1:1 convertible, both remain active)
- MKR β SKY (1:24,000 conversion ratio)
- SubDAOs β βStarsβ (Spark Protocol is the first Star β a lending protocol built on top of Sky)
- USDS adds a freeze function for compliance purposes (controversial in the community)
- SSR uses ERC-4626 standard
The underlying protocol mechanics (Vat, Dog, Clipper, etc.) remain the same. For this module, weβll use the original MakerDAO naming since thatβs what the codebase uses.
π Read: Dog.sol and Clipper.sol
Source: dss/src/dog.sol and dss/src/clip.sol
In Dog.bark(), trace:
- How the Vault is validated as unsafe
- The
grabcall that seizes collateral - How the
tab(debt + liquidation penalty) is calculated - The circuit breaker checks (
Hole/hole,Dirt/dirt)
In Clipper.kick(), trace:
- How the starting price is set (oracle price Γ buf)
- The auction state struct
- How
take()works: price calculation via Abacus, partial fills, refunds
π How to Study MakerDAO Liquidation 2.0
-
Start with
Dog.bark()β This is the entry point. Trace: how does it verify the vault is unsafe? (CallsVat.urns()andVat.ilks(), checksink Γ spot < art Γ rate.) How does it callVat.grab()to seize collateral? How does it compute thetab(total debt including penalty)? -
Read
Clipper.kick()β Afterbark()seizes collateral,kick()starts the auction. Focus on: howtop(starting price) is computed asoracle_price Γ buf, how the auction struct stores the state, and how the keeper incentive (tip+chip) is calculated and paid. -
Understand the Abacus price functions β Read
LinearDecreasefirst (simpler: price drops linearly over time). Then readStairstepExponentialDecrease(price drops in discrete steps). The key question: given atab(debt to cover) and the current auction price, how much collateral does thetake()caller receive? -
Trace a complete
take()call β This is where collateral is actually sold. Follow: price lookup via Abacus β compute collateral amount for the DAI offered β handle partial fills (buyer wants less than the full lot) β refund excess collateral to the vault owner β cancelsinviaVat.heal(). -
Study the circuit breakers β
tail(max auction duration),cusp(min price before reset),Hole/hole(max simultaneous DAI in auctions). These exist because of Black Thursday β without caps, a cascade of liquidations can overwhelm the system.
Donβt get stuck on: The redo() function initially β itβs for restarting stale auctions. Understand bark() β kick() β take() first, then come back to redo() and the edge cases.
π― Build Exercise: Liquidations and PSM
Workspace: workspace/src/part2/module6/exercise3-simple-dog/ β starter file: SimpleDog.sol, tests: SimpleDog.t.sol | Also: SimplePSM.sol, tests: SimplePSM.t.sol
Exercise 1: On a mainnet fork, simulate a liquidation:
- Open a Vault with ETH collateral near the liquidation ratio
- Mock the oracle to drop the price below the liquidation threshold
- Call
Dog.bark()to start the auction - Call
Clipper.take()to buy the collateral at the current auction price - Verify: correct amount of DAI paid, correct collateral received, debt cleared
Exercise 2: Read the PSM contract. Execute a USDC β DAI swap through the PSM on a mainnet fork. Verify the 1:1 conversion and fee application.
πΌ Job Market Context
What DeFi teams expect you to know:
-
βWhy did MakerDAO switch from English to Dutch auctions?β
- Good answer: English auctions were slow and required capital lockup
- Great answer: Black Thursday proved English auctions fail under network congestion β bots couldnβt bid, allowing $0 wins and $8.3M bad debt. Dutch auctions fix this: instant settlement, flash-loan compatible, no bidding rounds. The falling price naturally finds the market clearing level.
-
βWhatβs the trade-off of the PSM?β
- Good answer: It stabilizes the peg but makes DAI dependent on USDC
- Great answer: The PSM creates a hard peg floor/ceiling but at the cost of centralization β at peak, >50% of DAI was backed by USDC through the PSM. This is the stablecoin trilemma in action: DAI chose stability over decentralization. The Sky rebrand introduced USDS with a freeze function, pushing further toward the centralized end.
Interview Red Flags:
- π© Not knowing how CDPs differ from lending (minting vs redistributing)
- π© Confusing normalized debt (
art) with actual debt (art Γ rate) - π© Thinking liquidation auctions are the same as in traditional finance
Pro tip: If you can trace a frob() call through the Vatβs safety checks and explain each parameter, you demonstrate deeper protocol knowledge than 95% of DeFi developers. MakerDAOβs architecture shows up directly in interview questions at top DeFi teams.
π Summary: Liquidations, PSM, and DSR
β Covered:
- Liquidation 2.0: Dutch auctions (Dog + Clipper), why they replaced English auctions
- Auction price functions: LinearDecrease vs StairstepExponentialDecrease
- Circuit breakers: tail, cusp, Hole/hole β preventing liquidation cascades
- PSM: 1:1 peg stability, centralization trade-off, tin/tout fees
- DSR/SSR: demand-side lever for peg maintenance
- Sky rebrand: DAIβUSDS, MKRβSKY, sUSDS as ERC-4626
Next: Building a simplified CDP engine from scratch
π― Build Exercise: Simplified CDP Engine
SimpleCDP.sol
The exercises across this module build a minimal CDP system that captures the essential mechanisms: SimpleVat (accounting engine with frob/fold/grab), SimpleJug (stability fee compounding via rpow and drip), SimpleDog (liquidation trigger + Dutch auction), and SimplePSM (peg stability swaps with fee). Shared contracts (join adapters, stablecoin ERC-20, math library) are pre-built.
Workspace: All exercise scaffolds and tests are in the exercise folders linked from the Day 1, Day 2, and Day 4 exercise sections above. Each exercise has a scaffold with TODOs and a complete test suite.
π Summary: SimpleCDP
β Covered:
- Building the core CDP contracts: SimpleVat, SimpleJug, SimpleDog, SimplePSM
- Implementing the vault safety check, stability fee accumulator, and liquidation trigger
- Testing: full lifecycle, fee accrual, liquidation, debt ceiling, dust check, multi-collateral
Next: Comparing stablecoin designs across the landscape β overcollateralized, algorithmic, delta-neutral
π‘ Stablecoin Landscape and Design Trade-offs
π‘ Concept: Taxonomy of Stablecoins
1. Fiat-backed (USDC, USDT) β Centralized issuer holds bank deposits or T-bills equal to the stablecoin supply. Simple, stable, but requires trust in the issuer and is subject to censorship (addresses can be blacklisted).
2. Overcollateralized crypto-backed (DAI/USDS, LUSD) β Protocol holds >100% crypto collateral. Decentralized and censorship-resistant (depending on collateral composition), but capital-inefficient (you need $150+ of ETH to mint $100 of stablecoins).
3. Algorithmic (historical: UST/LUNA, FRAX, ESD, BAC) β Attempt to maintain peg through algorithmic supply adjustment without full collateral backing. Most have failed catastrophically.
4. Delta-neutral / yield-bearing (USDe by Ethena) β Holds crypto collateral and hedges price exposure using perpetual futures short positions. The yield comes from positive funding rates. Novel design but carries exchange counterparty risk and funding rate reversal risk.
π‘ Concept: Liquity: A Different CDP Design
Liquity (LUSD) takes a minimalist approach compared to MakerDAO:
Key differences from MakerDAO:
- No governance. Parameters are immutable once deployed. No governance token, no parameter changes.
- One-time fee instead of ongoing stability fee. Users pay a fee at borrowing time (0.5%β5%, adjusted algorithmically based on redemption activity). No interest accrues.
- 110% minimum collateral ratio. Much more capital-efficient than MakerDAOβs typical 150%+.
- ETH-only collateral. No multi-collateral complexity.
- Redemption mechanism: Any LUSD holder can redeem LUSD for $1 worth of ETH from the riskiest Vault (lowest collateral ratio). This creates a hard price floor.
- Stability Pool: LUSD holders can deposit into the Stability Pool, which automatically absorbs liquidated collateral at a discount. No auction needed β liquidation is instant.
Liquity V2 (2024-25): Introduces user-set interest rates (borrowers bid their own rate), multi-collateral support (LSTs like wstETH, rETH), and a modified redemption mechanism.
π Deep Dive: Liquity Redemption β Numeric Walkthrough
Scenario: LUSD trades at $0.97 on DEXes. An arbitrageur spots the opportunity.
System state β all active Troves, sorted by collateral ratio (ascending):
Trove A: 2 ETH collateral, 2,800 LUSD debt β CR = (2 Γ $2,000) / 2,800 = 142.8%
Trove B: 3 ETH collateral, 3,500 LUSD debt β CR = (3 Γ $2,000) / 3,500 = 171.4%
Trove C: 5 ETH collateral, 4,000 LUSD debt β CR = (5 Γ $2,000) / 4,000 = 250.0%
Step 1: Arbitrageur buys 3,000 LUSD on DEX at $0.97
Cost: 3,000 Γ $0.97 = $2,910
Step 2: Call redeemCollateral(3,000 LUSD)
System starts with the RISKIEST Trove (lowest CR = Trove A)
β Trove A: has 2,800 LUSD debt
Redeem 2,800 LUSD β receive $2,800 worth of ETH = 2,800 / $2,000 = 1.4 ETH
Trove A: CLOSED (0 debt, 0.6 ETH returned to Trove A owner)
Remaining to redeem: 3,000 - 2,800 = 200 LUSD
β Trove B: has 3,500 LUSD debt (next riskiest)
Redeem 200 LUSD β receive $200 worth of ETH = 200 / $2,000 = 0.1 ETH
Trove B: debt reduced to 3,300, collateral reduced to 2.9 ETH
CR now = (2.9 Γ $2,000) / 3,300 = 175.8% (improved!)
Remaining: 0 LUSD β
Step 3: Redemption fee (0.5% base, increases with volume)
Fee = 0.5% Γ 3,000 = 15 LUSD (deducted in ETH: 0.0075 ETH)
Total ETH received: 1.4 + 0.1 - 0.0075 = 1.4925 ETH
Step 4: Arbitrageur P&L
Received: 1.4925 ETH Γ $2,000 = $2,985
Cost: $2,910 (buying LUSD on DEX)
Profit: $75 (2.58% return)
This profit closes the peg gap:
- 3,000 LUSD bought on DEX β buying pressure β LUSD price rises toward $1
- 3,000 LUSD burned via redemption β supply decreases β further price support
Why riskiest-first? It improves system health β the lowest-CR troves pose the most risk. Redemption either closes them or pushes their CR higher. The base fee (0.5%, rising with redemption volume) prevents excessive redemptions that would disrupt healthy vaults.
The peg guarantee: Redemptions create a hard floor at ~$1.00 (minus the fee). If LUSD trades at $0.97, anyone can profit by redeeming. This arbitrage force pushes the price back up. The ceiling is softer β at ~$1.10 (the minimum CR), it becomes attractive to open new Troves and sell LUSD.
β οΈ The Algorithmic Stablecoin Failure Pattern
UST/LUNA (Terra, May 2022) is the canonical example. The mechanism:
- UST was pegged to $1 via an arbitrage loop with LUNA
- When UST > $1: burn $1 of LUNA to mint 1 UST (increase supply, push price down)
- When UST < $1: burn 1 UST to mint $1 of LUNA (decrease supply, push price up)
The death spiral: when confidence in UST dropped, holders rushed to redeem UST for LUNA. Massive LUNA minting cratered LUNAβs price, which reduced the backing for UST, causing more redemptions, more LUNA minting, more price collapse. $40+ billion in value was destroyed in days.
The lesson: Without external collateral backing, algorithmic stablecoins rely on reflexive confidence. When confidence breaks, thereβs nothing to stop the spiral. Every algorithmic stablecoin that relies purely on its own governance/seigniorage token for backing has either failed or abandoned that model.
π‘ Concept: Ethena (USDe): The Delta-Neutral Model
Ethena mints USDe against crypto collateral (primarily staked ETH) and simultaneously opens a short perpetual futures position of equal size. The net exposure is zero (delta-neutral), meaning the collateral value doesnβt change with ETH price movements.
How it works:
- User deposits stETH (or ETH, which gets staked)
- Ethena opens an equal-sized short perpetual position on centralized exchanges
- ETH price goes up β collateral gains, short loses β net zero
- ETH price goes down β collateral loses, short gains β net zero
- Revenue: staking yield (~3-4%) + funding rate income (shorts get paid when funding is positive)
Revenue breakdown:
- Staking yield: ~3-4% APR (consistent)
- Funding rate: historically ~8-15% APR average, but highly variable
- Combined: sUSDe has offered 15-30%+ APR at times
Risk factors:
- Funding rate reversal: In bear markets, funding rates go negative (shorts PAY longs). Ethenaβs insurance fund covers short periods, but prolonged negative funding would erode backing. During the 2022 bear market, funding was negative for months.
- Exchange counterparty risk: Positions are on centralized exchanges (Binance, Bybit, Deribit) via custodians (Copper, Ceffu). If an exchange fails or freezes, positions canβt be managed.
- Basis risk: Spot price and futures price can diverge, creating temporary unbacking.
- Custodian risk: Assets held in βoff-exchange settlementβ custody, not directly on exchanges.
- Insurance fund: ~$50M+ reserve for negative funding periods. If depleted, USDe backing degrades.
π Connection: The funding rate mechanics here connect directly to Part 3 Module 2 (Perpetuals), where youβll study how funding rates work in detail. Ethena is essentially using a DeFi primitive (perpetual funding) as a stablecoin backing mechanism.
π‘ Concept: GHO: Aaveβs Native Stablecoin
GHO is a decentralized stablecoin minted directly within Aave V3. It extends the lending protocol with stablecoin issuance β users who already have collateral in Aave can mint GHO against it without removing their collateral from the lending pool.
Key design choices:
- No separate CDP system β GHO uses Aave V3βs existing collateral and liquidation infrastructure
- Facilitators β entities authorized to mint/burn GHO. Aave V3 Pool is the primary facilitator, but others can be added (e.g., a flash mint facilitator)
- Interest rate is governance-set β not algorithmic. MakerDAOβs stability fee is also governance-set, but Aaveβs rate doesnβt depend on utilization since thereβs no supply pool
- stkAAVE discount β AAVE stakers get a discount on GHO borrow rates (incentivizes AAVE staking)
- Built on existing battle-tested infrastructure β Aaveβs oracle, liquidation, and risk management systems
Why it matters: GHO shows how a lending protocol can evolve into a stablecoin issuer without building separate infrastructure. Since you studied Aave V3 in Module 4, GHO is a natural extension of that knowledge.
π‘ Concept: crvUSD: Curveβs Soft-Liquidation Model (LLAMMA)
Curveβs stablecoin crvUSD introduces a novel liquidation mechanism called LLAMMA (Lending-Liquidating AMM Algorithm) that replaces the traditional discrete liquidation threshold with continuous soft liquidation.
How LLAMMA works:
- Collateral is deposited into a special AMM (not a regular lending pool)
- As collateral price drops, the AMM automatically converts collateral to crvUSD (soft liquidation)
- As price recovers, the AMM converts back from crvUSD to collateral (de-liquidation)
- No sudden liquidation event β instead, a gradual, continuous transition
- Borrower keeps their position throughout (unless price drops too far)
Why itβs novel:
- Traditional CDPs: price hits threshold β entire position liquidated β penalty applied β user loses collateral
- LLAMMA: price drops β collateral gradually converts β price recovers β collateral converts back
- Reduces liquidation losses for borrowers (no penalty on soft liquidation)
- Reduces bad debt risk for the protocol (continuous adjustment vs sudden cascade)
Trade-offs:
- During soft liquidation, the AMM-converted position earns less than holding pure collateral (similar to impermanent loss in an LP position)
- More complex than traditional liquidation β harder to reason about and audit
- LLAMMA pools need liquidity to function properly
π Connection: crvUSDβs LLAMMA is essentially an AMM (Module 2) repurposed as a liquidation mechanism (Module 4). It shows how DeFi primitives can be combined in unexpected ways.
π‘ Concept: FRAX: The Evolution from Algorithmic to Fully Backed
FRAX started as a βfractional-algorithmicβ stablecoin β partially backed by collateral (USDC) and partially by its governance token (FXS). The collateral ratio would adjust algorithmically based on market conditions.
The evolution:
- V1 (2020): Fractional-algorithmic β e.g., 85% USDC + 15% FXS backing
- V2 (2022): Moved toward 100% collateral ratio after algorithmic stablecoins collapsed (Terra)
- V3 / frxETH (2023+): Pivoted to liquid staking (frxETH, sfrxETH) and became fully collateralized
The lesson: The algorithmic component was abandoned because it created the same reflexive risk as Terra β when confidence drops, the algorithmic portion amplifies the problem. FRAXβs evolution mirrors the industryβs consensus: full collateral backing is necessary.
π Connection: frxETH and sfrxETH are liquid staking tokens covered in Part 3 Module 1. FRAXβs pivot illustrates how stablecoin protocols evolve toward safety.
Design Trade-off Matrix
| Property | DAI/USDS | LUSD | GHO | crvUSD | USDe | USDC |
|---|---|---|---|---|---|---|
| Decentralization | Medium | High | Medium | Medium | Low | Low |
| Capital efficiency | Low (150%+) | Medium (110%) | Low (Aave LTVs) | Medium (soft liq.) | ~1:1 | 1:1 |
| Peg stability | Strong (PSM) | Good (redemptions) | Moderate | Moderate | Good | Very strong |
| Yield | DSR/SSR | None | Discount for stkAAVE | None | High (15%+) | None |
| Liquidation | Dutch auction | Stability Pool | Aave standard | Soft (LLAMMA) | N/A | N/A |
| Failure mode | Bad debt, USDC dep. | Bad debt | Same as Aave | LLAMMA IL | Funding reversal | Regulatory |
π‘ Concept: The Fundamental Trilemma
Stablecoins face a trilemma between:
- Decentralization β no central point of failure or censorship
- Capital efficiency β not requiring significantly more collateral than the stablecoins minted
- Peg stability β maintaining a reliable $1 value
No design achieves all three. DAI sacrifices efficiency for decentralization and stability. USDC sacrifices decentralization for efficiency and stability. Algorithmic designs attempt efficiency and decentralization but sacrifice stability (and typically fail).
Understanding this trilemma is essential for evaluating any stablecoin design you encounter or build.
π Deep Dive: Peg Mechanism Comparison
How does each stablecoin actually maintain its $1 peg? The mechanisms are fundamentally different:
When price > $1 When price < $1
(too much demand) (too much supply)
ββββββββββββ¬ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β DAI/USDS β PSM: swap USDC β DAI at 1:1 β PSM: swap DAI β USDC at 1:1 β
β β β supply β price falls β β supply β price rises β
β β Also: lower stability fee β β Also: raise stability fee β β
β β more vaults open β more DAI β vaults close β less DAI β
ββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ€
β LUSD β Anyone can open vault at 110% β Redemption: burn 1 LUSD β β
β β cheap to mint β more supply β get $1 of ETH from riskiest β
β β (soft ceiling at $1.10) β vault (hard floor at $1.00) β
ββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ€
β GHO β Governance lowers borrow rate β Governance raises borrow rate β
β β β more minting β more supply β β repayment β less supply β
β β (slower response than PSM) β (slower response than PSM) β
ββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ€
β crvUSD β Minting via LLAMMA pools β PegKeeper contracts buy crvUSD β
β β + PegKeepers sell crvUSD β from pools, reducing supply β
β β into Curve pools β (automated, no governance) β
ββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ€
β USDe β Arbitrage: mint USDe at $1, β Arbitrage: buy USDe < $1, β
β β sell at market for > $1 β redeem for $1 of backing β
β β (requires whitelisting) β (requires whitelisting) β
ββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ€
β USDC β Circle mints new USDC for $1 β Circle redeems USDC for $1 bank β
β β bank deposit β transfer β
β β (centralized, instant) β (centralized, 1-3 business days)β
ββββββββββββ΄ββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββ
Speed of peg restoration:
USDC β PSM (DAI) > Redemption (LUSD) > PegKeeper (crvUSD) > Arb (USDe) > Governance (GHO)
β faster slower β
The pattern: Faster peg restoration requires either centralization (USDC, PSMβs USDC dependency) or capital lock-up (Liquityβs 110% CR). Slower mechanisms preserve decentralization but risk prolonged depegs during stress.
π― Build Exercise: Stablecoin Design Trade-offs
Workspace: workspace/test/part2/module6/exercise4b-peg-dynamics/ β test-only exercise: PegDynamics.t.sol (tests PSM peg restoration, reserve depletion, stability fee dynamics, and the fee/PSM feedback loop β requires SimplePSM and SimpleJug to be implemented)
Exercise 1: Liquity analysis. Read Liquityβs TroveManager.sol and StabilityPool.sol. Compare the liquidation mechanism (Stability Pool absorption) to MakerDAOβs Dutch auctions. Which is simpler? Which handles edge cases better? Write a comparison document.
Exercise 2: Peg stability simulation. Using your SimpleCDP from the Build exercise, simulate peg pressure scenarios:
- What happens when collateral prices crash 30%? Model the liquidation volume and its impact.
- What happens when demand for the stablecoin exceeds Vault creation? The price goes above $1. Show how the PSM resolves this.
- What happens if the PSMβs USDC reserves are depleted? The stablecoin trades above $1 with no easy correction.
Exercise 3: Stability fee as monetary policy. Using your Jug implementation, model how changing the stability fee affects Vault behavior:
- High fee β users repay and close Vaults β stablecoin supply decreases β price pressure upward
- Low fee β users open Vaults β stablecoin supply increases β price pressure downward
- The DSR works in reverse: high DSR β more DAI locked β supply decreases β price up
Map out the feedback loops. This is how decentralized monetary policy works.
πΌ Job Market Context β Module-Level Interview Prep
What DeFi teams expect you to know:
-
βExplain the stablecoin trilemma and where DAI sitsβ
- Good answer: DAI trades off capital efficiency for decentralization and stability
- Great answer: DAI started decentralized (ETH-only collateral) but the PSM made it USDC-dependent for better stability. The Sky rebrand pushed further toward centralization with USDSβs freeze function. Liquity V1 sits at the opposite extreme β fully decentralized and immutable, but less capital-efficient and narrower collateral. No design achieves all three.
-
βHow does crvUSDβs LLAMMA differ from traditional liquidation?β
- Good answer: It gradually converts collateral instead of a sudden liquidation event
- Great answer: LLAMMA is essentially an AMM where your collateral IS the liquidity. As price drops, the AMM sells your collateral for crvUSD automatically. If price recovers, it buys back. This eliminates the discrete liquidation penalty but introduces AMM-like impermanent loss during soft liquidation. Itβs a fundamentally different paradigm β continuous adjustment vs threshold-triggered liquidation.
-
βWhatβs the main risk with Ethenaβs USDe?β
- Good answer: Funding rates can go negative
- Great answer: Three correlated risks: (1) prolonged negative funding drains the insurance fund and erodes backing, (2) centralized exchange counterparty risk β positions are on Binance/Bybit/Deribit via custodians, (3) during a black swan, all three risks compound simultaneously (funding reversal + exchange stress + basis blowout). The model works great in bull markets with positive funding but hasnβt been tested through a severe extended downturn with the current AUM.
Interview Red Flags:
- π© Thinking algorithmic stablecoins without external collateral can work (Terra killed this thesis)
- π© Not knowing the difference between a CDP and a lending protocol
- π© Inability to explain how stability fees act as monetary policy
- π© Not recognizing that MakerDAOβs terse naming convention exists (demonstrates you havenβt read the code)
Pro tip: The stablecoin landscape is one of the most interview-relevant DeFi topics because it touches everything β oracles, liquidation, governance, monetary policy, risk management. Being able to compare MakerDAO vs Liquity vs Ethena design trade-offs demonstrates systems-level thinking that teams value highly.
π Summary: Stablecoin Landscape
β Covered:
- Stablecoin taxonomy: fiat-backed, overcollateralized, algorithmic, delta-neutral
- MakerDAO vs Liquity: governance vs immutability, Dutch auction vs Stability Pool
- GHO: stablecoin built on existing lending infrastructure (Aave V3)
- crvUSD: novel soft-liquidation via LLAMMA (AMM-based continuous adjustment)
- FRAX evolution: fractional-algorithmic β fully collateralized (lesson from Terra)
- Ethena USDe: delta-neutral hedging with funding rate revenue and its risks
- The fundamental trilemma: decentralization vs capital efficiency vs stability
- Terra collapse as the definitive failure case for uncollateralized algorithmic designs
Internalized patterns: CDPs mint money (create new stablecoins, closer to central banking than commercial banking). The Vat is the source of truth (frob() and normalized debt art x rate). Liquidation design is existential (Black Thursday proved auction mechanics can fail; Dutch auction vs Stability Pool vs LLAMMA). Peg stability requires trade-offs (PSM = stability + centralization, redemptions = decentralization + less capital-efficiency). Algorithmic stablecoins without external collateral fail (Terra $40B collapse). rpow() and rate accumulators are the mathematical backbone (per-second compounding via exponentiation by squaring). Stablecoins are the ultimate integration test (oracles, lending, liquidation, governance, AMMs). The landscape is diversifying (GHO, crvUSD, Ethena USDe each recombine DeFi primitives differently).
Next: Module 7 β ERC-4626 tokenized vaults, yield aggregation, inflation attacks
β οΈ Common Mistakes
Mistake 1: Confusing normalized debt (art) with actual debt (art Γ rate)
// β WRONG β reading art directly as the debt amount
(uint256 ink, uint256 art) = vat.urns(ilk, user);
uint256 debtOwed = art; // This is NORMALIZED debt, not actual
// β
CORRECT β multiply by the rate accumulator
(uint256 ink, uint256 art) = vat.urns(ilk, user);
(, uint256 rate,,,) = vat.ilks(ilk);
uint256 debtOwed = art * rate / RAY; // Actual debt in WAD
After years of accumulated stability fees, rate might be 1.15e27 (15% total fees). Reading art directly would understate the debt by 15%. This same mistake applies to Aaveβs scaledBalance vs actual balance.
Mistake 2: Forgetting to call drip() before reading or modifying debt
// β WRONG β rate is stale (hasn't been updated since last drip)
(, uint256 rate,,,) = vat.ilks(ilk);
uint256 debtOwed = art * rate / RAY; // Could be hours/days stale
// β
CORRECT β drip first to update the rate accumulator
jug.drip(ilk); // Updates rate to current timestamp
(, uint256 rate,,,) = vat.ilks(ilk);
uint256 debtOwed = art * rate / RAY; // Accurate to this block
If nobody has called drip() for a week, the rate is a week stale. Any debt calculation, vault safety check, or liquidation trigger that reads the stale rate will be wrong. In practice, keepers and frontends call drip() before state-changing operations. Your contracts should too.
Mistake 3: PSM decimal mismatch (USDC is 6 decimals, DAI is 18)
// β WRONG β treating USDC and DAI amounts as the same scale
uint256 usdcAmount = 1000e18; // 10^21 base units Γ· 10^6 decimals = 10^15 USDC ($1 quadrillion!)
psm.sellGem(address(this), usdcAmount);
// β
CORRECT β USDC uses 6 decimals
uint256 usdcAmount = 1000e6; // 1000 USDC ($1,000)
psm.sellGem(address(this), usdcAmount);
// The PSM internally handles the 6β18 decimal conversion via GemJoin
The PSMβs GemJoin adapter handles the decimal conversion (gem_amount * 10^(18-6)), but you must pass the USDC amount in USDCβs native 6-decimal scale. Passing 18-decimal amounts will either overflow or swap vastly more than intended.
Mistake 4: Not checking the dust minimum when partially repaying
// β WRONG β partial repay leaves vault below dust threshold
// Vault has 10,000 DAI debt, dust = 5,000 DAI
vat.frob(ilk, address(this), address(this), address(this), 0, -int256(8000e18));
// Tries to reduce debt to 2,000 DAI β REVERTS (below dust of 5,000)
// β
CORRECT β either repay fully (dart = -art) or keep above dust
// Option A: Full repay
vat.frob(ilk, ..., 0, -int256(art)); // Close to 0 debt
// Option B: Stay above dust
vat.frob(ilk, ..., 0, -int256(5000e18)); // Leaves 5,000 DAI β₯ dust
The dust parameter prevents tiny vaults whose gas costs for liquidation would exceed the recovered value. When a vault has debt, it must be either 0 or β₯ dust. This catches developers who try to partially repay without checking.
π Cross-Module Concept Links
β Backward References (Part 1 + Modules 1β5)
| Source | Concept | How It Connects |
|---|---|---|
| Part 1 Module 1 | mulDiv / fixed-point math | WAD/RAY/RAD arithmetic throughout the Vat; rmul/rpow for stability fee compounding |
| Part 1 Module 1 | Custom errors | Production CDP contracts use custom errors for vault safety violations, ceiling breaches |
| Part 1 Module 2 | Transient storage | Modern CDP implementations can use TSTORE for reentrancy guards during liquidation callbacks |
| Part 1 Module 5 | Fork testing / vm.mockCall | Essential for testing against live MakerDAO state and simulating oracle price drops for liquidation |
| Part 1 Module 5 | Invariant testing | Property-based testing for CDP invariants: total debt β€ total DAI, all vaults safe, rate monotonicity |
| Part 1 Module 6 | Proxy patterns | MakerDAOβs authorization system (wards/can) and join adapter pattern for upgradeable periphery |
| Module 1 | SafeERC20 / token decimals | Join adapters bridge external ERC-20 tokens to Vatβs internal accounting; decimal handling critical for multi-collateral |
| Module 1 | Fee-on-transfer awareness | Collateral join adapters must handle non-standard token behavior; PSM must handle USDCβs blacklist |
| Module 2 | AMM / Curve StableSwap | PSM uses 1:1 swap; crvUSDβs LLAMMA repurposes AMM as liquidation mechanism; Curve pools for peg monitoring |
| Module 3 | Oracle Security Module (OSM) | MakerDAO delays oracle prices by 1 hour via OSM β gives governance reaction time before liquidations |
| Module 3 | Chainlink / staleness checks | Collateral pricing for vault safety checks; oracle failure triggers emergency shutdown |
| Module 4 | Index-based accounting | Normalized debt (art Γ rate) is the same pattern as Aaveβs scaledBalance Γ liquidityIndex |
| Module 4 | Liquidation mechanics | Dutch auction (Dog/Clipper) parallels Aaveβs direct liquidation; Stability Pool parallels Compoundβs absorb |
| Module 5 | Flash loans / flash mint | Dutch auctions designed for flash loan compatibility; DssFlash mints unlimited DAI for flash borrowing |
β Forward References (Modules 7β9 + Part 3)
| Target | Concept | How Stablecoin/CDP Knowledge Applies |
|---|---|---|
| Module 7 (Yield/Vaults) | sUSDS as ERC-4626 | Sky Savings Rate packaged as standard vault interface β stablecoin meets tokenized vault |
| Module 7 (Yield/Vaults) | DSR as yield source | DAI Savings Rate and sUSDS as yield-bearing stablecoin deposits for vault strategies |
| Module 8 (Security) | CDP invariant testing | Invariant testing SimpleCDP: total debt β€ ceiling, all active vaults safe, rate accumulator monotonic |
| Module 8 (Security) | Peg stability threat model | Modeling peg attacks: PSM drain, oracle manipulation, governance parameter manipulation |
| Module 9 (Capstone) | Multi-collateral stablecoin | Building a decentralized stablecoin protocol β CDP engine, Dutch auction liquidation, flash mint, vault share collateral |
| Part 3 Module 1 (Liquid Staking) | LSTs as collateral | wstETH, rETH as CDP collateral types β requires exchange rate oracle chaining |
| Part 3 Module 2 (Perpetuals) | Funding rate mechanics | Ethenaβs USDe uses perpetual funding rates as stablecoin backing β studied in depth |
| Part 3 Module 8 (Governance) | Monetary policy governance | Governor for stability fee, DSR, debt ceiling parameter updates; governance attack surface |
π Production Study Order
Study these codebases in order β each builds on the previous oneβs patterns:
| # | Repository | Why Study This | Key Files |
|---|---|---|---|
| 1 | MakerDAO dss (Vat) | The foundational CDP engine β normalized debt, rate accumulator, frob() as the atomic vault operation | src/vat.sol |
| 2 | MakerDAO Jug | Stability fee accumulator β per-second compounding via drip(), same index pattern as lending protocols | src/jug.sol |
| 3 | MakerDAO Dog + Clipper | Liquidation 2.0 β Dutch auction mechanics, circuit breakers, keeper incentives (post-Black Thursday redesign) | src/dog.sol, src/clip.sol, src/abaci.sol |
| 4 | MakerDAO PSM | Peg Stability Module β 1:1 stablecoin swaps, tin/tout fee mechanism, centralization trade-off | src/psm.sol |
| 5 | Liquity V1 | Alternative CDP: no governance, 110% CR, Stability Pool instant liquidation, redemption mechanism | contracts/TroveManager.sol, contracts/StabilityPool.sol, contracts/BorrowerOperations.sol |
| 6 | crvUSD LLAMMA | Novel soft-liquidation via AMM β continuous collateral conversion, PegKeeper for peg maintenance | contracts/AMM.sol, contracts/Controller.sol |
Reading strategy: Start with the Vat β memorize the glossary (ilk, urn, ink, art, gem, dai, sin) and read frob() line by line. Then Jug for the fee accumulator. Dog + Clipper show the Dutch auction (trace bark() β kick() β take()). PSM is short and shows the peg mechanism. Liquity shows a radically different CDP design (no governance). crvUSD shows the frontier: AMM-based soft liquidation.
π Resources
MakerDAO/Sky:
- Technical docs
- Source code (dss)
- Vat detailed documentation
- Liquidation 2.0 (Dog & Clipper) documentation
- Developer guides
- Sky Protocol whitepaper
Liquity:
GHO:
crvUSD:
Ethena:
Stablecoin analysis:
- CDP classical design
- Terra post-mortem: Search βTerra LUNA collapse analysisβ for numerous detailed breakdowns
Black Thursday:
- MakerDAO Black Thursday post-mortem and Liquidation 2.0 rationale: MIP45 forum discussion
- ChainSecurity Liquidation 2.0 audit
Navigation: β Module 5: Flash Loans | Module 7: Vaults & Yield β