OpenZeppelin 对 Mantle Network 的 Mantle token 合约和桥合约进行了安全审计,发现了一些低风险问题和需要注意的点,包括误导性的注释、桥合约可以被重新初始化、以及一些代码上的优化空间。审计建议 Mantle 团队关注治理行为和资产转移,以确保系统的安全。
该安全评估由 OpenZeppelin 准备。
TypeTokenTimeline 从 2023-06-06 到 2023-06-13 语言 Solidity 总问题 16 (10 已解决) 严重性问题 0 (0 已解决) 高风险问题 0 (0 已解决) 中风险问题 0 (0 已解决) 低风险问题 3 (2 已解决) 注意 & 附加信息 13 (8 已解决) 客户报告问题 0 (0 已解决)
我们审计了 b2016dfb932d85b8b33a9294e8280aa04ca46975
commit 上的 mantle-token-contracts 仓库和 d627d242fe19f50f344f1ff4b27532d1757303a6
commit 上的 mantle 仓库。
以下合约在审计范围内:
contracts
├── L1
│ └── L1MantleToken.sol
└── Migration
└── MantleTokenMigrator.sol
packages
└── contracts
└── contracts
├── L1
│ └── messaging
│ └── L1StandardBridge.sol
└── L2
└── messaging
└── L2StandardBridge.sol
在审查 Bridge 合约时,我们假设跨域消息传递器按照文档所述工作。
Mantle token (MNT) 是 Mantle 网络的 Gas token。在以太坊网络上,它是一个可升级的 ERC-20 token,具有 mint、burn、permit 和 vote 扩展。在默认配置中,它允许每年最多铸造一次,并且具有可配置的 mint cap,限制了一次可以铸造多少 token,尽管这两个参数都可以由所有者更改。
Migrator 合约通过持有 MNT token 并以指定的比例将其兑换为 BIT 来促进从 BIT 到 MNT 的迁移。它还具有恢复错误发送的 token 以及将 BIT 和 MNT token 从合约发送到财政地址的功能。
Bridge 是 Optimism Bridge 合约的一个分支,扩展到处理 Mantle token。它允许任何人在以太坊主网上锁定 ETH 或任意 ERC-20 token,以换取 Mantle 网络上相应的 ERC-20 token,这些 token 以后可以销毁以恢复原始 token。值得注意的是,Bridge 本身不保证 Layer 2 token 的任何属性,也不保证与 Layer 1 (L1) 对应物有任何对应关系。例如,原始 token 的任何非标准功能,如 rebasing、黑名单或暂停功能,除非专门设计,否则不会在 Layer 2 (L2) 合约上复制。
Mantle token 是可升级的,这意味着 mint 冷却和其他常量可以更改,以及 token 的行为。
Bridge 尝试识别有效的 token 交换,但除了 ETH 和 MNT 之外,它信任 L2 token 能够识别相应的 L1 token 地址。因此,用户有责任验证他们只与值得信赖的 token 交互。
Mantle token 的所有者可以升级合约、铸造新 token、更改 mint cap 和转移所有权。
Migrator 合约的所有者可以暂停和取消暂停合约、更改财政地址、将 BIT 和 MNT token 转移到财政地址,以及将所有其他 ERC-20 token 转移到任意地址。
Mantle 团队声称这两个合约都将由治理机构拥有。
发现了以下误导性注释:
setMintCapNumerator
的 注释 引用了 MintCapNumeratorSet
事件,但它应该是 MintCapNumeratorChanged
。mint
函数的 注释 说 mint 时间间隔 "最初设置为 1 年",这表明它可以更新。它实际上是一个常量,只有在整个合约升级时才能更改。考虑相应地更新注释。
更新:已在 commit 2a04393 的 pull request #50 中解决。
L1StandardBridge
包含一个 保护条件,以防止它被重新初始化。但是,它假设 messenger
在初始化后将是非零的,这不能由 initialize
函数保证。这意味着可以初始化其他变量,然后覆盖它们。实际上,在设置 messenger 之前,合约将无法正常工作。
尽管如此,为了可预测性,请考虑确保 messenger 在初始化期间是非零的。
更新:已在 commit e641f0e 的 pull request #1027 中解决。
L1StandardBridge
实现合约将 messenger 设置为零地址,但这并不能阻止它被初始化。
为了限制攻击面,请考虑确保无法初始化实现合约。这可以通过将 messenger 设置为未使用的非零地址来实现。
更新:已确认,未解决。Mantle 团队表示:
我们将使用我们的代理合约在部署 Bridge 合约后立即进行初始化,并且代理合约只需要初始化一次。即使有人之后尝试初始化实现合约,也不会对我们的代理合约产生任何影响。
在 MantleTokenMigrator.sol
中,received
一词在多个地方拼写为 recieved
。考虑解决此印刷错误。
更新:已在 commit 6b78f54 的 pull request #53 中解决。存在一些不相关的更改,删除了几个实例。
MantleTokenMigrator
合约不期望接收 ETH,并且在 receive
和 fallback
函数上显式地 reverts。正如 Solidity 文档 中指出的那样,如果没有 receive
、fallback
和 payable
函数,Solidity 合约默认会在接收 ETH 时 reverts。考虑删除 receive
和 fallback
函数以简化代码库。
更新:已在 commit c662f74 的 pull request #45 中解决。
通过首先发出事件,然后更改存储变量,setMintCapNumerator
和 setTreasury
函数可以消耗更少的 gas。
例如,以下代码片段
uint256 previousMintCapNumerator = mintCapNumerator;
mintCapNumerator = _mintCapNumerator;
emit MintCapNumeratorChanged(msg.sender, previousMintCapNumerator, mintCapNumerator);
可以重写为:
emit MintCapNumeratorChanged(msg.sender, mintCapNumerator, _mintCapNumerator);
mintCapNumerator = _mintCapNumerator;
考虑重写 setMintCapNumerator
和 setTreasury
函数以节省 gas。
更新:已在 commit 4159ef3 的 pull request #48 中解决。
已发现一些不精确的文档字符串:
setMintCapNumerator
函数的 注释 的参数不遵循 Ethereum Natural Specification Format (NatSpec) 格式。migrateAllBIT
描述 一致,migrateBIT
描述应注意 _amount
必须为非零。考虑相应地更新文档字符串。
更新:已在 commits 97d88d6 和 0e76225 的 pull request #54 以及 commit d515706 的 pull request #55 中解决。
MantleTokenMigrator
合约的 sweepTokens
函数冗余地将 已知的 token 地址转换为 address
类型。考虑删除不必要的转换操作。
更新:已在 commit bb921bf 的 pull request #44 中解决。
L1MantleToken
合约继承了 多个合约,这些合约具有冗余的依赖项。这意味着某些合约被直接和间接地继承。例如,继承 ERC20VotesUpgradeable
使继承 ERC20PermitUpgradeable
和 ERC20Upgradeable
变得冗余。
这仍然是一个合理的模式,因为它提高了显式性,并使初始化顺序 更直观。但是,它也迫使 L1MantleToken
引入样板函数,这些函数与 token 的新逻辑无关。我们认为,从继承链中删除冗余将使合约更简单,更容易理解。考虑将继承链限制为必要的合约(即,ERC20BurnableUpgradeable
、OwnableUpgradeable
和 ERC20VotesUpgradeable
)。
更新:已确认,未解决。Mantle 团队表示:
我们不希望进行修改以提高显式性并使初始化顺序更直观。
Bridge 合约具有处理 MNT token 的特定逻辑,但 L1StandardBridge
合约 当前将 BIT token 与 L2 Mantle token 相关联。考虑重用 现有变量 来识别 MNT token 地址。
请注意,L2 token 也 仍然引用 BIT token,应相应地更新。
更新:已在 commit d140d18 的 pull request #1075 中解决。现在硬编码了正确的地址,而不是重用变量。
L1StandardBridge
有一个 函数 用于将 ETH 捐赠给合约。但是,此函数是从 Optimism 代码库中保留下来的,对于全新部署是不需要的。考虑删除它。
更新:已在 commit a47a661 的 pull request #1029 中解决。
所有存款和取款都通过 Bridge 传递一个任意数据参数。此参数在 双方 的事件中 发出,但在其他方面未使用。文档 声称 它是为了方便外部合约,但它没有传递到目的地,其他合约也无法读取这些事件。考虑澄清如何使用该参数,或者将其从传输中删除。
更新:已确认,未解决。Mantle 团队表示:
我们认为这是一个为后续跨链互操作性保留的数据接口。
Bridge 合约包含自定义逻辑以支持 存入 Mantle token。但是,只有 token 映射验证 与通用的 ERC-20 情况不同,因此 finalizeDeposit
加粗calldata加粗 规范可以在两个分支之间统一。类似地,只要 BVM_MANTLE
token 配置正确,就可以使用 通用的 ERC-20 逻辑 提取 Mantle token。这将删除不必要的取款逻辑,并使 finalizeMantleWithdrawal
函数 过时。考虑相应地简化代码。
更新:已确认,未解决。Mantle 团队表示:
我们划分此方法主要是为了方便为 L2 原生 token 添加必要的限制,并方便后续有针对性的维护。
finalizeMantleWithdrawal
函数 验证 它是否从 L2 Bridge 调用,但在 finalizeERC20Withdrawal
函数 上重复此检查。考虑删除冗余验证。
更新:已在 commit 5fbb898 的 pull request #1031 中解决。
_initiateWithdrawal
函数 声称 从 _from
地址检索 token,但实际上 从调用者那里获取它们。类似地,事件 引用调用者 而不是 _from
地址。此行为在当前代码库中是等效的,因为 两个调用函数 将消息发送者作为 _from
地址传递。尽管如此,为了可预测性和封装性,请考虑在函数内部使用 _from
地址。
更新:已确认,未解决。Mantle 团队表示:
我们将在未来的 Mantle 网络升级中考虑这一点。
L1StandardBridge
合约 硬编码了 MNT token 的地址。考虑创建一个常量变量并使用它代替硬编码的地址,以提高清晰度和可读性。
类似地,L2StandardBridge
合约 硬编码了 IL2StandardERC20
标识符。考虑使用更具表现力的 type(IL2StandardERC20).interfaceId
语句代替。
更新:已确认,未解决。Mantle 团队表示:
我们将在未来的 Mantle 网络升级中考虑这一点。目前,我们已决定对该地址进行硬编码,因为我们不希望 MNT token 地址在未来发生更改,因为 MNT token 本身是可升级的。
在整个代码库中发现了几个小漏洞和注意事项,并提出了修复建议。我们发现该代码库文档齐全,并感谢对现有库和系统的重用。
下面,我们提供了关于监控重要活动的建议,以便检测和预防潜在的错误。
虽然审计有助于识别当前实现中的代码级别问题,并有可能识别部署在生产环境中的代码级别问题,但我们鼓励 Mantle 团队考虑在生产环境中加入监控活动。对已部署合约的持续监控有助于识别潜在的威胁和影响生产环境的问题。为了提供完整的安全评估,监控建议部分提出了几个解决信任假设和超出范围组件的措施,这些措施可以从链上监控中受益。
存在多个具有严重安全影响的特权操作:
L1MantleToken
合约所有者可以:
MantleTokenMigrator
合约所有者可以:
L1StandardBridge
合约所有者可以升级合约,这使所有者可以访问 Bridge 保护的所有资金。考虑监控这些管理功能,以确保所有更改都是预期的。
考虑监控 MantleTokenMigrator
合约上发生的交换,以确保它们在合理的范围内。异常大、小或频繁的交易可能表明存在意外的边界情况。
考虑监视通过 Bridge 的 token 转移以识别:
这些可能表明用户界面错误、正在进行的攻击或其他意外的边界情况。
- 原文链接: blog.openzeppelin.com/ma...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!