# audit-liquidation-calculation

Audits Solidity liquidation mechanisms for calculation vulnerabilities including incorrect liquidator reward decimals making rewards too small/large, unprioritized liquidator rewards paid after other fees removing incentive, excessive protocol fees making liquidation unprofitable, minimum collateral requirements not accounting for liquidation costs, unaccounted yield/PNL not included in collateral valuation, missing swap fees during liquidation, and oracle sandwich self-liquidation manipulation

- **Kind:** skill
- **Source:** https://github.com/auditmos/skills
- **Page:** https://forefy.com/skills/2752f7b7-c7b0-404f-bbbf-3841cc12a75e
- **API (JSON + files):** https://forefy.com/api/asr/2752f7b7-c7b0-404f-bbbf-3841cc12a75e

---

## SKILL.md

---
name: audit-liquidation-calculation
description: Audits Solidity liquidation mechanisms for calculation vulnerabilities including incorrect liquidator reward decimals making rewards too small/large, unprioritized liquidator rewards paid after other fees removing incentive, excessive protocol fees making liquidation unprofitable, minimum collateral requirements not accounting for liquidation costs, unaccounted yield/PNL not included in collateral valuation, missing swap fees during liquidation, and oracle sandwich self-liquidation manipulation
allowed-tools: Read, Grep, Glob
license: MIT
compatibility: Designed for Claude Code (or similar products)
metadata:
  author: Tomasz Kowalczyk (tom@auditmos.com)
  version: "1.0"
---

# Liquidation Calculation Auditor

## When to Use
- Auditing liquidation reward calculations, fee distribution, collateral valuation
- User mentions: liquidation reward, protocol fee, minimum collateral, yield, PNL, self-liquidation, oracle manipulation, liquidation profitability
- Analyzing liquidation economics, fee structures, collateral calculations
- Reviewing reward priorities, decimal handling in liquidations

## Audit Workflow

**IMPORTANT: Announce skill usage at start of analysis**

Begin with: "I'm using the **audit-liquidation-calculation** skill to analyze this contract for liquidation calculation and economic vulnerabilities..."

1. **Scan for liquidation calculation operations**
   - Search: `liquidate`, `liquidationReward`, `liquidationBonus`, `protocolFee`, `collateralValue`, `minimumCollateral`, `yield`, `PNL`, `earnedYield`
   - Focus: reward calculations, fee priorities, collateral valuations, decimal handling

2. **Check against vulnerability patterns**
   - Reference `reference.md` for complete checklist
   - Compare code against `example.md`

3. **Validate exploitability**
   - **Check access control first** - grep for `onlyOwner|onlyAdmin|onlyGovernance` modifiers
   - Can non-privileged actors exploit liquidation calculation issues?
   - Are liquidator rewards calculated correctly with proper decimals?
   - Are rewards paid before or after other fees?
   - Do protocol fees make liquidation unprofitable?
   - Does minimum collateral account for liquidation costs?
   - Is yield/PNL included in collateral value?
   - Are swap fees charged during liquidation?
   - Can users self-liquidate profitably via oracle manipulation?
   - Verify no compensating protections exist
   - Downgrade severity if admin-only unless systemic liquidation failure

4. **Generate report**
   - Use deliverable template below
   - Include economic analysis and PoC
   - Rank by severity

## Core Vulnerability Patterns

See `reference.md` for full checklist. Key patterns:

1. Incorrect liquidator reward → decimal precision errors make rewards unusable
2. Unprioritized liquidator reward → other fees paid first, no incentive remains
3. Excessive protocol fee → 30%+ fees make liquidation unprofitable
4. Missing liquidation fees in requirements → positions unliquidatable at minimum
5. Unaccounted yield/PNL → collateral undervalued, unfair liquidations
6. No swap fee during liquidation → protocol loses revenue
7. Oracle sandwich self-liquidation → users profit from triggering oracle updates

**Code examples:** See `example.md`

## Severity Criteria

**Critical:** Liquidator rewards calculated incorrectly causing systemic liquidation failure, profitable self-liquidation via oracle manipulation, **MUST be exploitable by non-privileged actors**
**High:** Unprioritized rewards removing liquidation incentive, excessive protocol fees preventing liquidation, unaccounted yield causing unfair liquidations, **MUST be exploitable by non-privileged actors**
**Medium:** Missing swap fees during liquidation, minimum collateral not accounting for costs, **admin-only fee configuration issues with cascading liquidation impact**
**Low:** Suboptimal fee structures without security impact, **admin-only parameter issues without immediate liquidation impact**

**IMPORTANT:** Admin-only liquidation fee functions (onlyOwner, onlyAdmin, onlyGovernance) are **MEDIUM or LOW severity** unless:
- Invalid fee parameters cause systemic liquidation failure (e.g., protocol fee > liquidation bonus)
- Missing validation enables admin to drain liquidation rewards for personal gain
- Calculation errors directly lead to unprofitable liquidations and bad debt accumulation

## False Positives - Do NOT Flag

- Protocols with trusted liquidators where profitability not required
- Documented admin fee collection mechanisms
- Alternative reward structures with analysis showing profitability
- Intentional yield/PNL handling with documented rationale
- Protocols without oracle-based pricing (no oracle manipulation risk)
- **Admin-only fee setter functions** (onlyOwner, onlyAdmin) with documented validation and bounds
- Governance-controlled liquidation bonus parameters with analysis showing profitability
- Admin functions for protocol fee collection where liquidator rewards are prioritized

## Deliverable Format

**MANDATORY:** Before deliverable, verify each `checklist.md` item against codebase. Flag violations as findings.

Use template: `templates/report-template.md`

Each finding includes: severity, pattern #, file/lines, description, vulnerable code, economic impact analysis, PoC showing unprofitable liquidation or exploitation, remediation, gas impact.

## Key Principles

- **Decimal precision** - liquidator rewards must use correct decimals to be spendable
- **Priority** - liquidator rewards paid first, protocol fees second
- **Profitability** - total fees < liquidation bonus to maintain incentive
- **Completeness** - minimum collateral accounts for all liquidation costs
- **Fair valuation** - yield/PNL included in collateral calculations
- **Revenue capture** - protocol charges fees on liquidation swaps
- **Manipulation resistance** - prevent profitable self-liquidation via oracle updates

## Output Guidelines

**DO:**
- Reference specific lines and functions
- Provide economic analysis (fees vs rewards vs costs)
- Show PoCs demonstrating unprofitable liquidations
- Quantify reward calculation errors
- Calculate break-even points for liquidation profitability

