Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: lending description: > Auto-loaded by defi-auditor agent during Phase 2 when analyzing lending protocols. Provides patterns for: liquidation logic, interest rate models, collateral management, health factor calculations, bad debt handling. Core vulnerabilities: self-liquidation, precision loss, oracle manipulation, collateral factor attacks. user-invocable: false
Lending Protocol Patterns
This skill provides comprehensive knowledge for auditing DeFi lending protocols.
Core Lending Concepts
| Concept | Description | |
|---|---|---|
| Collateral Factor | Max borrow % against collateral | |
| Liquidation Threshold | Health level triggering liquidation | |
| Utilization Rate | Borrowed / Total Supplied | |
| Interest Rate | Function of utilization | |
| Health Factor | Collateral value / Debt value |
Why Lending Protocol Attacks Happen (Root Causes)
Root Cause 1: Health Factor Calculation Timing
The fundamental vulnerability: health factors computed from stale or cached data instead of real-time prices.
// VULNERABLE: Cached health factormapping(address => uint256) public healthFactors;function liquidate(address user) external {// Uses stale cached valuerequire(healthFactors[user] < 1e18);// Actual health factor may be different!}// SECURE: Compute in real-timefunction liquidate(address user) external {uint256 collateralValue = getCollateralValue(user); // Real-time oracleuint256 debtValue = getDebtValue(user);uint256 healthFactor = collateralValue * 1e18 / debtValue;require(healthFactor < 1e18);}
Attacker's view: "The protocol checks a stale health factor. I can manipulate prices between the check and liquidation, or liquidate users who are actually safe."
Root Cause 2: Liquidation Incentive Misconfiguration
Liquidation bonus set higher than the safety margin, making self-liquidation profitable.
// VULNERABLE: Bonus exceeds safety marginuint256 constant MAX_LTV = 80e16; // 80%uint256 constant LIQUIDATION_BONUS = 15e16; // 15%// Attacker borrows at 80% LTV, self-liquidates with 15% bonus// Net profit: 15% - (100% - 80%) = -5%... wait, this is a loss// But if bonus > (100% - LTV), it's profitable!uint256 constant LIQUIDATION_BONUS = 25e16; // 25% (DANGEROUS!)// Profit: 25% - 20% = 5% free money
Attacker's view: "I borrow at max LTV, wait for any price movement, then self-liquidate and pocket the bonus."
Root Cause 3: Interest Precision Loss
Small amounts round to zero during interest accrual, allowing dust attacks or precision exploits.
// VULNERABLE: Interest rounds to 0uint256 interest = principal * rate / SECONDS_PER_YEAR;// If principal * rate < SECONDS_PER_YEAR, interest = 0// Example: principal = 100 wei, rate = 1e16 (1%)// interest = 100 * 1e16 / 31536000 = 0 (rounds down)// SECURE: Use high-precision accumulatoruint256 interestAccumulator; // Ray (1e27) precisionuint256 newAccumulator = oldAccumulator + (rate * 1e27 / SECONDS_PER_YEAR);
Attacker's view: "I can deposit tiny amounts that accrue zero interest, or exploit rounding to avoid paying interest on small borrows."
Root Cause 4: Collateral Valuation Trust
Collateral value depends entirely on oracle accuracy. Stale, manipulated, or incorrect oracles break the entire protocol.
// VULNERABLE: Single oracle, no staleness checkfunction getCollateralValue(address user) public view returns (uint256) {uint256 price = oracle.getPrice(collateralToken); // Could be stale!return userBalance * price / 1e18;}// SECURE: Validate oracle freshnessfunction getCollateralValue(address user) public view returns (uint256) {(uint256 price, uint256 timestamp) = oracle.getPriceWithTimestamp(collateralToken);require(block.timestamp - timestamp <= MAX_STALENESS);return userBalance * price / 1e18;}
Attacker's view: "If I can manipulate the oracle or use a stale price, I can inflate collateral value and borrow more than safe."
Root Cause 5: Bad Debt Socialization Gap
When liquidation fails to recover full debt, bad debt accumulates and is socialized across remaining suppliers, creating insolvency.
// VULNERABLE: No bad debt handlingfunction liquidate(address user) external {uint256 collateralValue = getCollateralValue(user);uint256 debtValue = getDebtValue(user);// If collateral < debt, bad debt remains!// Suppliers lose money}// SECURE: Handle underwater positionsfunction liquidate(address user) external {uint256 collateralValue = getCollateralValue(user);uint256 debtValue = getDebtValue(user);if (collateralValue < debtValue) {uint256 badDebt = debtValue - collateralValue;// Socialize or use insurance fundinsuranceFund -= min(badDebt, insuranceFund);}}
Attacker's view: "If I can create a position where collateral < debt, the protocol becomes insolvent and I profit from the socialized loss."
Liquidation Vulnerabilities
1. Profitable Self-Liquidation
Root Cause: Root Cause 2 (Liquidation Incentive Misconfiguration)
Vulnerable Pattern:
// DANGEROUS: Liquidator bonus too highuint256 constant LIQUIDATION_BONUS = 15e16; // 15%function liquidate(address user, uint256 repayAmount) external {// Attacker can:// 1. Borrow at 80% LTV// 2. Wait for tiny price drop// 3. Self-liquidate with 15% bonus// = Free 15% - (100% - 80%) = Net profit!}
Check: Liquidation bonus should be less than (100% - Max LTV).
2. Liquidation Cascade
// When one liquidation triggers another// Large position liquidated → price drops → more liquidations// Mitigations:// - Gradual liquidation (partial)// - Circuit breakers// - Liquidation delays
3. Bad Debt Accumulation
Root Cause: Root Cause 5 (Bad Debt Socialization Gap)
Vulnerable Pattern:
// DANGEROUS: No bad debt handlingfunction liquidate(address user) external {// If collateral < debt after liquidation// Bad debt remains in protocol// Eventually becomes insolvent}// SECURE: Handle underwater positionsfunction liquidate(address user) external {uint256 collateralValue = getCollateralValue(user);uint256 debtValue = getDebtValue(user);if (collateralValue < debtValue) {// Socialize bad debt or use insurance funduint256 badDebt = debtValue - collateralValue;insuranceFund -= min(badDebt, insuranceFund);}}
Interest Rate Model
Standard Jump Rate Model
contract JumpRateModel {uint256 public baseRate; // Rate at 0% utilizationuint256 public multiplier; // Rate increase per utilizationuint256 public jumpMultiplier; // Rate increase above kinkuint256 public kink; // Utilization % where jump occursfunction getBorrowRate(uint256 cash, uint256 borrows)public view returns (uint256){uint256 utilization = borrows * 1e18 / (cash + borrows);if (utilization <= kink) {return baseRate + utilization * multiplier / 1e18;} else {uint256 normalRate = baseRate + kink * multiplier / 1e18;uint256 excessUtil = utilization - kink;return normalRate + excessUtil * jumpMultiplier / 1e18;}}}
Interest Rate Vulnerabilities
1. Rate Manipulation
// Attacker can manipulate utilization// 1. Flash loan large amount// 2. Deposit → lower utilization → lower rates// 3. Borrow at low rate// 4. Withdraw and repay flash loan
2. Precision Loss
Root Cause: Root Cause 3 (Interest Precision Loss)
// DANGEROUS: Interest rounds to 0 for small amountsuint256 interest = principal * rate / SECONDS_PER_YEAR;// If principal * rate < SECONDS_PER_YEAR, interest = 0// SOLUTION: Accumulate interest with high precisionuint256 interestAccumulator; // Ray (1e27) precision
Collateral Vulnerabilities
1. Collateral Factor Changes
// DANGEROUS: Instant collateral factor reductionfunction setCollateralFactor(address token, uint256 newFactor) external {collateralFactors[token] = newFactor;// Existing borrowers may become instantly liquidatable!}// SECURE: Grace period or gradual reductionfunction setCollateralFactor(address token, uint256 newFactor) external {require(newFactor >= collateralFactors[token] - MAX_DECREASE);pendingFactors[token] = newFactor;factorEffectiveTime[token] = block.timestamp + TIMELOCK;}
2. Collateral Oracle Manipulation
Root Cause: Root Cause 4 (Collateral Valuation Trust)
// Collateral valued using manipulable oracle// Attacker inflates collateral value → borrows more → profits// See oracle vulnerability patterns
3. Toxic Collateral
// Collateral that can't be liquidated:// - Pausable tokens// - Blacklistable tokens// - Low liquidity tokens// Mitigation: Whitelist collateral carefully
Borrowing Vulnerabilities
1. Borrow Cap Bypass
// VULNERABLE: Cap checked only on new borrowsfunction borrow(uint256 amount) external {require(totalBorrowed + amount <= borrowCap);// Interest accrual can push over cap!}// Check on any state-changing operation
2. Same-Block Borrow-Repay
// EXPLOIT: Borrow and repay in same block// No interest accrued = free leveragefunction borrow(uint256 amount) external {require(lastBorrowBlock[msg.sender] < block.number);lastBorrowBlock[msg.sender] = block.number;// ...}
Health Factor Calculations
Standard Formula
Health Factor = (Collateral Value × LTV) / Debt ValueHealth Factor > 1: SafeHealth Factor < 1: Liquidatable
Vulnerabilities
1. Stale Health Factor
Root Cause: Root Cause 1 (Health Factor Calculation Timing)
// DANGEROUS: Cached health factormapping(address => uint256) public healthFactors;// Health factors become stale as prices change// Always compute in real-time
2. Rounding Direction
// SECURE: Round health factor DOWN (safer)uint256 healthFactor = collateralValue * 1e18 / debtValue;// Round debt UP when calculatinguint256 debt = (borrowed * (1e18 + interest) + 1e18 - 1) / 1e18;
Common AAVE/Compound Patterns
Reserve Factor
// Protocol takes cut of interestuint256 interestToProtocol = interest * reserveFactor / 1e18;uint256 interestToSuppliers = interest - interestToProtocol;
Supply Index
// Track earnings per token// supplyIndex increases as interest accruesuint256 newIndex = oldIndex + (interest * 1e18 / totalSupply);// User earnings = balance * (currentIndex - userIndex) / 1e18
Lending Audit Checklist
Liquidation
- [ ] Liquidation bonus < (100% - max LTV)
- [ ] Bad debt handling exists
- [ ] Partial liquidation supported
- [ ] Liquidation can't be blocked (pausable tokens)
- [ ] Self-liquidation not profitable
Interest
- [ ] Interest accrues correctly over time
- [ ] No precision loss on small amounts
- [ ] Rate manipulation resistance
- [ ] Compound interest handled
Collateral
- [ ] Oracle is manipulation-resistant
- [ ] Collateral factor changes have timelock
- [ ] Collateral whitelist is reasonable
- [ ] Handles rebasing/weird tokens properly
Health Factor
- [ ] Computed in real-time
- [ ] Rounding favors protocol safety
- [ ] Handles all edge cases (0 debt, 0 collateral)
Access Control
- [ ] Only authorized can change parameters
- [ ] Emergency pause exists
- [ ] Parameter bounds enforced
Severity Classification
Critical
- Self-liquidation profitable
- Bad debt not handled
- Oracle manipulation allows draining
High
- Interest precision loss
- Collateral factor instant changes
- Same-block manipulation
Medium
- Missing borrow caps
- Liquidation can be blocked
- Rate model edge cases
Rationalization Table
| Root Cause | Historical Exploit | Detection Method | Severity | Mitigation | |
|---|---|---|---|---|---|
| Health Factor Calculation Timing | Stale price liquidations (Aave v1 oracle failures) | Check if health factor computed real-time from oracle | Critical | Always compute health factor in real-time, validate oracle freshness | |
| Liquidation Incentive Misconfiguration | Self-liquidation profit (Compound liquidation bonus > safety margin) | Verify: bonus < (100% - max LTV) | Critical | Set liquidation bonus ≤ (100% - max LTV), audit parameter changes | |
| Interest Precision Loss | Dust attack exploits (small amounts accrue zero interest) | Check interest calculation for rounding to zero | High | Use high-precision accumulators (ray/wad), test with small amounts | |
| Collateral Valuation Trust | Oracle manipulation attacks (Euler oracle dependency) | Audit oracle source, check staleness, validate multi-source | Critical | Use manipulation-resistant oracles, implement staleness checks, multi-oracle fallback | |
| Bad Debt Socialization Gap | Protocol insolvency (Euler bad debt cascade) | Check if collateral < debt is handled | Critical | Implement bad debt handling, insurance fund, or liquidation guarantees |