返回 SC 笔记
SC Day 80

Mini Lending — 清算机制 + 清算奖励

### 1. 清算机制为什么必要?

2026-06-29
第四阶段:综合实战 (73-80)
lendingliquidationliquidation-bonusbad-debtmevliquidation-bot

日期: 2026-06-29 方向: Solidity 阶段: 第四阶段:综合实战 (73-80) 标签: #lending #liquidation #liquidation-bonus #bad-debt #mev #liquidation-bot


今日目标

类型内容
学习清算机制原理、部分清算、清算奖励数学、坏账处理
实操实现 liquidate() 函数 + 完整测试场景
产出完整的清算功能 + 清算场景测试 + 清算机器人经济学分析

核心概念

1. 清算机制为什么必要?

借贷协议的核心风险是坏账——当抵押品价值低于债务价值时,借款人没有动力还款:

正常状态:
  抵押品: 10 ETH × $2000 = $20,000
  债务: $12,000 USDC
  → 借款人有动力还款(还 $12,000 可以拿回 $20,000 的 ETH)

水下状态:
  ETH 跌到 $1100
  抵押品: 10 ETH × $1100 = $11,000
  债务: $12,000 USDC
  → 借款人没有动力还款(还 $12,000 只能拿回 $11,000 的 ETH)
  → 如果不清算,协议承担 $1,000 坏账

清算的作用:在抵押品价值接近但尚未低于债务时(HF < 1 但抵押品仍 > 债务),允许第三方代为还款并获得折价抵押品。

2. 清算流程

清算人发起清算
    ↓
检查 1:borrower 的 HF < 1?
    ↓
检查 2:清算金额 ≤ 最大可清算金额(通常为 50% 的债务)
    ↓
计算:清算人偿还的债务金额
计算:清算人获得的抵押品金额 = 债务金额 + 清算奖励
    ↓
执行:清算人转入债务 Token
执行:清算人获得抵押品 Token
    ↓
更新:借款人的债务减少
更新:借款人的抵押品减少
    ↓
验证:清算后借款人的 HF 应该改善

3. 清算数学

清算奖励计算

清算人偿还的债务: debtToRepay (in debt token)
清算奖励: liquidationBonus (e.g., 5%)

清算人获得的抵押品价值(USD):
  collateralValueUSD = debtToRepay * debtPrice * (1 + liquidationBonus)

清算人获得的抵押品数量:
  collateralAmount = collateralValueUSD / collateralPrice

示例:
  清算人偿还: 5,000 USDC (价值 $5,000)
  清算奖励: 5%
  ETH 价格: $1,100

  抵押品价值: $5,000 × 1.05 = $5,250
  获得 ETH: $5,250 / $1,100 = 4.7727 ETH

  清算人利润: 4.7727 × $1,100 - $5,000 = $250 (5% 奖励)

最大清算金额

为什么限制清算比例(通常 50%)?

全额清算的问题:
  - 价格波动可能导致清算过度
  - 借款人丧失全部抵押品(用户体验差)
  - 大额抛售加剧价格下跌(清算级联)

部分清算的优势:
  - 只清算足够恢复 HF > 1 的部分
  - 保护借款人剩余抵押品
  - 减少市场冲击

Aave 的规则:
  - HF < 1: 最多清算 50% 的债务
  - HF < 0.95: 可以清算 100% 的债务(紧急情况)

坏账场景

当抵押品 < 债务时(极端市场情况):

场景:
  抵押品: 10 ETH × $800 = $8,000
  债务: $12,000 USDC
  差额: -$4,000(坏账)

处理方式:
  1. 清算人获得全部抵押品(10 ETH = $8,000)
  2. 清算人只需偿还: $8,000 / 1.05 ≈ $7,619
  3. 剩余债务: $12,000 - $7,619 = $4,381(坏账)
  4. 坏账从协议储备金中覆盖
  5. 如果储备金不足 → 社会化损失(所有存款人分摊)

Aave 的Safety Module:
  - AAVE token 质押者作为最后的安全网
  - 坏账从 Safety Module 中拍卖 AAVE 覆盖

