资讯动态

OpenZeppelin Contracts ERC-4626 如何防御通胀攻击?虚拟份额与 offset 的作用

发布时间:2026/9/13 10:22:56 来源:尧图企业网站定制
OpenZeppelin Contracts ERC-4626 如何防御通胀攻击虚拟份额与 offset 的作用【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts如果你用 OpenZeppelin Contracts 实现了一个 ERC-4626 金库vault上线时最常担心的安全问题之一就是“通胀攻击”inflation attack攻击者向空金库直接捐赠底层资产donation把份额价格抬高再利用deposit份额数量向零取整的机制让后来的用户存入的资产几乎全部被稀释。OpenZeppelin 从 v4.9 起在ERC4626中引入了“虚拟份额 虚拟资产 精度 offset”的组合防御默认 offset 为 0官方分析认为这已使攻击无利可图。这篇文章说明这套防御的运作方式、它在源码里的落点以及如何用仓库自带测试验证它确实生效。通胀攻击为什么成立先建立文档给出的攻击模型完整推导见 ERC-4626 指南的安全章节用户存款得到的份额数按资产与当前兑换率计算后向零取整。兑换得越少损失越大存入不足 1 份额对应的资产时用户直接得到 0 份额相当于向金库捐赠。攻击流程攻击者先向空金库deposit少量资产a_0再把a_1资产直接转入金库地址。金库资产增加但份额总数不变兑换率被拉向“右”金库进入危险状态。数学结论用户存款u只能换得u × a_0 / (a_0 a_1)份额。只要满足u 1 a_1 / a_0这笔存款就会被稀释到 0 份额。取a_0 1、a_1 u即可达成攻击成本与攻击收益基本等价——这正是空金库让攻击者可图的原因。典型触发时机攻击者盯住第一个做deposit的用户并抢跑frontrun其交易。防御原理虚拟份额、虚拟资产与 offset文档给出的防御方案借鉴自 YieldBox由两部分配合完成精度 offset让份额的十进制精度比底层资产高offset位用更多小数位表示份额。更高的初始兑换率意味着同样存款换到更多份额取整损失更小。虚拟份额与虚拟资产在换算公式里始终加入10^offset份虚拟份额和 1 份虚拟资产。它们的作用是在金库为空时锁定初始兑换率为10^offset并且吞掉捐赠的一部分。关键在第二点的经济效果。带 offsetδ时攻击者deposita_0后再捐赠a_1攻击者只拥有a_0 / (1 a_0)比例的份额因此捐赠后只能收回a_1 × a_0 / (1 a_0)剩余部分loss a_1 / (1 a_0)被金库的虚拟份额“没收”。用户u此时换到的份额为10^δ × u × (1 a_0) / (1 a_0 a_1)要把它压到 0 份额需要10^δ × u ≤ loss。由此得到文档的两个结论offset 0攻击者损失至少等于用户存款攻击在数学上不再盈利offset 0攻击成本比可抢走的价值高出10^offset倍量级变得极其不划算。下图展示 offset3 时攻击者资金受限下的攻击效果文档标注参数δ3a_01a_110^5防御在源码中的落点防御逻辑集中在 ERC4626.sol 的两个内部换算函数所有preview*与存取路径都经过它们function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { return assets.mulDiv(totalSupply() 10 ** _decimalsOffset(), totalAssets() 1, rounding); } function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { return shares.mulDiv(totalAssets() 1, totalSupply() 10 ** _decimalsOffset(), rounding); }分母里的 1就是虚拟资产分子里的10 ** _decimalsOffset()就是虚拟份额decimals()也基于 offset 计算_underlyingDecimals _decimalsOffset()即份额精度 资产精度 offset。offset 本身是可配置项默认值为 0function _decimalsOffset() internal view virtual returns (uint8) { return 0; }仓库提供了 ERC4626OffsetMock 演示如何自定义 offset构造参数_offset直接覆盖_decimalsOffset()。如果你要实现自己的 vault继承ERC4626并在构造函数传入底层资产即可默认获得该防御需要更高安全余量时再覆盖_decimalsOffset返回更大的 offset。ERC4626源码的 CAUTION 注释对防护边界有明确表述见 ERC4626.sol 第 20-46 行该机制不能完全阻止攻击但默认 offset (0) 已使攻击无利可图——虚拟份额没收的捐赠部分与攻击者预期收益相抵更大的 offset 让攻击成本比收益高出数量级。空或接近空金库的存款仍有被抢跑捐赠抬高价格的风险文档建议 vault 部署者做一笔不小金额的初始存款让价格操纵在成本上不可行。提现withdraw同样可能受滑点影响用户侧可通过校验实际到账金额来保护自己文档提到可用 ERC4626Router 一类做该检查的 wrapper。虚拟份额会“捕获”金库累积价值的一小部分若金库发生亏损后用户集中赎回虚拟份额会让先退出的人损失更小、后退出的人损失更大。想回退到 v4.9 之前的行为只需覆盖_convertToShares和_convertToAssets。另一条硬性限制任何不增加对应资产就铸造份额的机制都会破坏兑换率本合约不能与ERC20FlashMint组合。用测试验证防御效果仓库测试文件 ERC4626.test.js 中有一组针对“inflation attack: offset price by direct deposit of assets”的用例可直接复现并核对防御效果。测试前置动作是向金库捐赠 1 单位资产来偏移价格模拟攻击者 donation// Donate 1 token to the vault to offset the price await this.token.$_mint(this.vault, parseToken(1n));随后deposit用例验证即使价格已被捐赠偏移previewDeposit与Deposit事件给出的份额仍按“实际资产 虚拟资产 / 实际份额 虚拟份额”的有效比率计算用户不会拿到 0 份额。测试代码内嵌的表格记录了不同 offset 下捐赠 1 单位后再存入 1 单位资产、最终可赎回的资产数测试用例预期值offset存入资产可赎回资产01.0000000000000000000.61.0000000000000000000.999999000000000000181.0000000000000000000.999999999999999999并给出结论攻击仍然可能发生但被 offset 显著抬高成本——攻击要成功捐赠规模必须比受害者存款大 10^offset 倍。另有mint用例说明用mint固定份额数可防通胀攻击但会使 mint 非常昂贵。在仓库内运行验证仓库是只读参考环境实际执行请在你本地 clone 后进行npm install安装依赖npm test运行 Hardhat 测试npm install npm testpackage.json 中test脚本为. scripts/set-max-old-space-size.sh hardhat test也可用 Hardhat 的--grep参数只跑通胀攻击相关用例例如npm test -- --grep inflation attack。另外两个值得注意的边界用例ERC4626.t.sol 中的testFuzzDecimalsOverflow表明对 18 位精度的底层资产当 offset 落在 238 到 255 区间时decimals()会因uint8溢出而 panicARITHMETIC_UNDER_OR_OVERFLOW。选择 offset 时要保证资产精度加 offset 不超过 255。ERC4626.test.js 的decimals overflow用例以 offset 243/250/255 验证同一行为$ERC4626OffsetMock即上文提到用于自定义 offset 的 mock。边界与下一步这套防御的目标是“让攻击不盈利”不是“阻止捐赠发生”空金库上线时配合一笔初始存款能进一步把价格操纵的成本抬到不可行。若你在定制 vault任何对存取逻辑的覆盖都必须同步反映到preview*函数文档明确提醒不要直接覆盖面向公共的deposit/mint/withdraw/redeem否则会造成deposit与mint之间行为不一致。需要更高安全余量时覆盖_decimalsOffset()返回大于 0 的值即可换算逻辑无需改动想恢复无虚拟份额的旧行为则覆盖两个_convertTo*函数。深入防御数学的完整推导含 a_0、a_1、u 各阶段的资产/份额/兑换率表格见 erc4626.adoc收费扩展行为可参考文档中指向的 ERC4626Fees.sol 示例。【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价