---
# System prepended metadata

title: Understanding Aave V2 Code (5/n) - Liquidation Mechanism

---

# Understanding Aave V2 Code (5/n) - Liquidation Mechanism
## TLDR
- **When**: Health factor < 1.0 (the only trigger condition)
- **What**: Liquidators repay up to 50% of a borrower's debt and receive collateral + liquidation bonus (5-10%)
- **How**: Two settlement modes
    - **aToken mode**: Receive interest-bearing tokens (always succeeds, no liquidity check needed)
    - **Underlying mode**: Receive actual assets (requires pool has enough liquidity)
- **Why**: Protects protocol solvency while giving borrowers recovery opportunity through 50% close factor cap

## What's Liquidation in Aave V2
### Simple explanation
- Your collateral loses value or your debt grows too much
- Health factor drops below 1.0 (positoin underwater)
- Anyone can step in as a liquidator to repay part of your debt
- Liquidator gets your collateral + liquidation bonus (5-10%) as profit
- You lose collateral but debt is reduced, hopefully recovering HF to > 1.0

### Why liquidation exists
- **Protocol safety**: Prevent bad debt accumulation
- **Lender protection**: Ensure depositors can always withdraw their funds
- **System solvency**: Keep total collateral > total debt across protocol
- **Market efficiency**: Quick price discovery during volatile conditions

### What trigger liquidation?
- Health factor < 1.0 (the ONLY condition)
- Caused by
    - Collateral price drops
    - Debt price increases
    - Interest accrual on debt (debt grows over time)
    - User withdrawing collateral without repaying debt

### Who can liquidate?
- Anyone, it's permissionless
- Usually done by bots competing for profit
- Common liquidators: specialized MEV bots, keeper networks, whale traders

### What liquidators get?
- Collateral and liquidation bonus (typically 5-10% of debt repaid)

### What borrowers lose?
- Collateral seized, but debt is reduced by the amount liquidator paid
- Net loss is the liquidation bonus amount

## High-Level Flow
```
                       ┌──────────────┐
                       │   Liquidator │
                       │  Initiates   │
                       └──────┬───────┘
                              │ Want to repay borrower's USDC position
                              │ and get aETH or underlying ETH
                  ┌───────────▼──────────────┐
                  │  Calculate Health Factor │
                  └───────────┬──────────────┘
                              │
                       ┌──────▼──────┐
                       │  HF < 1.0?  │
                       └──┬───────┬──┘
                  YES     │       │     NO
                  ┌───────▼       ▼────────┐
                  │                REJECT   │
                  │                         │
          ┌───────▼────────┐                │
          │ Validate Call  │                │
          │ - Reserves OK? │                │
          │ - Collateral?  │                │
          │ - Has debt?    │                │
          └───────┬────────┘                │
                  │ ALL CHECKS PASS         │
          ┌───────▼────────────┐            │
          │ Calculate Amounts  │            │
          │ - Max debt (50%)   │            │
          │ - Collateral needed│            │
          │ - Check liquidity  │            │
          └───────┬────────────┘            │
                  │                         │
          ┌───────▼────────────┐            │
          │  Update State      │            │
          │  - Debt reserve    │            │
          │  - Accrue interest │            │
          └───────┬────────────┘            │
                  │                         │
          ┌───────▼────────────┐            │
          │  Burn Debt Tokens  │            │
          │  - Variable/Stable │            │
          │  - Update rates    │            │
          └───────┬────────────┘            │
                  │                         │
                  │                         │
      ┌───────────▼────────────┐            │
      │ receiveAToken?         │            │
      └────┬──────────────┬────┘            │
    TRUE   │              │  FALSE          │
  ┌────────▼────┐    ┌────▼─────────┐       │
  │Transfer     │    │Update State  │       │
  │aTokens      │    │Burn aTokens  │       │
  │             │    │Withdraw ETH  │       │
  │             │    │Update rates  │       │
  └────────┬────┘    └────┬─────────┘       │
           │              │                 │
           └──────┬───────┘                 │
                  │                         │
          ┌───────▼────────────┐            │
          │ Liquidator Pays    │            │
          │ Transfer USDC      │            │
          │ to pool            │            │
          └───────┬────────────┘            │
                  │                         │
          ┌───────▼────────────┐            │
          │  Emit Event        │            │
          │  Return Success    │            │
          └────────────────────┘            │
                                            │
                  ┌─────────────────────────┘
                  │
          ┌───────▼────────┐
          │   REJECTED     │
          └────────────────┘
```

