托管乐观预言机审计

这是一篇OpenZeppelin对UMA协议的ManagedOptimisticOracleV2代码仓库进行安全审计的报告。 审计发现了一些低风险问题,包括货币默认不接受债券、错误触发事件、缺少白名单合约的接口验证、缺少测试套件等。同时,报告也提出了代码改进建议,并指出了文档中存在的一些不准确之处。UMA团队已解决或确认了大部分问题。

目录

概要

TypeOraclesTimelineFrom 2025-07-29To 2025-07-31LanguagesSolidityTotal Issues15 (14 resolved)Critical Severity Issues0 (0 resolved)High Severity Issues0 (0 resolved)Medium Severity Issues0 (0 resolved)Low Severity Issues6 (6 resolved)Notes & Additional Information7 (6 resolved)Client Reported Issues2 (2 resolved)

范围

OpenZeppelin 审计了 UMAprotocol/managed-oracle 仓库,提交版本为 fc03083。经过差异审计的文件与 UMAprotocol/protocol 仓库中的 6a23be1 提交版本中的对应文件进行了比较。

以下文件在审计范围内:

 src
├── common
│   ├── implementation
│   │   ├── AddressWhitelist.sol (diff)
│   │   └── DisableableAddressWhitelist.sol
│   └── interfaces
│       └── DisableableAddressWhitelistInterface.sol
└── optimistic-oracle-v2
    └── implementation
        ├── ManagedOptimisticOracleV2.sol
        └── OptimisticOracleV2.sol (diff)

经过审计的代码库的最终状态,包括所有已实施的解决方案,反映在提交版本 5b33321 中。

系统概述

UMA 乐观预言机协议旨在高效、快速地解决链上数据请求。它采用“提议和争议”模型,即提出的值在设定的“活跃”期后被接受为真,前提是没有人对其提出挑战。提议者抵押一定数量的债券以提交价格,如果另一方认为该价格不正确,他们也可以抵押一定数量的债券来对其提出异议。

未经争议的提议将以乐观的方式解决,奖励提议者,而有争议的价格将升级到 UMA 的数据验证机制 (DVM) 以进行最终解决,然后由 DVM 确定获胜者并相应地分配债券和奖励。ManagedOptimisticOracleV2 对此核心协议进行了扩展,引入了一个管理控制层,允许指定的管理者为特定请求自定义参数,如债券大小和活跃期。

新增合约

ManagedOptimisticOracleV2 合约实施了一个多层级的、基于角色的访问控制系统来管理预言机的操作参数。在最高层级,DEFAULT_ADMIN_ROLE 拥有升级合约实现的唯一权限。在此之下,REGULAR_ADMIN 角色负责管理系统范围内的设置,包括配置默认的提议者和请求者白名单,以及设置约束,如最大债券和最小活跃期。该合约还引入了一个 REQUEST_MANAGER 角色,该角色由 REGULAR_ADMIN 管理。此角色能够将自定义配置应用于单个价格请求,允许它通过设置特定的债券金额、活跃期和每个请求的提议者白名单来覆盖默认参数。

DisableableAddressWhitelist 合约通过引入一个切换其强制执行的机制来扩展基础的 AddressWhitelist 合约。当禁用强制执行时,所有地址都被视为已列入白名单,有效地绕过了底层白名单检查。

差异变更

更新了AddressWhitelist 合约,以确保与最新的 OpenZeppelin 版本兼容,并提高其可扩展性,以便继承合约。重构了 OptimisticOracleV2 合约,以支持使用 UUPS 代理模式进行升级,并且其几个函数已变为虚拟函数,以便允许像 ManagedOptimisticOracleV2 这样的子合约进行扩展。此重构还通过用 Solidity 的原生溢出保护替换 SafeMath 库来实现了代码现代化。

安全模型和信任假设

如上所述,ManagedOptimisticOracleV2 合约涉及多个角色,并且该系统的安全模型依赖于分层访问控制以及关于其管理员和配置的一组关键信任假设。负责这些角色的实体应遵循帐户安全性的最佳实践,包括强大的密钥管理以及在适用的情况下进行多因素身份验证。协议的完整性和正常运行取决于这些假设始终成立。

  • 假设持有 DEFAULT_ADMIN_ROLEREGULAR_ADMINREQUEST_MANAGER 角色的实体以协议及其用户的最大利益行事。AddressWhitelistDisableableAddressWhitelist 合约的所有者被信任能够适当地管理白名单条目和强制执行状态。
  • requesterWhitelistdefaultProposerWhitelist 和任何 customProposerWhitelists 的正确设置和持续维护对于控制访问和参与至关重要。
  • 假设 DisableableAddressWhitelist 机制的 isEnforced 状态得到正确管理,并且符合所需的访问策略。
  • ManagedOptimisticOracleV2 合约中使用的白名单预计与 DisableableAddressWhitelist 合约的实现相匹配。
  • 提议者和争议者必须信任请求者的链上合约能够诚实地处理由价格请求生命周期中的状态转换触发的回调。这些回调可以使请求者能够拒绝服务 (DoS) 转换,如争议或结算。

