Periphery变更审计

本次OpenZeppelin对Across协议的合约仓库进行了差异化审计,重点关注SpokePoolPeriphery合约及其相关组件。审计发现了包括高、中、低风险在内的多个问题,主要集中在智能合约中swap逻辑,签名处理,EIP-712编码,重放攻击等方面,所有发现的问题均已被修复。本次审计旨在提升Across协议的跨链桥功能和用户体验,确保资产转移的安全性和效率。

目录

总结

Type跨链时间线 从 2025-05-15 到 2025-05-26 语言 Solidity 总问题 13 (13 个已解决) 严重问题 0 (0 个已解决) 高危问题 1 (1 个已解决) 中危问题 3 (3 个已解决) 低危问题 3 (3 个已解决) 注释 & 补充信息 6 (6 个已解决)

范围

OpenZeppelin 对 across-protocol/contracts 仓库进行了差异审计,基础版本为提交 7362cd0 (master),头部版本为提交 b84dbfa

以下文件在审计范围内:

 contracts
├── external
│   └── interfaces
│       ├── IERC20Auth.sol
│       └── IPermit2.sol
├── handlers
│   └── MulticallHandler.sol
├── interfaces
│   └── SpokePoolPeripheryInterface.sol
├── libraries
│   └── PeripherySigningLib.sol
└── SpokePoolPeriphery.sol

系统概述

Across 协议是一种跨链桥,旨在实现跨各种网络的 ERC-20 代币和原生资产的快速且经济高效的转移。 它允许用户(存款人)在源链上锁定资产,然后由中继者在目标链上提供给他们,这些中继者预付自己的资金。 该协议通过发送中继者选择的链上可用的资金,或者在特定链没有足够资金时利用以太坊上的 HubPool 来偿还中继者。 本次审计的重点是一组新的外围智能合约,旨在增强与 Across V3 生态系统交互的功能、灵活性和用户体验。

SpokePoolPeriphery

SpokePoolPeriphery 合约充当 Across 协议面向用户的入口点,显着扩展了启动跨链转移的可用选项。 其核心功能包括:

  • Swap and Bridge(交换和桥接):一个旗舰功能,允许用户桥接资产,即使他们没有 SpokePool 所需的特定代币。 合约可以获取用户指定的 swapToken,在指定的外部交易所执行交易以将其转换为 Across 接受的 inputToken,然后启动桥接存款——所有这些都在一个原子交易中完成。 此功能支持按比例调整输出,如果交换产生的 inputToken 多于用户指定的最小值,则可以按比例增加在目标链上收到的代币数量。
  • 多功能代币授权:该合约集成了多种行业标准机制,用于授权用户转移代币,从而提供灵活性并实现高效的 gas 交互:
    • 用于预先批准代币的标准 ERC-20 transferFrom
    • 原生货币(例如,ETH)存款,如果提供非零 msg.value,则会自动包装成其 WETH 等价物
    • EIP-2612 permit
    • Permit2 (permitWitnessTransferFrom),用于通过规范的 Permit2 合约进行批量批准和更高级的基于签名的权限
    • EIP-3009 receiveWithAuthorization,用于支持此 ERC-20 扩展的代币
  • 通过 SwapProxy 进行隔离的交换执行:为了增强安全性和模块化,所有交换操作都委托给专用的 SwapProxy 合约。 SpokePoolPeriphery 部署此代理并将代币转账给它以进行交换。 然后,SwapProxy 处理对指定交易所或 Permit2 合约的代币批准,并在目标交易所上执行交换 calldata。 最后,输出代币被转移回 SpokePoolPeriphery 合约。

MulticallHandler 变更

MulticallHandler 合约所做的更改包括添加了 makeCallWithBalance 函数,该函数可用于用 MulticallHandler 合约的指定代币余额填充给定的 calldata,并使用此修改后的 calldata 调用目标合约。 每当指定 calldata 时,目标链上到达的代币数量未知时,此功能非常有用,当使用来自 SpokePoolPeriphery 合约的 交换和桥接 功能时,可能会出现这种情况,并且存款人在签署存款数据时不知道交换的输出金额。

