| name | Aave V4 Liquidation |
| description | This skill should be used when the user asks about "liquidation", "liquidationCall", "target health factor", "liquidation bonus", "Dutch auction", "close factor", or needs to understand Aave V4's redesigned liquidation engine. |
| version | 0.1.0 |
Aave V4 Liquidation
Aave V4 introduces a redesigned liquidation mechanism with Target Health Factor, Dutch-auction style liquidation bonuses, and improved dust handling.
Overview
┌──────────────────────────────────────────────────────────────┐
│ 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
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
// 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:
┌──────────────────────────────────────────────────────────────┐
│ 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
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:
/// @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
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
/// @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:
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
┌──────────────────────────────────────────────────────────────┐
│ 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:
// 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
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:
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
/// @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
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