Mantle V2 Solidity 合约审计

本文是对 Mantle V2 智能合约代码进行审计的报告,审计发现了 1 个严重问题和多个中低风险问题;Mantle V2 是一个以太坊二层扩容方案,将 L2 上的原生货币从 ETH 更改为 MNT,代码库与 Optimism 代码库有很多相似之处。审计报告对合约, 特权角色,安全模型,风险问题都做了详细分析。

目录

概要

TypeL2 RollupTimelineFrom 2024-01-31To 2024-03-01LanguagesSolidityTotal Issues29 (12 resolved, 1 partially resolved)Critical Severity Issues1 (1 resolved)High Severity Issues0 (0 resolved)Medium Severity Issues3 (3 resolved)Low Severity Issues7 (1 resolved)Notes & Additional Information18 (7 resolved, 1 partially resolved)

范围

我们审计了 mantlenetworkio/mantle-v2 仓库,提交哈希为 e29d360

以下文件在审计范围内:

 packages/contracts-bedrock/contracts
├── L1
│   ├── L1CrossDomainMessenger.sol
│   ├── L1ERC721Bridge.sol
│   ├── L1StandardBridge.sol
│   ├── L2OutputOracle.sol
│   ├── OptimismPortal.sol
│   ├── ResourceMetering.sol
│   └── SystemConfig.sol
└── L2
│   ├── BaseFeeVault.sol
│   ├── BVM_ETH.sol
│   ├── CrossDomainOwnable.sol
│   ├── CrossDomainOwnable2.sol
│   ├── CrossDomainOwnable3.sol
│   ├── GasPriceOracle.sol
│   ├── L1Block.sol
│   ├── L1FeeVault.sol
│   ├── L2CrossDomainMessenger.sol
│   ├── L2ERC721Bridge.sol
│   ├── L2StandardBridge.sol
│   ├── L2ToL1MessagePasser.sol
│   └── SequencerFeeVault.sol

此外,我们还对下列合约进行了有限的审查,因为它们是上述范围内合约的依赖项。因此,提出了一些低级别和备注级别的问题。然而,并未对这些文件进行完整的审计。

 ├── universal
│   ├── ERC721Bridge.sol
│   ├── StandardBridge.sol
│   ├── CrossDomainMessenger.sol
│   ├── OptimismMintableERC20.sol
│   ├── Semver.sol
│   └── FeeVault.sol

最后,以下文件仅根据与基础提交 bb0ff70 的差异进行了审计:

 ├── deployment
│   ├── PortalSender.sol
│   ├── SystemDictator.sol
├── legacy
│   └── LegacyERC20MNT.sol
├── libraries
│   ├── Burn.sol
│   ├── Encoding.sol
│   ├── Hashing.sol
│   ├── Predeploys.sol
│   ├── Types.sol

系统概述

Mantle V2 是一个以太坊二层(L2)扩展解决方案,它使用欺诈证明而不是有效性证明来保障安全性。该协议旨在提供低交易费用和高吞吐量,同时保持完整的 EVM 兼容性。Mantle V2 构建在以太坊之上,使用 OP Stack,因此与 Optimism 有许多相似之处。就差异而言,最重要的是 L2 中使用的原生货币从 ETH 更改为 Mantle Token (MNT)。MNT 是以太坊主网上的 ERC-20 代币,因此必须对代码进行调整。在深入了解所有具体更改的细节之前,值得总结一下 Mantle v2 堆栈是什么以及它是如何工作的。该系统基于两个主要的合约目录,即 L1L2 目录。

顾名思义,L1 目录将包含管理以下内容的合约:

  • 将 ERC-20、ERC-721、MNT 和 ETH 存入 L1 合约。
  • L2 桥接完成的事件emission
  • 用于证明最终确定提款交易(存入 L2 的反向操作)的其他功能。
  • 辅助合约,例如 ResourceMetering 合约,用于根据 EIP-1559 处理 gas 单位测量,L2OutputOracle 合约,它托管 L2 区块的最终状态根,以及 SystemConfig 合约,它仅用于检索不同参数的系统配置。

另一方面,L2 目录将包含管理以下内容的合约:

L1L2 的三个主要部分如下:

在 L1 上,ERC-20 资产、MNT 代币和 ETH 通过 L1StandardBridge 合约进行管理,而 ERC-721 资产通过 L1ERC721Bridge 合约进行管理。在 L2 上,可以找到等效的合约,即 L2StandardBridgeL2ERC721Bridge 合约。