**DON'T:**
- Report intentional design choices with documentation
- Flag missing features with alternative mechanisms in place
- Use vague terms ("might be unprofitable")
- Ignore gas costs and on-chain fee context

## checklist.md

# Liquidation Calculation Audit Checklist

Run through all items systematically before generating report. Flag violations as findings.

---

## 1. Liquidator Reward Calculation

**Pattern: Incorrect decimal precision in reward calculations**

- [ ] Liquidator reward scaled to collateral token decimals
- [ ] No hardcoded 1e18 when collateral != 18 decimals
- [ ] Reward calculations tested with 6, 8, 18 decimal tokens
- [ ] Bonus percentage applied correctly (e.g., 110% = debt * 11/10)
- [ ] No overflow/underflow in reward calculations
- [ ] Reward amount >0 for all valid liquidations

**Search terms:** `liquidationReward`, `liquidationBonus`, `calculateReward`

**Files to check:** Liquidation functions, reward calculation helpers

---

## 2. Fee Priority and Payment Order

**Pattern: Liquidator reward unprioritized, paid after other fees**

- [ ] Liquidator reward calculated before protocol fees
- [ ] Liquidator reward not reduced by subsequent fee deductions
- [ ] Protocol fees taken from remaining collateral after reward
- [ ] Fee payment order explicitly documented in code
- [ ] No edge cases where reward = 0 due to other fees

**Search terms:** `protocolFee`, fee calculation order in `liquidate()`

**Files to check:** Liquidation functions, fee distribution logic

---

## 3. Protocol Fee Economics

**Pattern: Excessive protocol fees make liquidation unprofitable**

- [ ] Protocol fee <30% of liquidation bonus
- [ ] Net liquidator reward >estimated gas costs
- [ ] Fee structure tested across position sizes (small/medium/large)
- [ ] Minimum profitable position size documented
- [ ] Fee changes require governance/timelock

**Search terms:** `PROTOCOL_FEE`, `protocolFeeRate`, fee constants

**Files to check:** Fee configuration, liquidation functions

**Analysis required:**
```
liquidation_bonus = 10%
protocol_fee = 5%
net_reward = bonus - protocol_fee = 5%
gas_cost = $20 (example)
minimum_profitable = gas_cost / net_reward = $400 position
```

---

## 4. Minimum Collateral Requirements

**Pattern: Minimum collateral doesn't account for liquidation costs**

- [ ] Minimum collateral > debt + liquidation_bonus + protocol_fee + gas_buffer
- [ ] Positions at minimum threshold profitably liquidatable
- [ ] Buffer accounts for gas price volatility (2-5x typical gas)
- [ ] Different minimums for different collateral types (if applicable)
- [ ] Liquidation ratio >100% (typically 120-150%)

**Search terms:** `minimumCollateral`, `LIQUIDATION_RATIO`, `MIN_COLLATERAL_RATIO`

**Files to check:** Borrow functions, collateral validation

**Example calculation:**
```
debt = 100 USDC
liquidation_ratio = 120%
minimum_collateral = 120 USDC

liquidation_bonus = 10% = 12 USDC
protocol_fee = 30% of bonus = 3.6 USDC
gas_cost = ~$5 = 5 USDC
net_reward = 12 - 3.6 = 8.4 USDC
profitable? 8.4 > 5 = YES
```

---

## 5. Yield and PNL Inclusion

**Pattern: Earned yield/positive PNL not included in collateral value**

- [ ] Collateral value includes deposited + earned_yield
- [ ] Positive PNL included in collateral calculations
- [ ] Yield-bearing tokens use current balance, not deposit amount
- [ ] PNL updated before liquidation checks
- [ ] No unfair liquidations due to missing yield
- [ ] Users can withdraw earned yield before liquidation

**Search terms:** `getCollateralValue`, `totalCollateral`, `isLiquidatable`, `getPNL`, `getYield`

**Files to check:** Collateral valuation functions, liquidation checks, vault integrations

**Red flags:**
- `userDeposits[user]` instead of `vault.balanceOf(user)` for yield vaults
- PNL calculation without including unrealized gains
- Liquidation checks before yield accrual updates

---

## 6. Swap Fee Application

**Pattern: Missing swap fees during liquidation**

- [ ] Swap fees charged when liquidation involves token swaps
- [ ] Fee rate consistent with non-liquidation swaps (typically 0.3%)
- [ ] Fees go to protocol treasury, not liquidator
- [ ] Fee not deducted from liquidator reward
- [ ] Swap path optimizes for best price + fees

**Search terms:** `_swap`, `swapFee`, liquidation swap operations

**Files to check:** Liquidation functions with token swaps, DEX integrations

**Note:** This is economic optimization, not critical security issue

---

## 7. Self-Liquidation Protection

**Pattern: Users can profitably self-liquidate via oracle manipulation**

- [ ] Self-liquidation restricted (`require(msg.sender != user)` or equivalent)
- [ ] Oracle updates have delays/cooldowns before liquidation allowed
- [ ] Multiple oracle sources prevent single-point manipulation
- [ ] Liquidation bonus can't be extracted by user-controlled accounts
- [ ] Time delay between oracle update and liquidation eligibility
- [ ] Self-liquidation attempts revert or penalized

**Search terms:** `liquidate`, oracle update functions, self-liquidation checks

**Files to check:** Liquidation functions, oracle integration, access controls

**Attack flow to check:**
```
1. User position becomes liquidatable
2. User calls updateOracle() (if permissionless)
3. User immediately liquidates self via alt account
4. User receives liquidation bonus
```

---

## Position Size Analysis

For each finding, calculate:

1. **Minimum profitable position**
   - Formula: `gas_cost / net_liquidator_reward_percentage`
   - Example: $20 gas / 5% reward = $400 minimum

2. **Break-even collateral ratio**
   - Formula: `debt * (1 + liquidation_costs / liquidation_bonus)`
   - Example: 100 debt * (1 + 20/10) = 300 collateral

3. **Maximum protocol fee before unprofitable**
   - Formula: `liquidation_bonus - (gas_cost / position_size)`
   - Example: 10% bonus - (20 / 1000) = 8% max fee

---

## Gas Cost Estimates (for profitability analysis)

**Ethereum mainnet:**
- Simple liquidation: ~150k gas (~$30 at 50 gwei, $2000 ETH)
- Complex liquidation (swaps): ~300k gas (~$60)

**L2s (Arbitrum/Optimism):**
- Simple liquidation: ~150k gas (~$0.50 at 0.1 gwei equivalent)
- Complex liquidation: ~300k gas (~$1)