低风险

默认情况下,币种不接受保证金

ManagedOptimisticOracleV2 合约引入了按币种定义的最大保证金金额,这些金额存储在 maximumBonds 映射中。这些限制可以在初始化期间由合约初始化器设置,也可以稍后由授权管理员设置。如果未为给定的币种显式设置最大保证金,则该映射会返回其默认值零。因此,传递给合约的任何非零的的保证金都会通过 _validateBond() 函数的验证,从而有效地阻止了该币种的使用,直到完成配置。

如果此行为是故意的,请考虑在代码和面向用户的文档中明确记录它。或者,不要默认为拒绝,而是考虑更改逻辑,使得未设置最大保证金的的币种没有强制限制。

更新:pull request #22 的提交版本 ab33c3e 中已解决。UMA 团队添加了两条注释,以阐明在未配置保证金范围时默认拒绝币种的情况。

RequestManagerAddedRequestManagerRemoved 事件可能会被错误地触发

ManagedOptimisticOracleV2 合约addRequestManagerremoveRequestManager 函数负责分配和撤销 REQUEST_MANAGER 角色,并通过 AccessControlUpgradeablegrantRolerevokeRole 函数来实现。

这些函数还会无条件地触发 RequestManagerAddedRequestManagerRemoved 事件,而不管角色是否实际上被授予或撤销。这意味着,如果为已经具有 REQUEST_MANAGER 角色的地址调用 addRequestManager,或者为没有该角色的地址调用 removeRequestManager,则仍将触发相应的事件,这可能会误导依赖这些事件来跟踪状态更改的链下服务。此外,考虑到 AccessControlUpgradeable 合约已经触发了 RoleGrantedRoleRevoked 事件,因此这些事件是多余的。

考虑完全删除 RequestManagerAddedRequestManagerRemoved 事件。

更新:pull request #23 的提交版本 46774d9 中已解决。

缺少白名单合约的接口验证

ManagedOptimisticOracleV2 合约包含多个接受外部合约地址的函数,这些地址应实现 DisableableAddressWhitelistInterface,例如 setDefaultProposerWhitelistsetRequesterWhitelistrequestManagerSetProposerWhitelist 函数。但是,这些函数不验证提供的地址是否实际实现了预期的接口,如果传递了无效的合约,这可能导致运行时错误或不正确的行为。

考虑在 AddressWhitelistDisableableAddressWhitelist 合约中支持 ERC-165 标准,以促进安全和标准化的接口检测。此外,考虑通过运行时检查强制执行接口遵从性,以确保提供的白名单合约实现了 DisableableAddressWhitelistInterface

更新:pull request #17 的提交版本 7bb517c 中已解决。DisableableAddressWhitelist 合约已替换为 DisabledAddressWhitelist,从而将实现从所有者控制的列表强制执行切换更改为可以在 ManagedOptimisticOracleV2 合约中设置的硬编码实现。现在,DisabledAddressWhitelistAddressWhitelist 合约都扩展了 ERC165,而 ManagedOptimisticOracleV2 在每次设置白名单时都会执行运行时检查。

缺少测试套件

在范围内的合约,包括 OptimisticOracleV2ManagedOptimisticOracleV2,实现了一个用于管理乐观价格请求的复杂系统。此系统涉及复杂的状态转换、以债券形式存在的财务风险以及多层级的访问控制模型。因此,依赖协议的安全性至关重要地依赖于此逻辑的正确性。

在整个仓库中,缺少范围内的合约的专用测试套件。如果没有自动化测试,则没有正式的、可重复的过程来验证合约是否按预期运行。验证复杂的状态机、访问控制逻辑和财务计算的正确性留给了手动审查,这容易出错且耗时。此外,缺少回归测试套件意味着将来对代码库的更改可能会在不知不觉中引入关键漏洞而未被检测到。

考虑实现一个全面的测试套件,该套件涵盖范围内的合约的功能。该套件应包括针对各个函数的单元测试、用于验证合约之间交互的集成测试以及用于在实际的链上环境中模拟行为的 Fork 测试。这些测试应验证预期的结果、边缘情况的正确处理以及对访问控制模型的遵守。建立强大的测试覆盖率将显着提高系统的可维护性和安全态势。

