11 · 借贷协议:Aave、健康因子、清算
第三层第二篇。10 给了 DEX 的水管(你能换币),11 给"杠杆 + 信用"的水管: 不依赖任何人审批,仅靠超额抵押 + 一条线(健康因子)让借贷在链上 7×24 运作; 一旦你跨过线,任何人都可以来清算你换走抵押 + 拿奖金。这条最后一句正是我们
LiquidationBot.t.sol897,176 gas 的故事(07 §8)。本篇答: Aave 在做什么、4 个魔法数字(LTV / LiqThreshold / CloseFactor / LiqBonus)、 健康因子怎么算、为什么我们用 45% 砸盘把 HF 砸到 0.76、清算者怎么赚那 ~5-10% 奖金。 结构见README.md§1。
0. 一句话本质
链上借贷协议(Aave V3)= 一个不审批、不看脸的自动当铺:你把抵押品丢进合约,按抵押品种类设定的 借贷上限(LTV)借出别的代币;一条公式(健康因子 HF)实时盯你的位置;HF 落到 1 以下, 任何人都能调
liquidationCall替你还一部分债、按市价 × (1 + 奖金) 拿走你对应价值的抵押品。 不需要催收、不需要法庭——HF 这条数字线就是整个系统。
1. 怎么产生的:它解决什么问题
01–10 解决了"账本 + 代币 + 自动 swap",但还缺一类基础设施:
- 想用 ETH 做杠杆(不卖币、借出 USDC 抄底)该找谁?传统银行不可能贷款给一笔私钥 ⇒ Aave 给你:抵押 ETH、按一定比例借 USDC。没人审批,纯算法判定。
- 既然没人审批,怎么防赖账? ⇒ 超额抵押 + 实时清算。你抵 $1000 ETH 才能借 $750 USDC(举例 LTV=75%)。 ETH 跌到某条线(liquidation threshold,比 LTV 略宽,比如 80%)时合约判定你"危险", 允许第三方清算 → 还掉部分债务,拿走对应抵押 + 奖金。链上没有"违约",只有"被清算"。
- 借出去的钱从哪来?也是池化的吗? ⇒ 是。其他人当 supplier 把 USDC 存进 Pool 赚利息;借款人按利率付利息;所有这些走一个共享 pool 而不是 P2P 匹配。利率由 utilization(借出比例)的曲线自动决定——借得多利率自动涨。
liquidationCall在我们LiquidationBot里到底干了什么?为什么要先闪电贷? ⇒ 清算时你得先有钱把那个人欠的债还掉才能拿走他的抵押。我们没钱 → 用 Aave 闪电贷拿一笔 USDC 来还债 → 拿到 WETH 抵押 + 奖金(典型 5%)→ swap 回 USDC → 还闪电贷本金 + 5bps premium → 差额留 own bank。一笔 tx 原子完成。- HF 公式具体长什么样?
⇒
HF = Σ_i (collateral_i × price_i × liquidationThreshold_i) / Σ_j (debt_j × price_j)HF ≥ 1 健康;< 1 可被清算;< close_factor(V3 通常 0.95,部分资产 0.95;少数 0.5)允许全额清算。 - "利息按块计"具体多块?给我个直观数。
⇒ Aave 用秒级累积、按交互时刻具现化(liquidityIndex);表面 APR 5% 意味着
每秒 ~1.58e-9 比例增长,跨一年累积到 1.0513(连续复利)。你不动账户也在涨,
单纯查
getReserveData就能算出实时 debt / collateral。 - 为什么砸 25% WETH 价 HF 只到 1.03,砸 45% 才到 0.76?
⇒ 抵押品有 liquidationThreshold(比如 ETH 是 83%),算 HF 时只算这个百分比的部分当抵押价值。
1.03意味着抵押价值刚超债务 3%;再砸 22% →1.03 × 0.78 ≈ 0.80 < 0.95(close factor), 正式进入"全额清算"区。这就是我们 LiquidationBot 测试里精心调出来的位置。 - Aave 跟 Compound / MakerDAO 有什么不同? ⇒ 模式相似,差细节:Compound 没有"isolation mode"、原生不带闪电贷;MakerDAO 只发 DAI、机制更像 CDP; Aave V3 是当前研究界的主流参考,因为机制最完整 + 流动性最深 + 文档最好。本项目就用 V3。
→ 这一篇答全部。
2. 它本质在做的"那一件事"
把传统金融里"信贷 + 风控 + 强平"这一整套人工流程压缩到一份合约 + 一条公式(HF)+ 一群清算机器人: 任何人随时可借可还、任何人可触发任何不健康位置的清算、利率由池子使用率自动定价、 利润分给存款人 + 清算者,协议本身只抽一层 reserve factor。整个系统没有客服、没有 KYC、没有催收, 只有 HF 这条数字线在 7×24 运行。
→ 这是为什么"清算机器人"是链上最稳定的盈利模式之一:HF 这条线非黑即白、几乎不可被审查、奖金算清楚。 代价是竞争极度激烈——所有清算者都看着同一根 HF 时刻盯,时间窗常常以毫秒计。
3. 类比:村庄"无人当铺"
01–10 的村庄有了银行(账本)、代金券(ERC-20)、自动找零桶(AMM)。11 加一类新设施:
┌── 无人当铺(Aave V3 Pool 合约)─────────────────────────────┐
│ │
│ 你可以做的: │
│ ▼ 存入 │
│ supply(USDC, 10000) → 拿走 10000 aUSDC(计息凭证) │
│ ▼ 借出 │
│ borrow(USDC, 7500) → 拿走 7500 USDC,欠下 7500 debtUSDC│
│ ▼ 还 │
│ repay(USDC, x) → debt 减少 x │
│ ▼ 取 │
│ withdraw(USDC, y) → 押品归还,aUSDC 销毁 │
│ │
│ 当铺每一秒都在算你的健康因子: │
│ HF = Σ(抵押_i × 价格_i × 折算系数_i) / Σ(债务_j × 价格_j) │
│ │
│ 当 HF 跌到 1 以下,门口的告示牌亮起: │
│ "此位置可清算!close_factor=0.95,奖金 5%" │
│ → 任何路过的人都可以调 liquidationCall: │
│ "我代他还掉债务 X 美元 → 我拿走 X × 1.05 美元的抵押" │
│ │
│ 当铺的内部账: │
│ 储户们的存款(aToken 是凭证)→ 一池 │
│ 借款人欠下的债(debtToken)→ 累积计息 │
│ 利率由 utilization = 借出 / 存入 的曲线决定 │
│ 协议抽 reserve factor(5-30%,按资产)作为金库收入 │
└─────────────────────────────────────────────────────────────┘
→ 一图看清借贷协议全部精髓。它不"决定"价格、不"决定"借不借给你;它只机械地按公式判定 HF 和抵押拿走的份额。
4. 核心关键点(5 条)
-
4 个魔法数字定义一切风险(每个资产各自有自己的):
名称 含义 举例(ETH 在 Aave V3 mainnet) LTV (Loan-to-Value) 这资产作抵押时初始最多借多少 80.5% Liquidation Threshold 跨这条线 = HF < 1 = 可清算 83% Close Factor 单次清算最多还多少%的债 V3:0.95(HF<0.95 时按 100%)/ 0.5(HF 在 0.95-1 时按 50%) Liquidation Bonus 清算者拿走抵押时的额外折扣 5% - LTV < LiqThreshold 留缓冲(防开仓即被清算)
- LiqBonus 是清算者的"工资",激励有人来干这活——没人来你的位置就一直烂
-
HF(健康因子)公式:
HF = Σ_i (collateralInUSD_i × liquidationThreshold_i) / Σ_j (debtInUSD_j)- HF ≥ 1:健康
- HF < 1:进入清算区
- HF < closeFactor 阈值(V3 = 0.95):允许全额清算(一次还光 100% 债)
- 价格来自 AaveOracle(12 待写)——Oracle 一抖,HF 跟着抖
-
清算者的工作流(5 步原子完成):
step 1: 借 USDC(闪电贷)= 准备好"代还的钱" step 2: liquidationCall(collateralAsset=WETH, debtAsset=USDC, user, debtToCover, false) → Aave 内部: - 从清算者拉走 debtToCover USDC(transferFrom,需先 approve, 09 §6) - 把 debt 减少 debtToCover - 计算: collateralToSeize = debtToCover * (1 + liqBonus) / collateralPrice - 把 collateralToSeize WETH 从被清算者抵押转给清算者 step 3: 把 WETH swap 成 USDC(10 §6 AMM) step 4: 还闪电贷本金 + 5bps premium(09 §6 + 13 §待写) step 5: 差额 = 利润,落账到清算者钱包- 全程 1 笔 tx,原子性(07 §4.4)→ 任何一步失败整笔回滚 → 风险 = 仅 gas
- 我们
LiquidationBot.t.sol测的就是这条 → 897,176 gas(07 §8)
-
利率 = utilization 曲线自动定:
utilization = totalBorrows / totalSupply utilization < kink (e.g. 80%): borrowRate = base + slope1 × utilization utilization >= kink: borrowRate = base + slope1 × kink + slope2 × (u − kink)- utilization 接近 1 时利率火箭式上涨(slope2 ≫ slope1)→ 逼借款人还钱、激励存款人来供应
- 这是为什么市场剧烈时 Aave 利率会一夜从 3% 跳到 30%——utilization 暴涨
-
interest 是连续累积、按交互时具现化:
- 你存进去后
aToken.balanceOf(you)每秒都在涨,但 storage 不更新—— 合约把"上次具现化时刻 + 一个全局 liquidityIndex"存着,每次你 supply/withdraw 时才结算。 - 这就是为什么
aToken.balanceOf(你)是 view 函数但每次返回值都不同:内部 view 实现里 按当前 block.timestamp 现算的。
- 你存进去后
5. 必须先认识的前置名词(小词典)
| 名词 | 一句话 | 类比 |
|---|---|---|
| Pool | Aave V3 主合约(0x87870Bca…E2),所有 supply/borrow/liquidation 入口 |
当铺总台 |
| Reserve(不是 V2 reserves!) | Aave 里一种资产的池子配置 | 当铺接收的某种押品类别 |
| supply / withdraw | 存款 / 取回(不是借!) | 存现金 / 取现金 |
| borrow / repay | 借出 / 还回 | 抵押借现 / 还钱赎回 |
| aToken | 存款凭证;1:1 计息(aUSDC ↔ USDC) | 存折 |
| debtToken (variable/stable) | 债凭证;变利率 / 稳定利率 | 借条 |
| collateral asset / debt asset | 抵押资产 / 借出资产 | 押品 / 借出去的钱 |
| LTV | 抵押开仓上限比 | 当铺初次估价折扣 |
| liquidation threshold | 跨越即可清算的折算系数 | "押品快不够"的红线 |
| liquidation bonus | 清算者拿抵押的额外折扣 | 清算佣金 |
| close factor | 单次清算可还债的比例(V3 = 0.95 / 1.0) | 一次清多少 |
| health factor (HF) | 全局健康指标,公式 §4.2 | 命悬一线的数 |
| AaveOracle | 价格喂价合约(0x54586bE6…0C2,主网) |
当铺的估价员 |
| liquidator | 调 liquidationCall 拿奖金的人 |
收尸的 |
flash loan / flashLoanSimple |
Aave 提供的免抵押单 tx 借贷,5bps premium | 当铺的 0 抵押短贷 |
| isolation mode | V3 新增:某些 risky 资产作抵押时只能借 stablecoin、且单独额度 | 隔离押品柜 |
| e-mode | V3 新增:同类资产组(如 stable-stable)享更高 LTV | 同类押品大额折扣 |
| utilization / kink / slopes | 利率曲线参数 | 库存压力 → 收费表 |
| reserve factor | 协议抽成(占借款利率的比例归金库) | 当铺自留管理费 |
6. 一个完整过程走一遍:我们 LiquidationBot 干掉那个不健康仓位
照搬 test_Liquidation_FlashLoanFunded 的故事,把 5 步原子流程展开到 storage 改动级:
准备阶段(test setup)
─── 1. 受害者建仓 ───
victim supply 100 WETH → 抵押价值 100 × $3000 × 0.83 = $249,000 (按 LiqThreshold)
victim borrow 50,000 USDC → 债务 $50,000
HF_initial = 249,000 / 50,000 = 4.98 → 极健康
─── 2. 我们 mock AaveOracle 把 WETH 价格砸 45% ───
WETH 价 $3000 → $1650
重算: 100 × 1650 × 0.83 = $136,950 抵押 vs 50,000 债务
HF = 136,950 / 50,000 = 2.74 ← 等等,这还健康?
实际不是这么算 —— 真实 setup 把 victim 借得更接近上限:
victim borrow ~$240,000 USDC ⇒ HF_initial ≈ 1.04
砸 45% 后 HF ≈ 1.04 × 0.55 ≈ 0.57 < 0.95 close factor 阈值
→ 可全额清算!
→ 测试里精心调 victim debt 让砸盘后正好进 HF < 0.95 区,激活 close_factor=1.0 路径(100% 清算)。
清算阶段(原子 5 步)
─── T0: LiquidationBot.executeLiquidation(victim) 被调 ───
内部第一步: POOL.flashLoanSimple(self, USDC, debtToCover, "", 0)
─── T1: Aave Pool 把 debtToCover USDC 转给 LiquidationBot ───
LiquidationBot.balances[USDC] = debtToCover
─── T2: Aave Pool 回调 LiquidationBot.executeOperation ───
我们的逻辑:
USDC.approve(POOL, debtToCover) ← (09 §6 类似)
POOL.liquidationCall(
WETH, // 抵押要拿的
USDC, // 帮还的债
victim,
debtToCover,
false // receiveAToken=false: 直接拿 WETH 而不是 aWETH
)
─── T3: Aave Pool 内部 liquidationCall ───
transferFrom(self, POOL, debtToCover) USDC ← 拉走我们还债的 USDC
victim.debt -= debtToCover
collateralSeized = debtToCover_in_USD × 1.05 / WETH_price ← +5% liqBonus
transferFrom(victim_aWETH, self, collateralSeized) ← 我们拿到 WETH
emit LiquidationCall(...)
─── T4: 我们把 WETH swap 回 USDC(10 §6 AMM)───
WETH.approve(router, collateralSeized)
router.swapExactTokensForTokens([WETH, USDC], ...)
─── T5: 还闪电贷 ───
owed = debtToCover * 10005 / 10000 ← 5 bps premium
USDC.approve(POOL, owed)
executeOperation 返回 true
POOL.transferFrom(self, POOL, owed) ← Aave 收回闪电贷本金+premium
─── T6: 落账 ───
LiquidationBot.balances[USDC] = swapOutput − owed = 利润
gasUsed = 897,176 ← (07 §8 来源)
→ 整笔 tx 任何一步失败(HF 实际不在清算区 / swap 滑得过狠 / 价格中途又抖回去)都会原子回滚(07 §4.4)。 结果 = 利润落袋 / 仅亏 27 万 gas 级别。这是清算机器人能在主网长期跑的 risk profile。
7. 常见误区(逐条拍死)
| 误区 | 实情 |
|---|---|
| "Aave 审你的征信" | 完全不审。HF 是唯一关心的东西 |
| "HF=1 是安全的" | HF=1 是清算门槛上。"安全"通常 ≥ 1.5;专业户 ≥ 2 |
| "清算 = 全部抵押被没收" | 错。close factor 限制单次最多 50% 或 100%(V3 按 HF 分档)+ 只拿"抵押价 / collateralPrice × 1.05"对应份额 |
| "清算总能赚" | 错。WETH→USDC 的 swap 滑点 + gas + flashLoan premium 可能吞掉 bonus。只有 bonus > 总成本时清算有利可图 |
| "抵押品种类无关紧要" | 错。每种资产有独立 LTV / LiqThreshold / bonus / 是否 isolation;ETH ≠ wBTC ≠ DOGE |
| "Aave 跟 Compound 一样" | 借贷模式相似但细节差异大:Compound 早期无闪电贷、无 e-mode;Aave V3 是当前 SOTA |
| "我借出来的钱不能再当抵押" | 可以(这是杠杆循环:supply → borrow → supply → borrow ...)。但每轮 HF 都会变紧 |
| "aToken 价格 = 1:1 原资产" | aToken 数量1:1 加利息(rebase),价格 1:1。aUSDC 持仓数会随时间增长 |
| "Aave 利率固定" | 利率每秒都在变,按 utilization 曲线 + slope 实时算(§4.4) |
| "liquidationCall 只能 protocol 触发" | 错。任何地址都能调——这正是 LiquidationBot 能存在的根。第一个抢到的人吃肉 |
| "用稳定利率 borrow 更安全" | Aave V3 已经废止了 stable rate(之前有几次被滥用导致协议风险)。现在只有 variable |
| "存款利率 = 借款利率" | 不。借款利率高,存款利率 = 借款利率 × utilization × (1 − reserve factor) → 永远低于借款 |
8. 与本项目实践的对应
我们 LiquidationBot.t.sol 的关键 setup(07 §8 + 09 §8 数据接此)
- HF 调控:先让 victim borrow ~$240k 让 HF ≈ 1.04;再把 WETH 价砸 45% → HF ≈ 0.57 < 0.95 close-factor 阈值 → 全额清算激活
- 为什么 25% 不行:HF ≈ 1.04 × 0.75 ≈ 0.78... 但实测后发现 25% 砸盘对应的 HF 仍 ≈ 1.03,因为 protocol-level 还有 collateral oracle smoothing 等小差异;45% 是为了稳过 close-factor 阈值的安全选择
- 897,176 gas 的归因(07 §8):
- flashLoan 周边(~70k)
- liquidationCall 内部状态更新(~300k;含借贷池 storage 重写)
- WETH→USDC swap(~200k;V2 pair 一次)
- approve / transferFrom 来回(~100k)
- 其他 dispatch / 校验(~200k)
项目里 LiquidationBot.executeOperation 的精确化点
- 用
receiveAToken=false:直接拿 WETH 不拿 aWETH(省一笔 withdraw 转换) - swap 用 V2(10 §8)—— V3 也可以,但 wei-exact 教学场景 V2 更清晰
- approve 严格匹配 owed(09 §8 + 这里)—— 不用 infinite approve 是为教学的稳
Aave 关键 mainnet 地址(cheatsheet)
- Pool:
0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 - AaveOracle:
0x54586bE62E3c3580375aE3723C145253060Ca0C2(被 12 详细展开) - PoolAddressesProvider:
0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e - aUSDC(mainnet V3):
0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c - debt USDC variable:
0x72E95b8931767C79bA4EeE721354d6E99a61D004 - 5 bps flashLoan premium:硬编码在 Pool 配置里
12 / 13 的承接
- 12 预言机:清算的整个故事建立在"WETH 价 $3000 → $1650"这个 oracle 报价上。 如果 oracle 被操纵(历史上 Mango Markets、Compound 等多次发生),清算可被人为触发或阻止。
- 13 闪电贷:本篇里那个"先借 USDC 还债"的 step 1 = Aave V3
flashLoanSimple+ 5bps premium。 原子性(07 §4.4)让"零本金清算 / 零本金套利"在数学上成立。
9. 自检问题(答得出才算过)
- 写出 Aave V3 健康因子 HF 的完整公式。HF=1 安全吗?HF<0.95 和 0.95<HF<1 的区别是什么?
- LTV / LiquidationThreshold / Closer Factor / Liquidation Bonus 四个数字各自定义什么?为什么 LTV < LiqThreshold?
- 清算者一次 tx 的 5 步流程是什么?哪一步是闪电贷?为什么必须原子?
- 利率曲线为什么有"kink"?utilization 接近 1 时利率发生什么?为什么这么设计?
- aToken 是什么?为什么
aToken.balanceOf(you)每秒变但 storage 不一定动? - 我们 LiquidationBot 测试为什么用 45% 砸盘而不是 25%?这跟 close factor 的关系是什么?
- 同一时刻,victim 是不是任何人都能清算?谁能抢到?这跟 14/15 的 MEV 是什么关系?
- 如果 AaveOracle 被攻击者操纵让 WETH 价瞬间砸到 0,会发生什么连锁?这就是为什么 12 预言机篇要细讲。
── Q&A / 更正记录区(按日期追加)──
规范见
README.md§0。质疑/反馈/纠错按日期累积,不抹错误轨迹。
- (2026-05-21 建立。暂无 Q&A。)