ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"

前言

2023年,某 DeFi 金库刚上线,攻击者只花了1块钱,就把整个金库的钱全卷走了。这不是科幻,这就是 ERC-4626 金库的"捐款陷阱"------通胀攻击(Inflation Attack)。

这个漏洞利用了金库在初始阶段的设计缺陷,让攻击者几乎零成本稀释其他用户的份额,最终把整个金库的资金卷走。本文会用通俗的语言讲清楚这个攻击是怎么回事,同时也会给开发者提供技术细节和防御方案。

ERC-4626 是什么

ERC-4626 是以太坊上一种标准化金库合约(Vault)。工作原理很简单:

  • 你存入底层资产(比如 USDT、ETH)
  • 金库给你份额(shares,也叫 vault token)
  • 以后你可以用份额赎回资产,还能赚取收益

核心公式:

scss 复制代码
你得到的份额 = (你存的钱 × 总份额) ÷ 总资产

关键点 :这是整数除法,会向下取整(小数直接砍掉)。这就是漏洞的根源。

ERC-4626 存款流程

graph LR A[用户存入资产
如 USDT] --> B[金库计算份额
shares = assets × totalShares ÷ totalAssets] B --> C[用户获得份额
vault token] C --> D[后续可赎回
按比例取回资产] style A fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px style B fill:#e5dbff,stroke:#5f3dc4,stroke-width:2px style C fill:#ffe8cc,stroke:#d9480f,stroke-width:2px style D fill:#c5f6fa,stroke:#0c8599,stroke-width:2px

通胀攻击原理

通俗解释

想象一下:你买10000张彩票,结果系统把它四舍五入成0张......这就是通胀攻击的本质。

攻击者通过"捐款"把金库的总资产拉得很大,而总份额很小,导致后续用户存钱时,计算出来的份额因为向下取整变成0。用户的钱进了金库,但什么都没拿到,最后攻击者把所有钱全拿走。

攻击流程(4步)

graph TD A[步骤1:攻击者存入 1 个 T] --> B[获得 1 个份额
totalAssets=1, totalShares=1] B --> C[步骤2:攻击者捐款 100000 个 T
不通过 deposit,直接转账] C --> D[totalAssets=100001
totalShares=1 不变] D --> E[步骤3:受害者存入 10000 个 T] E --> F[计算份额
10000 × 1 ÷ 100001 ≈ 0.09999] F --> G[向下取整 = 0 份额
受害者什么都没得到!] G --> H[步骤4:攻击者赎回 1 个份额] H --> I[卷走全部 110001 个 T
攻击成功!] style A fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px style C fill:#ffe3e3,stroke:#c92a2a,stroke-width:2px style E fill:#fff4e6,stroke:#e67700,stroke-width:2px style G fill:#ffe3e3,stroke:#c92a2a,stroke-width:3px style I fill:#ffe3e3,stroke:#c92a2a,stroke-width:3px

攻击成本 :几乎为0(第一次的1个T最后也拿回来了) 攻击获利:受害者的全部存款

技术深入:数学根源

ERC-4626 的 convertToShares 函数使用了整数除法:

solidity 复制代码
function convertToShares(uint256 assets) public view returns (uint256) {
    if (totalAssets() == 0) {
        return assets;
    }
    return totalSupply() * assets / totalAssets();  // 向下取整
}

totalAssets 远大于 totalSupply 时,assets * totalSupply 还没除,就因为整数除法被抹掉小数。攻击者通过 donate 把 totalAssets 拉到极大,让任何后续 deposit 的分子都被"稀释"成0。

正常情况 vs 攻击后对比

graph LR subgraph normal["正常情况"] N1[totalAssets = 100
totalShares = 100] N2[用户存入 10 个资产] N3[计算:10 × 100 ÷ 100 = 10] N4[获得 10 个份额 ✓] N1 --> N2 --> N3 --> N4 end subgraph attack["攻击后情况"] A1[totalAssets = 100001
totalShares = 1] A2[用户存入 10000 个资产] A3[计算:10000 × 1 ÷ 100001 ≈ 0.09999] A4[向下取整 = 0 份额 ✗] A1 --> A2 --> A3 --> A4 end style N1 fill:#d3f9d8,strok9e44,stroke-width:2px style N4 fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px style A1 fill:#ffe3e3,stroke:#c92a2a,stroke-width:2px style A4 fill:#ffe3e3,stroke:#c92a2a,stroke-width:3px

真实案例