更新:pull request #25 的提交版本 31b47fd 中已解决。添加了 ManagedOptimisticOracleV2 合约的测试。

有问题的白名单实现

AddressWhitelist 合约 提供了用于管理批准地址列表的功能。它使用一个映射来存储地址的状态,并使用一个单独的动态数组 whitelistIndices 来存储添加的每个地址。getWhitelist 函数会迭代此数组以返回当前活动的成员列表。

当前白名单的实现有以下几个缺点:

  • removeFromWhitelist 函数 仅将地址的状态更改为 Out,但不会将其从 whitelistIndices 数组中删除。这种“软删除”会导致数组无限增长,从而导致枚举的 Gas 成本增加。
  • getWhitelist 函数 会迭代这个不断增长的数组两次,这使得它效率极低,并且如果列表变得太大,则会产生潜在的 Out-of-Gas 情况,如注释中所述。
  • 与只有两个状态(OutIn)相比,使用三种状态(NoneOutIn)不仅是多余的,而且还引入了不一致的状态。首先,如果一个地址从未被添加过,但却被从白名单中删除,则其状态将设置为 Out。其次,如果该地址随后被添加到白名单中,则该地址将 不会添加到 whitelistIndices,但仍标记为 In 并触发一个事件,尽管该地址不会包含在 getWhitelist 中。
  • 由于该项目已经使用了 OpenZeppelin Contracts 库,因此这种自定义实现也引入了冗余代码,因为 EnumerableSet 库中已经提供了一个更强大且 Gas 效率更高的替代方案。

考虑重构 AddressWhitelist 合约以使用 OpenZeppelin 提供的 EnumerableSet.AddressSet 库。请注意,此采用将转移存储布局。因此,应谨慎处理依赖于 AddressWhitelist 的可升级合约。

更新:pull request #24 的提交版本 81bd067 中已解决。

最小活跃时间可以设置在有效范围之外

ManagedOptimisticOracleV2 合约的 setMinimumLiveness 函数 允许管理员设置新的全局最小活跃时间,而无需验证该值是否落在底层 OptimisticOracleV2 强制执行的范围内。如果 minimumLiveness 设置为高于 OptimisticOracleV2 接受的最大值,则 请求者请求管理器 后续尝试设置自定义活跃时间值的操作将失败,从而导致该功能的 DoS。

虽然这种情况不太可能在实践中发生,因为它需要来自特权管理员的错误配置调用,但可以通过添加验证来完全缓解风险。因此,考虑更新 _setMinimumLiveness 函数以强制执行新值是否符合 OptimisticOracleV2 接受的范围,从而防止任何意外的错误配置。

更新:pull request #7 的提交版本 bd0fe60 中已解决。

备注 & 补充信息

使用存储空隙进行可升级性

<code>OptimisticOracleV2.sol</code> 合约 被设计为可升级的,并且当前使用存储空隙模式 ( uint256[998] private __gap;) 来为将来的状态变量预留空间。这是一种广泛使用的技术,用于防止父合约使用新变量升级时子合约中的存储冲突。

但是,虽然功能性很好,但是存储空隙模式是一种手动且有些不透明的管理可升级性的方法。它没有为如何组织存储提供清晰、结构化的方法,尤其是在具有复杂继承关系的合约中。这会使开发人员和审计人员难以推理出整体存储布局。

考虑采用 EIP-7201 中定义的命名空间存储模式。此标准为管理可升级合约中的存储布局提供了一个稳健而明确的约定。通过将相关的状态变量分组到结构中,并根据命名空间 ID 为它们分配一个唯一的、确定性的存储位置,EIP-7201 使存储布局模块化且易于理解。采用此标准将提高合约可升级性策略的长期可维护性和开发人员的人体工程学。

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

_这是一个非常复杂的更改,并且会破坏已部署的合约(自上周以来)的存储假设,这些合约仍在改用 __gap。感谢你提出这一点,我们将考虑在未来的合约中使用此模式。_

view 函数上不一致的重入保护使用

在整个 OptimisticOracleV2 合约中,external view 函数(如 getRequestgetState)都受到 nonReentrantView 修改器 的保护。此修改器可防止在执行更改状态的函数期间将这些函数回调到合约中,从而确保它们无法从临时不一致的状态中读取数据。