## Data Structure
```solidity
struct LiquidationCallLocalVars {
    uint256 userCollateralBalance;              // user collateral's balance
    uint256 userStableDebt;                     // user stable debt address
    uint256 userVariableDebt;                   // user variable debt address
    uint256 maxLiquidatableDebt;                // max liquidatable debt amount (50% close factor limit)
    uint256 actualDebtToLiquidate;              // final debt amount to liquidate
    uint256 liquidationRatio;                   // unused variable
    uint256 maxAmountCollateralToLiquidate;     // unused variable (moved to AvailableCollateralToLiquidateLocalVars)
    uint256 userStableRate;                     // unused variable
    uint256 maxCollateralToLiquidate;           // max amount of collateral will be transferred to liquidators
    uint256 debtAmountNeeded;                   // amount of debt can be covered by available collateral
    uint256 healthFactor;                       // user whole positions' health factor (need to be < 1.0 to be liquidatable)
    uint256 liquidatorPreviousATokenBalance;    // liquidator's aToken balance before receiving liquidation collateral
    IAToken collateralAtoken;                   // collateral aToken contract
    bool isCollateralEnabled;                   // unused variable
    DataTypes.InterestRateMode borrowRateMode;  // unused variable
    uint256 errorCode;                          // error code of validate liquidation call
    string errorMsg;                            // error message of validate liquidation call
}
```
| Variable | Usage | 
|----------|----------|
| healthFactor  | Check if the position is liquidatable, used in `validateLiquidationCall` |
| userStableDebt  | Stable rate debt, used in `getUserCurrentDebt` to calculate stable debt balance |
| userVariableDebt  | Variable rate debt, used in `getUserCurrentDebt` to calculate variable debt balance |
| maxLiquidatableDebt  | Cap on debt liquidation, calculated as `max liquidatable debt = (stable debt + variable debt) * close factor` |
| errorCode, errorMsg  | Validation results, return value of `validateLiquidationCall` for early return if error |
| collateralAtoken  | Collateral contract, used for transfer/ burn operations |
| userCollateralBalance  | User's collateral balance, used as input in `_calculateAvailableCollateralToLiquidate` |
| maxCollateralToLiquidate  | **Final collateral amount** to transfer to liquidators, return value of `_calculateAvailableCollateralToLiquidate`, used everywhere (transfer, burn, events) |
| debtAmountNeeded  | Collateral constrainted debt amount to be repaid (may be less than requested), return value of `_calculateAvailableCollateralToLiquidate`, adjust `actualDebtToLiquidate` if needed |
| actualDebtToLiquidate  | **Final debt amount** to liquidate, used everywhere (burn, transfer, events) |
| liquidatorPreviousATokenBalance  | Auto-enable collateral, enable collateral for liquidators as 1st-time recipients |

## Math & Key Concepts
### Health Factor - The Liquidation Trigger
- The health factor determines if a position can be liquidated. It represents the safety margin of a borrowing position
- Formula = `Total collateral * Liquidation threshold / Total debt`
- Liquidation condition
    - When HF < 1.0, then the position becomes liquidatable

### Liquidation Mechanics
#### Close Factor - Max Liquidation
- Rule: Max 50% of user's debt position can be liquidated in a single transaction
- Formula: `maxLiquidatableDebt = (userStableDebt + userVariableDebt) × LIQUIDATION_CLOSE_FACTOR_PERCENT`
- Why 50% cap?
    - Give borrower chance to add collateral to recover the position
    - Encourage multiple liquidators
    - Prevent liquidation spirals: liquidator seizes the asset then sell, price decrease further, more positions become liquidatable
- Summary
  | Aspect                 | Without Close Factor | With 50% Close Factor |
  |------------------------|----------------------|-----------------------|
  | Max liquidation        | 100% of debt         | 50% of debt           |
  | User protection        | None                 | Strong                |
  | Recovery chance        | No                   | Yes                   |
  | Liquidator competition | Winner-takes-all     | Multiple participants |
  | Cascade risk           | High                 | Lower                 |
  | Protocol safety        | Vulnerable           | Balanced              |
  | User experience        | Poor                 | Better                |

#### Liquidation Bonus - Liquidator Incentive 
- A percentage premium given to liquidators on top of the debt they repay.
- Example
    - You repay $1,000 of someone's debt
    - With a 5% liquidation bonus, you receive collateral worth $1,050
    - Your profit: $50 (minus gas costs)
