# Liquidation Flow
End-to-end execution flow for liquidating unhealthy positions in Aave V3.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Point** | `Pool.liquidationCall(collateralAsset, debtAsset, user, debtToCover, receiveAToken)` |
| **Key Transformations** | [Debt Value → Collateral Amount](../transformations/index.md#liquidation-calculations) |
| **State Changes** | Burn debt, burn/transfer collateral |
| **Events Emitted** | `LiquidationCall`, `ReserveUsedAsCollateralEnabled` (conditional) |
---
## Flow Diagram
```mermaid
flowchart TD
%% Styling definitions
classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px
classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px
classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px
classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px
classDef error fill:#ff0000,stroke:#000000,color:#fff
classDef critical stroke:#ff0000,stroke-width:3px
%% Entry point
Entry["Pool.liquidationCall
collateralAsset, debtAsset,
user, debtToCover,
receiveAToken"] --> Execute["LiquidationLogic
executeLiquidationCall"]
subgraph StateUpdate ["1. State Updates"]
direction TB
UpdateState["ReserveLogic
updateState (debt)
Updates indexes"]
end
subgraph HealthCheck ["2. Health Factor / Safety Checks"]
direction TB
CalcAccount["GenericLogic
calculateUserAccountData
Calculates HF, totalDebt"] --> Validate["ValidationLogic
validateLiquidationCall
CRITICAL: HF < 1.0"]
class Validate validation
class Validate critical
end
subgraph Calculations ["3. Liquidation Calculations"]
direction TB
Validate --> CalcDebt["_calculateDebt
Gets variable + stable debt"]
CalcDebt --> CalcConfig["_getConfigurationData
closeFactor: HF<0.95=100%
HF>=0.95=50%"]
CalcConfig --> CalcCollateral["_calculateAvailableCollateralToLiquidate
KEY: collateralAmount =
debtToCover.percentMul(bonus)
.wadToRay().rayDiv(price)"]
class CalcCollateral transformation
end
Execute --> StateUpdate
StateUpdate --> HealthCheck
HealthCheck --> Calculations
CalcCollateral --> BurnDebt["_burnDebtTokens
CRITICAL: Burns debt"]
subgraph TokenBurn ["4. Debt Token Burn"]
direction TB
BurnDebt --> VarDebtBurn{"Variable Debt
>= debtToCover?"}
VarDebtBurn -->|Yes| VDBurn["VariableDebtToken
burn"]
VarDebtBurn -->|No| VarStableBurn["Burn all variable +
partial stable"]
VarStableBurn --> VDMint["VariableDebtToken
burn(userVariableDebt)"]
VDMint --> SDMint["StableDebtToken
burn(remaining)"]
VDBurn --> IsoLogic
SDMint --> IsoLogic
end
subgraph CollateralMgmt ["5. Collateral Management"]
direction TB
IsoLogic["IsolationModeLogic
updateIsolatedDebtIfIsolated"]
class IsoLogic storage
IsoLogic --> UpdateRates["ReserveLogic
updateInterestRates"]
UpdateRates --> ATokensCheck{"receiveAToken?"}
ATokensCheck -->|Yes| LiquidateATokens["_liquidateATokens
Transfer aTokens"]
ATokensCheck -->|No| BurnCollateral["_burnCollateralATokens
Burn aTokens + transfer"]
LiquidateATokens --> CollatValidate["ValidationLogic
validateUseAsCollateral"]
class CollatValidate validation
CollatValidate --> EnableCollat["UserConfig
setUsingAsCollateral"]
class EnableCollat storage
EnableCollat --> CollatEvent["EMIT
ReserveUsedAsCollateralEnabled"]
class CollatEvent event
BurnCollateral --> BurnAToken["AToken
burn"]
class BurnAToken transformation
BurnAToken --> TransferUnderlying["IERC20
safeTransfer"]
class TransferUnderlying storage
end
TokenBurn --> CollateralMgmt
CollatEvent --> RepayTransfer["IERC20
safeTransferFrom
Liquidator pays debt"]
class RepayTransfer storage
TransferUnderlying --> RepayTransfer
RepayTransfer --> FinalEvent["EMIT
LiquidationCall"]
class FinalEvent event
%% Error annotations
%% CRITICAL: HF must be < 1.0 - validation reverts otherwise
%% CRITICAL: Collateral calculation applies liquidation bonus
%% CRITICAL: Debt token burn is irreversible
%% CRITICAL: Close factor limits liquidation amount
%% Link styles for critical paths
linkStyle 10 stroke:#ff0000,stroke-width:3px
linkStyle 16 stroke:#ff0000,stroke-width:3px
linkStyle 22 stroke:#ff0000,stroke-width:3px
```
---
## Step-by-Step Execution
### 1. Entry Point
**File:** `contracts/protocol/pool/Pool.sol`
```solidity
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external virtual override {
LiquidationLogic.executeLiquidationCall(
_reserves,
_reservesList,
_usersConfig[user],
_eModeCategories,
DataTypes.ExecuteLiquidationCallParams({
collateralAsset: collateralAsset,
debtAsset: debtAsset,
user: user,
debtToCover: debtToCover,
receiveAToken: receiveAToken,
priceOracle: ADDRESSES_PROVIDER.getPriceOracle(),
userEModeCategory: _usersEModeCategory[user],
priceOracleSentinel: ADDRESSES_PROVIDER.getPriceOracleSentinel()
})
);
}
```
### 2. Execute Liquidation Call
**File:** `contracts/protocol/libraries/logic/LiquidationLogic.sol`
```solidity
function executeLiquidationCall(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.ExecuteLiquidationCallParams memory params
) external {
LiquidationCallLocalVars memory vars;
// Get debt reserve data
DataTypes.ReserveData storage debtReserve = reserves[params.debtAsset];
DataTypes.ReserveCache memory debtReserveCache = debtReserve.cache();
// Update debt reserve state
debtReserve.updateState(debtReserveCache);
// Calculate user account data
(
vars.totalCollateralInBaseCurrency,
vars.totalDebtInBaseCurrency,
vars.avgLtv,
vars.avgLiquidationThreshold,
vars.healthFactor,
vars.hasZeroLtvCollateral
) = GenericLogic.calculateUserAccountData(
reserves,
reservesList,
eModeCategories,
DataTypes.CalculateUserAccountDataParams({
userConfig: userConfig,
reservesCount: params.reservesCount,
user: params.user,
oracle: params.oracle,
userEModeCategory: params.userEModeCategory
})
);
// Validate liquidation
ValidationLogic.validateLiquidationCall(
debtReserveCache,
params.debtAsset,
params.user,
vars.healthFactor,
vars.totalDebtInBaseCurrency,
params.priceOracleSentinel
);
// Get user's debt
vars.userVariableDebt = IERC20(debtReserveCache.variableDebtTokenAddress)
.balanceOf(params.user);
vars.userStableDebt = IERC20(debtReserveCache.stableDebtTokenAddress)
.balanceOf(params.user);
vars.userTotalDebt = vars.userVariableDebt + vars.userStableDebt;
// Get collateral reserve data
DataTypes.ReserveData storage collateralReserve = reserves[params.collateralAsset];
DataTypes.ReserveCache memory collateralReserveCache = collateralReserve.cache();
// Get user's collateral balance
vars.userCollateralBalance = IERC20(collateralReserveCache.aTokenAddress)
.balanceOf(params.user);
// Determine close factor
vars.closeFactor = vars.healthFactor > CLOSE_FACTOR_HF_THRESHOLD
? DEFAULT_LIQUIDATION_CLOSE_FACTOR
: MAX_LIQUIDATION_CLOSE_FACTOR;
// Calculate actual debt to liquidate
vars.actualDebtToLiquidate = params.debtToCover >
vars.userTotalDebt.percentMul(vars.closeFactor)
? vars.userTotalDebt.percentMul(vars.closeFactor)
: params.debtToCover;
// Calculate available collateral to liquidate
(
vars.actualDebtToLiquidate,
vars.actualCollateralToLiquidate,
vars.liquidationProtocolFeeAmount
) = _calculateAvailableCollateralToLiquidate(
collateralReserve,
debtReserve,
collateralReserveCache,
debtReserveCache,
collateralAssetPrice,
debtAssetPrice,
vars.actualDebtToLiquidate,
vars.userCollateralBalance,
vars.liquidationBonus
);
// Burn debt tokens
_burnDebtTokens(
debtReserve,
params.debtAsset,
params.user,
vars.actualDebtToLiquidate,
vars.userVariableDebt,
vars.userStableDebt
);
// Update isolation mode debt if applicable
IsolationModeLogic.updateIsolatedDebtIfIsolated(
reserves,
reservesList,
userConfig,
debtReserveCache,
vars.actualDebtToLiquidate
);
// Update debt reserve interest rates
debtReserve.updateInterestRates(
debtReserveCache,
params.debtAsset,
0,
vars.actualDebtToLiquidate
);
// Handle collateral
if (params.receiveAToken) {
_liquidateATokens(
reserves,
reservesList,
userConfig,
collateralReserve,
collateralReserveCache,
params,
vars
);
} else {
_burnCollateralATokens(
collateralReserve,
collateralReserveCache,
params,
vars
);
}
// Transfer debt repayment from liquidator
IERC20(params.debtAsset).safeTransferFrom(
msg.sender,
debtReserveCache.aTokenAddress,
vars.actualDebtToLiquidate
);
emit LiquidationCall(
params.collateralAsset,
params.debtAsset,
params.user,
vars.actualDebtToLiquidate,
vars.actualCollateralToLiquidate,
msg.sender,
params.receiveAToken
);
}
```
### 3. Calculate Available Collateral
**File:** `contracts/protocol/libraries/logic/LiquidationLogic.sol`
```solidity
function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage debtReserve,
DataTypes.ReserveCache memory collateralReserveCache,
DataTypes.ReserveCache memory debtReserveCache,
uint256 collateralAssetPrice,
uint256 debtAssetPrice,
uint256 debtToCover,
uint256 userCollateralBalance,
uint256 liquidationBonus
) internal view returns (uint256, uint256, uint256) {
// Calculate collateral amount equivalent to debt
uint256 collateralAmount = debtToCover
.percentMul(PercentageMath.PERCENTAGE_FACTOR + liquidationBonus) // Add bonus
.wadToRay()
.rayDiv(collateralAssetPrice);
// Cap at user's collateral balance
uint256 maxCollateralToLiquidate = userCollateralBalance.rayMul(
collateralReserveCache.liquidityIndex
);
if (collateralAmount > maxCollateralToLiquidate) {
// Recalculate debt to cover with capped collateral
collateralAmount = maxCollateralToLiquidate;
debtToCover = collateralAmount
.rayMul(collateralAssetPrice)
.rayToWad()
.percentDiv(PercentageMath.PERCENTAGE_FACTOR + liquidationBonus);
}
// Calculate protocol fee on liquidation bonus
uint256 liquidationProtocolFee = collateralReserveCache
.reserveConfiguration
.getLiquidationProtocolFee();
uint256 liquidationProtocolFeeAmount;
if (liquidationProtocolFee != 0) {
uint256 bonusCollateral = collateralAmount -
debtToCover.wadToRay().rayMul(debtAssetPrice).rayToWad();
liquidationProtocolFeeAmount = bonusCollateral.percentMul(
liquidationProtocolFee
);
}
return (
debtToCover,
collateralAmount - liquidationProtocolFeeAmount,
liquidationProtocolFeeAmount
);
}
```
**[TRANSFORMATION]:** See [Liquidation Calculations](../transformations/index.md#liquidation-calculations) for detailed formula breakdown
### 4. Burn Debt Tokens
**File:** `contracts/protocol/libraries/logic/LiquidationLogic.sol`
```solidity
function _burnDebtTokens(
DataTypes.ReserveData storage debtReserve,
address debtAsset,
address user,
uint256 debtToCover,
uint256 userVariableDebt,
uint256 userStableDebt
) internal {
if (userVariableDebt >= debtToCover) {
// Burn only variable debt
IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user,
debtToCover,
debtReserveCache.nextVariableBorrowIndex
);
} else {
// Burn all variable debt + some stable debt
if (userVariableDebt != 0) {
IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user,
userVariableDebt,
debtReserveCache.nextVariableBorrowIndex
);
}
uint256 stableDebtToBurn = debtToCover - userVariableDebt;
if (stableDebtToBurn != 0) {
IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn(
user,
stableDebtToBurn
);
}
}
}
```
### 5. Liquidate via aToken Transfer
**File:** `contracts/protocol/libraries/logic/LiquidationLogic.sol`
```solidity
function _liquidateATokens(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory collateralReserveCache,
DataTypes.ExecuteLiquidationCallParams memory params,
LiquidationCallLocalVars memory vars
) internal {
// Transfer aTokens from user to liquidator
IAToken(collateralReserveCache.aTokenAddress).transferOnLiquidation(
params.user,
msg.sender,
vars.actualCollateralToLiquidate
);
// Check if liquidator can use collateral
if (!userConfig.isUsingAsCollateral(collateralReserve.id)) {
bool canUseAsCollateral = ValidationLogic.validateUseAsCollateral(
reserves,
reservesList,
collateralReserveCache
);
if (canUseAsCollateral) {
userConfig.setUsingAsCollateral(collateralReserve.id, true);
emit ReserveUsedAsCollateralEnabled(
params.collateralAsset,
msg.sender
);
}
}
}
```
### 6. Liquidate via Collateral Burn
**File:** `contracts/protocol/libraries/logic/LiquidationLogic.sol`
```solidity
function _burnCollateralATokens(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory collateralReserveCache,
DataTypes.ExecuteLiquidationCallParams memory params,
LiquidationCallLocalVars memory vars
) internal {
// Update collateral reserve state
collateralReserve.updateState(collateralReserveCache);
// Burn user's collateral aTokens
IAToken(collateralReserveCache.aTokenAddress).burn(
params.user,
msg.sender,
vars.actualCollateralToLiquidate,
collateralReserveCache.nextLiquidityIndex
);
// Transfer underlying to liquidator
IERC20(params.collateralAsset).safeTransfer(
msg.sender,
vars.actualCollateralToLiquidate
);
}
```
### 7. Validation Checks
**File:** `contracts/protocol/libraries/logic/ValidationLogic.sol`
```solidity
function validateLiquidationCall(
DataTypes.ReserveCache memory debtReserveCache,
address debtAsset,
address user,
uint256 healthFactor,
uint256 totalDebtInBaseCurrency,
address priceOracleSentinel
) internal view {
require(
healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
);
require(totalDebtInBaseCurrency != 0, Errors.NO_DEBT);
// Check oracle sentinel if configured
if (priceOracleSentinel != address(0)) {
require(
IPriceOracleSentinel(priceOracleSentinel).isLiquidationAllowed(),
Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED
);
}
}
```
---
## Amount Transformations
### Debt to Collateral Conversion
```
debtToCover (in debt Asset decimals)
|
v
// Get prices
collateralPrice = oracle.getAssetPrice(collateralAsset)
debtPrice = oracle.getAssetPrice(debtAsset)
|
v
// Calculate collateral with bonus
collateralAmount = debtToCover
.percentMul(100% + liquidationBonus) // Add liquidation incentive
.wadToRay() // Convert to RAY precision
.rayDiv(collateralPrice) // Divide by collateral price
|
v
// Check against available collateral
maxCollateral = userCollateralBalance.rayMul(liquidityIndex)
if (collateralAmount > maxCollateral):
collateralAmount = maxCollateral
debtToCover = recalculate(collateralAmount) // Work backwards
|
v
// Calculate protocol fee on bonus only
bonusCollateral = collateralAmount - debtValueInCollateral
protocolFee = bonusCollateral.percentMul(liquidationProtocolFee)
|
v
liquidatorReceives = collateralAmount - protocolFee
debtToRepay = recalculatedDebtToCover
```
### Example Calculation
```
User position:
- Debt: 1000 USDC
- Collateral: 1 ETH
- ETH price: $1500
- Liquidation threshold: 80%
- HF = (1 ETH * $1500 * 80%) / $1000 = 1.2 (healthy)
... price drops ...
New ETH price: $1100
New HF = (1 ETH * $1100 * 80%) / $1000 = 0.88 (unhealthy!)
Liquidation:
- debtToCover = 1000 USDC * 50% = 500 USDC (close factor)
- liquidationBonus = 8%
- collateralAmount = 500 USDC * 1.08 / $1100 per ETH
= 0.4909 ETH
- protocolFee = 10% of bonus = 10% * (0.4909 - 0.4545) ETH
= 0.00364 ETH
- liquidator receives = 0.4909 - 0.00364 = 0.4873 ETH
```
**Key Points:**
- Liquidation bonus incentivizes liquidators
- Protocol fee taken from bonus portion only
- Close factor limits how much can be liquidated at once
- HF < 0.95: 50% close factor, HF >= 0.95: 100% close factor
---
## Event Details
### LiquidationCall Event
```solidity
event LiquidationCall(
address indexed collateralAsset, // Collateral being seized
address indexed debtAsset, // Debt being repaid
address indexed user, // User being liquidated
uint256 debtToCover, // Amount of debt repaid
uint256 liquidatedCollateralAmount, // Amount of collateral seized
address liquidator, // msg.sender
bool receiveAToken // True if liquidator got aTokens
);
```
### ReserveUsedAsCollateralEnabled Event
Emitted when liquidator receives aTokens and enables collateral.
```solidity
event ReserveUsedAsCollateralEnabled(
address indexed reserve,
address indexed user
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `HEALTH_FACTOR_NOT_BELOW_THRESHOLD` | `healthFactor >= 1.0` | ValidationLogic.sol |
| `NO_DEBT` | User has no debt to liquidate | ValidationLogic.sol |
| `PRICE_ORACLE_SENTINEL_CHECK_FAILED` | Sentinel disallows liquidation | ValidationLogic.sol |
---
## Related Flows
- [Borrow Flow](./borrow.md) - How debt is created
- [Supply Flow](./supply.md) - How collateral is deposited
- [Health Factor Calculation](../transformations/index.md#e-mode-calculations)
---
## Source File Locations
```
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/LiquidationLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/protocol/libraries/logic/GenericLogic.sol
contracts/protocol/libraries/logic/IsolationModeLogic.sol
contracts/protocol/tokenization/AToken.sol
contracts/protocol/tokenization/VariableDebtToken.sol
contracts/protocol/tokenization/StableDebtToken.sol
contracts/protocol/libraries/logic/ReserveLogic.sol
```