ManagedOptimisticOracleV2 合约中,引入了新的 external view 函数,即 getCustomProposerWhitelistgetProposerWhitelistWithEnforcementStatus。但是,这些函数未使用 nonReentrantView 修改器。这与父合约已建立的安全模式形成了不一致。虽然由于其他地方进行的健全状态检查,此遗漏没有发现直接的漏洞利用,但它代表了与父合约的偏差。

考虑将 nonReentrantView 修改器添加到 ManagedOptimisticOracleV2 中的所有新的 external view 函数。这将强制执行整个合约系统中一致的安全态势,提高开发人员和审计人员的清晰度,并加强合约以防止可能由未来集成产生的潜在重入向量。

更新:pull request #28 的提交版本 77b6f82 中已解决。

代码改进机会

在整个代码库中,发现了多个代码改进机会:

考虑解决已识别的实例,以提高代码库的质量和可维护性。

更新: 已在 pull request #27 的 commit dc580dd 中解决。

命名建议

在整个代码库中,发现了多个改进命名的机会:

为了提高代码库的可读性,请考虑解决上述建议。

更新: 已在 pull request #16 的 commit acba673pull request #5 的 commit f747326 中解决。 团队表示:

由于历史原因,我们希望坚持使用 whitelist 命名。 许多系统都使用 whitelist 命名。 我们将来会考虑重命名。

使用自定义错误

从 Solidity 版本 0.8.4 开始,自定义错误提供了一种更简洁且更经济高效的方式来向用户解释操作失败的原因。

OptimisticOracleV2ManagedOptimisticOracleV2 合约使用 requirerevert 消息。

为了简洁和节省 gas,请考虑使用自定义错误替换 requirerevert 消息。

更新: 已在 pull request #8 的 commit 7d085a8 中解决。

缺少安全联系方式

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

OptimisticOracleV2ManagedOptimisticOracleV2AddressWhitelist,和 DisableableAddressWhitelist 合约没有安全联系方式。

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

更新: 已在 pull request #10 的 commit 210c517 中解决。

函数可见性过度宽松

在整个代码库中,发现了多个函数可见性不必要地宽松的实例:

ManagedOptimisticOracleV2 中:

OptimisticOracleV2 中:

为了更好地传达函数的预期用途,请考虑将函数的可见性更改为仅在需要时才允许。 否则,请考虑记录合同预计将由第三方开发人员扩展。

更新: 已在 pull request #26 的 commit ce47fb4 中解决。

客户端报告

默认情况下不执行 DisableableAddressWhitelist

DisableableAddressWhitelist 合约 扩展了 AddressWhitelist 合约 并引入了 [isEnforced 标志](https://github.com/UMAprotocol/managed-oracle/blob/fc03083eca91c880efa8918c6d9532af9362f00d/src/common/implementation/DisableableAddressWhitelist.sol#L12) 以控制 whitelist 逻辑是否处于活动状态。 默认情况下,此标志在构造期间未显式设置,因此默认为 false,这意味着不强制执行 whitelist,并且任何地址都将通过检查。 这使得所有者有可能错误地认为 whitelist 已激活,而没有显式启用它。

考虑修改 DisableableAddressWhitelist 合约以将 isEnforced 标志默认设置为 true,从而确保除非明确禁用,否则强制执行 whitelist 行为。

更新: 已在 pull request #6 的 commit 2a816e1 中解决。 UMA 团队表示:

不再相关,因为我们从 DisableableAddressWhitelist 转移到两个单独的合约:DisabledAddressWhitelist + AddressWhiteList,它们具有与其名称一致的明确行为

误导性或不完整的文档

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

  • getCustomProposerWhitelist 函数的 @return NatSpec 应更改为 @return DisableableAddressWhitelistInterface,按照实现。
  • getManagedRequestId 函数的 @dev NatSpec 不正确。

考虑解决上述实例以提高代码库的清晰度。

更新: 已在 pull request #9 的 commit b8a7ea2 中解决。 随着 DisableableAddressWhitelistInterface 的删除,第一个建议不再适用。

结论

本报告总结了对托管 oracle 协议为期三天的审计。 我们的分析表明,该协议的设计和实现从根本上是合理的。 我们已经确定了可以进行代码改进的几个领域,这些领域已在本报告中详细说明为发现。 这些建议旨在增强现有代码库的稳健性和效率。

该代码库具有良好的注释和完整的文档,这极大地促进了审核过程。 我们还要赞扬 UMA 团队在进行内部审计方面采取的积极措施,该审计已经确定了我们提出的几个相同的要点。 很高兴与他们合作,我们感谢他们对安全和代码质量的承诺。

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

0 条评论

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