**BSC/Polygon:**
- Simple liquidation: ~150k gas (~$0.10)
- Complex liquidation: ~300k gas (~$0.20)

Adjust minimum position sizes based on deployment chain.

---

## Common Issues to Flag

**CRITICAL:**
- [ ] Liquidator reward uses wrong decimals (hardcoded 1e18 for USDC)
- [ ] Self-liquidation allowed with user-triggered oracle updates
- [ ] Reward calculation can be 0 or overflow

**HIGH:**
- [ ] Liquidator reward paid after protocol fees (can be reduced to 0)
- [ ] Protocol fee >30% of bonus
- [ ] Yield/positive PNL not included in collateral value
- [ ] No oracle update delay before liquidation

**MEDIUM:**
- [ ] Minimum collateral doesn't account for liquidation costs
- [ ] Missing swap fees during liquidation
- [ ] Liquidation unprofitable for small positions (<$100)

**LOW:**
- [ ] Suboptimal fee distribution
- [ ] Missing documentation on fee priorities
- [ ] No minimum position size enforced

---

## False Positives to Avoid

**DO NOT flag:**
1. Trusted liquidator systems (admin/keeper bots)
2. Documented high protocol fees with alternative incentives
3. Yield claiming delays for gas optimization (if documented)
4. Manual oracle updates (admin-only, no manipulation risk)
5. Fixed rewards with profitability analysis provided

**DO flag:**
1. Trustless liquidations without profitability guarantees
2. Unclear fee priorities in code
3. Missing yield in valuation (even if documented - causes unfair liquidations)
4. Permissionless oracle updates without liquidation delays
5. Self-liquidation allowed (even if documented - enables extraction)

## example.md

# Liquidation Calculation Vulnerability Examples

Vulnerable and secure code patterns.

---

## 1. Incorrect Liquidator Reward Calculation

### ❌ Vulnerable: Hardcoded 1e18 for USDC collateral

```solidity
contract VulnerableLiquidation {
    IERC20 public collateralToken; // USDC (6 decimals)
    IERC20 public debtToken; // DAI (18 decimals)

    uint256 constant LIQUIDATION_BONUS = 110; // 110%

    function liquidate(address user) external {
        uint256 collateral = collateralToken.balanceOf(user);
        uint256 debt = debtToken.balanceOf(user);

        // VULNERABLE: assumes 18 decimals for USDC
        uint256 reward = (debt * LIQUIDATION_BONUS) / 1e18;
        // If debt = 1000 DAI (1000e18), reward = 1100e0 = 0.0011 USDC!

        collateralToken.transfer(msg.sender, reward); // Transfers 0
    }
}
```

**Impact:** Liquidator receives 0 reward, liquidation unprofitable.

### ✅ Secure: Scaled to collateral decimals

```solidity
contract SecureLiquidation {
    IERC20 public collateralToken;
    IERC20 public debtToken;
    uint8 public collateralDecimals;
    uint8 public debtDecimals;

    uint256 constant LIQUIDATION_BONUS = 110; // 110%

    function liquidate(address user) external {
        uint256 collateral = collateralToken.balanceOf(user);
        uint256 debt = debtToken.balanceOf(user);

        // Scale debt to collateral decimals
        uint256 debtInCollateralDecimals = debt *
            (10 ** collateralDecimals) / (10 ** debtDecimals);

        uint256 reward = (debtInCollateralDecimals * LIQUIDATION_BONUS) / 100;

        require(collateral >= reward, "Insufficient collateral");
        collateralToken.transfer(msg.sender, reward);
    }
}
```

---

## 2. Unprioritized Liquidator Reward

### ❌ Vulnerable: Protocol fee paid first

```solidity
contract VulnerableFeeOrder {
    uint256 constant PROTOCOL_FEE_RATE = 5000; // 50%
    uint256 constant LIQUIDATION_BONUS = 110; // 10% bonus

    function liquidate(address user) external {
        uint256 collateral = getCollateral(user);
        uint256 debt = getDebt(user);

        // VULNERABLE: protocol fee first
        uint256 protocolFee = (collateral * PROTOCOL_FEE_RATE) / 10000;
        collateral -= protocolFee;

        uint256 liquidatorReward = collateral - debt;
        // If protocolFee large enough, liquidatorReward = 0!

        protocolFeesAccrued += protocolFee;
        transfer(msg.sender, liquidatorReward); // May be 0
        transfer(treasury, debt);
    }
}
```

**Example:**
- Collateral: 1100 USDC
- Debt: 1000 USDC
- Protocol fee: 550 USDC (50%)
- Remaining: 550 USDC
- Liquidator reward: 550 - 1000 = -450 (reverts or 0)

### ✅ Secure: Liquidator paid first

```solidity
contract SecureFeeOrder {
    uint256 constant PROTOCOL_FEE_RATE = 500; // 5%
    uint256 constant LIQUIDATION_BONUS = 110; // 10% bonus

    function liquidate(address user) external {
        uint256 collateral = getCollateral(user);
        uint256 debt = getDebt(user);

        // Calculate liquidator reward first
        uint256 bonusAmount = (debt * LIQUIDATION_BONUS) / 100;
        uint256 liquidatorReward = bonusAmount - debt;

        // Protocol fee from remaining collateral
        uint256 protocolFee = (liquidatorReward * PROTOCOL_FEE_RATE) / 10000;
        uint256 netLiquidatorReward = liquidatorReward - protocolFee;

        require(collateral >= bonusAmount, "Insufficient collateral");

        transfer(msg.sender, netLiquidatorReward);
        transfer(treasury, debt + protocolFee);
    }
}
```

---

## 3. Excessive Protocol Fee

### ❌ Vulnerable: 50% protocol fee

```solidity
contract VulnerableProtocolFee {
    uint256 constant PROTOCOL_FEE_RATE = 5000; // 50%
    uint256 constant LIQUIDATION_BONUS = 110; // 10%

    function liquidate(address user) external {
        uint256 debt = 1000e6; // 1000 USDC
        uint256 bonus = (debt * LIQUIDATION_BONUS) / 100; // 1100 USDC
        uint256 liquidatorReward = bonus - debt; // 100 USDC

        // VULNERABLE: 50% protocol fee
        uint256 protocolFee = (liquidatorReward * PROTOCOL_FEE_RATE) / 10000;
        uint256 netReward = liquidatorReward - protocolFee; // 50 USDC

        // Gas cost: ~$20
        // Net profit: $50 - $20 = $30
        // Only profitable for positions >$500
        // Positions <$500 accumulate as bad debt

        transfer(msg.sender, netReward);
    }
}
```