两个桥都有共同的函数来初始化到另一域的资产转移,例如 bridgeMNTbridgeERC20bridgeETHbridgeERC721 函数,以及最终确定来自另一域的桥接,例如 finalizeBridgeERC20finalizeBridgeMNTfinalizeBridgeETHfinalizeBridgeERC721 函数。ERC-20 和 ERC-721 资产锁定在桥合约中,而 MNT 和 ETH 在桥接到另一域时转移到 L1CrossDomainMessenger / L2CrossDomainMessenger 合约,或者在最终确定到当前域的桥接时从它们转移。

消息传递者

L1CrossDomainMessengerL2CrossDomainMessenger 合约仅仅是桥和 OptimismPortal / L2toL1MessagePasser 之间的中介。消息传递者不区分资产,他们唯一做的是传递任意的 编码消息 以及 MNT 或 ETH。当桥接到另一域时,会触发 sendMessage 函数,而当最终确定来自另一域的桥接时,会调用 relayMessage 函数。任何 sendMessage 调用的目标都是 OptimismPortal / L2toL1MessagePasser,而 relayMessage 执行的目标通常是桥合约。

门户和消息传递器

OptimismPortal (portal) 合约是从 L1 向 L2 存入资产时 L1 执行的最终点,而 L2toL1MessagePasser 合约是从 L2 向 L1 提款的最终点。这些合约发出事件,其参数是被转移资产的编码消息。两者之间一个值得注意的区别是,OptimismPortal 托管了用于证明和最终确定从 L2 到 L1 的任何提款交易的机制,而 L2toL1MessagePasser 仅仅传递从 L2 到 L1 的消息。提款交易的验证是 L2 状态根被使用确保提款交易已在 L2 上有效触发的地方。OptimismPortalL2toL1MessagePasser 合约是有效持有 MNT 和 ETH 的合约。

背后的原因是,人们可以跳过从桥到消息传递者的整个流程并直接针对它们,从而节省 gas。但是,这是一个不太用户友好的流程,并且容易出错。此外,用户也只能跳过桥并通过消息传递者发送任意数据。消息传递者会将执行传递到门户或 L2 上的消息传递器。Mantle v2 引入的主要区别是将 L2 上的原生货币从 ETH 更改为 MNT。这意味着 ETH 在 L2 上转换为 ERC-20 资产,特别是 OptimismMintableERC20 代币的一个实例。这带有一些细微之处:

  • 存款交易将 ERC-20 MNT 转移到 L1 系统,而在 L2 上,它是原生货币。MNT 作为原生货币,在 L2 最终确定执行之前,在协议级别铸造,就像原始 Optimism 代码中 ETH 的情况一样。
  • ETH 被转移到 L1 系统中,这由 L2 系统中 ERC-20 WETH 代币的铸造来表示。WETH 首先被铸造,然后转移到 L2 存款最终确定的发起者。铸造过程在协议级别执行。

当从 L2 桥接到 L1 时:

  • 原生 MNT 被收集到 L2 系统中,并通过在构造时自毁的合约在稍后阶段被销毁,从而有效地将原生货币从流通中移除。然后,在 L1 系统上释放相应数量的 ERC-20 Mantle 代币。
  • WETH 首先在 L2 上被销毁,然后作为原生货币在 L1 上释放。与铸造过程相反,销毁过程发生在合约级别。

由于 MNT 到 ETH 的汇率会波动,为了正确计算交易费用,GasPriceOracle 合约中引入了 tokenRatio 值。tokenRatio 表示与 ETH 相比 MNT 的价值,这使得能够正确计算费用值。此代币比率在客户端级别进行管理,并且旨在由受信任的运营商在每次价格关系发生变化时进行调整。

Mantle v2 引入了许多其他功能,这些功能在协议级别实现,并且被认为超出了当前智能合约列表的范围。我们建议查看以下官方页面,以进行更深入的新功能分析。

特权角色

系统中存在多个特权参与者:

在任何时候,都假定所有这些特殊参与者都已正确配置,并且他们的任何私钥都没有泄露。

安全模型和信任假设

