{"content":{"title":"ERC-4626 通货膨胀攻击概述","body":"作者: Konstantin Nekrasov - MixBytes 的安全研究员\r\n\r\n![](https://img.learnblockchain.cn/2025/03/09/35.jpg)\r\n\r\n## 简介\r\n\r\n通货膨胀攻击是一个广泛存在的问题，针对 [ERC-4626 代币化保险库标准](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/)，并且直到最近才得到关注。这种攻击允许恶意行为者盗取脆弱池中的首次存款，可能导致毫无防备的投资者遭受重大损失。这个漏洞在 [OpenZeppelin ERC4626 审计](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/audits/2022-10-ERC4626.pdf) 中得到了强调。\r\n\r\n![](https://img.learnblockchain.cn/2025/03/09/image.png)\r\n\r\n许多项目使用 ERC-4626 标准，如 [erc4626.info](https://erc4626.info/)，而且 [在实际操作中有价值数百万美元的交易](https://media.dedaub.com/latent-bugs-in-billion-plus-dollar-code-c2e67a25b689#b606) 可能被利用（但幸运的是没有）。因此，我们决定撰写一篇文章，描述这种攻击以及如何防范它。\r\n\r\n##  定义\r\n\r\n在早期阶段，任何利用“铸造股份”功能以交换基础资产的交易池都可能受到通货膨胀攻击，导致投资者将部分或全部资金损失给池或黑客。\r\n\r\n该漏洞源于“铸造股份”功能中的舍入问题，如以下公式所示：\r\n\r\n```coffeescript\r\nsharesAmount = totalShares * assetAmount / asset.balanceOf(address(this))\r\n```\r\n\r\n黑客可以操纵分母，使受害者获得零或一股保险库。\r\n\r\n## 示例\r\n\r\n让我们来看三个示例。\r\n\r\n示例 1. 将股份舍入为零\r\n\r\n该示例基于 [OpenZeppelin ERC4626](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8177c4620e049b2749c2069651d7d5b4691e23d2/contracts/token/ERC20/extensions/ERC4626.sol) 实现：\r\n\r\n```javascript\r\nabstract contract ERC4626 is ERC20 {\r\n    IERC20 asset;\r\n\r\n    constructor(IERC20 asset_) {\r\n        asset = asset_;\r\n    }\r\n\r\n    function totalAssets() public view returns (uint256) {\r\n        return asset.balanceOf(address(this));\r\n    }\r\n\r\n    function convertToShares(uint256 assets) public view returns (uint256) {\r\n        if (totalAssets() == 0) {\r\n            return assets;\r\n        }\r\n        return totalSupply() * assets / totalAssets();\r\n    }\r\n\r\n    function convertToAssets(uint256 shares) public view returns (uint256) {\r\n        return totalAssets() * shares / totalSupply();\r\n    }\r\n\r\n    function deposit(uint256 assets) public {\r\n        asset.transferFrom(msg.sender, address(this), assets);\r\n        _mint(msg.sender, convertToShares(assets));\r\n    }\r\n\r\n    function burn(uint256 shares) public {\r\n        _burn(msg.sender, shares);\r\n        asset.transfer(msg.sender, convertToAssets(shares));\r\n    }\r\n}\r\n```\r\n\r\n攻击场景：\r\n\r\n1. 黑客后运行一个 ERC4626 池的创建交易。\r\n2. 黑客为自己铸造了一股：deposit(1)。因此，totalAsset()==1，totalSupply()==1。\r\n3. 黑客前运行受害者想要存入 20,000 USDT（20,000.000000）的存款。\r\n4. 黑客在受害者面前膨胀分母：asset.transfer(20\\_000e6)。现在 totalAsset()==20\\_000e6 + 1，totalSupply()==1。\r\n5. 接下来，受害者的交易被处理。受害者获得 1 \\* 20\\_000e6 / (20\\_000e6 + 1) == 0 股份。受害者获得零股份。\r\n6. 黑客销毁自己的股份并拿走所有的资金。\r\n\r\n如何修复？一个选项是限制铸造零股份，但这单独并没有完全解决漏洞，正如第二个示例所示。\r\n\r\n示例 2. 舍入为一股\r\n\r\n假设我们在上述示例的 deposit() 函数中添加了以下条件：\r\n\r\n```coffeescript\r\nrequire(convertToShares(assets) != 0);\r\n```\r\n\r\n然后攻击变得更加复杂：\r\n\r\n1. 黑客后运行一个 ERC4626 池的创建交易。\r\n2. 黑客为自己铸造了一股：deposit(1)。因此，totalAsset()==1，totalSupply()==1。\r\n3. 黑客前运行受害者想要存入 20,000 USDT（20,000.000000）的存款。\r\n4. 黑客在受害者面前膨胀分母：asset.transfer(10\\_000e6)。现在 totalAsset()==10\\_000e6 + 1，totalSupply()==1。\r\n5. 接下来，受害者的交易被处理。受害者获得 1 \\* 20\\_000e6 / (10\\_000e6 + 1) == 1 股份。受害者仅获得一股，与黑客持有的股份相同。\r\n6. 黑客销毁自己的股份并获得池中的一半，大约是 30\\_000e6 / 2 == 15\\_000e6，因此他们的利润为 +5,000（占受害者存款的 25%）。\r\n\r\n如何修复？有 [不同的方法](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706)。例如，你可以在首次存款时铸造“死亡股份”。\r\n\r\n示例 3. 纠缠和死亡股份\r\n\r\n假设我们采取额外步骤，实施“死亡股份”技术，该技术 [由 UniswapV2 使用](https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L121) 以保护池的 deposit() 函数：\r\n\r\n```cpp\r\nuint constant NUMBER_OF_DEAD_SHARES = 1000;\r\n\r\nfunction deposit(uint256 assets) public {\r\n    asset.transferFrom(msg.sender, address(this), assets);\r\n    uint shares = convertToShares(assets);\r\n\r\n    if (totalShares() == 0) {\r\n        _mint(address(0), NUMBER_OF_DEAD_SHARES);\r\n        shares -= NUMBER_OF_DEAD_SHARES;\r\n    }\r\n\r\n    _mint(msg.sender, shares);\r\n}\r\n```\r\n\r\n攻击的复杂性增加了三倍，尽管黑客不再能窃取资金，但仍然可以利用纠缠机会：\r\n\r\n1. 黑客后运行一个 ERC4626 池的创建交易。\r\n2. 黑客铸造了 1,000 股：deposit(1000)。因此，totalAsset()==1000，totalSupply()==1000。注意在此示例中 balanceOf(hacker) == 0 和 balanceOf(address(0)) == 1000。\r\n3. 黑客前运行受害者想要存入 20,000 USDT（20,000.000000）的存款。\r\n4. 黑客在受害者面前膨胀分母：asset.transfer(20\\_000\\_000e6)。现在 totalAsset() == 20\\_000\\_000e6 + 1000，totalSupply() == 1000。\r\n5. 接下来，受害者的交易被处理。受害者获得 1000 \\* 20\\_000e6 / (20\\_000\\_000e6 + 1000) == 0 股份。受害者获得零股份，将其存款损失给池。\r\n6. 因此，黑客燃烧任何受害者的存款，但花费千倍的资金才能做到这一点。\r\n\r\n## 结论\r\n\r\n在本文中，我们分析了几种可能导致 ERC-4626 保险库受到通货膨胀攻击的漏洞代码示例。我们讨论了几种如何防范攻击的方法及其缺陷。有关不同方法的详细列表，以及它们的优缺点，可以在 [OpenZeppelin GitHub 问题](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706) 中找到。审计员和开发人员了解这种攻击的工作原理以确保 DeFi 保险库和池的早期阶段安全至关重要。\r\n\r\n### 相关链接\r\n\r\n1. [«为 ERC4626 通货膨胀攻击实施或推荐减缓措施» - OpenZeppelin GitHub 问题](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706)\r\n2. [OpenZeppelin ERC4626 实现](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8177c4620e049b2749c2069651d7d5b4691e23d2/contracts/token/ERC20/extensions/ERC4626.sol)\r\n3. [UniswapV2 死亡股份铸造代码](https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L121)\r\n4. [OpenZeppelin ERC4626 安全报告](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/audits/2022-10-ERC4626.pdf)\r\n5. [«十亿-plus 美元代码中的潜在错误»](https://media.dedaub.com/latent-bugs-in-billion-plus-dollar-code-c2e67a25b689) 和 [易受攻击的百万美元交易示例](https://media.dedaub.com/latent-bugs-in-billion-plus-dollar-code-c2e67a25b689#b606)\r\n6. [Riley Holterhus 关于 «通货膨胀攻击」 的说明](https://www.youtube.com/watch?v=_pO2jDgL0XE)\r\n\r\n- MixBytes 是谁？\r\n\r\n\r\n\r\n[MixBytes](https://mixbytes.io/) 是一支专业区块链审计员和安全研究员团队，专注于为 EVM 兼容和 Substrate 基础项目提供全面的智能合约审计和技术咨询服务。请在 [X](https://twitter.com/MixBytes) 上关注我们，随时了解最新的行业趋势和见解。\r\n\r\n>- 原文链接： [mixbytes.io/blog/overvie...](https://mixbytes.io/blog/overview-of-the-inflation-attack)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/24680","address":null},"history":"bafkreiajovtw5gpzsjukkz2bxe6carsxukbhbuhxwcqpiqp5wecpfniazu","timestamp":1741499142,"version":1}