**Impact:** Small positions (<$500) unprofitable to liquidate.

### ✅ Secure: <10% protocol fee

```solidity
contract SecureProtocolFee {
    uint256 constant PROTOCOL_FEE_RATE = 500; // 5%
    uint256 constant LIQUIDATION_BONUS = 110; // 10%

    function liquidate(address user) external {
        uint256 debt = 1000e6; // 1000 USDC
        uint256 bonus = (debt * LIQUIDATION_BONUS) / 100; // 1100 USDC
        uint256 liquidatorReward = bonus - debt; // 100 USDC

        uint256 protocolFee = (liquidatorReward * PROTOCOL_FEE_RATE) / 10000;
        uint256 netReward = liquidatorReward - protocolFee; // 95 USDC

        // Gas cost: ~$20
        // Net profit: $95 - $20 = $75
        // Profitable for positions >$200

        transfer(msg.sender, netReward);
    }
}
```

---

## 4. Missing Liquidation Fees In Minimum Collateral

### ❌ Vulnerable: Minimum = debt

```solidity
contract VulnerableMinimum {
    uint256 constant LIQUIDATION_RATIO = 100; // 100%

    function borrow(uint256 amount) external {
        uint256 collateral = userCollateral[msg.sender];
        uint256 debt = userDebt[msg.sender];

        // VULNERABLE: minimum = debt
        require(collateral * 100 >= debt * LIQUIDATION_RATIO, "Insufficient");
        // At minimum: collateral = debt
        // Liquidation reward = 0!

        userDebt[msg.sender] += amount;
    }
}
```

**Impact:** Positions at minimum threshold unliquidatable (no reward).

### ✅ Secure: Accounts for liquidation costs

```solidity
contract SecureMinimum {
    uint256 constant LIQUIDATION_RATIO = 120; // 120%
    uint256 constant LIQUIDATION_BONUS = 110; // 10%

    function borrow(uint256 amount) external {
        uint256 collateral = userCollateral[msg.sender];
        uint256 debt = userDebt[msg.sender];

        // Minimum: collateral = 120% of debt
        require(collateral * 100 >= debt * LIQUIDATION_RATIO, "Insufficient");
        // At minimum: collateral = 1200, debt = 1000
        // Liquidation bonus = 1100
        // Liquidator reward = 1200 - 1100 = 100 (10% profit)
        // After 5% protocol fee: 95
        // Enough to cover gas + profit

        userDebt[msg.sender] += amount;
    }
}
```

---

## 5. Unaccounted Yield/PNL

### ❌ Vulnerable: Ignores earned yield

```solidity
contract VulnerableYieldTracking {
    mapping(address => uint256) public userDeposits;
    IYieldVault public vault;

    function getCollateralValue(address user) public view returns (uint256) {
        // VULNERABLE: returns deposit, ignores earned yield
        return userDeposits[user];
    }

    function isLiquidatable(address user) public view returns (bool) {
        uint256 collateral = getCollateralValue(user);
        uint256 debt = getDebt(user);
        return collateral < debt * 120 / 100;
        // User liquidated despite having sufficient collateral + yield!
    }
}
```

**Example:**
- User deposits: 1000 USDC
- Earned yield: 200 USDC
- Total value: 1200 USDC
- Debt: 1000 USDC
- `getCollateralValue()` returns 1000 (ignores yield)
- User liquidated despite 1200 > 1000 * 1.2

### ✅ Secure: Includes earned yield

```solidity
contract SecureYieldTracking {
    mapping(address => uint256) public userShares;
    IYieldVault public vault;

    function getCollateralValue(address user) public view returns (uint256) {
        // Correct: returns current balance including yield
        return vault.balanceOf(user);
    }

    function isLiquidatable(address user) public view returns (bool) {
        uint256 collateral = getCollateralValue(user); // Includes yield
        uint256 debt = getDebt(user);
        return collateral < debt * 120 / 100;
    }
}
```

---

## 6. Missing Swap Fee During Liquidation

### ❌ Vulnerable: No swap fee

```solidity
contract VulnerableSwapFee {
    function liquidate(address user) external {
        uint256 collateral = getCollateral(user); // WETH
        uint256 debt = getDebt(user); // USDC

        // VULNERABLE: no swap fee
        uint256 usdcReceived = _swap(WETH, USDC, collateral);
        // Protocol loses 0.3% revenue

        _repayDebt(user, usdcReceived);
    }

    function _swap(address from, address to, uint256 amount)
        internal returns (uint256)
    {
        // Direct swap, no fee
        return dex.swap(from, to, amount);
    }
}
```

### ✅ Secure: Charges swap fee

```solidity
contract SecureSwapFee {
    uint256 constant SWAP_FEE = 30; // 0.3%

    function liquidate(address user) external {
        uint256 collateral = getCollateral(user); // WETH
        uint256 debt = getDebt(user); // USDC

        // Charge swap fee
        uint256 swapFee = (collateral * SWAP_FEE) / 10000;
        uint256 amountToSwap = collateral - swapFee;

        uint256 usdcReceived = _swap(WETH, USDC, amountToSwap);

        protocolFees[WETH] += swapFee;
        _repayDebt(user, usdcReceived);
    }
}
```

---

## 7. Oracle Sandwich Self-Liquidation

### ❌ Vulnerable: Self-liquidation allowed

```solidity
contract VulnerableSelfLiquidation {
    IOracle public oracle;

    function updateOracle() external {
        // VULNERABLE: anyone can update oracle
        oracle.update();
    }

    function liquidate(address user) external {
        require(isLiquidatable(user), "Not liquidatable");

        uint256 collateral = getCollateral(user);
        uint256 debt = getDebt(user);
        uint256 bonus = (debt * 110) / 100;

        // VULNERABLE: no check for self-liquidation
        transfer(msg.sender, collateral);
        transfer(treasury, debt);
        // User can liquidate themselves via alt account!
    }
}
```

**Attack flow:**
1. User position becomes liquidatable (collateral = 1100, debt = 1000)
2. User calls `updateOracle()` when price favorable
3. User immediately calls `liquidate(userAddress)` from alt account
4. User receives 1100 collateral, pays 1000 debt
5. User profits 100 (10% liquidation bonus extracted)

### ✅ Secure: Prevents self-liquidation