- Formula: `collateralToSeize = (debtRepaid × debtPrice / collateralPrice) × liquidationBonus`
    - `liquidationBonus` is stored as basis points + 10,000
    - eg. 10,500 = 105% = 5% bonus
- Why does it exists
    - Incentivize liquidators: Liquidator can earn a profit
    - Compensate for liquidators' risks (price volatility, gas costs, competition, slippage)
    - Ensure protocol health: Fast liquidation prevent bad debt accumulation, keep the protocol solvent, and protect lenders from losses
- Aave v2 liquidation bonuses
    - Stablecoins = 5% bonus
        | Asset | Liquidation Bonus | Percentage |
        |-------|-------------------|------------|
        | DAI   | 10500             | 5%         |
        | USDC  | 10500             | 5%         |
        | TUSD  | 10500             | 5%         |
    - Volatile assets = 10% bonus
        | Asset | Liquidation Bonus | Percentage |
        |-------|-------------------|------------|
        | WETH  | 10500             | 5%         |
        | WBTC  | 11000             | 10%        |
        | AAVE  | 11000             | 10%        |
        | LINK  | 11000             | 10%        |
        | UNI   | 11000             | 10%        |
        | MKR   | 11000             | 10%        |
- Summary
  | Component       | Value                         | Notes                              |
  |-----------------|-------------------------------|------------------------------------|
  | Stablecoins     | 5%                            | Low risk assets                    |
  | Volatile assets | 10%                           | High risk assets                   |
  | Who pays?       | User being liquidated         | Penalty for undercollateralization |
  | Who receives?   | Liquidator                    | Profit + risk compensation         |
  | Purpose         | Incentivize fast liquidations | Protect protocol from bad debt     |

### Collateral Calculation - The Two-Way Check
#### Forward Calculation (Debt -> Collateral)
> "How much collateral do we need for this debt amount?"
- Formula: 
    - `maxAmountCollateralToLiquidate = (debtAssetPrice × debtToCover × liquidationBonus) / collateralPrice`

#### Reverse Calculation (Collateral -> Debt)
> "How much debt can this collateral cover?"
- Triggered when: `maxAmountCollateralToLiquidate` > `userCollateralBalance`
- Formula:
    - `debtAmountNeeded = (collateralPrice × collateralAmount) / (debtAssetPrice × liquidationBonus)`
#### The Two-Way Check Logic
- Step 1: Forward calculation
    - Want to liquidate: $50,000 debt
    - Need 26.25 ETH
- Step 2: Check avaiability
    - User has 10 ETH
    - 26.25 ETH > 10 ETH -> Insufficient collateral
- Step 3: Reverse calculation
    - Can only cover: $19,047 debt
    - Will seize for liquidator: 10 ETH (all available)
- Step 4: Adjust actual liquidation amount
    - actualDebtToLiquidate = min(50,000, 19,047) = $19,047

### Two Liquidation Model
#### Receive the aToken directly
- Mechanism
    - Liquidator gets the interest-bearing aTokens
    - Just transfer aTokens from user to liquidator
    - No withdrawal from pool needed
    - **No liquidity check required**
- Key properties
    - **Always available**: aTokens exist as accounting entries, not physical assets
    - **Instant execution**: No dependency on pool liquidity
    - **Interest bearing**: Liquidator continues earning interest on received aTokens
    - **Redemption flexibility**: Can withdraw underlying later when liquidity exists
#### Receive the underyling asset
- Liquidator gets actual underlying asset (eg. ETH, not aETH)
- Must BURN aTokens and WITHDRAW underlying asset from the pool
- **Reqire liquidity check**: Pool must have enough underlying asset
- The liquidity mismatch problem
    - aToken contract balance sheet
    ```
    ┌─────────────────────────────────────────────────┐
    │ ASSETS (What pool controls)                     │
    │ ─────────────────────────────────────────────   │
    │ Physical Tokens: 20 ETH  ← Available liquidity  │
    │ Loan Portfolio:  80 ETH  ← Borrowed by users    │
    │ ─────────────────────────────────────────────   │
    │ Total Assets:   100 ETH                         │
    │                                                 │
    │ LIABILITIES (What pool owes)                    │
    │ ─────────────────────────────────────────────   │
    │ aToken Supply:  100 aETH ← Depositor claims     │
    │ Each aETH = claim on ~1 ETH worth               │
    └─────────────────────────────────────────────────┘
    ```
    - Why liquidity check is required
    ```
    Scenario: User has 30 aETH, needs to liquidate 25 ETH

    Mode 1 Liquidator accepts aToken:
    ✅ Transfer 25 aETH to liquidator
        - Just accounting: aToken[user] -= 25, aToken[liquidator] += 25
        - Pool still has 20 ETH physical, 80 ETH borrowed
        - No problem!

    Mode 2 Liquidator accepts underlying asset:
    ❌ Send 25 ETH to liquidator
        - Need to withdraw 25 ETH from pool
        - Pool only has 20 ETH available
        - INSUFFICIENT LIQUIDITY!
        - Transaction must fail
    ```