代码实战

liquidate() 函数实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// 接续 Day 77 的 MiniLendingPool 合约

/// @title MiniLendingPool - 清算功能
/// @notice Day 80 版本:新增 liquidate() + 完善清算机制
contract MiniLendingPool_V3 is MiniLendingPool_V2 {

    // ============ 清算常量 ============

    /// @dev 默认最大清算因子(基点):50%
    uint256 public constant DEFAULT_CLOSE_FACTOR_BPS = 5000;

    /// @dev 严重水下时的最大清算因子(基点):100%
    uint256 public constant FULL_CLOSE_FACTOR_BPS = 10000;

    /// @dev 严重水下的 HF 阈值(0.95)
    uint256 public constant SEVERE_HF_THRESHOLD = 95e16;

    // ============ 清算事件 ============

    event Liquidation(
        address indexed liquidator,
        address indexed borrower,
        address indexed debtAsset,
        address collateralAsset,
        uint256 debtRepaid,
        uint256 collateralSeized,
        uint256 liquidationBonus
    );

    event BadDebtRealized(
        address indexed borrower,
        address indexed asset,
        uint256 badDebtAmount
    );

    // ============ 清算错误 ============

    error HealthFactorAboveThreshold();
    error InvalidLiquidationAmount();
    error InsufficientCollateral();
    error LiquidationAmountExceedsCloseFactor();

    // ============ 核心函数:清算 ============

    /// @notice 清算不健康的借款仓位
    /// @param borrower 被清算的借款人
    /// @param debtAsset 清算人偿还的债务资产
    /// @param collateralAsset 清算人获得的抵押品资产
    /// @param debtToRepay 清算人偿还的债务数量
    function liquidate(
        address borrower,
        address debtAsset,
        address collateralAsset,
        uint256 debtToRepay
    ) external nonReentrant {
        if (debtToRepay == 0) revert ZeroAmount();

        // 步骤 1:累计利息(两个相关市场都要更新)
        _accrueInterest(debtAsset);
        _accrueInterest(collateralAsset);

        // 步骤 2:验证借款人 HF < 1
        uint256 borrowerHF = _calculateHealthFactor(borrower);
        if (borrowerHF >= HEALTH_FACTOR_THRESHOLD) {
            revert HealthFactorAboveThreshold();
        }

        // 步骤 3:计算最大可清算金额
        uint256 maxRepayable = _getMaxLiquidationAmount(borrower, debtAsset, borrowerHF);
        if (debtToRepay > maxRepayable) {
            revert LiquidationAmountExceedsCloseFactor();
        }

        // 步骤 4:计算清算人获得的抵押品数量
        (uint256 collateralToSeize, uint256 bonusCollateral) =
            _calculateCollateralToSeize(
                debtAsset,
                collateralAsset,
                debtToRepay
            );

        // 步骤 5:验证借款人有足够的抵押品
        UserPosition storage borrowerCollateral = positions[borrower][collateralAsset];
        MarketState storage collateralState = marketStates[collateralAsset];

        uint256 borrowerCollateralBalance = _convertToAssets(
            borrowerCollateral.supplyShares,
            collateralState.totalSupplyShares,
            collateralState.totalSupplyAssets
        );

        if (collateralToSeize > borrowerCollateralBalance) {
            // 坏账情况:抵押品不足以覆盖债务+奖励
            // 清算人获得全部抵押品,但只需偿还对应金额的债务
            collateralToSeize = borrowerCollateralBalance;
            debtToRepay = _calculateDebtFromCollateral(
                debtAsset,
                collateralAsset,
                collateralToSeize
            );

            // 记录坏账
            uint256 remainingDebt = _getUserDebt(borrower, debtAsset) - debtToRepay;
            if (remainingDebt > 0) {
                _handleBadDebt(borrower, debtAsset, remainingDebt);
            }
        }

        // 步骤 6:执行清算

        // 6a. 减少借款人的债务
        _repayDebt(borrower, debtAsset, debtToRepay);

        // 6b. 减少借款人的抵押品(转换为份额)
        uint256 sharesToSeize = _assetsToSharesRoundUp(
            collateralToSeize,
            collateralState.totalSupplyShares,
            collateralState.totalSupplyAssets
        );
        borrowerCollateral.supplyShares -= sharesToSeize;
        collateralState.totalSupplyShares -= sharesToSeize;
        collateralState.totalSupplyAssets -= collateralToSeize;

        // 6c. 清算人转入债务 Token
        IERC20(debtAsset).safeTransferFrom(msg.sender, address(this), debtToRepay);

        // 6d. 清算人获得抵押品 Token
        IERC20(collateralAsset).safeTransfer(msg.sender, collateralToSeize);

        // 步骤 7:验证清算后状态
        // 借款人的 HF 应该改善(不强制 > 1,坏账情况下可能仍 < 1)
        uint256 newHF = _calculateHealthFactor(borrower);
        require(
            newHF > borrowerHF || _getUserTotalDebt(borrower) == 0,
            "Liquidation did not improve health factor"
        );

        emit Liquidation(
            msg.sender,
            borrower,
            debtAsset,
            collateralAsset,
            debtToRepay,
            collateralToSeize,
            bonusCollateral
        );
    }

    // ============ 清算计算辅助函数 ============

    /// @notice 计算最大可清算金额
    function _getMaxLiquidationAmount(
        address borrower,
        address debtAsset,
        uint256 currentHF
    ) internal view returns (uint256) {
        uint256 totalDebt = _getUserDebt(borrower, debtAsset);

        // 确定 close factor
        uint256 closeFactorBps;
        if (currentHF < SEVERE_HF_THRESHOLD) {
            // 严重水下:允许清算 100%
            closeFactorBps = FULL_CLOSE_FACTOR_BPS;
        } else {
            // 一般情况:最多清算 50%
            closeFactorBps = DEFAULT_CLOSE_FACTOR_BPS;
        }

        return totalDebt * closeFactorBps / PERCENTAGE_FACTOR;
    }

    /// @notice 计算清算人获得的抵押品数量
    /// @return collateralAmount 总抵押品数量(含奖励)
    /// @return bonusAmount 奖励部分的抵押品数量
    function _calculateCollateralToSeize(
        address debtAsset,
        address collateralAsset,
        uint256 debtToRepay
    ) internal view returns (uint256 collateralAmount, uint256 bonusAmount) {
        uint256 debtPrice = _getAssetPriceUSD(debtAsset);
        uint256 collateralPrice = _getAssetPriceUSD(collateralAsset);

        MarketConfig storage collateralConfig = marketConfigs[collateralAsset];
        uint8 debtDecimals = marketConfigs[debtAsset].decimals;
        uint8 colDecimals = collateralConfig.decimals;

        // 债务价值(USD,18位精度)
        uint256 debtValueUSD = debtToRepay * debtPrice / (10 ** debtDecimals);

        // 含清算奖励的抵押品价值
        uint256 bonusFactorBps = PERCENTAGE_FACTOR + collateralConfig.liquidationBonusBps;
        uint256 collateralValueUSD = debtValueUSD * bonusFactorBps / PERCENTAGE_FACTOR;

        // 转换为抵押品数量
        collateralAmount = collateralValueUSD * (10 ** colDecimals) / collateralPrice;

        // 奖励部分
        uint256 baseCollateral = debtValueUSD * (10 ** colDecimals) / collateralPrice;
        bonusAmount = collateralAmount - baseCollateral;
    }

    /// @notice 从抵押品金额反算应偿还的债务
    function _calculateDebtFromCollateral(
        address debtAsset,
        address collateralAsset,
        uint256 collateralAmount
    ) internal view returns (uint256) {
        uint256 debtPrice = _getAssetPriceUSD(debtAsset);
        uint256 collateralPrice = _getAssetPriceUSD(collateralAsset);

        uint8 debtDecimals = marketConfigs[debtAsset].decimals;
        uint8 colDecimals = marketConfigs[collateralAsset].decimals;

        uint256 collateralValueUSD = collateralAmount * collateralPrice / (10 ** colDecimals);

        MarketConfig storage colConfig = marketConfigs[collateralAsset];
        uint256 bonusFactorBps = PERCENTAGE_FACTOR + colConfig.liquidationBonusBps;

        // 反向计算:debt = collateralValue / (1 + bonus)
        uint256 debtValueUSD = collateralValueUSD * PERCENTAGE_FACTOR / bonusFactorBps;

        return debtValueUSD * (10 ** debtDecimals) / debtPrice;
    }

    // ============ 内部辅助函数 ============

    /// @notice 内部还款逻辑(清算时使用)
    function _repayDebt(address borrower, address asset, uint256 amount) internal {
        MarketState storage state = marketStates[asset];
        UserPosition storage position = positions[borrower][asset];

        uint256 currentDebt = _getUserDebt(borrower, asset);
        require(amount <= currentDebt, "Repay exceeds debt");

        uint256 newDebt = currentDebt - amount;
        position.borrowBalance = newDebt;
        position.userBorrowIndex = state.borrowIndex;

        state.totalBorrows -= amount;

        if (newDebt == 0) {
            _removeFromBorrowList(borrower, asset);
        }
    }

    /// @notice 处理坏账
    function _handleBadDebt(
        address borrower,
        address asset,
        uint256 badDebtAmount
    ) internal {
        MarketState storage state = marketStates[asset];

        // 方案 1:从协议储备金覆盖
        if (state.reserveBalance >= badDebtAmount) {
            state.reserveBalance -= badDebtAmount;
        } else {
            // 方案 2:社会化损失(从总存款中扣除)
            uint256 covered = state.reserveBalance;
            state.reserveBalance = 0;

            uint256 uncovered = badDebtAmount - covered;
            // 所有存款人分摊损失
            state.totalSupplyAssets -= uncovered;
        }

        // 清除借款人的剩余债务
        UserPosition storage position = positions[borrower][asset];
        position.borrowBalance = 0;
        position.userBorrowIndex = state.borrowIndex;
        _removeFromBorrowList(borrower, asset);

        state.totalBorrows -= badDebtAmount;

        emit BadDebtRealized(borrower, asset, badDebtAmount);
    }

    function _getUserTotalDebt(address user) internal view returns (uint256 total) {
        for (uint256 i = 0; i < marketList.length; i++) {
            total += _getUserDebt(user, marketList[i])
                * _getAssetPriceUSD(marketList[i])
                / (10 ** marketConfigs[marketList[i]].decimals);
        }
    }

    function _assetsToSharesRoundUp(
        uint256 assets,
        uint256 totalShares,
        uint256 totalAssets
    ) internal pure returns (uint256) {
        uint256 numerator = assets * (totalShares + VIRTUAL_SHARES);
        uint256 denominator = totalAssets + VIRTUAL_ASSETS;
        return (numerator + denominator - 1) / denominator;
    }
}