值得注意的是,存款人自己负责提供正确的代币和偏移量,应在其中填充余额,请记住余额可以用小于 uint256 的类型表示,并且指定错误的偏移量可能会导致意想不到的后果,例如资金损失。 用户还应记住,makeCallWithBalance 函数不适用于需要提供负代币数量作为参数的交易所,因为它只能用非负余额填充 calldata。 鼓励所有存款人研究 makeCallWithBalance 函数的文档,以了解其所有风险和限制。

PeripherySigningLib

PeripherySigningLib 是一个库,支持 SpokePoolPeriphery 的基于签名的功能。 它的贡献包括:

  • 标准化哈希:为 BaseDepositDataFeesDepositDataSwapAndDepositData 结构体提供计算符合 EIP-712 规范的类型化数据哈希的函数。 这确保了一致且安全的签名生成和验证。
  • 签名反序列化:提供一个实用函数,用于将原始字节签名解析为其 v, r, s 组件,从而简化主合约逻辑中的签名处理。

安全模型和新的信任假设

这些外围合约的引入扩展了 Across 协议的功能,因此,为其安全模型和特定信任假设引入了新的元素:

  • swapAndBridge 功能依赖于用户(或受信任的前端)指定的外部交易所。 交换期间用户资金的安全性取决于所选交易所的安全性以及所提供的 routerCalldata 的完整性。 受损的交易所或恶意 calldata 可能会导致资金损失。
  • 用户(或代表他们行事的前端)负责参数的正确性和安全性,例如交换的交易所地址、路由器 calldata、MulticallHandler 指令和 EIP-712 签名消息,因为不正确或恶意的输入可能导致交易失败、资金损失或意外交互。 假设用户仅使用受信任的交易所,指定合理的最小代币输出数量,并提供正确的 EIP-712 签名。 此外,假设他们为 MulticallHandler 合约指定了正确的 calldata,并且他们注意在每次与该合约交互结束时将剩余在该合约中的任何代币转账到他们的帐户。
  • Permit2 的使用意味着对规范的 Permit2 合约的安全性和运营完整性的信任。 假设此合约以正确的方式运行。 同样重要的是要注意,SpokePoolPeriphery 合约依赖于在其部署的链上存在 Permit2 合约。 我们假设 SpokePoolPeriphery 合约将仅部署在 Permit2 合约存在的区块链上。
  • 用户负责提交正确的交换数据,包括但不限于,指定合理的最小交换输出代币数量和存款输出数量。 假设用户为存款和交换指定正确的数据。
  • 提交者(例如,中继者)应始终在提交之前离线模拟签名的交换交易。 由于 swapProxy 使用任意 routerCalldata 盲目调用用户指定的 exchange,因此恶意的签名者可以将其指向一个合约,例如,该合约进入无限循环或执行返回炸弹攻击,在恢复之前耗尽所有 gas。 如果没有模拟,中继者将承担失败调用的全部 gas 成本(gas 恶意攻击),并且不会获得任何补偿。

高危

传递给 Permit2.permit 函数的 Nonce 不正确

SwapProxy 合约的 performSwap 函数允许使用几种不同的方法为交换提供代币给指定的交易所。 特别是,它允许通过 Permit2 合约批准交换的代币。 为了做到这一点,它将给定的代币数量批准给 Permit2 合约,并调用 Permit2 合约的 permit 函数

然而,为该调用指定的 nonce对于整个合约来说是全局的,而 Permit2 合约为每个(所有者、代币、支出者)元组存储一个单独的 nonce。 因此,任何尝试使用与第一次 performSwap 函数调用中使用的(代币,支出者)对不同的对,都将由于 nonce 不匹配而恢复

考虑在 SwapProxy 合约中为每个(代币,支出者)对存储和使用单独的 nonce。

更新: 已在提交 3cd99c4pull request #1013 中解决。

中危

可能在 SpokePoolPeriphery 上发生重放攻击

SpokePoolPeriphery 合约允许用户将代币存入或交换并存入 SpokePool。 为了做到这一点,资产首先从存款人的帐户转移,可以选择交换为不同的代币,然后最终存入 SpokePool。