#### Liquidation Mode Comparison: Pros & Cons
| **Criteria** | **Mode 1: Receive aToken** | **Mode 2: Receive Underlying** |
|--------------|---------------------------|-------------------------------|
| **Execution Reliability** | ✅ Guaranteed - never fails due to liquidity | ❌ Can fail if insufficient pool liquidity |
| **Gas Costs** | ✅ Lower - simple transfer operation | ❌ Higher - burn + withdraw + transfers |
| **Interest Earnings** | ✅ Keep earning - continue accruing interest | ❌ Stop earning - forgo future interest |
| **Scalability** | ✅ Unlimited - liquidate any amount regardless of utilization | ❌ Limited - can't exceed available liquidity |
| **Access Speed** | ❌ Delayed - must withdraw later (subject to liquidity) | ✅ Immediate - get underlying asset instantly |
| **Protocol Exposure** | ❌ Ongoing - holding aTokens = smart contract risk | ✅ None - done with Aave after liquidation |
| **Transaction Steps** | ❌ Two-step - need separate redemption transaction | ✅ One-step - direct usability, can sell/use immediately |
| **Amount Certainty** | ✅ Exact aToken amount guaranteed | ✅ Exact underlying amount (if succeeds) |

### How Aave v2 Handles Liquidity & Solvency Risk
- Solvency risk: Risk that total liabilities > total assets (bankruptcy risk)
- Liquidity risk: Risk of inability to meet short-term obligations despite being solvent
- How Aave V2 handles the risks

    | Risk Type      | TradFi                   | Aave V2 Individual User                  | Aave V2 Protocol                            |
    |----------------|--------------------------|------------------------------------------|---------------------------------------------|
    | **Solvency Risk**  | Assets < Liabilities     | Collateral × Threshold < Debt (HF < 1.0) |  N/A               |
    | Trigger        | Balance sheet underwater | Price crash, debt growth                 |  N/A               |
    | Measure        | Net Worth                | Health Factor                            |  N/A                            |
    | Resolution     | Bankruptcy, bail-in      | Liquidation (restore HF)                 |  N/A                          |
    | **Liquidity Risk** | Can't meet withdrawals   | N/A (user-level concept)                 | Can't honor aToken redemptions              |
    | Trigger        | Bank run                 | N/A                                      | High utilization + mass withdrawals         |
    | Measure        | Cash ratio               | N/A                                      | Utilization rate                            |
    | Resolution     | Central bank loan        | N/A                                      | Interest rate spikes & Allow liquidator to accept aTokens|

## Step by Step Code Walkthrough
### Step 1: Entry point - `liquidationCall()`
```solidity
// LendingPool.sol
function liquidationCall(
    address collateralAsset,    // asset to seize from borrower
    address debtAsset,          // the borrowed asset to repay
    address user,               // the borrower being liquidated
    uint256 debtToCover,        // how much debt the liquidator wants to repay
    bool receiveAToken          // if true, receive aTokens; else receive underlying
) external override whenNotPaused {
    // fetch the address of the LendingPoolCollateralManager contract
    address collateralManager = _addressesProvider.getLendingPoolCollateralManager();

    // use delegatecall to execute the liquidationCall in LendingPoolCollateralManager's context
    //solium-disable-next-line
    (bool success, bytes memory result) =
        collateralManager.delegatecall(
        abi.encodeWithSignature(
            'liquidationCall(address,address,address,uint256,bool)',
            collateralAsset,
            debtAsset,
            user,
            debtToCover,
            receiveAToken
        )
        );

    // ensure the delegatecall succeed (didn't revert)
    require(success, Errors.LP_LIQUIDATION_CALL_FAILED);

    // decode the return values
    (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));

    // check the operation succeed, otherwise revert with error msg
    require(returnCode == 0, string(abi.encodePacked(returnMessage)));
}
```
- **Key purpose**
    - Liquidate an unhealthy position collateral-wise (HF < 1.0)
    - Use `delegatecall` to bypass the 24kb contract size limit

