OpenZeppelin 对 Across Protocol 的 Solana 跨链桥代码进行了一次审计,主要关注了移除自中继、允许历史完成期限、更新 SVM 接口、移除启用存款路由检查以及测试原生 SOL 存款等方面的更改。审计发现了一个中等严重性和一个低严重性的问题,以及一些需要注意和补充的信息。
TypeCross ChainTimelineFrom 2025-03-31To 2025-04-08LanguagesRustTotal Issues5 (4 resolved, 1 partially resolved)Critical Severity Issues0 (0 resolved)High Severity Issues0 (0 resolved)Medium Severity Issues1 (1 resolved)Low Severity Issues1 (1 resolved)Notes & Additional Information3 (2 resolved, 1 partially resolved)
OpenZeppelin 对 across-protocol/contracts 仓库在 commit 71e990b 处的 solana-march-audit-2 分支进行了差异审计。
审计范围包括以下 pull requests:
之前,当中继填充期间接收方代币账户与中继者代币账户匹配时,由于接收方和接收者是同一个账户,因此不会发生代币转账。在 solidity 版本中,这是通过提前返回来实现的,这也绕过了消息处理并引入了一个潜在的 bug。
已实施的缓解措施是简单地移除提前返回的行为,以便在每种情况下都处理消息。为了与 Solidity 版本保持一致,Solana 代码库中的自中继优化也被移除。
此更改删除了存款期间完成截止时间的下限,允许截止时间在过去。 这允许中继者乐观地完成存款,这在某些情况下可能是可取的,或者避免因不同链之间块时间戳的差异而导致的同步错误。
此更改更新了整个代码库中的命名约定,以匹配 Solidity 对应项。 特别是,所有对 "v3" 的引用都已被删除。
在 SVM spoke 程序的先前迭代中,存款路径(即,存款的代币类型和目标链)必须由管理员创建并启用。 如果尝试使用尚未启用的路径进行存款,则会生成错误。 正在审查的更改删除了与创建和启用路径账户相关的所有逻辑,有效地删除了围绕哪些代币类型可以发送到哪些链的所有限制。 虽然现在可以使用任何路径,但由于存款人需要将代币发送到金库账户,因此必须初始化金库代币账户 PDA 才能接收代币。 提供了一个辅助脚本 来初始化金库 PDA,任何人都可以执行它。
由于 SVM SpokePool 中的所有代币转账都使用 SPL 代币转账,因此必须包装原生 SOL 才能进行存款。 已经添加了一个演示通过包装进行原生存款的脚本。
SpokePool 程序的存款操作旨在从调用者处提取代币(即,签名人),这些代币可以代表任何 depositor
账户进行存款。 但是,当签名者不是存款人时,将使用存款人代币账户作为源地址执行 transfer_from
操作。 除非存款人已将至少输入金额委托给 state PDA,否则此类操作将失败。 这会带来两个后果:
depositor_token_account
传递,并使用除输入代币和金额之外的任意参数执行成功的存款。 然后,恶意用户可以提交包含他们自己的接收者地址的存款,以窃取存款人资金。 这是可能的,因为 state PDA 作为 authority 和签名者种子 传递给 transfer_checked
调用,当委托给该调用时,将通过 转移验证逻辑。 请注意,这种情况可以通过 Solana 交易中的原生指令批处理来缓解,该批处理通常涉及在一个操作中进行委托和转移,从而降低了抢先交易的可能性,但仍然是可能的。考虑以与 depositor_token_account
相同的方式验证 signer
代币账户,并从签名者代币账户执行转移。
更新: 已在 pull request #971 中解决,commit 为 58f2665。 团队表示:
我们用两个不同的 PDA 替换了一个“state” PDA——一个用于存款,一个用于 fill_relay。 现在,用户必须先明确委托给正确的 PDA,然后任何人才可以提取他们的代币,从而恢复安全第三方存款并消除单个 authority 被滥用以窃取资金的风险。
Pull request #939 移除了 Solana SVM Spoke 中的路径启用功能,允许存款人提供任何输入代币用于任何目标链,而不仅仅是那些启用了路径的代币。 以前,当启用路径时,将创建用于用户存款的金库的关联代币账户 (ATA)。 如果用户尝试存款到禁用的路径,也会生成 DisabledRoute
错误。 由于此时所有路径都将被启用,因此用户可能会尝试提交存款,但未初始化金库 ATA。 这将导致存款交易失败,且没有描述性错误消息。
因此,如果尚未创建金库,则存款人必须自己初始化 PDA,例如,使用 createVault.ts
脚本。 但是,对于大多数用户来说,这不是一个简单的过程,并且可能不清楚为什么存款失败。 这可能导致用户放弃他们的存款尝试并考虑其他桥接解决方案。 更直接的解决方案是在使用 init_if_needed
标志的deposit
期间初始化金库,就像管理员启用路线时 以前完成的那样。
考虑添加 init_if_needed
标志或在 deposit
函数的文档字符串中清楚地记录所需的金库创建过程。
更新: 已在 pull request #957 中解决,commit 为 3b8cf77。 团队表示:
我们在
deposit
方法中将init_if_needed
标志添加到vault
账户。
Pull request #861 重命名了几个函数和类型,以从 Solana spoke 代码库中删除 "V3" 限定符。 但是,重命名不完整:
handle_v3_across_message
函数的 HandleV3AcrossMessage
结构体,以及 HANDLE_V3_ACROSS_MESSAGE_DISCRIMINATOR
常量。nativeDeposit.ts
提到了 "V3" 存款。考虑更新所有提到 "V3" 的函数和注释,以与代码库的其余部分保持一致。
更新: 已在 pull request #964 中部分解决,commit 为 a78241f。 团队表示:
我们通过从 SVM 代码库的其余部分删除 "V3" 的提及来解决此问题。 唯一的例外是
handle_v3_across_message
函数及其相关的结构体和常量,因为 "V3" 概念也保留在 EVM 中,其中AcrossMessageHandler
仍然在其接口中具有handleV3AcrossMessage
。
在整个代码库中,发现了多个误导性文档的实例:
nativeDeposit.ts
的 第 68 行 说明在启用路由时会创建金库 ATA。 但是,pull request #939 中已删除了路由启用。createVault.ts
的 第 1 行 说该脚本由链管理员使用,但任何人都可以使用它在必要时创建金库 PDA。考虑更正上述注释以提高代码库的整体清晰度和可读性。
更新: 已在 pull request #963 中解决,commit 为 24017ac。 团队表示:
我们通过澄清脚本中的注释来解决此问题。
fill_relay
函数接受 _relay_hash
参数,但 FillRelay
结构体约束 将其称为 relay_hash
(没有前导下划线)。
为了避免混淆,在使用 instruction
约束时,请考虑保持一致的变量名。
更新: 已在 pull request #962 中解决,commit 为 9f8a199。 团队表示:
我们通过在使用
instruction
约束时使用相同的变量名来解决此问题。
审查的更改包括对 SVM Spoke 的增量更新。 它们稍微概括了功能并提高了与 EVM Spoke 的一致性。 代码库中没有发现重大问题。
感谢 Risk Labs 团队提供的广泛文档以及他们在整个审计期间的配合。
- 原文链接: blog.openzeppelin.com/sv...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!