跨链消息传递和建立在其上的资产桥接是每个 L2 解决方案不可或缺的一部分。但是,这些跨链通信的正确性取决于底层节点和客户端操作。因此,我们假设跨链通信发出的数据已从一层正确中继到另一层。鉴于区分 MNT 和 ETH 的复杂性增加(在 L1 和 L2 上),大量逻辑在协议级别处理。与原始 Optimism 代码相比,这使得系统合约和客户端具有更大的依赖关系。更准确地说,假设存款交易将完全按照请求执行。有关由于某些不正确的节点中继或客户端执行导致资金可能卡住的反例,请参见附录。

此外,建立 MNT 到 ETH 价格关系的代币比率被假定为具有抗操纵性,如官方文档中所述。

严重级别:危急

BVM_ETHMNT 存放在消息传递器中的资金可能被盗

L2CrossDomainMessenger 合约中,relayMessage 函数 将对 _target 执行任意的外部调用。与此同时,该函数应在外部调用中失败,并且具有处理这种情况的逻辑。如果外部调用失败,则 failedMessages[versionedHash] 映射将被设置为 true,此时,任何人都可以重试事务的执行。

当从 L1 向 L2 转移 ETH 时,如果用户通过了 L1StandardBridge 逻辑,则将铸造 BVM_ETH 并将其转移到 L2 上的 L2CrossDomainMessenger,然后触发 relayMessage 执行以将这些 BVM_ETH 移动到其最终目的地。同样,如果用户在从 L2 向 L1 转移 MNT 时通过了 L2StandardBridge [合约](https://github.com/mantlenetworkio/mantle-v2/blob/e29d360904db5e5ec81888885f7b7250- 访问冷存储,需要消耗 2600 单位的 gas。

  • 调用一个不存在的目标,需要消耗 25000 单位的 gas。
  • 调用中的一个正值 msg.value 将增加 9000 单位的 gas 成本。

另外,请注意 hasMinGas 函数的第二个参数是以下两个变量的总和:

  • RELAY_RESERVED_GAS,设置为 40000 单位的 gas。这是对外部调用后继续执行 relayMessage 所需 gas 量的估计。这与 Optimism 代码相比没有变化。
  • RELAY_GAS_CHECK_BUFFER,设置为 5000 单位的 gas,表示在 hasMinGas 函数和外部调用之间应该使用的量。这与 Optimism 代码相比也没有变化。

hasMinGas 函数包含以下公式:

 [ gasLeft - (40000 + _reservedGas) ] * 63/64 >= _minGas

这里,_reservedGas 是 45000 单位的 gas,其中只有 5000 单位被估计为外部调用之前的缓冲区。考虑到所有这些,在 gas 估计和外部调用之间,总共有 5000 的缓冲区加上 40000 的剩余部分,移除了 36600 单位 gas 的最坏情况,总共有 8400 单位 gas(并且不是 文档中提到的 5700)。在外部调用之后,另外 40000 单位的 gas 被预留下来以完成正常执行。

L1CrossDomainMessenger 覆盖在代码中添加了一些额外的指令:一个对 MNT token 合约的 approve 调用,以防被转发的消息包含 MNT token 的转移,以及第二个approve,用于将 allowance 设置回 0,这在外部调用后重复进行。第二个 approval 是否需要取决于是否存在外部调用的目标未消耗给定 approvals 的情况。

一个 ERC-20 token 的 approval 可以跨越几千到 30000 或 40000 单位的 gas,远远超过了提供的几千单位的缓冲区。一些OptmismMintableERC20 token 的实例对于每个 approve 调用甚至可能消耗超过 40000 单位的 gas。approve 调用的 gas 消耗绝对取决于值是从零设置为正值还是反过来,或者从非零设置为非零值。请注意,对于 L2CrossDomainMessengerrelayMessage 函数,可以提出类似的论点。

鉴于以上情况,请考虑重新评估 RELAY_GAS_CHECK_BUFFERRELAY_RESERVED_GAS 的值,并确定是否需要第二个 approval,以避免由于原始 Optimism 代码中包含的额外指令而导致意外的 gas 失败,这些代码对这些默认估计值没有任何更改。此外,考虑到从 Optimism 代码添加的逻辑,应该调整 gas 缓冲区以确保添加足够的开销,以使交易不会失败。值得注意的是,可以设计为失败的 relayMessage 调用可以阻止存款和取款的最终确定,从而为 DoS 攻击打开大门。

更新: 已在 pull request #114 的提交 67f0904 和提交 92ebaf9 中解决。

Gas 估计在 finalizeWithdrawalTransaction 中可能失败

OptimismPortal 合约的 finalizeWithdrawalTransaction 函数 已从原始 Optimism 代码中略作更改,以适应桥接 MNT 所需的更改,与其他资产的桥接分开。与合约中的许多其他函数一样,该函数应该在外部调用失败且 tx.originESTIMATION_ADDRESS回滚。相反,如果外部调用没有失败,即使 tx.originESTIMATION_ADDRESS,调用也不应该回滚。

由于引入的更改,原始 Optimism 行为不再保留。现在,即使外部调用没有失败并且 tx.originESTIMATION_ADDRESS,如果唯一被桥接的资产是 ETH,finalizeWithdrawalTransaction 执行仍然可能失败。这是因为 MNT 转移是否成功的布尔标志默认为 false,导致调用回滚。但是,如果没有 MNT 被转移,这不应该发生。正确的修复方法是将 l1mntSuccess 布尔值 默认设置为 true,以便保持相同的 Optimism 行为,但这也会使布尔值无用,如 issue N01 中所述。

考虑使 gas 估计行为与从 Optimism 继承并在代码库的其他部分保持不变的行为一致。这样做时,请考虑提到关于正在使用的无用布尔变量的问题。

更新: 已在 pull request #105 的提交 6022c06 中解决。

不必要的 Payable 函数定义

L2OutputOracle 合约的 proposeL2Output 函数 被定义为 payable,但它不处理任何 msg.value。虽然该函数被限制为只能由 PROPOSER 调用,但如果传递任何 msg.value,资金可能会卡在合约中,因为没有办法将它们取出。

考虑 proposeL2Output 是否必须是 payable 并记录原因。或者,考虑删除 payable 属性。

更新: 已在 pull request #138 的提交 af0d029 中解决。

低风险

资产可能会卡在合约中

在整个代码库中,在一些情况下,由于外部调用失败,资产可能会被锁定在合约中。以下是两个例子:

  • 当从 L2 桥接 ETH 到 L1 时,OptimismPortalfinalizeWithdrawalTransaction 函数 被调用。这将尝试调用L1StandardBridgefinalizeBridgeETH 函数,该函数执行一个外部调用到 ETH 的接收者。但是,如果这样的调用失败,返回的 success 布尔值将为 false,并且 finalizeWithdrawalTransaction专门tx.originESTIMATION_ADDRESS 时回滚,但如果调用者是尝试最终确定桥接回 L1 的普通用户,则不会回滚。此外,finalizedWithdrawals 映射将设置为此特定提款为 true,从而阻止任何将来重放交易并使其工作的尝试。结果是 ETH 将卡在 OptimismPortal 中。

  • 当从 L2 桥接 ERC-721 token 到 L1 时,将调用相同的 finalizeWithdrawalTransaction,但这次将在 finalizeERC721Bridge 函数中调用 L1ERC721Bridge。此函数将有效地执行L1ERC721Bridge 到 ERC-721 token 接收者的 safeTransferFromsafeTransferFrom 的使用意味着也触发一个 _checkOnERC721Received Hook,如果接收者是一个未实现正确接口来接收 token 的合约,则会回滚。如果调用回滚,则会发生与之前相同的情况,因为 finalizeWithdrawalTransaciton 不会回滚,并且提款将被标记为已完成。这里的结果是 ERC-721 token 将卡在桥中。

考虑在合约的文档字符串中记录此类行为,或者采取补救措施。

请注意,ETH 卡在 OptimismPortal 中的情况是从 Optimism 合约继承的,并且 Optimism 已经采取了一种立场,他们将此责任委托给应该了解风险的用户。引用他们的一个问题

OptimismPortal 的一个怪癖是它没有交易重放。如果交易失败,它将直接失败,并且与之关联的所有 ETH 将保留在 OptimismPortal 合约中。用户已被警告过这一点并了解风险,因此 Optimism 不对用户错误承担任何责任。

更新: 已确认,未解决。Mantle 团队表示:

不修复;我们已经在 CrossDomainMessenger 合约级别实施了交易重放。

采用不寻常的可升级性模式

在代码库中,一些合约旨在可升级,因此可能定义了一个 __gap 变量。可升级合约旨在通过代理调用,因此它们通常具有一个 initialize 函数,该函数通过代理调用。此函数设置代理存储槽的初始变量值。为了防止有人直接初始化实现合约,在某些情况下,在构造函数内部调用 initialize 函数。但是,这会消耗不必要的 gas 并且不是最佳的。所有可初始化合约都扩展了 OpenZeppelin Initializable 合约,该合约定义了一个内部函数,称为 _disableInitializers,它执行相同的禁用实现初始化的任务,但不会通过将变量设置为不必要的值来浪费 gas。

考虑调用 _disableInitializers 函数而不是在构造函数中调用 initialize 函数。

此外,某些合约似乎有错放和错误使用的 __gap 变量,如 ERC721BridgeL1ERC721Bridge 合约的情况。前者有两个不占用任何槽位的 immutable 变量,并且 __gap 变量被设置为具有 49 个槽位的大小,尽管规范值为 50,而后者有一个被 deposits 映射占用的槽位. 这可能会产生误导,因为它可能解释了 __gap 变量的 49 个槽位大小而不是 50 个槽位大小。但是,contract L1ERC721Bridge is ERC721Bridge, Semver 定义中存储布局的工作方式是首先定义 ERC721Bridge 槽位,然后是 Semver 槽位,最后是 L1ERC721Bridge 作为最后一个。

考虑审查代码库并始终使用可升级性标准模式,其中 __gap 变量的大小反映了当前上下文合约占用的存储槽位数量。

更新: 已确认,未解决。Mantle 团队表示:

不修复。它不会影响主要逻辑。

事件中发出了错误的值

每次将 tokenRatio 变量更新为新值时,都会在 GasPriceOracle 合约中发出 TokenRatioUpdated 事件。其参数是先前和新设置的值。但是,该事件错误地两次发出新的 token ratio,而不是发出先前的 token ratio,然后是新的 token ratio。

考虑将 previousTokenRatio 变量分配给当前的 token ratio,而不是使用的输入参数。

更新: 已在 pull request #138 的提交 572600a 中解决。

不完整的文档字符串

在整个代码库中,有几个部分具有不完整的文档字符串:

考虑将所有不安全的 ABI 编码替换为 abi.encodeCall,后者会检查所提供的值是否实际匹配被调用函数期望的类型,并且还可以避免因手误引起的错误。

更新: 已确认,未解决。Mantle 团队表示:

没有必要修复此问题。

缺少文档字符串

在整个 代码库 中,有几个部分没有文档字符串。例如:

  • BVM_ETH 合约的 mint 函数没有文档。
  • GasPriceOracle 合约的变量事件修饰符 没有文档。
  • L1CrossDomainMessenger 合约的 L1_MNT_ADDRESS 变量缺少文档。同样的变量在 L1StandardBridge缺少 文档字符串,在 L2StandardBridge 合约中也缺少 文档字符串。
  • L2StandardBridge 合约的 bridgeMNTTo 函数没有文档。
  • OptimismPortal 合约的 depositTransaction 函数 中,可以同时桥接 ETH 和 MNT。如果是这种情况,用户不应使用通过 L2CrossDomainMessengerL2StandardBridge 进行桥接的正常流程,因为这仅支持一次桥接一种资产。考虑警告用户注意这一点。

考虑彻底记录作为任何合约公共 API 一部分的所有函数(及其参数)。实现敏感功能的函数,即使不是 public,也应清楚地记录。编写文档字符串时,请考虑遵循 以太坊自然规范格式 (NatSpec)。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

注意事项和补充信息

不必要的布尔值

在整个代码库中,存在定义了布尔值但逻辑上没有用的情况:

  • L1StandardBridge 合约的 _initiateBridgeMNT 函数中 approve 调用的 success 。Mantle token 的 approve 函数 要么 revert,要么返回 true,没有任何情况下其结果值为 false。
  • OptimismPortal 合约的 finalizeWithdrawalTransaction 函数的 l1mntSuccess 变量 要么为 true,要么 transfer 调用 reverted。 永远不会为 false。
  • L2CrossDomainMessenger 合约的 relayMessage 函数的 ethSuccess 变量 要么为 true,要么 approve 函数 reverted。每当评估其值时,它永远不会为 false。

考虑重构代码以避免使用不必要的布尔值。这样做时,应注意保持相同的执行流程,尤其是在评估当前不必要的布尔值的地方。

更新: 已在 pull request #128 的 commit d8efd33 中解决。

误导性的文档字符串

在整个代码库中发现了几个不正确或具有误导性的文档字符串的实例:

BVM_ETH.sol:

  • 12 行:BVM_ETH 定义上方的注释已过时,可能会产生误导

LegacyERC20MNT.sol:

  • 39 行、47 行、55 行、63 行:“ETH”应为“MNT”

Burn.sol:

  • 34, 35 行:“ETH”应为“MNT”
  • 19, 21 行的文档字符串提到 gas 函数“燃烧”特定量的 gas。但是,gas 的数量并没有燃烧,而是被消耗了

Types:

  • mntTxValueethTxValue 共享相同的文档,但它们在 UserDepositTransaction 中服务于不同的目的。
  • WithdrawalTransaction 的 struct 文档中,可以删除不存在的字段 value 的文档字符串。此外,mntValueethValue 字段都没有文档。

考虑更新具有误导性的文档字符串实例,以提高清晰度和可读性。

更新: 已在 pull request #129 的 commit fd4fc03 中部分解决。BVM_ETH.sol 中过时的注释仍然存在。

重复的 Getter 函数

SequencerFeeVault 合约的 RECIPIENT 变量声明public。但是,它还有一个特定的 getter 定义。

考虑删除重复的 getter,只保留一个实例来检索 RECIPIENT 变量的值。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

public 函数可以声明为 external

在整个代码库中,有多个 实例 定义了 public 函数的合约。但是,这些函数可以定义为 external

为了节省 gas 并提高代码清晰度,请考虑审查代码库并将所有未在代码本身中调用的函数标记为 external

更新: 已在 pull request #130 的 commit c6f2d81 中解决。

代码风格不一致

ERC721Bridge 合约有一个特定的 require 语句,以确保调用者是 EOA 而不是合约。但是,其他合约有一个特定的 修饰符 称为 onlyEOA,用于相同的目的。

考虑在整个代码库中一致地使用 onlyEOA 修饰符,以提高代码的可读性和质量。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

拼写错误

在代码库中,有一些文档字符串包含拼写错误的实例:

  • OptimismPortal 合约的 第 32 行,“whcih”应为“which”。
  • OptimismPortal 合约的 第 72 行,第一个文档字符串行缺少“If the value”,因此在逻辑上与第二个文档字符串行没有联系。

考虑审查整个代码库并解决拼写错误,以提高代码质量和可读性。

更新: 已在 pull request #131 的 commit 53fe7ce 中解决。

变量命名不遵循 Solidity 风格指南

根据 Solidity 风格指南建议,私有或内部变量标识符应以 _ 作为前缀。在整个代码库中,有多个 实例 的变量命名不符合这些准则。

考虑审查代码库并修复任何不规则的变量命名实例,并采用所有的 Solidity 风格指南,以提高整体代码质量和可读性。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

使用魔法常量

L1CrossDomainMessenger 中,正在使用 魔法常量。在链接的实例中,检查可以从 < 2 更改为 <= MESSAGE_VERSION

考虑始终定义具有显式名称的常量,以提高代码库的可读性和可理解性。

更新: 已在 pull request #132 的 commit 75e7984 中解决。但是,在 L2CrossDomainMessenger 上也发生了同样的情况,但尚未在那里修复。

使用单步所有权转移

CrossDomainOwnableGasPriceOracle 合约中,所有权以单步转移。这可能会带来风险,因为设置不正确的地址意味着合约的所有权将永久丢失,没有恢复的方法。

考虑使用两步所有权转移流程,例如 OpenZeppelin 的 Ownable2Step

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

缺少索引事件参数

在整个 代码库 中,一些事件没有将其参数索引:

考虑索引事件参数,以提高链下服务搜索和过滤特定事件的能力。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

缺少安全联系人

在智能合约中提供特定的安全联系人(例如电子邮件或 ENS 名称)可以大大简化个人在代码中发现漏洞时进行沟通的过程。这种做法是有益的,因为它允许代码所有者规定漏洞披露的沟通渠道,从而消除了因缺乏如何披露的知识而导致沟通不畅或无法报告的风险。此外,如果合约包含第三方库并且在这些库中出现错误,则这些库的维护人员可以更轻松地与有关问题的相关人员联系并提供缓解说明。

在整个 代码库 中,有很多合约没有安全联系人的实例。

考虑在合约定义上方添加包含安全联系人的 NatSpec 注释。建议使用 @custom:security-contact 约定,因为它已被 OpenZeppelin Wizardethereum-lists 采用。

更新: 已确认,未解决。Mantle 团队表示:

没有必要修复此问题。

不必要的类型转换

LegacyERC20MNT 合约中,address(_who) 类型转换是不必要的。

为了提高代码库的整体清晰度、意图和可读性,请考虑删除不必要的类型转换。

更新: 已在 pull request #133 的 commit 9032ff2 中解决。

未使用的代码

Hashing 库合约的 hashDepositTransaction 函数在代码库中从未使用过。此外,以下代码最终也保持未使用状态,因为它目前仅辅助 hashDepositTransaction 函数:

为了提高代码库的整体清晰度、意图和可读性,请考虑删除任何未使用的代码。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

预部署合约的地址未排序

Predeploys 库中地址的常量值没有按递增顺序排列,这在需要添加新地址时容易出错。

考虑按递增顺序排列所有地址。

更新: 已确认,未解决。Mantle 团队表示:

无需修复。

地址被移除了两次

SystemDictator 合约中,正在调用 step3 函数,以从 AddressManager 合约中移除已弃用的地址。但是,BVM_CanonicalTransactionChain 地址被移除了两次,第一次是在第 293 行,第二次是在第 300 行

考虑只移除一次 BVM_CanonicalTransactionChain 的已弃用地址。

更新: 已在 pull request #135 的 commit 4ed9335 中解决。

预部署合约缺少自定义文档标签

在整个 代码库 中,[Predeploys 库](https://github.com/mantlenetworkio/mantle-v2/blob/e29d360904db5e5ec- 将ETH从L1桥接到L2的正常用户执行流程需要用户首先在L1StandardBridge上触发_initiateBridgeETH函数。这将调用L1CrossDomainMessenger合约的sendMessage函数,然后该函数将调用OptimismPortaldepositTransaction函数。执行此操作时,事件中发出的from参数是L1CrossDomainMessenger别名地址,而toL2CrossDomainMessenger

  • 可以跳过整个流程,直接调用OptimismPortal,传递正确的参数来执行完全相同的操作。但是,这可以通过从外部拥有的帐户直接触发depositTransaction或通过用户控制的中间合约来完成。如果是后者,则此时from参数将是用户控制的合约的别名地址。

现在假设我们处于最新的场景中,并且步骤5未能执行。在协议级别,将恢复步骤3中拍摄的快照。在这种假设情况下,from会铸造一些WETH,但是这些WETH不会转移到L2CrossDomainMessenger。现在,由于这种模式,用户可以使用这次ethValue == msg.value == 0ethTxValue与以前相同的值,通过其控制的合约再次触发交易。这次,WETH不会被铸造,但是与之前相同的金额将从它们卡住的地方(用户控制的地址的别名地址,没有私钥来解锁资金)转移到原始接收者L2CrossDomainMessenger,从而有效地解锁资金。

所有这些似乎解决了重要的issue。但是,它也引入了一种极端情况:如果从L1CrossDomainMessenger执行depositTransaction,并且relayMessage执行失败,则资金将卡在L1CrossDomainMessener的别名地址中,因为它无法再次使用不同的ethValueethTxValue值调用depositTransaction。但是,这种情况极不可能发生,并且只能在少数极端情况下发生。如果发生以下情况,relayMessage函数可能会revert:

1) 没有足够的gas来正确完成执行的任何步骤(甚至在外部调用之前或在最低gas检查之前)。2) 使用的消息的版本 >= 2。3) msg.value != _mntValue。4) 如果gasleft() - RELAY_RESERVED_GAS下溢。

即使不应发生上述任何情况,但将来开发引入错误的可能可能会打破此假设。本文的目的是展示节点和客户端的正确性假设被打破的影响。因此,我们强烈建议彻底测试端到端的跨链功能。另一方面,如果发生上述任何一种情况,资金将被卡住,而恢复资金的唯一方法是升级合约或在协议级别修复issue。两种可能的解决方案可能是:

  • L1CrossDomainMessenger合约中引入一个特殊的受限访问函数,以使用自定义参数调用depositTransaction。这样,可以使用msg.value == 0ethTxValue != 0复制调用并解锁资金。
  • 在协议级别引入一种机制,该机制使用如上的自定义参数调用depositTransaction,其中msg.sender == L1CrossDomainMessenger & tx.origin != L1CrossDomainMessenger。这将达到相同的结果。

如果最终未应用任何补救措施,请考虑记录这种情况,描述涉及的潜在风险。

  • 原文链接: blog.openzeppelin.com/ma...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。