完整清算测试场景

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";

contract LiquidationTest is Test {
    // ... setUp() 省略

    /// @notice 测试 1:基础清算流程
    function test_Liquidate_BasicFlow() public {
        // 设置:Alice 存 10 ETH,借 12,000 USDC
        _setupAliceBorrowPosition(10 ether, 12_000e6);

        // ETH 跌到 $1,300
        // 加权抵押: 10 * 1300 * 0.85 = $11,050
        // 债务: $12,000
        // HF = 11050/12000 = 0.92 < 1
        ethPriceFeed.setPrice(1300e8);

        uint256 hfBefore = pool.getHealthFactor(alice);
        assertLt(hfBefore, 1e18, "Should be underwater");

        // 清算人偿还 6,000 USDC (50%)
        uint256 debtToRepay = 6_000e6;
        vm.prank(liquidator);
        pool.liquidate(alice, address(usdc), address(weth), debtToRepay);

        // 验证清算人获得了抵押品
        uint256 liquidatorWETH = weth.balanceOf(liquidator);
        assertGt(liquidatorWETH, 0, "Liquidator should receive collateral");

        // 验证清算奖励 (5%)
        // 债务价值: $6,000
        // 含奖励抵押品价值: $6,000 × 1.05 = $6,300
        // 获得 ETH: $6,300 / $1,300 ≈ 4.846 ETH
        uint256 expectedCollateral = 6300e18 / 1300; // 约 4.846 ETH
        assertApproxEqRel(
            liquidatorWETH,
            expectedCollateral,
            1e16 // 1% 误差
        );

        // 验证 Alice 的 HF 改善
        uint256 hfAfter = pool.getHealthFactor(alice);
        assertGt(hfAfter, hfBefore, "HF should improve after liquidation");
    }

    /// @notice 测试 2:HF > 1 时不能清算
    function test_Liquidate_HealthyPosition_Reverts() public {
        _setupAliceBorrowPosition(10 ether, 8_000e6);

        // ETH = $2000, HF > 1
        vm.prank(liquidator);
        vm.expectRevert(
            MiniLendingPool.HealthFactorAboveThreshold.selector
        );
        pool.liquidate(alice, address(usdc), address(weth), 4_000e6);
    }

    /// @notice 测试 3:超过 close factor 的清算被拒绝
    function test_Liquidate_ExceedsCloseFactor_Reverts() public {
        _setupAliceBorrowPosition(10 ether, 12_000e6);
        ethPriceFeed.setPrice(1300e8);

        // 尝试清算 60% (> 50% close factor)
        uint256 debtToRepay = 7_200e6;
        vm.prank(liquidator);
        vm.expectRevert(
            MiniLendingPool.LiquidationAmountExceedsCloseFactor.selector
        );
        pool.liquidate(alice, address(usdc), address(weth), debtToRepay);
    }

    /// @notice 测试 4:严重水下时可以 100% 清算
    function test_Liquidate_SeverelyUnderwater_FullClose() public {
        _setupAliceBorrowPosition(10 ether, 15_000e6);

        // ETH 暴跌到 $1,000
        // HF = (10 * 1000 * 0.85) / 15000 = 0.567 < 0.95
        ethPriceFeed.setPrice(1000e8);

        uint256 hf = pool.getHealthFactor(alice);
        assertLt(hf, 95e16, "HF should be < 0.95");

        // 可以清算 100%
        uint256 fullDebt = pool.getUserDebt(alice, address(usdc));
        vm.prank(liquidator);
        pool.liquidate(alice, address(usdc), address(weth), fullDebt);

        // Alice 应该无债务了
        assertEq(pool.getUserDebt(alice, address(usdc)), 0);
    }

    /// @notice 测试 5:坏账处理
    function test_Liquidate_BadDebt() public {
        _setupAliceBorrowPosition(10 ether, 15_000e6);

        // ETH 暴跌到 $500
        // 抵押品: 10 * 500 = $5,000
        // 债务: $15,000
        // 坏账: $10,000
        ethPriceFeed.setPrice(500e8);

        uint256 reserveBefore = pool.getReserveBalance(address(usdc));

        vm.prank(liquidator);
        pool.liquidate(alice, address(usdc), address(weth), 15_000e6);

        // 清算人应该获得了全部 10 ETH
        uint256 liquidatorWETH = weth.balanceOf(liquidator);
        assertEq(liquidatorWETH, 10 ether);

        // 应该有坏账被记录
        // 具体坏账金额取决于清算人实际偿还了多少
    }

    /// @notice 测试 6:带利息的清算
    function test_Liquidate_WithAccruedInterest() public {
        _setupAliceBorrowPosition(10 ether, 12_000e6);

        // 时间推进 180 天
        vm.warp(block.timestamp + 180 days);

        // 利息累计后债务增加
        uint256 debtWithInterest = pool.getUserDebt(alice, address(usdc));
        assertGt(debtWithInterest, 12_000e6, "Debt should have grown");

        // ETH 价格下跌触发清算
        ethPriceFeed.setPrice(1400e8);

        uint256 hf = pool.getHealthFactor(alice);
        if (hf < 1e18) {
            vm.prank(liquidator);
            pool.liquidate(
                alice,
                address(usdc),
                address(weth),
                debtWithInterest / 2 // 50% 清算
            );
        }
    }

    /// @notice 测试 7:清算利润计算验证
    function test_Liquidate_ProfitabilityCheck() public {
        _setupAliceBorrowPosition(10 ether, 12_000e6);
        ethPriceFeed.setPrice(1300e8);

        uint256 debtToRepay = 5_000e6;

        uint256 liquidatorUSDCBefore = usdc.balanceOf(liquidator);
        uint256 liquidatorWETHBefore = weth.balanceOf(liquidator);

        vm.prank(liquidator);
        pool.liquidate(alice, address(usdc), address(weth), debtToRepay);

        uint256 usdcSpent = liquidatorUSDCBefore - usdc.balanceOf(liquidator);
        uint256 wethReceived = weth.balanceOf(liquidator) - liquidatorWETHBefore;

        // 计算利润
        uint256 wethValueUSD = wethReceived * 1300 / 1e18; // 简化计算
        uint256 profit = wethValueUSD - usdcSpent / 1e6;

        // 利润应该约等于 5% 的清算奖励
        uint256 expectedProfit = usdcSpent / 1e6 * 5 / 100;
        assertApproxEqRel(
            profit,
            expectedProfit,
            5e16 // 5% 误差
        );
    }

    // 辅助函数
    function _setupAliceBorrowPosition(uint256 ethAmount, uint256 usdcBorrow) internal {
        vm.startPrank(alice);
        weth.approve(address(pool), ethAmount);
        pool.supply(address(weth), ethAmount);
        pool.borrow(address(usdc), usdcBorrow);
        vm.stopPrank();
    }
}

