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

下一章将详细讲解利率模型和数学原理。