```solidity
contract SecureSelfLiquidation {
    IOracle public oracle;
    mapping(address => uint256) public lastOracleUpdate;

    uint256 constant ORACLE_DELAY = 1 hours;

    function updateOracle() external {
        oracle.update();
        lastOracleUpdate[msg.sender] = block.timestamp;
    }

    function liquidate(address user) external {
        require(msg.sender != user, "Cannot self-liquidate");
        require(isLiquidatable(user), "Not liquidatable");

        // Prevent immediate liquidation after oracle update
        require(
            block.timestamp > lastOracleUpdate[msg.sender] + ORACLE_DELAY,
            "Oracle update delay"
        );

        uint256 collateral = getCollateral(user);
        uint256 debt = getDebt(user);
        uint256 bonus = (debt * 110) / 100;

        transfer(msg.sender, collateral);
        transfer(treasury, debt);
    }
}
```

---

## Complete Secure Liquidation Example

```solidity
contract CompleteLiquidation {
    IERC20 public collateralToken;
    IERC20 public debtToken;
    IYieldVault public vault;
    IOracle public oracle;

    uint8 public immutable collateralDecimals;
    uint8 public immutable debtDecimals;

    uint256 constant LIQUIDATION_RATIO = 120; // 120%
    uint256 constant LIQUIDATION_BONUS = 110; // 10%
    uint256 constant PROTOCOL_FEE_RATE = 500; // 5%
    uint256 constant ORACLE_DELAY = 1 hours;

    mapping(address => uint256) public userShares;
    mapping(address => uint256) public userDebt;
    mapping(address => uint256) public lastOracleUpdate;

    constructor(address _collateral, address _debt, address _vault) {
        collateralToken = IERC20(_collateral);
        debtToken = IERC20(_debt);
        vault = IYieldVault(_vault);

        collateralDecimals = IERC20Metadata(_collateral).decimals();
        debtDecimals = IERC20Metadata(_debt).decimals();
    }

    function getCollateralValue(address user) public view returns (uint256) {
        // Include earned yield
        return vault.balanceOf(user);
    }

    function isLiquidatable(address user) public view returns (bool) {
        uint256 collateral = getCollateralValue(user);
        uint256 debt = userDebt[user];

        // Scale debt to collateral decimals
        uint256 debtScaled = debt * (10 ** collateralDecimals) /
            (10 ** debtDecimals);

        return collateral * 100 < debtScaled * LIQUIDATION_RATIO;
    }

    function liquidate(address user) external {
        // Prevent self-liquidation
        require(msg.sender != user, "Cannot self-liquidate");

        // Oracle delay
        require(
            block.timestamp > lastOracleUpdate[msg.sender] + ORACLE_DELAY,
            "Oracle delay"
        );

        require(isLiquidatable(user), "Not liquidatable");

        uint256 collateral = getCollateralValue(user);
        uint256 debt = userDebt[user];

        // Scale properly
        uint256 debtScaled = debt * (10 ** collateralDecimals) /
            (10 ** debtDecimals);

        // Calculate liquidator reward FIRST
        uint256 bonusAmount = (debtScaled * LIQUIDATION_BONUS) / 100;
        uint256 liquidatorReward = bonusAmount - debtScaled;

        // Protocol fee from reward
        uint256 protocolFee = (liquidatorReward * PROTOCOL_FEE_RATE) / 10000;
        uint256 netLiquidatorReward = liquidatorReward - protocolFee;

        require(collateral >= bonusAmount, "Insufficient collateral");

        // Transfer in priority order
        vault.withdraw(user, netLiquidatorReward, msg.sender);
        vault.withdraw(user, debtScaled, address(this));
        vault.withdraw(user, protocolFee, treasury);

        userDebt[user] = 0;
        userShares[user] = 0;
    }

    function borrow(uint256 amount) external {
        uint256 collateral = getCollateralValue(msg.sender);
        uint256 debt = userDebt[msg.sender] + amount;

        // Scale debt to collateral decimals
        uint256 debtScaled = debt * (10 ** collateralDecimals) /
            (10 ** debtDecimals);

        // Enforce minimum that accounts for liquidation costs
        require(
            collateral * 100 >= debtScaled * LIQUIDATION_RATIO,
            "Insufficient collateral"
        );

        userDebt[msg.sender] = debt;
        debtToken.transfer(msg.sender, amount);
    }

    function updateOracle() external {
        oracle.update();
        lastOracleUpdate[msg.sender] = block.timestamp;
    }
}
```

**Key features:**
1. ✅ Correct decimal scaling
2. ✅ Liquidator paid first
3. ✅ Low protocol fee (5%)
4. ✅ Minimum accounts for liquidation costs
5. ✅ Includes earned yield in collateral value
6. ✅ Prevents self-liquidation
7. ✅ Oracle update delay

## reference.md

# Liquidation Calculation Vulnerability Reference

Complete vulnerability patterns for liquidation calculation issues.

---

## 1. Incorrect Liquidator Reward Calculation

### Description
Decimal precision errors in liquidator reward calculations result in rewards that are too small (unusable) or too large (protocol insolvency).

### Technical Details
- Liquidator rewards must match collateral token decimals
- Common errors: hardcoding 1e18 when collateral uses 6 decimals (USDC)
- Result: reward = 0 or astronomically large value
- Makes liquidation unprofitable or impossible

### Code Pattern
```solidity
// Vulnerable: assumes 18 decimals
uint256 reward = (debt * liquidationBonus) / 1e18;

// Vulnerable: doesn't scale to collateral decimals
function calculateReward(uint256 collateral, uint256 bonus)
    returns (uint256)
{
    return collateral * bonus / 100; // Wrong precision
}
```

### Detection
- Search: `liquidationReward`, `liquidationBonus`, calculation patterns
- Check: reward calculation uses collateral token decimals
- Verify: reward precision matches token precision

### Severity
**Critical** - Prevents all liquidations or causes protocol insolvency

---

## 2. Unprioritized Liquidator Reward

### Description
Liquidator rewards paid after other fees (protocol fees, penalties) can be reduced to zero, removing liquidation incentive.

### Technical Details
- Liquidator reward must be paid first from seized collateral
- If protocol fee taken first: `reward = collateral - protocolFee - debt`
- If protocol fee large enough: `reward = 0`
- Liquidators have no incentive to liquidate

### Code Pattern
```solidity
// Vulnerable: protocol fee paid first
function liquidate(address user) external {
    uint256 collateral = getCollateral(user);
    uint256 debt = getDebt(user);

    uint256 protocolFee = collateral * protocolFeeRate / 100;
    protocolFeesAccrued += protocolFee;

    uint256 remaining = collateral - protocolFee;
    uint256 liquidatorReward = remaining - debt; // Can be 0!

    transfer(msg.sender, liquidatorReward);
}
```