可以从存款人的帐户以几种不同的方式转移资产,包括批准,然后transferFrom 调用通过 ERC-2612 permit 函数批准,然后 transferFrom通过 Permit2 合约转移,以及通过 ERC-3009 receiveWithAuthorization 函数转移。 后三种方法需要额外的用户签名,并且可以由任何人代表给定的用户执行。 然而,对于使用 ERC-2612 permit 和 ERC-3009 receiveWithAuthorization 进行存款或交换和存款的签名数据不包含 nonce,因此,用于这些方法的签名可以稍后重放。

如果受害者签署了依赖于 ERC-2612 permit 函数的函数的数据,并且想再次使用相同的方法和代币存款在由 depositQuoteTimeBuffer 参数确定的时间窗口内,则可以执行攻击。 在这种情况下,攻击者可以首先代表受害者批准代币,然后调用 swapAndBridgeWithPermit 函数depositWithPermit 函数,提供过去存款或交换和存储的签名,其中包括比批准金额少的代币。

因此,代币将被存入并可能交换,使用来自旧签名的数据,迫使受害者执行意外的交换或将代币桥接到与预期不同的链。 此外,由于攻击消耗了 permit 批准的一部分,因此在存款人再次完全批准代币之前,无法代表存款人使用新签名来存款代币。 在依赖于 ERC-3009 receiveWithAuthorization 函数的函数的情况下,也可以进行类似的攻击,但这需要转移的代币数量与过去的数量相同。

考虑向 SwapAndDepositDataDepositData 结构体添加一个 nonce 字段,并在 SpokePoolPeriphery 合约中为每个用户存储一个 nonce,当签名被验证和接受时,应该递增 nonce。

更新: 已在 pull request #1015 中解决。 Across 团队添加了一个 permitNonces 映射,并使用 nonce 字段扩展了 SwapAndDepositDataDepositData。 在 swapAndBridgeWithPermitdepositWithPermit 中,合约现在在验证 EIP-712 签名之前调用 _validateAndIncrementNonce(signatureOwner, nonce),从而确保每个基于许可的操作只能执行一次。 ERC-3009 路径继续依赖于代币自己的 nonce; 这里的重放需要代币同时实现 ERC-2612 和 ERC-3009,用户在两个签名中重用完全相同的 nonce,并且两者都在狭窄的 fillDeadlineBuffer 内执行。 鉴于这些条件不太可能收敛,因此实际风险可以忽略不计。

可能通过 Permit2 对交换进行 DoS 攻击

SwapProxy 合约包含 performSwap 函数,允许调用者以两种方式执行交换:通过批准或将代币发送到指定的交易所,或者通过 Permit2 合约批准代币。 然而,由于可以提供任何地址作为 exchange 参数,并通过 performSwap 函数的 routerCalldata 参数提供任何调用数据,因此可能会强制 SwapProxy 合约对任意地址执行任意调用

攻击者可以利用这一点,迫使 SwapProxy 合约调用 Permit2 合约的 invalidateNonces 函数,指定任意的支出者和高于当前 nonce 的 nonce。 因此,给定(代币,支出者)对的 nonce 将被更新。 如果稍后再次调用 performSwap 函数,它将尝试使用后续 nonce,该 nonce 已被攻击者无效化,并且 Permit2 中的代码将由于 nonce 不匹配而恢复

由于 performSwap 函数是传递给 Permit2 合约的 nonce 被更新的唯一位置,因此在某个交易所交换给定代币的可能性将被永久阻止,这会影响 SpokePoolPeriphery 合约中与交换代币相关的所有函数。 可以为许多不同的(代币,交易所)对执行攻击。

考虑不允许 exchange 参数等于 Permit2 合约地址。

更新: 已在提交 713e76bpull request #1016 中解决。

不正确的 EIP-712 编码

PeripherySigningLib包含某些类型的 EIP-712 编码,以及生成符合 EIP-712 规范的哈希数据的辅助函数。 然而,SwapAndDepositData 结构体的数据类型不正确,因为它包含 枚举类型TransferType 成员,EIP-712 标准不支持该成员。

考虑替换用于生成 SwapAndDepositData 结构体数据类型的 TransferType 枚举名称,使用 uint8 以符合 EIP-712。

更新: 已在提交 c9aaec6pull request #1017 中解决。

低危

deposit 将不适用于非 EVM 目标链