### Step 2: Detailed Implementation
```solidity
// LendingPoolCollateralManager.sol
function liquidationCall(
    address collateralAsset,
    address debtAsset,
    address user,
    uint256 debtToCover,
    bool receiveAToken
) external override returns (uint256, string memory) { // return success/ error code and msg
// load storage reference
    // collateral reserve (the asset being seized)
    DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset]; 
    // debt reserve (the borrowed asset being repaid)
    DataTypes.ReserveData storage debtReserve = _reserves[debtAsset]; 
    // user configuration
    DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user];

// create struct to store local variables (avoid stack too deep)
    LiquidationCallLocalVars memory vars;

// calculate user's health factor
    (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
        user,
        _reserves,
        userConfig,
        _reservesList,
        _reservesCount,
        _addressesProvider.getPriceOracle()
    );

// get user's current stable and variable debt balance
    (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);

// ...
}
```
**Key Purpose**: Prepare for liquidation validation and debt & collateral amount calculation

### Step 3: Validation Liquidation Condition
```solidity
// return error code & message
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
    collateralReserve,      // collateral reserve (the asset being seized)
    debtReserve,            // debt reserve (the borrowed asset being repaid)
    userConfig,             // user config
    vars.healthFactor,      // user's health factor (calculated above)
    vars.userStableDebt,    // user's stable debt balance
    vars.userVariableDebt   // user's varaible debt balance
);
// check error code
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
    return (vars.errorCode, vars.errorMsg);
}

// ValidationLogic.sol
function validateLiquidationCall(
    DataTypes.ReserveData storage collateralReserve,
    DataTypes.ReserveData storage principalReserve,
    DataTypes.UserConfigurationMap storage userConfig,
    uint256 userHealthFactor,
    uint256 userStableDebt,
    uint256 userVariableDebt
) internal view returns (uint256, string memory) { // return error code & msg
    if ( // both collateral & debt reserve need to be active, otherwise return error
        !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
    ) {
        return (
        uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE),
        Errors.VL_NO_ACTIVE_RESERVE
        );
    }

    // health factor needs to be < 1. otherwise return error
    if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
        return (
        uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
        Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD
        );
    }

    // Two collateral condition must be both true
    bool isCollateralEnabled =
        // reserve level: liq. threshold > 0
        collateralReserve.configuration.getLiquidationThreshold() > 0 &&
        // user level: user enabled this deposit as collateral 
        userConfig.isUsingAsCollateral(collateralReserve.id);

    // if collateral isn't enabled as collateral by user, it cannot be liquidated
    if (!isCollateralEnabled) {
        return (
        uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
        Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED
        );
    }

    // user must have debt in principal asset
    if (userStableDebt == 0 && userVariableDebt == 0) {
        return (
        uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED),
        Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
        );
    }

    // all check passes, liquidation is valid
    return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS);
}
```
**Key purpose**: Validation the liquidation is allowed
- HF is below 1.0
- User has debt in the debtAsset
- Both collateral & debt reserves needs to be active
    - Collateral reserve inactive: Can't safely transfer collateral to liquidator (paused tokens, bad oracle etc.)
    - Debt reserve inactive: Can't safely accept debt repayment (depegged, broken oracle etc.)
- Collateral must be enabled
    - Only assets actively used as collateral can be seized, on both reserve and user config level
- If validation fails, return early with error

### Step 4: Calculate Max Liquidatable Debt & Actual Debt To Liquidate
```solidity
// max liquidatable debt = (stable debt + variable debt) * close factor
// LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000 = 50%
vars.maxLiquidatableDebt = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
    LIQUIDATION_CLOSE_FACTOR_PERCENT
);

// determine the actual debt to liquidate
// if debtToCover > maxLiquidatableDebt, then actualDebtToLiquidate = maxLiquidatableDebt
// otherwise, actualDebtToLiquidate = debtToCover
vars.actualDebtToLiquidate = debtToCover > vars.maxLiquidatableDebt
    ? vars.maxLiquidatableDebt
    : debtToCover;
```
**Key purpose**
- From the debt side, calculate the max amount that is allowed to liquidate
- Formula: `min( maxLiquidatableDebt, debtToCover )`

