Aave V4 Liquidation
Aave V4 introduces a redesigned liquidation mechanism with Target Health Factor, Dutch-auction style liquidation bonuses, and improved dust handling.
Overview
code
┌──────────────────────────────────────────────────────────────┐ │ V4 LIQUIDATION REDESIGN │ ├──────────────────────────────────────────────────────────────┤ │ │ │ V3 Approach: │ │ • Fixed 50% close factor │ │ • Static liquidation bonus │ │ • User often over-liquidated │ │ │ │ V4 Approach: │ │ • Target Health Factor (configurable) │ │ • Variable liquidation bonus (Dutch-auction) │ │ • Liquidate just enough to reach target HF │ │ • Dynamic dust handling │ │ │ └──────────────────────────────────────────────────────────────┘
Key Differences from V3
| Aspect | V3 | V4 |
|---|---|---|
| Close Factor | Fixed 50% (100% when HF < 0.95) | Dynamic based on Target HF |
| Liquidation Bonus | Static per asset | Variable (Dutch-auction style) |
| Goal | Repay fixed % of debt | Restore to Target HF |
| User Experience | Often over-liquidated | Minimally liquidated |
Liquidation Configuration
solidity
struct LiquidationConfig {
uint64 targetHealthFactor; // Target HF after liquidation (>= 1e18)
uint64 healthFactorForMaxBonus; // HF below which max bonus applies (< 1e18)
uint16 liquidationBonusFactor; // % of max bonus at threshold (BPS)
}
// Per-reserve configuration
struct DynamicReserveConfig {
uint16 collateralFactor; // Collateral Factor
uint16 maxLiquidationBonus; // Max bonus (e.g., 105_00 = 5% bonus)
uint16 liquidationFee; // Protocol fee on bonus (BPS)
}
Example Configuration
solidity
// Spoke-wide config
LiquidationConfig memory config = LiquidationConfig({
targetHealthFactor: 1.05e18, // Restore to 1.05 HF
healthFactorForMaxBonus: 0.90e18, // Max bonus when HF < 0.90
liquidationBonusFactor: 80_00 // 80% of max bonus at threshold
});
// Per-reserve config (e.g., ETH)
DynamicReserveConfig memory ethConfig = DynamicReserveConfig({
collateralFactor: 82_50, // 82.5% CF
maxLiquidationBonus: 105_00, // 5% max bonus
liquidationFee: 10_00 // 10% of bonus goes to protocol
});
Dutch-Auction Liquidation Bonus
The liquidation bonus varies based on the borrower's health factor:
code
┌──────────────────────────────────────────────────────────────┐ │ LIQUIDATION BONUS VS HEALTH FACTOR │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Bonus │ │ │ │ maxLB ─────────────────────────────┐ │ │ │ │ │ │ │ ╱ │ │ │ │ ╱ │ │ │ │ ╱ │ │ │ │ minLB ────────────────╱ │ │ │ └─────────────────────────────────────┼─────────── │ │ hfForMaxBonus 1.0 │ │ │ (0.90) (threshold) │ │ │ │ minLB = (maxLB - 100%) × lbFactor + 100% │ │ Example: maxLB=105%, lbFactor=80% │ │ minLB = 5% × 80% + 100% = 104% │ │ │ └──────────────────────────────────────────────────────────────┘
Bonus Calculation
solidity
library LiquidationLogic {
uint64 internal constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
/// @notice Calculate liquidation bonus based on health factor
function calculateLiquidationBonus(
uint256 healthFactor,
uint256 maxLiquidationBonus,
uint256 healthFactorForMaxBonus,
uint256 liquidationBonusFactor
) internal pure returns (uint256) {
if (healthFactor <= healthFactorForMaxBonus) {
// Max bonus region
return maxLiquidationBonus;
}
// Linear interpolation between min and max
uint256 minBonus = PercentageMath.PERCENTAGE_FACTOR +
(maxLiquidationBonus - PercentageMath.PERCENTAGE_FACTOR)
.percentMul(liquidationBonusFactor);
uint256 range = HEALTH_FACTOR_LIQUIDATION_THRESHOLD - healthFactorForMaxBonus;
uint256 position = healthFactor - healthFactorForMaxBonus;
return maxLiquidationBonus -
(maxLiquidationBonus - minBonus).mulDiv(position, range);
}
}
Target Health Factor
V4 liquidates just enough to restore the user to Target HF:
solidity
/// @notice Calculate debt to liquidate to reach target HF
function calculateDebtToLiquidate(
uint256 totalCollateralValue,
uint256 avgCollateralFactor,
uint256 totalDebtValue,
uint256 targetHealthFactor,
uint256 liquidationBonus
) internal pure returns (uint256) {
// Current HF = (collateral × CF) / debt
// Target HF after liquidation:
// (collateral - seized) × CF / (debt - repaid) = targetHF
// Solve for debt to repay:
uint256 numerator = totalDebtValue.wadMul(targetHealthFactor) -
totalCollateralValue.percentMul(avgCollateralFactor);
uint256 denominator = targetHealthFactor -
liquidationBonus.percentMul(avgCollateralFactor);
return numerator.wadDiv(denominator);
}
Example
code
User Position: - Collateral: $10,000 (CF = 80%) - Debt: $8,500 - HF = $10,000 × 0.80 / $8,500 = 0.94 (liquidatable!) Target HF: 1.05 Liquidation Bonus: 104% (at HF 0.94) Debt to liquidate: $2,127 Collateral seized: $2,127 × 1.04 = $2,212 After liquidation: - Collateral: $10,000 - $2,212 = $7,788 - Debt: $8,500 - $2,127 = $6,373 - New HF = $7,788 × 0.80 / $6,373 = 0.977... ≈ 1.05 ✓
Liquidation Call
solidity
/// @notice Liquidate a user position
function liquidationCall(
uint256 collateralReserveId,
uint256 debtReserveId,
address user,
uint256 debtToCover,
bool receiveShares
) external {
// 1. Validate liquidation eligibility
require(user != msg.sender, CannotLiquidateSelf());
uint256 healthFactor = getUserHealthFactor(user);
require(healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD, HealthyPosition());
// 2. Get reserves and positions
Reserve storage collateralReserve = _reserves[collateralReserveId];
Reserve storage debtReserve = _reserves[debtReserveId];
require(collateralReserve.flags.liquidatable(), NotLiquidatable());
// 3. Calculate liquidation bonus
DynamicReserveConfig storage config = _dynamicConfig[collateralReserveId][...];
uint256 liquidationBonus = LiquidationLogic.calculateLiquidationBonus(
healthFactor,
config.maxLiquidationBonus,
_liquidationConfig.healthFactorForMaxBonus,
_liquidationConfig.liquidationBonusFactor
);
// 4. Calculate amounts
(
uint256 actualDebtToLiquidate,
uint256 collateralToSeize,
uint256 protocolFee
) = LiquidationLogic.calculateLiquidationAmounts(
user,
debtToCover,
collateralReserveId,
debtReserveId,
liquidationBonus,
config.liquidationFee,
_liquidationConfig.targetHealthFactor
);
// 5. Handle debt repayment
_handleDebtRepayment(debtReserveId, user, actualDebtToLiquidate);
// 6. Handle collateral transfer
_handleCollateralSeizure(
collateralReserveId,
user,
msg.sender,
collateralToSeize,
protocolFee,
receiveShares
);
// 7. Handle deficit if needed
_handleDeficitIfNeeded(user);
emit LiquidationCall(...);
}
Dust Handling
V4 prevents tiny leftover positions:
solidity
uint256 internal constant DUST_LIQUIDATION_THRESHOLD = 1_000e26; // $1,000 in base units
/// @notice Adjust liquidation to prevent dust
function adjustForDust(
uint256 remainingDebt,
uint256 maxLiquidatable,
bool fullRepayIntent
) internal pure returns (uint256) {
// If remaining debt would be below threshold
if (remainingDebt - maxLiquidatable < DUST_LIQUIDATION_THRESHOLD) {
if (fullRepayIntent) {
// Allow full liquidation
return remainingDebt;
} else {
// Revert to prevent dust
revert DustDebtRemaining();
}
}
return maxLiquidatable;
}
Dust Scenarios
code
┌──────────────────────────────────────────────────────────────┐ │ DUST HANDLING │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Scenario 1: Normal liquidation │ │ Remaining debt: $5,000 │ │ To liquidate: $2,000 │ │ After: $3,000 (> $1,000 threshold) ✓ │ │ │ │ Scenario 2: Would create dust │ │ Remaining debt: $1,500 │ │ To liquidate: $800 │ │ After: $700 (< $1,000 threshold) │ │ → If fullRepayIntent: liquidate full $1,500 │ │ → Otherwise: revert │ │ │ │ Scenario 3: Collateral exhausted │ │ All collateral seized, debt remains │ │ → Report deficit to Hub │ │ │ └──────────────────────────────────────────────────────────────┘
Protocol Fee
A portion of the liquidation bonus goes to the protocol:
solidity
// Liquidation fee is a % of the bonus (not the total) uint256 bonusAmount = collateralValue.percentMul(liquidationBonus) - collateralValue; uint256 protocolFee = bonusAmount.percentMul(config.liquidationFee); // Liquidator receives: collateral × bonus - protocolFee // Protocol receives: protocolFee (as shares via Hub)
Example
code
Debt repaid: $1,000 Liquidation bonus: 5% Gross collateral seized: $1,050 Bonus amount: $50 Protocol fee: $50 × 10% = $5 Liquidator receives: $1,045 worth of collateral Protocol receives: $5 worth of shares
Deficit Handling
If a user has no collateral left but still has debt:
solidity
function _handleDeficitIfNeeded(address user) internal {
if (_hasRemainingDebt(user) && !_hasRemainingCollateral(user)) {
// Report deficit to Hub
for each debt reserve {
(uint256 drawnDebt, uint256 premiumDebt) = getUserDebt(reserveId, user);
uint256 totalDeficit = drawnDebt + premiumDebt;
IHubBase.PremiumDelta memory premiumDelta = ...;
reserve.hub.reportDeficit(
reserve.assetId,
drawnDebt,
premiumDelta
);
// Clear user position
delete _userPositions[user][reserveId];
}
}
}
View Functions
solidity
/// @notice Get liquidation bonus for current health factor
function getLiquidationBonus(
uint256 collateralReserveId,
address user
) external view returns (uint256) {
uint256 hf = getUserHealthFactor(user);
DynamicReserveConfig storage config = _getDynamicConfig(collateralReserveId, user);
return LiquidationLogic.calculateLiquidationBonus(
hf,
config.maxLiquidationBonus,
_liquidationConfig.healthFactorForMaxBonus,
_liquidationConfig.liquidationBonusFactor
);
}
/// @notice Preview liquidation amounts
function previewLiquidation(
uint256 collateralReserveId,
uint256 debtReserveId,
address user,
uint256 debtToCover
) external view returns (
uint256 actualDebtToLiquidate,
uint256 collateralToSeize,
uint256 protocolFee
);
Events
solidity
event LiquidationCall(
uint256 indexed collateralReserveId,
uint256 indexed debtReserveId,
address indexed user,
address liquidator,
bool receiveShares,
uint256 debtToLiquidate,
uint256 drawnSharesToLiquidate,
IHubBase.PremiumDelta premiumDelta,
uint256 collateralToLiquidate,
uint256 collateralSharesToLiquidate,
uint256 collateralSharesToLiquidator
);
event UpdateLiquidationConfig(LiquidationConfig config);
Reference Files
- •
src/spoke/Spoke.sol- liquidationCall implementation - •
src/spoke/libraries/LiquidationLogic.sol- Liquidation calculations - •
src/spoke/interfaces/ISpoke.sol- Liquidation types - •
docs/overview.md- Liquidation design documentation