### Detection
- Search: order of fee calculations in `liquidate()` functions
- Check: liquidator reward calculated before other fees
- Verify: reward amount not dependent on remaining balance after fees

### Severity
**High** - Removes liquidation incentive, accumulates bad debt

---

## 3. Excessive Protocol Fee

### Description
Protocol fees >30% of seized collateral make liquidation unprofitable after gas costs.

### Technical Details
- Liquidation bonus typically 5-15%
- Gas costs: ~100-300k gas (~$5-$50 depending on chain/prices)
- If protocol takes >30% of bonus: liquidator loses money
- Example: 10% bonus, 5% protocol fee = 5% net = unprofitable for small positions

### Code Pattern
```solidity
// Vulnerable: 50% protocol fee
uint256 constant PROTOCOL_FEE_RATE = 5000; // 50%

function liquidate(address user) external {
    uint256 collateral = getCollateral(user);
    uint256 debt = getDebt(user);
    uint256 bonus = (collateral - debt) * 110 / 100; // 10% bonus

    uint256 protocolFee = bonus * PROTOCOL_FEE_RATE / 10000; // 50% of bonus!
    uint256 liquidatorReward = bonus - protocolFee;
    // liquidatorReward too small after gas
}
```

### Detection
- Search: `PROTOCOL_FEE`, `protocolFeeRate` in liquidation functions
- Calculate: net liquidator reward after fees
- Verify: net reward > gas costs for minimum positions

### Severity
**High** - Makes liquidation unprofitable, bad debt accumulates

---

## 4. Missing Liquidation Fees In Minimum Collateral Requirements

### Description
Minimum collateral requirements don't account for liquidation costs (gas + fees), making positions unliquidatable at minimum.

### Technical Details
- Minimum collateral should be: `debt + liquidation_costs + buffer`
- Liquidation costs: gas (~$5-$50) + protocol fees
- If minimum = debt: no reward for liquidator at threshold
- Users can stay at minimum, never liquidatable profitably

### Code Pattern
```solidity
// Vulnerable: minimum = debt only
function borrow(uint256 amount) external {
    uint256 collateral = userCollateral[msg.sender];
    uint256 debt = userDebt[msg.sender];

    require(collateral >= debt, "Insufficient collateral"); // Wrong!
    // Should be: collateral >= debt * 1.2 (or higher)

    userDebt[msg.sender] += amount;
}
```

### Detection
- Search: `minimumCollateral`, collateral ratio checks in borrow functions
- Check: minimum accounts for liquidation bonus + fees + gas
- Verify: positions at minimum are profitably liquidatable

### Severity
**Medium** - Allows unliquidatable positions at minimum threshold

---

## 5. Unaccounted Yield/PNL In Collateral Valuation

### Description
Earned yield or positive PNL not included in collateral value causes unfair liquidations and lost user funds.

### Technical Details
- Collateral value should include: deposited + earned_yield + positive_PNL
- If only deposited counted: users liquidated despite being solvent
- Users lose earned yield they should be able to withdraw
- Particularly critical in yield-bearing vaults, perpetuals

### Code Pattern
```solidity
// Vulnerable: ignores earned yield
function getCollateralValue(address user) returns (uint256) {
    return userDeposits[user]; // Wrong! Missing yield
}

function isLiquidatable(address user) returns (bool) {
    uint256 collateral = getCollateralValue(user); // Understated
    uint256 debt = getDebt(user);
    return collateral < debt * LIQUIDATION_RATIO;
    // User liquidated despite having sufficient collateral + yield
}
```

### Detection
- Search: `getCollateralValue`, `isLiquidatable`, yield/PNL tracking
- Check: collateral calculation includes all value sources
- Verify: yield-bearing tokens use current balance, not deposit amount

### Severity
**High** - Causes unfair liquidations, users lose earned funds

---

## 6. No Swap Fee During Liquidation

### Description
Protocol doesn't charge swap fees when liquidation involves token swaps, losing revenue.

### Technical Details
- Liquidation often requires swapping collateral to repay debt
- Normal swaps charge 0.3% fee, liquidation swaps should too
- Lost revenue compounds over many liquidations
- Not a security issue but economic inefficiency

### Code Pattern
```solidity
// Vulnerable: no swap fee charged
function liquidate(address user) external {
    uint256 collateralAmount = getCollateral(user);
    uint256 debtAmount = getDebt(user);

    // Swap collateral token to debt token
    uint256 debtTokenReceived = _swap(
        collateralToken,
        debtToken,
        collateralAmount
    ); // No fee charged!

    _repayDebt(user, debtTokenReceived);
}
```

### Detection
- Search: swap operations in `liquidate()` functions
- Check: swap fee applied to liquidation swaps
- Verify: fee goes to protocol, not liquidator

### Severity
**Medium** - Protocol loses revenue, not security critical

---

## 7. Oracle Sandwich Self-Liquidation

### Description
Users can trigger oracle price updates to create profitable self-liquidation opportunities, extracting value from protocol.

### Technical Details
- Oracles update prices based on external triggers
- User can: 1) trigger oracle update when favorable, 2) immediately self-liquidate at new price
- Liquidation bonus paid to user's alt account
- Effectively: user extracts liquidation bonus by gaming oracle timing
- Requires oracle manipulation or just timing oracle updates

### Code Pattern
```solidity
// Vulnerable: allows self-liquidation
function liquidate(address user) external {
    require(isLiquidatable(user), "Not liquidatable");

    uint256 collateral = getCollateral(user);
    uint256 debt = getDebt(user);
    uint256 bonus = (collateral - debt) * 110 / 100;

    // No check: msg.sender != user
    // User can liquidate themselves via alt account
    transfer(msg.sender, collateral);
    transfer(treasury, debt - bonus);
}

// User flow:
// 1. Price drops, user becomes liquidatable
// 2. User calls updateOracle() to trigger price update
// 3. User calls liquidate(userAddress) from alt account
// 4. User receives liquidation bonus
```

### Detection
- Search: `liquidate()` functions, oracle update mechanisms
- Check: self-liquidation restrictions (`msg.sender != user`)
- Check: oracle update delays/cooldowns between update and liquidation
- Verify: liquidation bonus can't be gamed via oracle timing

### Severity
**Critical** - Direct value extraction via oracle manipulation