### Step 5: Determine How Much Collateral Can be Seized & Corresponding Debt To Be Repaid
```solidity
// return max collateral to liquidate, and actual debt amount supported by the collateral level
(
    vars.maxCollateralToLiquidate,
    vars.debtAmountNeeded
) = _calculateAvailableCollateralToLiquidate(
    collateralReserve,
    debtReserve,
    collateralAsset,
    debtAsset,
    vars.actualDebtToLiquidate,
    vars.userCollateralBalance
);
// if there isn't enough collateral to cover the actual debt to liquidate, 
// downsize the debt amount to be repaid to smaller amount
if (vars.debtAmountNeeded < vars.actualDebtToLiquidate) {
    vars.actualDebtToLiquidate = vars.debtAmountNeeded;
}

// LendingPoolCollateralManager.sol
function _calculateAvailableCollateralToLiquidate(
    DataTypes.ReserveData storage collateralReserve,
    DataTypes.ReserveData storage debtReserve,
    address collateralAsset,
    address debtAsset,
    uint256 debtToCover,
    uint256 userCollateralBalance
) internal view returns (uint256, uint256) { // return collateral amount and debt amount needed
    uint256 collateralAmount = 0;
    uint256 debtAmountNeeded = 0;
    // get the price oracle
    IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());

    AvailableCollateralToLiquidateLocalVars memory vars;

    // get the price of the collateral asset
    vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
    // get the price of the debt asset
    vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);
    // get the liquidation bonus and collateral decimals
    (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
        .configuration
        .getParams();
    vars.debtAssetDecimals = debtReserve.configuration.getDecimals();

    // This is the maximum possible amount of the selected collateral that can be liquidated, given the
    // max amount of liquidatable debt
    // max collateral to liquidate = (debt to cover * debt price * liq. bonus / collateral price
    vars.maxAmountCollateralToLiquidate = vars
        .debtAssetPrice
        .mul(debtToCover)
        .mul(10**vars.collateralDecimals)
        .percentMul(vars.liquidationBonus)
        .div(vars.collateralPrice.mul(10**vars.debtAssetDecimals));

    // user doesn't have enough collateral
    if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
        // take the max of user collateral balance
        collateralAmount = userCollateralBalance;
        // reverse calculate the debt amount (less than debtToCover)
        // debt amount needed = (collateral amount * collateral price) / (debt price * liq. bonus)
        debtAmountNeeded = vars
        .collateralPrice
        .mul(collateralAmount)
        .mul(10**vars.debtAssetDecimals)
        .div(vars.debtAssetPrice.mul(10**vars.collateralDecimals))
        .percentDiv(vars.liquidationBonus);
    } else {
        // user has enough collateral
        collateralAmount = vars.maxAmountCollateralToLiquidate; // calculated collateral needed
        debtAmountNeeded = debtToCover; // same as input
    }

    return (collateralAmount, debtAmountNeeded);
}
```
- **Key purpose**
    - From the collateral side, calculate the amount of debt can be repaid, and the amount of collateral can be transferred to the liquidator
- **Why need both**
    - `maxCollateralToLiquidate`: How much collateral to transfer to the liquidator
    - `debtAmountNeeded`: How much debt actually can be repaid
- **Math relationship**
    - Forward: collateralAmount = (debtAmountNeeded × debtPrice × bonus) / collateralPrice
    - Reverse: debtAmountNeeded = (collateralAmount × collateralPrice) / (debtPrice × bonus)
- **Example**
    ```
    User Position:
    - Collateral: 1 ETH @ $2,000 = $2,000
    - Debt: $10,000 USDC
    - Liquidation bonus: 5%

    Liquidator wants to liquidate: $5,000 USDC

    Step 1 Forward Calculation (Debt -> Collateral)
    maxAmountCollateralToLiquidate = (debtPrice × debtToCover × bonus) / collateralPrice
                                  = ($1 × $5,000 × 1.05) / $2,000
                                  = 2.625 ETH needed

    Step 2 Check Collateral Availability
    if (2.625 ETH > userCollateralBalance(1 ETH)) {
        // User doesn't have enough!
        // Branch A: Insufficient collateral
    }

    Step 3 Reverse Calculate (Collateral -> Debt)
    debtAmountNeeded = (collateralPrice × collateralAmount) / (debtPrice × bonus)
                   = ($2,000 × 1 ETH) / ($1 × 1.05)
                   = $1,904.76  // LESS than $5,000
    
    Step 4 Return value
    return (
        maxCollateralToLiquidate: 1 ETH,    // How much collateral to give liquidator
        debtAmountNeeded: $1,904.76         // How much debt can actually be repaid
    );
    ```