时间线与影响

  • 2023-2025年:多个知名 DeFi 项目早期金库都被这样攻击过
  • 涉及金额:数百万美元的潜在风险交易(幸运的是大部分未被利用)
  • 高危时期:新金库刚上线、TVL(总锁仓价值)接近0的时候

实际案例

Morpho DAO

  • 防御方式:在金库初始化时存入资产,对应的份额铸造给金库自身(作为非操作地址)
  • 效果:初始存入的资产越多,攻击难度越大
  • 缺点:这些资产实际上被锁死了

YieldBox

  • 防御方式:使用虚拟份额和虚拟资产
  • 效果:不需要烧掉任何代币就能达到防御效果
  • 实现:设置 asset offset 为1,supply offset 为1e8

防御方案

用户层面:如何保护自己

如果你是 DeFi 用户,记住这几点:

  1. 不要急着冲新上线的金库 - 等 TVL 起来、审计报告出来再参与
  2. 观察项目是否使用了防御措施 - 查看项目文档或审计报告
  3. 小额测试 - 第一次存款用小额测试,确认能正常获得份额

开发者层面:技术防御

目前主流有3种成熟防护方案:

1. 虚拟份额 + 小数偏移量(最推荐)

这是 OpenZeppelin 官方推荐的方案:

solidity 复制代码
uint256 private constant VIRTUAL_SHARES = 1e6;
uint256 private constant VIRTUAL_ASSETS = 1e6;

// 修改计算公式
totalAssets = realAssets + VIRTUAL_ASSETS;
totalShares = realShares + VIRTUAL_SHARES;

工作原理

graph TD A[OpenZeppelin 防御方案] --> B[虚拟份额 + 小数偏移量] B --> C[增加虚拟资产
VIRTUAL_ASSETS = 1e6] B --> D[增加虚拟份额
VIHARES = 1e6] C --> E[totalAssets = realAssets + VIRTUAL_ASSETS] D --> F[totalShares = realShares + VIRTUAL_SHARES] E --> G[限制汇率操纵能力] F --> G G --> H[攻击者捐款被虚拟资产稀释] H --> I[攻击无利可图 ✓] style A fill:#e7f5ff,stroke:#1971c2,stroke-width:2px style B fill:#e5dbff,stroke:#5f3dc4,stroke-width:2px style G fill:#ffe8cc,stroke:#d9480f,stroke-width:2px style I fill:#d3f9d8,stroke:#2f9e44,stroke-width:3px

效果

  • 即使 totalAssets 被捐到100000,也不会让新用户的 shares 直接变成0
  • 虚拟资产和份额会捕获部分捐款,使攻击无利可图
  • 偏移量越大,攻击者损失越多,安全性越高

2. 最小存款检查

deposit 前加入最小金额检查

solidity 复制代码
require(assets >= MIN_DEPOSIT, "Deposit too small");

优点 :实现简单 缺点:提高了使用门槛,对小额用户不友好

3. 使用 OpenZeppelin ERC4626 最新版

OpenZeppelin 已在 v5+ 版本中默认加入虚拟份额保护,直接继承使用即可:

solidity 复制代码
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";

contract MyVault is ERC4626 {
    constructor(IERC20 asset) ERC4626(asset) ERC20("MyVault", "MVT") {}
}

总结

一句话记住这个漏洞

攻击者捐了一大笔钱,把金库的总资产吹得很大,后续存钱的人就什么份额都拿不到,最后捐款的人把所有钱全拿走。

给用户的建议

  • 新金库别急着冲,等 TVL 起来再说
  • 观察项目是否有审计报告和防御措施

给开发者的建议

  • 使用 OpenZeppelin 最新版 ERC4626
  • 或实现虚拟份额 + 小数偏移量防护
  • 在金库初始化时进行充分测试

技术漏洞可以修复,但安全意识要时刻保持。DeFi 的世界很精彩,但也充满风险,保护好自己的资产才是第一位的。


参考资料

相关推荐
hpoenixf43 分钟前
2026 年前端面试问什么
前端·面试
还是大剑师兰特1 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷1 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
大阿明1 小时前
Spring Boot(快速上手)
java·spring boot·后端
mengchanmian2 小时前
前端node常用配置
前端
墨香幽梦客2 小时前
API集成技术规范:RESTful与GraphQL在企业系统对接中的应用对比
后端·restful·graphql
华洛2 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq2 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
彭波3963 小时前
.NET Framework 3.5问题修复教程!可以离线修复
windows·安全·电脑·.net·开源软件
A黄俊辉A3 小时前
vue css中 :global的使用
前端·javascript·vue.js