Aave V3 借贷机制详解
1. 概述
Aave V3 的核心借贷操作包括:
- Supply (存款):将资产存入流动性池
- Borrow (借贷):以抵押品借出资产
- Repay (还款):偿还借贷债务
- Withdraw (取款):从流动性池取出资产
- Liquidation (清算):清算不健康的借贷头寸
2. Supply (存款功能)
2.1 存款流程图
用户调用 supply()
│
▼
┌───────────────────────────────────┐
│ 1. ValidationLogic.validateSupply │
│ - 检查储备是否激活 │
│ - 检查储备是否未暂停 │
│ - 验证存款金额 > 0 │
│ - 检查是否超过供应上限 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 2. reserve.updateState │
│ - 更新流动性指数 │
│ - 更新借贷指数 │
│ - 累积协议费用 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 3. reserve.updateInterestRates │
│ - 计算新利用率 │
│ - 应用利率策略 │
│ - 更新利率 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 4. 资产转移 │
│ - 从用户转移到 aToken 合约 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 5. 铸造 aToken │
│ - 为用户铸造等量 aToken │
│ - 首次存款自动启用抵押品 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 6. 发射 Supply 事件 │
└───────────────────────────────────┘
2.2 核心代码实现
// SupplyLogic.sol
function executeSupply(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteSupplyParams memory params
) external {
DataTypes.ReserveData storage reserve = reservesData[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
// 1. 更新储备状态(累积利息)
reserve.updateState(reserveCache);
// 2. 验证存款参数
ValidationLogic.validateSupply(reserveCache, reserve, params.amount);
// 3. 更新利率
reserve.updateInterestRates(reserveCache, params.asset, params.amount, 0);
// 4. 转移基础资产到 aToken 合约
IERC20(params.asset).safeTransferFrom(
msg.sender,
reserveCache.aTokenAddress,
params.amount
);
// 5. 铸造 aToken
bool isFirstSupply = IAToken(reserveCache.aTokenAddress).mint(
msg.sender, // caller
params.onBehalfOf, // 接收者
params.amount, // 金额
reserveCache.nextLiquidityIndex // 当前流动性指数
);
// 6. 首次存款自动启用抵押品(如果允许)
if (isFirstSupply) {
if (
ValidationLogic.validateAutomaticUseAsCollateral(
reservesData,
reservesList,
userConfig,
reserveCache.reserveConfiguration,
reserveCache.aTokenAddress
)
) {
userConfig.setUsingAsCollateral(reserve.id, true);
emit ReserveUsedAsCollateralEnabled(params.asset, params.onBehalfOf);
}
}
emit Supply(params.asset, msg.sender, params.onBehalfOf, params.amount, params.referralCode);
}2.3 存款验证逻辑
function validateSupply(
DataTypes.ReserveCache memory reserveCache,
DataTypes.ReserveData storage reserve,
uint256 amount
) internal view {
require(amount != 0, Errors.INVALID_AMOUNT);
(bool isActive, bool isFrozen, , , bool isPaused) =
reserveCache.reserveConfiguration.getFlags();
require(isActive, Errors.RESERVE_INACTIVE);
require(!isPaused, Errors.RESERVE_PAUSED);
require(!isFrozen, Errors.RESERVE_FROZEN);
// 检查供应上限
uint256 supplyCap = reserveCache.reserveConfiguration.getSupplyCap();
require(
supplyCap == 0 ||
((IAToken(reserveCache.aTokenAddress).scaledTotalSupply() +
uint256(reserve.accruedToTreasury)).rayMul(reserveCache.nextLiquidityIndex) + amount) <=
supplyCap * (10**reserveCache.reserveConfiguration.getDecimals()),
Errors.SUPPLY_CAP_EXCEEDED
);
}3. Borrow (借贷功能)
3.1 借贷流程图
用户调用 borrow()
│
▼
┌───────────────────────────────────┐
│ 1. 检查隔离模式状态 │
│ - 是否在隔离模式 │
│ - 隔离模式抵押品地址 │
│ - 隔离模式债务上限 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 2. ValidationLogic.validateBorrow │
│ - 验证储备状态 │
│ - 检查借贷是否启用 │
│ - 验证金额 │
│ - 检查抵押品是否充足 │
│ - 验证健康因子 │
│ - 检查借贷上限 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 3. 铸造债务代币 │
│ ├─ STABLE: StableDebtToken │
│ └─ VARIABLE: VariableDebtToken │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 4. 更新用户配置 │
│ - 标记用户借贷状态 │
│ - 更新隔离模式债务(如适用) │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 5. 更新利率 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 6. 转移资产给用户 │
└───────────────────────────────────┘
3.2 双利率模式
Aave V3 支持两种借贷利率模式:
| 模式 | 代币 | 特点 | 适用场景 |
|---|---|---|---|
| STABLE (固定利率) | StableDebtToken | 利率在借款时锁定 | 长期借贷、利率上行预期 |
| VARIABLE (浮动利率) | VariableDebtToken | 利率随市场波动 | 短期借贷、利率下行预期 |
3.3 核心代码实现
// BorrowLogic.sol
function executeBorrow(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteBorrowParams memory params
) public {
DataTypes.ReserveData storage reserve = reservesData[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
// 1. 检查隔离模式状态
(
bool isolationModeActive,
address isolationModeCollateralAddress,
uint256 isolationModeDebtCeiling
) = userConfig.getIsolationModeState(reservesData, reservesList);
// 2. 验证借贷参数
ValidationLogic.validateBorrow(
reservesData,
reservesList,
eModeCategories,
DataTypes.ValidateBorrowParams({
reserveCache: reserveCache,
userConfig: userConfig,
asset: params.asset,
userAddress: params.onBehalfOf,
amount: params.amount,
interestRateMode: params.interestRateMode,
maxStableLoanPercent: params.maxStableRateBorrowSizePercent,
reservesCount: params.reservesCount,
oracle: params.oracle,
userEModeCategory: params.userEModeCategory,
priceOracleSentinel: params.priceOracleSentinel,
isolationModeActive: isolationModeActive,
isolationModeCollateralAddress: isolationModeCollateralAddress,
isolationModeDebtCeiling: isolationModeDebtCeiling
})
);
uint256 currentStableRate = 0;
bool isFirstBorrowing = false;
// 3. 根据利率模式铸造债务代币
if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) {
// 固定利率借贷
currentStableRate = reserve.currentStableBorrowRate;
(
isFirstBorrowing,
reserveCache.nextTotalStableDebt,
reserveCache.nextAvgStableBorrowRate
) = IStableDebtToken(reserveCache.stableDebtTokenAddress).mint(
params.user,
params.onBehalfOf,
params.amount,
currentStableRate
);
} else {
// 浮动利率借贷
(isFirstBorrowing, reserveCache.nextScaledVariableDebt) =
IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint(
params.user,
params.onBehalfOf,
params.amount,
reserveCache.nextVariableBorrowIndex
);
}
// 4. 首次借贷更新用户配置
if (isFirstBorrowing) {
userConfig.setBorrowing(reserve.id, true);
}
// 5. 隔离模式债务更新
if (isolationModeActive) {
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt +=
(params.amount /
10 **
(reserveCache.reserveConfiguration.getDecimals() -
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
emit IsolationModeTotalDebtUpdated(
isolationModeCollateralAddress,
nextIsolationModeTotalDebt
);
}
// 6. 更新利率
reserve.updateInterestRates(
reserveCache,
params.asset,
0,
params.releaseUnderlying ? params.amount : 0
);
// 7. 转移资产给用户
if (params.releaseUnderlying) {
IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(
params.user,
params.amount
);
}
emit Borrow(
params.asset,
params.user,
params.onBehalfOf,
params.amount,
params.interestRateMode,
params.interestRateMode == DataTypes.InterestRateMode.STABLE
? currentStableRate
: reserve.currentVariableBorrowRate,
params.referralCode
);
}3.4 借贷验证逻辑
function validateBorrow(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.ValidateBorrowParams memory params
) internal view {
require(params.amount != 0, Errors.INVALID_AMOUNT);
// 检查储备状态
ValidateBorrowLocalVars memory vars;
(
vars.isActive,
vars.isFrozen,
vars.borrowingEnabled,
vars.stableRateBorrowingEnabled,
vars.isPaused
) = params.reserveCache.reserveConfiguration.getFlags();
require(vars.isActive, Errors.RESERVE_INACTIVE);
require(!vars.isPaused, Errors.RESERVE_PAUSED);
require(!vars.isFrozen, Errors.RESERVE_FROZEN);
require(vars.borrowingEnabled, Errors.BORROWING_NOT_ENABLED);
// 检查 Oracle 哨兵状态(L2 序列器)
require(
params.priceOracleSentinel == address(0) ||
IPriceOracleSentinel(params.priceOracleSentinel).isBorrowAllowed(),
Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED
);
// 计算用户账户数据
(
vars.userCollateralInBaseCurrency,
vars.userDebtInBaseCurrency,
vars.currentLtv,
vars.currentLiquidationThreshold,
vars.healthFactor,
vars.hasZeroLtvCollateral
) = GenericLogic.calculateUserAccountData(
reservesData,
reservesList,
eModeCategories,
DataTypes.CalculateUserAccountDataParams({
userConfig: params.userConfig,
reservesCount: params.reservesCount,
user: params.userAddress,
oracle: params.oracle,
userEModeCategory: params.userEModeCategory
})
);
require(vars.userCollateralInBaseCurrency != 0, Errors.COLLATERAL_BALANCE_IS_ZERO);
require(vars.currentLtv != 0, Errors.LTV_VALIDATION_FAILED);
// 计算借贷后的健康因子
require(
vars.healthFactor > HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
vars.amountInBaseCurrency =
IPriceOracleGetter(params.oracle).getAssetPrice(params.asset) *
params.amount /
vars.assetUnit;
// 检查抵押品是否足够支持新借贷
require(
vars.userCollateralInBaseCurrency.percentMul(vars.currentLtv) >=
vars.userDebtInBaseCurrency + vars.amountInBaseCurrency,
Errors.COLLATERAL_CANNOT_COVER_NEW_BORROW
);
// 检查借贷上限
vars.borrowCap = params.reserveCache.reserveConfiguration.getBorrowCap();
require(
vars.borrowCap == 0 ||
(IAToken(params.reserveCache.aTokenAddress).scaledTotalSupply().rayMul(
params.reserveCache.nextVariableBorrowIndex
) + params.amount) <= vars.borrowCap * vars.assetUnit,
Errors.BORROW_CAP_EXCEEDED
);
}4. Repay (还款功能)
4.1 还款流程图
用户调用 repay()
│
▼
┌───────────────────────────────────┐
│ 1. 获取用户当前债务 │
│ - 固定利率债务 │
│ - 浮动利率债务 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 2. ValidationLogic.validateRepay │
│ - 验证还款金额 │
│ - 验证债务存在 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 3. 计算实际还款金额 │
│ - min(用户支付, 总债务) │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 4. 销毁债务代币 │
│ - burn StableDebtToken 或 │
│ - burn VariableDebtToken │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 5. 更新用户配置 │
│ - 如果债务清零则标记为非借贷 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 6. 更新隔离模式债务(如适用) │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 7. 更新利率并转移资产 │
└───────────────────────────────────┘
4.2 核心代码实现
// BorrowLogic.sol
function executeRepay(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteRepayParams memory params
) external returns (uint256) {
DataTypes.ReserveData storage reserve = reservesData[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
// 1. 获取用户当前债务
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(
params.onBehalfOf,
reserveCache
);
// 2. 验证还款
ValidationLogic.validateRepay(
reserveCache,
params.amount,
params.interestRateMode,
params.onBehalfOf,
stableDebt,
variableDebt
);
// 3. 计算实际还款金额
uint256 paybackAmount = params.interestRateMode == DataTypes.InterestRateMode.STABLE
? stableDebt
: variableDebt;
if (params.amount < paybackAmount) {
paybackAmount = params.amount;
}
// 4. 销毁债务代币
if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) {
(reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate) =
IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(
params.onBehalfOf,
paybackAmount
);
} else {
reserveCache.nextScaledVariableDebt = IVariableDebtToken(
reserveCache.variableDebtTokenAddress
).burn(
params.onBehalfOf,
paybackAmount,
reserveCache.nextVariableBorrowIndex
);
}
// 5. 更新用户配置
if (stableDebt + variableDebt - paybackAmount == 0) {
userConfig.setBorrowing(reserve.id, false);
}
// 6. 更新隔离模式债务
(
bool isolationModeActive,
address isolationModeCollateralAddress,
) = userConfig.getIsolationModeState(reservesData, reservesList);
if (isolationModeActive) {
uint128 isolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt;
uint128 debtToDecrement = (paybackAmount /
10 ** (reserveCache.reserveConfiguration.getDecimals() -
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
reservesData[isolationModeCollateralAddress].isolationModeTotalDebt =
isolationModeTotalDebt > debtToDecrement
? isolationModeTotalDebt - debtToDecrement
: 0;
}
// 7. 更新利率
reserve.updateInterestRates(reserveCache, params.asset, paybackAmount, 0);
// 8. 转移资产
if (params.useATokens) {
IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
reserveCache.aTokenAddress,
paybackAmount,
reserveCache.nextLiquidityIndex
);
} else {
IERC20(params.asset).safeTransferFrom(
msg.sender,
reserveCache.aTokenAddress,
paybackAmount
);
}
IAToken(reserveCache.aTokenAddress).handleRepayment(
msg.sender,
params.onBehalfOf,
paybackAmount
);
emit Repay(params.asset, params.onBehalfOf, msg.sender, paybackAmount, params.useATokens);
return paybackAmount;
}5. Liquidation (清算功能)
5.1 清算机制概述
清算是 Aave 协议风险管理的核心机制,当借款人的健康因子低于 1 时,允许第三方清算人偿还部分债务并获得抵押品作为奖励。
5.2 健康因子计算
健康因子 (HF) = (Σ 抵押品价值_i × 清算阈值_i) / 总债务
HF > 1 → 头寸安全
HF ≤ 1 → 头寸可被清算
5.3 动态清算因子
// 清算因子根据健康因子动态调整
uint256 internal constant DEFAULT_LIQUIDATION_CLOSE_FACTOR = 0.5e4; // 50%
uint256 public constant MAX_LIQUIDATION_CLOSE_FACTOR = 1e4; // 100%
uint256 public constant CLOSE_FACTOR_HF_THRESHOLD = 0.95e18; // 0.95
// 计算清算因子
uint256 closeFactor = healthFactor > CLOSE_FACTOR_HF_THRESHOLD
? DEFAULT_LIQUIDATION_CLOSE_FACTOR // HF > 0.95 → 最多清算 50%
: MAX_LIQUIDATION_CLOSE_FACTOR; // HF ≤ 0.95 → 可清算 100%5.4 清算流程图
清算人调用 liquidationCall()
│
▼
┌───────────────────────────────────┐
│ 1. 获取抵押品和债务储备数据 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 2. 验证清算条件 │
│ - 健康因子 < 1 │
│ - 用户有对应抵押品 │
│ - 用户有对应债务 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 3. 计算可清算债务 │
│ - 根据健康因子确定清算因子 │
│ - 计算最大可清算金额 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 4. 计算可获得抵押品 │
│ - 基础抵押品 = 债务 / 价格 │
│ - 总抵押品 = 基础 × 清算奖励 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ 5. 执行清算 │
│ - 销毁债务代币 │
│ - 转移抵押品给清算人 │
│ - 更新用户配置 │
└───────────────────────────────────┘
5.5 核心代码实现
// LiquidationLogic.sol
function executeLiquidationCall(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.ExecuteLiquidationCallParams memory params
) external {
LiquidationCallLocalVars memory vars;
DataTypes.ReserveData storage collateralReserve = reservesData[params.collateralAsset];
DataTypes.ReserveData storage debtReserve = reservesData[params.debtAsset];
DataTypes.UserConfigurationMap storage userConfig = usersConfig[params.user];
DataTypes.ReserveCache memory debtReserveCache = debtReserve.cache();
debtReserve.updateState(debtReserveCache);
// 1. 验证清算条件
(
vars.userCollateralBalance,
vars.actualDebtToLiquidate,
vars.actualCollateralToLiquidate,
vars.healthFactor
) = _calculateAvailableCollateralToLiquidate(
collateralReserve,
debtReserveCache,
params.collateralAsset,
params.debtAsset,
params.debtToCover,
userConfig,
params.user,
vars.collateralPrice,
vars.debtAssetPrice,
vars.liquidationBonus
);
ValidationLogic.validateLiquidationCall(
userConfig,
collateralReserve,
DataTypes.ValidateLiquidationCallParams({
debtReserveCache: debtReserveCache,
totalDebt: vars.userDebt,
healthFactor: vars.healthFactor,
priceOracleSentinel: params.priceOracleSentinel
})
);
// 2. 销毁债务代币
if (vars.userVariableDebt >= vars.actualDebtToLiquidate) {
// 全部从可变债务清算
debtReserveCache.nextScaledVariableDebt = IVariableDebtToken(
debtReserveCache.variableDebtTokenAddress
).burn(params.user, vars.actualDebtToLiquidate, debtReserveCache.nextVariableBorrowIndex);
} else {
// 先清算可变债务,再清算固定债务
if (vars.userVariableDebt != 0) {
debtReserveCache.nextScaledVariableDebt = IVariableDebtToken(
debtReserveCache.variableDebtTokenAddress
).burn(params.user, vars.userVariableDebt, debtReserveCache.nextVariableBorrowIndex);
}
(
debtReserveCache.nextTotalStableDebt,
debtReserveCache.nextAvgStableBorrowRate
) = IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn(
params.user,
vars.actualDebtToLiquidate - vars.userVariableDebt
);
}
// 3. 更新利率
debtReserve.updateInterestRates(
debtReserveCache,
params.debtAsset,
vars.actualDebtToLiquidate,
0
);
// 4. 转移抵押品
if (params.receiveAToken) {
// 清算人接收 aToken
_liquidateAToken(usersConfig, collateralReserve, params, vars);
} else {
// 清算人接收底层资产
_burnCollateralAToken(collateralReserve, params, vars);
}
// 5. 协议费用处理
if (vars.liquidationProtocolFeeAmount != 0) {
uint256 liquidityIndex = collateralReserve.getNormalizedIncome();
uint256 scaledDownProtocolFee = vars.liquidationProtocolFeeAmount.rayDiv(liquidityIndex);
uint256 scaledDownUserBalance = vars.userCollateralBalance.rayDiv(liquidityIndex);
if (scaledDownUserBalance <= scaledDownProtocolFee) {
// 用户余额不足以支付协议费用
vars.liquidationProtocolFeeAmount = vars.userCollateralBalance;
}
IAToken(collateralReserve.aTokenAddress).transferOnLiquidation(
params.user,
address(IAToken(collateralReserve.aTokenAddress).RESERVE_TREASURY_ADDRESS()),
vars.liquidationProtocolFeeAmount
);
}
// 6. 更新用户配置
if (vars.userDebt == vars.actualDebtToLiquidate) {
userConfig.setBorrowing(debtReserve.id, false);
}
if (vars.userCollateralBalance - vars.actualCollateralToLiquidate == 0) {
userConfig.setUsingAsCollateral(collateralReserve.id, false);
emit ReserveUsedAsCollateralDisabled(params.collateralAsset, params.user);
}
// 7. 从清算人转移债务资产
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
);
}5.6 清算奖励计算
function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory debtReserveCache,
address collateralAsset,
address debtAsset,
uint256 debtToCover,
uint256 userCollateralBalance,
uint256 liquidationBonus,
IPriceOracleGetter oracle
) internal view returns (uint256, uint256, uint256) {
AvailableCollateralToLiquidateLocalVars memory vars;
// 获取价格
vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);
// 计算基础抵押品数量
// baseCollateral = (debtPrice × debtAmount) / collateralPrice
vars.baseCollateral = ((vars.debtAssetPrice * debtToCover * vars.collateralAssetUnit)) /
(vars.collateralPrice * vars.debtAssetUnit);
// 加上清算奖励
// maxCollateral = baseCollateral × liquidationBonus
vars.maxCollateralToLiquidate = vars.baseCollateral.percentMul(liquidationBonus);
// 如果用户抵押品不足,调整清算金额
if (vars.maxCollateralToLiquidate > userCollateralBalance) {
vars.collateralAmount = userCollateralBalance;
// 反向计算实际可清算的债务
vars.debtAmountNeeded = ((vars.collateralPrice * vars.collateralAmount * vars.debtAssetUnit) /
(vars.debtAssetPrice * vars.collateralAssetUnit)).percentDiv(liquidationBonus);
} else {
vars.collateralAmount = vars.maxCollateralToLiquidate;
vars.debtAmountNeeded = debtToCover;
}
// 计算协议费用
if (vars.liquidationProtocolFeePercentage != 0) {
vars.bonusCollateral = vars.collateralAmount - vars.collateralAmount.percentDiv(liquidationBonus);
vars.liquidationProtocolFee = vars.bonusCollateral.percentMul(vars.liquidationProtocolFeePercentage);
}
return (
vars.collateralAmount - vars.liquidationProtocolFee,
vars.debtAmountNeeded,
vars.liquidationProtocolFee
);
}5.7 清算示例
场景: 用户 Alice 存入 10 ETH (价值 $20,000),借出 15,000 USDC
ETH 价格下跌导致健康因子 < 1
参数:
- ETH 价格: $1,800 (下跌后)
- 清算阈值: 82.5%
- 清算奖励: 5%
- 健康因子: (10 × 1800 × 0.825) / 15000 = 0.99
清算计算:
- 清算因子: 50% (HF > 0.95)
- 可清算债务: 15000 × 50% = 7,500 USDC
- 基础抵押品: 7500 / 1800 = 4.17 ETH
- 清算人获得: 4.17 × 1.05 = 4.38 ETH
- 清算人利润: 4.38 × 1800 - 7500 = $378 (约 5%)
6. Withdraw (取款功能)
6.1 取款验证
function validateWithdraw(
DataTypes.ReserveCache memory reserveCache,
uint256 amount,
uint256 userBalance
) internal pure {
require(amount != 0, Errors.INVALID_AMOUNT);
require(amount <= userBalance, Errors.NOT_ENOUGH_AVAILABLE_USER_BALANCE);
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlags();
require(isActive, Errors.RESERVE_INACTIVE);
require(!isPaused, Errors.RESERVE_PAUSED);
}6.2 健康因子检查
取款后必须确保用户健康因子仍然大于 1:
function validateHFAndLtv(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap memory userConfig,
address asset,
address from,
uint256 reservesCount,
address oracle,
uint8 userEModeCategory
) internal view {
// 只有当用户有债务时才需要检查
if (!userConfig.isBorrowingAny()) {
return;
}
(, , , , uint256 healthFactor, bool hasZeroLtvCollateral) = GenericLogic.calculateUserAccountData(
reservesData,
reservesList,
eModeCategories,
DataTypes.CalculateUserAccountDataParams({
userConfig: userConfig,
reservesCount: reservesCount,
user: from,
oracle: oracle,
userEModeCategory: userEModeCategory
})
);
require(
healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
}7. 小结
| 操作 | 主要逻辑 | 关键验证 |
|---|---|---|
| Supply | 转入资产 → 铸造 aToken | 储备状态、供应上限 |
| Borrow | 铸造债务代币 → 转出资产 | 抵押品充足、健康因子、借贷上限 |
| Repay | 转入资产 → 销毁债务代币 | 债务存在、金额有效 |
| Withdraw | 销毁 aToken → 转出资产 | 余额充足、健康因子 |
| Liquidate | 偿还债务 → 获得抵押品+奖励 | 健康因子 < 1 |
下一章将详细讲解利率模型和数学原理。