# Lending

> 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.

- Skill: `purpleailab/lending` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/lending`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/lending/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/lending

---


# 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.

```solidity
// VULNERABLE: Cached health factor
mapping(address => uint256) public healthFactors;

function liquidate(address user) external {
    // Uses stale cached value
    require(healthFactors[user] < 1e18);
    // Actual health factor may be different!
}

// SECURE: Compute in real-time
function liquidate(address user) external {
    uint256 collateralValue = getCollateralValue(user);  // Real-time oracle
    uint256 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.

```solidity
// VULNERABLE: Bonus exceeds safety margin
uint256 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.

```solidity
// VULNERABLE: Interest rounds to 0
uint256 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 accumulator
uint256 interestAccumulator; // Ray (1e27) precision
uint256 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.

```solidity
// VULNERABLE: Single oracle, no staleness check
function getCollateralValue(address user) public view returns (uint256) {
    uint256 price = oracle.getPrice(collateralToken);  // Could be stale!
    return userBalance * price / 1e18;
}

// SECURE: Validate oracle freshness
function 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.

```solidity
// VULNERABLE: No bad debt handling
function liquidate(address user) external {
    uint256 collateralValue = getCollateralValue(user);
    uint256 debtValue = getDebtValue(user);
    
    // If collateral < debt, bad debt remains!
    // Suppliers lose money
}

// SECURE: Handle underwater positions
function liquidate(address user) external {
    uint256 collateralValue = getCollateralValue(user);
    uint256 debtValue = getDebtValue(user);
    
    if (collateralValue < debtValue) {
        uint256 badDebt = debtValue - collateralValue;
        // Socialize or use insurance fund
        insuranceFund -= 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:**
```solidity
// DANGEROUS: Liquidator bonus too high
uint256 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

```solidity
// 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:**
```solidity
// DANGEROUS: No bad debt handling
function liquidate(address user) external {
    // If collateral < debt after liquidation
    // Bad debt remains in protocol
    // Eventually becomes insolvent
}

// SECURE: Handle underwater positions
function liquidate(address user) external {
    uint256 collateralValue = getCollateralValue(user);
    uint256 debtValue = getDebtValue(user);

    if (collateralValue < debtValue) {
        // Socialize bad debt or use insurance fund
        uint256 badDebt = debtValue - collateralValue;
        insuranceFund -= min(badDebt, insuranceFund);
    }
}
```

---

## Interest Rate Model

### Standard Jump Rate Model

```solidity
contract JumpRateModel {
    uint256 public baseRate;      // Rate at 0% utilization
    uint256 public multiplier;    // Rate increase per utilization
    uint256 public jumpMultiplier; // Rate increase above kink
    uint256 public kink;          // Utilization % where jump occurs

    function 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**
```solidity
// 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)

```solidity
// DANGEROUS: Interest rounds to 0 for small amounts
uint256 interest = principal * rate / SECONDS_PER_YEAR;
// If principal * rate < SECONDS_PER_YEAR, interest = 0

// SOLUTION: Accumulate interest with high precision
uint256 interestAccumulator; // Ray (1e27) precision
```

---

## Collateral Vulnerabilities

### 1. Collateral Factor Changes

```solidity
// DANGEROUS: Instant collateral factor reduction
function setCollateralFactor(address token, uint256 newFactor) external {
    collateralFactors[token] = newFactor;
    // Existing borrowers may become instantly liquidatable!
}

// SECURE: Grace period or gradual reduction
function 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)

```solidity
// Collateral valued using manipulable oracle
// Attacker inflates collateral value → borrows more → profits
// See oracle vulnerability patterns
```

### 3. Toxic Collateral

```solidity
// Collateral that can't be liquidated:
// - Pausable tokens
// - Blacklistable tokens
// - Low liquidity tokens

// Mitigation: Whitelist collateral carefully
```

---

## Borrowing Vulnerabilities

### 1. Borrow Cap Bypass

```solidity
// VULNERABLE: Cap checked only on new borrows
function 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

```solidity
// EXPLOIT: Borrow and repay in same block
// No interest accrued = free leverage

function 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 Value

Health Factor > 1: Safe
Health Factor < 1: Liquidatable
```

### Vulnerabilities

**1. Stale Health Factor**

**Root Cause**: Root Cause 1 (Health Factor Calculation Timing)

```solidity
// DANGEROUS: Cached health factor
mapping(address => uint256) public healthFactors;

// Health factors become stale as prices change
// Always compute in real-time
```

**2. Rounding Direction**
```solidity
// SECURE: Round health factor DOWN (safer)
uint256 healthFactor = collateralValue * 1e18 / debtValue;

// Round debt UP when calculating
uint256 debt = (borrowed * (1e18 + interest) + 1e18 - 1) / 1e18;
```

---

## Common AAVE/Compound Patterns

### Reserve Factor

```solidity
// Protocol takes cut of interest
uint256 interestToProtocol = interest * reserveFactor / 1e18;
uint256 interestToSuppliers = interest - interestToProtocol;
```

### Supply Index

```solidity
// Track earnings per token
// supplyIndex increases as interest accrues
uint256 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 |