---

## Validation Checklist

For each liquidation function, verify:

1. **Decimal Precision**
   - [ ] Liquidator rewards scaled to collateral token decimals
   - [ ] No hardcoded 1e18 assumptions
   - [ ] Reward calculations tested with 6/8/18 decimal tokens

2. **Fee Priority**
   - [ ] Liquidator reward calculated first
   - [ ] Protocol fees taken from remaining balance after reward
   - [ ] Reward amount not reduced by other fees

3. **Fee Economics**
   - [ ] Protocol fee <30% of liquidation bonus
   - [ ] Net liquidator reward >gas costs for minimum positions
   - [ ] Fee structure analyzed for different position sizes

4. **Minimum Collateral**
   - [ ] Minimum accounts for: debt + liquidation_bonus + protocol_fee + gas_buffer
   - [ ] Positions at minimum threshold profitably liquidatable
   - [ ] Buffer accounts for gas price volatility

5. **Yield/PNL Inclusion**
   - [ ] Collateral value includes earned yield
   - [ ] Positive PNL included in collateral calculations
   - [ ] Yield-bearing tokens use current balance not deposit
   - [ ] PNL updated before liquidation checks

6. **Swap Fees**
   - [ ] Swap fees charged during liquidation if applicable
   - [ ] Fees go to protocol treasury
   - [ ] Fee rate consistent with non-liquidation swaps

7. **Self-Liquidation Protection**
   - [ ] Self-liquidation restricted (`msg.sender != user`)
   - [ ] Oracle update delays prevent sandwich liquidation
   - [ ] Liquidation bonus can't be extracted via timing
   - [ ] Multiple oracle price sources prevent manipulation

---

## Common False Positives

**DO NOT flag these:**

1. **Trusted liquidator systems** - Profitability not required for admin liquidators
2. **Documented fee structures** - Intentional high fees with alternative incentives
3. **Yield distribution delays** - Documented yield claiming delays for gas optimization
4. **Manual oracle updates** - Admin-only oracle updates (no user manipulation risk)
5. **Fixed reward structures** - With analysis showing profitability across position sizes

**DO flag these:**

1. **Trustless liquidators** - Without profitability guarantees
2. **Undocumented fee priorities** - Unclear whether liquidator paid first
3. **Missing yield in calculations** - Even if documented, causes unfair liquidations
4. **User-triggered oracles** - Without delays/restrictions on liquidation
5. **Self-liquidation allowed** - Even if documented, enables value extraction

## templates

```

```

## templates/report-template.md

# Liquidation Calculation Security Audit Report

## Executive Summary

**Contract:** [Contract Name]
**Audit Date:** [Date]
**Auditor:** [Name/Team]

**Findings Overview:**
- Critical: X
- High: X
- Medium: X
- Low: X

## Findings

---

### [SEVERITY] Finding #X: [Vulnerability Title]

**Pattern:** #X - [Pattern Name from reference.md]

**Location:** `[contract_name.sol:line_numbers]`

**Description:**

[Detailed explanation of the calculation vulnerability]

**Vulnerable Code:**

```solidity
// Highlight the problematic calculation
function liquidate(address user) external {
    // Show the specific calculation issue
}
```

**Economic Impact:**

[Analyze calculation correctness and profitability:]
- **Liquidator reward:** [Calculated value in token decimals]
- **Expected reward:** [What it should be]
- **Decimal error:** [If applicable - e.g., "1e18 assumed for 6-decimal token"]
- **Protocol fee:** [Amount and percentage of bonus]
- **Net liquidator profit:** [Reward - protocol_fee - gas_cost]
- **Is liquidation profitable?** [Yes/No with calculation]
- **Missing value:** [Yield/PNL not accounted for, if applicable]

**Proof of Concept:**

```solidity
// Executable test case showing calculation error or exploitation
contract PoC {
    function testIncorrectRewardCalculation() public {
        // 1. Setup position with specific decimals
        // 2. Trigger liquidation
        // 3. Calculate actual reward vs expected
        // 4. Demonstrate error

        // Expected: 100 USDC reward
        // Actual: 0.0001 USDC (decimal error)
    }
}
```

**Scenario:**
1. [Position setup - tokens, amounts, decimals]
2. [Position becomes liquidatable]
3. [Liquidator calls liquidate()]
4. [Calculation shows error - wrong decimals, unprioritized fee, missing yield]
5. [Result: unprofitable liquidation / unfair liquidation / value extraction]

**Impact Analysis:**

**Short-term:**
- [Immediate impact - e.g., "All liquidations fail due to 0 rewards"]

**Long-term:**
- [Systemic impact - e.g., "Bad debt accumulates, protocol insolvent"]

**Scale:**
- [Quantify affected liquidations - e.g., "All USDC collateral positions"]

**Remediation:**

```solidity
// Fixed implementation
function liquidate(address user) external {
    Position memory pos = positions[user];

    // Fix decimal handling
    uint256 debtScaled = pos.debt * (10 ** collateralDecimals)
        / (10 ** debtDecimals);

    // Calculate liquidator reward FIRST
    uint256 bonusAmount = (debtScaled * LIQUIDATION_BONUS) / 100;
    uint256 liquidatorReward = bonusAmount - debtScaled;

    // Protocol fee from reward
    uint256 protocolFee = (liquidatorReward * PROTOCOL_FEE_RATE) / 10000;
    uint256 netReward = liquidatorReward - protocolFee;

    // Include yield in collateral value
    uint256 collateral = vault.balanceOf(user); // Not userDeposits[user]

    require(collateral >= bonusAmount, "Insufficient collateral");

    // ... complete liquidation
}
```

**Recommendations:**
1. [Primary fix - e.g., "Scale rewards to collateral token decimals"]
2. [Fee structure - e.g., "Pay liquidator reward before protocol fees"]
3. [Valuation - e.g., "Include earned yield in collateral calculations"]
4. [Self-liquidation - e.g., "Add require(msg.sender != user)"]

**Gas Impact:** [Estimated additional gas cost for fix]

**Economic Calculation:**

Before fix:
```
Collateral: 1000 USDC (6 decimals)
Debt: 900 DAI (18 decimals)
Reward calc: (900e18 * 110) / 1e18 = 990e0 = 0.00000099 USDC
Net: unusable reward (liquidation impossible)
```

After fix:
```
Collateral: 1000 USDC (1000e6)
Debt scaled: 900 DAI → 900e6 (scaled to USDC decimals)
Bonus: (900e6 * 110) / 100 = 990e6
Reward: 990e6 - 900e6 = 90e6 = 90 USDC
Protocol fee: 90 * 5% = 4.5 USDC
Net reward: 85.5 USDC - $20 gas = $65.5 profit
```