4. 清算机器人经济学

清算机器人的盈利模型:

收入:
  - 清算奖励 (通常 5-10%)
  - 例如:清算 $100,000 债务 → 获得 $105,000 抵押品 → 利润 $5,000

成本:
  - Gas 费用:$10-100 (以太坊主网)
  - 闪电贷手续费:0.09% (Aave) 或 0 (dYdX)
  - 监控基础设施:~$200/月
  - 价格滑点(卖出抵押品时):0.1-1%

净利润 = 清算奖励 - Gas - 闪电贷费 - 滑点

盈利条件:
  清算金额 > 约 $2,000 (以太坊主网)
  清算金额 > 约 $50 (L2)

竞争格局:
  ┌────────────────────────────────────┐
  │  清算 MEV 竞争                      │
  │                                    │
  │  1. 监控:订阅 pending txs          │
  │  2. 检测:计算所有借款人的 HF        │
  │  3. 抢跑:用高 Gas 费提交清算交易    │
  │  4. Flashbots:通过 MEV 拍卖        │
  │                                    │
  │  利润压缩:竞争导致清算利润趋近 0    │
  └────────────────────────────────────┘
/// @title 清算机器人示例(闪电贷清算)
contract FlashLiquidator {
    IPool public lendingPool;
    IFlashLoanProvider public flashLoan;
    ISwapRouter public swapRouter;

    /// @notice 使用闪电贷执行清算
    function executeLiquidation(
        address borrower,
        address debtAsset,
        address collateralAsset,
        uint256 debtToRepay
    ) external {
        // 步骤 1:闪电贷借入债务 Token
        bytes memory params = abi.encode(
            borrower, debtAsset, collateralAsset, debtToRepay
        );
        flashLoan.flashLoan(address(this), debtAsset, debtToRepay, params);
    }

    /// @notice 闪电贷回调
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata params
    ) external returns (bytes32) {
        (address borrower, address debtAsset, address collateralAsset, uint256 debtToRepay) =
            abi.decode(params, (address, address, address, uint256));

        // 步骤 2:批准并执行清算
        IERC20(debtAsset).approve(address(lendingPool), debtToRepay);
        lendingPool.liquidate(borrower, debtAsset, collateralAsset, debtToRepay);

        // 步骤 3:将获得的抵押品换成债务 Token
        uint256 collateralReceived = IERC20(collateralAsset).balanceOf(address(this));
        IERC20(collateralAsset).approve(address(swapRouter), collateralReceived);

        swapRouter.exactInputSingle(
            ISwapRouter.ExactInputSingleParams({
                tokenIn: collateralAsset,
                tokenOut: debtAsset,
                fee: 3000, // 0.3%
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: collateralReceived,
                amountOutMinimum: amount + fee, // 至少够还闪电贷
                sqrtPriceLimitX96: 0
            })
        );

        // 步骤 4:还闪电贷(amount + fee)
        IERC20(debtAsset).approve(msg.sender, amount + fee);

        // 利润留在合约中
        return keccak256("FlashLoan");
    }

    /// @notice 提取利润
    function withdrawProfit(address token) external {
        // onlyOwner
        uint256 balance = IERC20(token).balanceOf(address(this));
        IERC20(token).transfer(msg.sender, balance);
    }
}