SpokePoolPeriphery 合约的 deposit 函数允许用户将原生价值存入 SpokePool。 然而,它的 recipientexclusiveRelayer 参数都是 address 类型,并且强制转换bytes32。 因此,无法将包装的原生代币桥接到非 EVM 区块链。

考虑更改 deposit 函数的 recipientexclusiveRelayer 参数的类型,以便允许调用者为存款指定非 EVM 地址。

更新: 已在提交 3f34af6pull request #1018 中解决。

_swapAndBridge 中的整数溢出

_swapAndBridge 函数中,调整后的输出金额计算为depositData.outputAmountreturnAmount 的乘积除以 minExpectedInputTokenAmount。 如果 depositData.outputAmount * returnAmount 超过 2^256–1,则交易将在乘法步骤中立即恢复,即使最终除法结果可以容纳。 这种中间溢出对于用户来说是不可见的,他们只能看到没有解释性错误消息的通用失败。

考虑使用 OpenZeppelin 的 Math.mulDiv(a, b, c) 来计算 floor(a*b/c) 而没有中间溢出。 或者,考虑记录可能的溢出场景。

更新: 已通过记录潜在的溢出场景在提交 e872f04pull request #1020 中解决。

不灵活的费用接收者字段阻止开放中继

目前,每个 DepositDataSwapAndDepositData 有效负载必须包含一个硬编码的费用接收者地址,并且在成功存款或交换和桥接后,外围设备会将提交费用支付给该确切地址。 虽然这确保了用户提前确切地知道谁将收到他们的费用,但它也阻止了开放的中继器竞争或在所选的中继器表现不佳或不可用时的回退选项。

考虑在 SwapAndDepositData 中保留显式费用接收者字段选项,但引入“零地址”约定:

  • 如果费用接收者等于零地址,则外围设备应默认使用 msg.sender 作为付款人。
  • 如果费用接收者不是零地址,则将费用转移到签名的 recipient

更新: 已在提交 f2218c0pull request #1021 中解决。

注释 & 补充信息

函数重命名建议

SpokePoolPeriphery 合约的 deposit 函数允许用户将原生价值存入 SpokePool。 虽然可以指定 inputToken 参数,但无法通过此函数存入其他代币。 因此,可以将其重命名为 depositNative 或类似的名称,以明确这一事实。

考虑重命名 deposit 函数,以提高代码库的可读性。

更新: 已在提交 a69ad79pull request #1019 中解决。

优化机会

在整个代码库中,发现了多个代码优化机会:

考虑扩展上述实例中的文档,以提高代码库的清晰度。

Update:** 在提交 047283epull request #1023 中已解决。

拼写错误

在整个代码库中,发现了多个拼写错误的实例:

  • MulticallHandler.sol 文件的 第 48 行 中,“calldData”应为“callData”。
  • SpokePoolPeripheryInterface.sol 文件的 第 113 行 中,可以删除 "on"。
  • SpokePoolPeriphery.sol 文件的 第 500 行 中,“depositData/swapAndDepositData”可以为“DepositData/SwapAndDepositData”。

考虑更正所有拼写错误的实例,以提高代码库的清晰度和可读性。

Update:** 在提交 18296cbpull request #1024 中已解决。

未使用的代码

在整个代码库中,发现了多个未使用的代码实例:

为了提高代码库的整体清晰度和可维护性,请考虑删除任何未使用的代码实例。

Update:** 在提交 767cb9fpull request #1025 中已解决。

误导性文档

在整个代码库中,发现了多个具有误导性的文档实例:

考虑修复上述实例,以提高代码库的清晰度。

Update:** 在提交 f8f484apull request #1026 中已解决。

结论

对 periphery 合约进行的审查变更引入了将资产存入 SpokePool 的新可能性。它们使第三方实体能够代表任何提供有效签名的用户存入或 swap-and-deposit 资金。此外,它们可以保护用户在为存款指定不正确的 SpokePool 地址时不会丢失其原生 token。

虽然审计发现了一些与 swap 逻辑和签名处理相关的问题,但发现代码是可靠且组织良好的。感谢 Risk Labs 团队的积极响应并在整个审计过程中回答审计团队的问题。

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

0 条评论

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