AMM & Lending Protocol Design Patterns
Distilled from DeFiLlama data covering the top AMM protocols ($8.7B TVL combined: Uniswap V3 $1.71B, Curve $1.85B, PancakeSwap $1.67B, Raydium $1.05B) and lending protocols ($42.9B TVL combined: Aave V3 $23.9B, Morpho $6.7B, Sky/MakerDAO $7.3B, Compound $1.3B).
AMM Design Patterns
1. Constant Product Formula (x·y = k)
The foundational AMM invariant, introduced by Uniswap V1/V2:
x * y = k
Where x and y are reserve amounts of token A and B. The spot price is x/y. Every swap moves along the curve, maintaining k.
Key properties:
- Price impact grows non-linearly with trade size
- Price slippage formula:
output = (y * amountIn) / (x + amountIn) - 0.3% fee adds:
amountInWithFee = amountIn * 997 / 1000
Implementation pattern (Uniswap V2 style):
function swap(uint amount0Out, uint amount1Out, ...) external {
uint balance0 = IERC20(token0).balanceOf(address(this)) - amount0Out;
uint balance1 = IERC20(token1).balanceOf(address(this)) - amount1Out;
require(balance0 * balance1 >= uint(reserve0) * reserve1, "K"); // invariant check
}
Pitfall: K-invariant check alone doesn't prevent flash loan exploitation — must check K after the callback resolves.
2. Concentrated Liquidity (Uniswap V3)
Uniswap V3 allows LPs to provide liquidity within a custom price range [P_lower, P_upper]. This achieves up to 4000× more capital efficiency for stable pairs but requires active management.
Key formulas:
liquidity L = sqrt(x * y)
x = L * (sqrt(P_upper) - sqrt(P)) / (sqrt(P) * sqrt(P_upper))
y = L * (sqrt(P) - sqrt(P_lower))
Design implications:
- Single-sided liquidity: if price moves outside range, LP holds 100% of the out-of-range token
- Fee tiers: 0.01% (stable pairs), 0.05% (major pairs), 0.30% (standard), 1.00% (exotic)
- Tick spacing: lower fee tiers have finer tick granularity
Implementation pitfalls:
- Out-of-range positions earn no fees — active management required
- Tick math requires careful rounding (round against LP to prevent K violations)
sqrtPriceX96fixed-point format introduces precision constraints
3. StableSwap / Curve Formula
For assets that should trade near 1:1 (e.g., USDC/USDT, stETH/ETH), the StableSwap invariant:
A * n^n * sum(x_i) + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i))
Where A is the amplification coefficient controlling how "flat" the curve is near equilibrium.
Design implications:
- Higher
A→ less price slippage near peg, more divergence when depeg occurs Ashould be governed carefully — sudden changes break arbitrage assumptions- Used by all major stablecoin pools, LSD pools (stETH/ETH), and cross-chain bridges
4. AMM Fee Calculation & LP Token Math
LP token minting (Uniswap V2 first deposit):
liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
Subsequent deposits (proportional):
liquidity = min(amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1);
Critical pitfall: If an attacker donates tokens to the pool before the first LP deposits, they can inflate the price and manipulate the sqrt(amount0 * amount1) calculation for the first LP. Fix: enforce MINIMUM_LIQUIDITY = 1000 permanently burned to lock the initial ratio.
5. Impermanent Loss (IL)
IL is the loss LPs experience vs holding when prices diverge:
IL = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1
| Price change | IL |
|---|---|
| 2× (or 0.5×) | 5.7% |
| 5× (or 0.2×) | 25.5% |
| 10× (or 0.1×) | 42.5% |
For protocol designers: IL protection mechanisms (IL insurance, single-sided staking) attempt to compensate LPs but introduce significant protocol risk if miscalibrated.
Lending Protocol Design Patterns
6. Utilization-Based Interest Rate Curves
The standard lending interest rate model (Compound/Aave):
if utilization <= kink:
borrowRate = baseRate + (utilization / kink) * slopeBeforeKink
else:
borrowRate = baseRate + slopeBeforeKink + ((utilization - kink) / (1 - kink)) * slopeAfterKink
Typical parameters (Aave USDC):
- Base rate: 0%
- Kink: 80% utilization
- Slope before kink: 4% APR
- Slope after kink: 75% APR (aggressive — incentivizes repayments)
Why the kink matters: At 80% utilization, the rate jumps sharply. This protects depositors from bank runs — if too many users borrow, rates become punitive, incentivizing repayment before the pool empties.
7. Health Factor & Liquidation Mechanics
Health Factor:
HF = sum(collateral_i * liquidationThreshold_i) / totalDebt
When HF < 1, the position is liquidatable.
Liquidation process (Aave-style):
- Liquidator calls
liquidationCall(collateralAsset, debtAsset, user, debtToCover) - Liquidator repays
debtToCoveron behalf of user - Liquidator receives
debtToCover * liquidationBonusin collateral - Typical liquidation bonus: 5–15% depending on collateral risk tier
Implementation pitfall — liquidation cascade: When collateral prices drop sharply, many positions become liquidatable simultaneously. Mass liquidations create selling pressure on the collateral asset, which drops prices further. Aave mitigates this via:
- Gradual liquidation (cap on single liquidation amount)
- Supply caps per asset
- Risk-tiered isolation mode for high-volatility assets
8. Flash Loans
Flash loans are uncollateralized loans that must be repaid within the same transaction:
function flashLoan(address receiver, address asset, uint256 amount, bytes calldata params) external {
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
IERC20(asset).transfer(receiver, amount);
IFlashLoanReceiver(receiver).executeOperation(asset, amount, fee, params);
require(IERC20(asset).balanceOf(address(this)) >= balanceBefore + fee, "not repaid");
}
Legitimate use cases: Arbitrage, self-liquidation, collateral swaps, one-tx leverage entry/exit.
Attack surface: Flash loans amplify every oracle manipulation and reentrancy attack. Never use spot prices from the same block as a flash loan for any financial decision.
9. Reserve Factor & Protocol Revenue
Lending protocols extract fees by taking a cut of interest payments:
supplierAPY = borrowAPY * utilizationRate * (1 - reserveFactor)
protocolRevenue = totalInterestPaid * reserveFactor
Typical reserve factors: 5–25%. Higher reserve factors reduce depositor APY and make the protocol less competitive.
10. ERC-4626 Tokenized Vault Standard
Modern lending implementations use ERC-4626 to standardize vault interfaces:
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
function convertToShares(uint256 assets) external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256);
Critical invariant: convertToAssets(convertToShares(x)) <= x. If not maintained, vault is exploitable.
Common pitfall: Inflation attack on first deposit (same as AMM). Mitigation: virtual shares/assets offset (totalAssets + 1 / totalSupply + 1).
Composability Patterns & Risks
Flash Loan + AMM + Lending Interaction
The typical DeFi "Lego" attack pattern:
- Flash loan 1M USDC
- Deposit USDC as collateral in lending protocol
- Borrow asset X against it
- Sell X on AMM to manipulate price
- Exploit the manipulated price
- Repay flash loan
Defense: Protocols should use time-weighted prices, not spot prices, for any action that can follow a flash loan in the same block.
Design Checklist
- AMM: Is the K-invariant checked after the full callback resolves?
- AMM V3: Is tick rounding direction correct (round against LP)?
- AMM first deposit: Is MINIMUM_LIQUIDITY enforced to prevent donation attacks?
- Lending: Does the interest rate curve have a kink to protect against bank runs?
- Lending: Are liquidations capped per transaction to prevent cascades?
- Flash loans: Does any price oracle read in the same block become exploitable?
- ERC-4626: Is the
convertToShares/convertToAssetsround-trip safe against inflation attacks?