关键要点总结

要点说明
清算保护协议在坏账发生前通过第三方介入偿还债务
部分清算默认最多清算 50%,减少市场冲击
清算奖励5-10% 激励清算人快速行动
坏账处理储备金覆盖 → 社会化损失 → Safety Module
清算级联大规模清算可能形成死亡螺旋
MEV 竞争清算利润被 MEV 搜索者竞争压缩

常见误区

  1. "清算对借款人是惩罚" — 清算实际上保护了整个协议和所有存款人
  2. "清算总是有利润的" — 在坏账场景下清算人可能无法获得足够奖励
  3. "清算越快越好" — 过于激进的清算可能加剧价格下跌(清算级联)
  4. "50% close factor 是固定的" — 不同协议有不同的策略,Aave V3 根据 HF 动态调整
  5. "清算机器人很容易赚钱" — MEV 竞争非常激烈,利润正在被压缩

面试关联

面试题:借贷协议的清算机制如何设计?需要考虑哪些问题?

30 秒回答: 清算在 HF < 1 时触发,允许第三方代为还款并获得折价抵押品(5-10% 奖励)。关键设计包括:部分清算限制(50%)防止过度清算、坏账处理机制(储备金/社会化损失)、以及清算奖励的平衡(太低无人清算,太高损害借款人)。