---

### [SEVERITY] Finding #X: [Next Vulnerability]

[Repeat above structure for each finding]

---

## Severity Definitions

**Critical:** Incorrect liquidator reward decimals preventing all liquidations, profitable self-liquidation via oracle manipulation enabling value extraction. Protocol insolvency risk.

**High:** Unprioritized liquidator rewards removing liquidation incentive, excessive protocol fees making liquidation unprofitable, unaccounted yield causing unfair liquidations.

**Medium:** Minimum collateral not accounting for liquidation costs, missing swap fees during liquidation, liquidation unprofitable for small positions.

**Low:** Suboptimal fee structures, missing documentation, gas inefficiencies without security impact.

## Recommendations Summary

### Immediate Actions (Critical/High)
1. [List critical calculation fixes to deploy immediately]
   - Example: "Fix decimal scaling in reward calculations"
   - Example: "Prioritize liquidator rewards over protocol fees"
   - Example: "Include earned yield in collateral valuation"

### Short-term Improvements (Medium)
1. [List medium-priority enhancements]
   - Example: "Increase minimum collateral to account for liquidation costs"
   - Example: "Add swap fees during liquidation"

### Long-term Enhancements (Low)
1. [List optimization opportunities]
   - Example: "Dynamic protocol fees based on position size"

## Checklist Results

Based on `checklist.md`:

- [x] **Decimal precision:** Rewards scaled to collateral decimals ✓/✗
- [x] **Fee priority:** Liquidator paid before protocol fees ✓/✗
- [x] **Fee economics:** Protocol fee <30% of bonus ✓/✗
- [x] **Minimum collateral:** Accounts for liquidation costs ✓/✗
- [x] **Yield inclusion:** Earned yield in collateral value ✓/✗
- [x] **Swap fees:** Charged during liquidation if applicable ✓/✗
- [x] **Self-liquidation:** Prevented via checks or delays ✓/✗

## Economic Analysis

### Liquidation Profitability Model

**Assumptions:**
- Gas price: [X gwei]
- Liquidation gas cost: [Y gas]
- Current bonus: [Z%]
- Protocol fee: [A%]

**Net Liquidator Reward:**
```
Bonus = debt × Z%
Protocol fee = bonus × A%
Net reward = bonus - protocol_fee
Gas cost = X gwei × Y gas = B USD
Net profit = net_reward - gas_cost
```

**Minimum Profitable Position:**
```
Gas cost = B USD
Net reward rate = Z% - (Z% × A%) = C%
Minimum debt = B / C%
```

**Current minimum:** [D USD]
**Recommended minimum:** [B / C% USD]

### Decimal Error Impact

**USDC (6 decimals) example:**
```
Incorrect: reward = (debt_18_decimals × 110) / 1e18 = ~0 USDC
Correct: reward = (debt_6_decimals × 110) / 100 = actual USDC
```

**Impact:** [All USDC liquidations fail / X% of positions affected]

### Fee Priority Impact

**Protocol fee first:**
```
Collateral: 1100
Protocol fee (50%): 550
Remaining: 550
Liquidator reward: 550 - 1000 = negative (fails)
```

**Liquidator paid first:**
```
Bonus: 1100
Liquidator reward: 100
Protocol fee (50% of reward): 50
Net liquidator: 50 (positive)
```

### Yield Inclusion Impact

**Without yield:**
```
User deposits: 1000
Earned yield: 200
Collateral value (incorrect): 1000
Debt: 900
Health: 1000/900 = 111% (liquidatable at 120%)
Result: unfair liquidation
```

**With yield:**
```
User deposits: 1000
Earned yield: 200
Collateral value (correct): 1200
Debt: 900
Health: 1200/900 = 133% (healthy)
Result: fair
```

## Testing Recommendations

### Unit Tests
- [ ] Liquidation reward calculation with 6/8/18 decimal tokens
- [ ] Fee priority (liquidator paid first)
- [ ] Protocol fee edge cases (0%, 50%, 100%)
- [ ] Minimum collateral threshold liquidation profitability
- [ ] Yield inclusion in collateral valuation
- [ ] Self-liquidation prevention
- [ ] Oracle update delays

### Integration Tests
- [ ] Multi-token liquidations with different decimals
- [ ] Fee distribution to liquidator vs protocol
- [ ] Yield vault integration in liquidations
- [ ] Oracle manipulation attempts
- [ ] Liquidation profitability at various gas prices

### Economic Simulations
- [ ] Liquidation profitability across position sizes
- [ ] Protocol fee impact on liquidation volume
- [ ] Yield accrual impact on liquidation rates
- [ ] Minimum collateral sufficiency under volatility

## Appendix

### Decimal Conversion Reference

| Token | Decimals | Example Amount | Raw Value |
|-------|----------|----------------|-----------|
| USDC  | 6        | 1000 USDC      | 1000e6    |
| DAI   | 18       | 1000 DAI       | 1000e18   |
| WBTC  | 8        | 1 WBTC         | 1e8       |

**Scaling formula:**
```
amount_in_token_B = amount_in_token_A × (10^decimals_B) / (10^decimals_A)
```

### Gas Cost Analysis

**Ethereum mainnet (50 gwei, $2000 ETH):**
- Simple liquidation: 150k gas = $15
- Complex (with swaps): 300k gas = $30

**L2 (Arbitrum/Optimism):**
- Simple: 150k gas = $0.50
- Complex: 300k gas = $1

**Adjust minimum position sizes accordingly**

### Fee Structure Best Practices

**Liquidation bonus:** 5-15% (incentive for liquidators)
**Protocol fee:** <10% of bonus (maintain profitability)
**Net liquidator reward:** >gas cost + buffer

**Example:**
- Bonus: 10%
- Protocol fee: 5% of bonus = 0.5% of debt
- Net liquidator: 9.5% of debt
- Minimum position: gas_cost / 9.5%

### Self-Liquidation Prevention

**Method 1:** Direct check
```solidity
require(msg.sender != user, "Cannot self-liquidate");
```

**Method 2:** Oracle delay
```solidity
require(
    block.timestamp > lastOracleUpdate[msg.sender] + ORACLE_DELAY,
    "Oracle delay"
);
```

**Method 3:** Whitelist liquidators
```solidity
require(isAuthorizedLiquidator[msg.sender], "Not authorized");
```