### Step 6: If Liquidators Want Underlying Asset -> Liquidity Check
```solidity
// if liquidators want to receive the underlying asset directly (not aToken)
if (!receiveAToken) {
    // get the current balance of underlying asset in the aToken contract
    uint256 currentAvailableCollateral =
    IERC20(collateralAsset).balanceOf(address(vars.collateralAtoken));
    // if there is not enough underlying asset, return error code & msg
    if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
        return (
            uint256(Errors.CollateralManagerErrors.NOT_ENOUGH_LIQUIDITY),
            Errors.LPCM_NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
        );
    }
}
```
**Key purpose**: Check if the aToken contract have enough underlying asset to send to the liquidator

### Step 7: Update Debt Reserve's State
```solidity
// update debt reserve's variable borrow index, liquidity index, mint treasury fees, and update timestamp
debtReserve.updateState();
```
**Key purpose**: Update debt reserve state with accrued interest before any state-changing operation (burn debt tokens, update collateral, transfer assets)

### Step 8: Burn Debt Tokens
```solidity
// if user has enough variable debt, burn all the debt from variable debt token
if (vars.userVariableDebt >= vars.actualDebtToLiquidate) {
    IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
    user,
    vars.actualDebtToLiquidate,     // final debt to repay
    debtReserve.variableBorrowIndex // variable borrow index
    );
} else {
    // If the user doesn't have variable debt, burn all variable debt token, then the remaining from stable debt token
    if (vars.userVariableDebt > 0) {
        IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
            user,
            vars.userVariableDebt,
            debtReserve.variableBorrowIndex
        );
    }
    IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
        user,
        vars.actualDebtToLiquidate.sub(vars.userVariableDebt)
    );
}
```
**Key purpose**: Burn the amount of repaid debt token from variable and/or stable debt contract

### Step 9: Update Interest Rates For Debt Reserve
```solidity
debtReserve.updateInterestRates(
    debtAsset,                  // debt reserve address
    debtReserve.aTokenAddress,  // debt reserve aToken address
    vars.actualDebtToLiquidate, // liquidity added
    0                           // liquidity taken
);
```
**Key purpose**: As debt being repaid, liquidity increases, borrow rates & liquidity rate decrease

### Step 9: Transfer aTokens Or Underlying Asset To Liquidators
```solidity
// if liquidator wants aTokens
if (receiveAToken) {
    // get liquidator's previous aToken balance
    vars.liquidatorPreviousATokenBalance = IERC20(vars.collateralAtoken).balanceOf(msg.sender);
    // transfer aTokens from user to the liquidator
    vars.collateralAtoken.transferOnLiquidation(
        user, // from user 
        msg.sender, // to liquidator
        vars.maxCollateralToLiquidate); // final amount of aTokens

    // if it's liquidator's 1st aTokens of this asset
    if (vars.liquidatorPreviousATokenBalance == 0) {
        // enable the asset as collateral for the liquidator automatically
        DataTypes.UserConfigurationMap storage liquidatorConfig = _usersConfig[msg.sender];
        liquidatorConfig.setUsingAsCollateral(collateralReserve.id, true);
        emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender);
    }
}
// if liquidator wants underlying asset directly
else {
    // update collateral reserve's indexes
    collateralReserve.updateState();
    // update collateral reserve's borrow rates & liquidity rate
    collateralReserve.updateInterestRates(
        collateralAsset,                // reserve address
        address(vars.collateralAtoken), // aToken address
        0,                              // liquidity added
        vars.maxCollateralToLiquidate   // liquidity taken
    );

    // Burn the equivalent amount of aToken, sending the underlying to the liquidator
    vars.collateralAtoken.burn(
        user,                               // user
        msg.sender,                         // receiver of the underlying
        vars.maxCollateralToLiquidate,      // amount
        collateralReserve.liquidityIndex    // index
    );
}

// AToken.sol
function burn(
    address user,
    address receiverOfUnderlying,
    uint256 amount,
    uint256 index
) external override onlyLendingPool {
    // calculated scaled amount using index
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT);
    // burn scaled amount
    _burn(user, amountScaled);
    // transfer the actual amount to the receiver
    IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount);
    // emit events
    emit Transfer(user, address(0), amount);
    emit Burn(user, receiverOfUnderlying, amount, index);
}
```
- **Key purpose**
    - Transfer seized collateral to liquidators in aTokens or underlying asset