2 分钟回答: 清算机制是借贷协议的安全基石。设计时需要考虑六个维度:第一是触发条件——基于健康因子(加权抵押品/总债务),低于 1 即可清算。第二是清算比例——通常限制为 50% 部分清算,减少对借款人和市场的冲击;严重水下时(HF < 0.95)允许 100% 清算。第三是清算奖励——通常 5% 作为清算人的激励,需要平衡:太低没人清算,太高损害借款人。第四是坏账处理——当抵押品已不足以覆盖债务时,依次从协议储备金、Safety Module、或社会化损失中处理。第五是清算级联风险——大规模清算导致抵押品抛售、价格下跌、更多清算的死亡螺旋。可以通过设置合理的 LTV 和清算阈值之间的缓冲区来缓解。第六是 MEV 影响——清算交易的利润会被 MEV 搜索者竞争,实际上形成了高效的清算市场。

追问准备

  • Q: 如果没人来清算怎么办? A: 协议可以自建清算机器人、与 Keeper 网络集成(如 Gelato/Chainlink Keepers)、或者设置更高的清算奖励吸引清算人。
  • Q: 坏账如何社会化? A: 从 totalSupplyAssets 中直接扣除,相当于所有存款人的份额对应的资产减少了。Aave 使用 Safety Module(AAVE 质押者)作为第一道防线。

参考资源

资源说明
Aave V3 清算文档生产级清算设计
Compound 清算文档Compound 清算机制
Liquidation MEV 分析学术论文
Euler Finance 清算创新的清算设计
Morpho Blue 清算极简清算实现
DeFi 坏账分析监控协议健康状态