- **Key operation**
    - Model 1: Receive aTokens
        - Transfer aTokens from user to liquidator
        - Auto enable the asset as collateral for liquidator if it's 1st time receiving the aToken
        - No pool state changes -> No updates needed
    - Model 2: Receive underlying
        - Update collateral reserve state (accrue interest to current time)
        - Update interest rates (recalculate as liquidity decreases due to collateral withdraw)
        - Burn aTokens and send underlying asset to liquidator

### Step 10: Cleanup
```solidity
// if all collateral was seized, disable this asset as collateral for the user
if (vars.maxCollateralToLiquidate == vars.userCollateralBalance) {
    userConfig.setUsingAsCollateral(collateralReserve.id, false);
    emit ReserveUsedAsCollateralDisabled(collateralAsset, user);
}
```
**Key purpose**: Disable the asset as collateral if all of it is seized by the liquidator

### Step 11: Liquidator Repay the Debt
```solidity
// liquidator transfer the debt amount to the reserve pool, repaying borrower's debt
IERC20(debtAsset).safeTransferFrom(
    msg.sender,                 // from liquidator
    debtReserve.aTokenAddress,  // to debt reserve aToken address
    vars.actualDebtToLiquidate  // final debt amount to liquidate
);
```
**Key purpose**: Liquidator transfer the final debt amount to the protocol's reserve pool, repaying borrower's position

### Step 12: Emit Event
```solidity
emit LiquidationCall(
    collateralAsset,
    debtAsset,
    user,
    vars.actualDebtToLiquidate,
    vars.maxCollateralToLiquidate,
    msg.sender,
    receiveAToken
);
```

## Key Insights
### 1. Portfolio-Level Health, Not Asset-Level
Aave liquidates entire portfolios, not individual positions:
- **Health factor is global**: Calculated across ALL collateral and ALL debt, not per asset pair
- **Cross-collateral protection**: Your USDC deposit helps protect your ETH collateral because they're aggregated in HF calculation
- **Liquidation flexibility**: Once HF < 1.0, liquidators can repay ANY debt and seize ANY collateral (subject to 50% cap)
- **No cherry-picking healthy positions**: If your portfolio HF ≥ 1.0, no liquidation is possible even if individual pairs look underwater

### 2. Atomic Execution Eliminates Trust Assumptions
Every liquidation is all-or-nothing:
- **No partial states**: Either the full transaction succeeds (debt burned + collateral transferred + payment received) or everything reverts
- **Trust-minimized**: Liquidators can't take collateral without paying; users can't have debt reduced without liquidators receiving collateral
- **Protocol safety**: Impossible to create bad debt through failed liquidations

### 3. Dual Settlement Modes Decouple Solvency from Liquidity
The aToken settlement option is Aave's killer feature for crisis resilience:

**Why it matters:**
- **Solvency enforcement persists during bank runs**: Liquidations execute even when 99% of pool assets are borrowed
- **Liquidity risk becomes optional**: Liquidators voluntarily absorb redemption timing by accepting aTokens instead of forcing pool withdrawals
- **Liquidations become balance-sheet operations**: No immediate market selling required—collateral changes hands as accounting entries first
- **MEV competition without fragility**: Liquidator bots compete on speed without depending on AMM liquidity or forcing cascading sell pressure

**The trade-off:**
- aToken mode: Guaranteed execution, ongoing yield, deferred liquidity access
- Underlying mode: Immediate exit, no smart contract exposure, but can fail if pool is illiquid

This design ensures that **undercollateralized positions get liquidated even when the protocol faces liquidity constraints**, preventing the death spiral seen in other lending protocols during market stress.


## Reference
- Aave v2 whitepaper: https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf
- Aave v2 repo: https://github.com/aave/protocol-v2

## Discussion
- Twitter: [@chloe_zhuX](https://x.com/Chloe_zhuX)
- Telegram: [@Chloe_zhu](https://t.me/chloe_zhu)
- GitHub: [@Chloezhu010](https://github.com/Chloezhu010)

---

*Last updated: Dec 24th, 2025*
*Part of my #LearnInPublic Defi series*