LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 区块链资产 > Compound开放预言机审计

Compound开放预言机审计

2020-04-30 DeFi传教士 来源:区块链网络


Compound的开发预言机系统是一个协议,目标是基于受信任的报告源提供链上价格预言。这个预言机允许报告者提交资产价格并计算出所有报告样本的中间值,然后提供这个中间值作为资产的官方参考价格。

审计历史和当前范围

在这次审计中我们调查了Compound开放预言机系统仓库的所有Solidity智能合约。审计的提交版本是e7a928334e5e454a88eec38e4ee1be5ee3b13f08。精确的文件在本次范围的有:
* contracts/DelFiPrice.sol
* contracts/OpenOracleData.sol
* contracts/OpenOraclePriceData.sol
* contracts/OpenOracleView.sol

所有链下系统关联到开放预言机在本次审计范围忽略。此外,我们既也不审计预言机与Compound的集成,也不审计生态中任何其他的项目。
我们先前已审计了Compound协议的几个阶段,详细如下。但这是我们对开放预言机系统的第一次审计。
1.Compound公共仓库提交版本f385d71983ae5c5799faae9b2dfea43e5cf75262一些列的Compound智能合约。阅读报告总结。
2.一个补丁引入针对危险管理函数的时间延迟和暂停其他功能的能力。这个补丁反映在Compound公共仓库的提交版本681833a557a282fba5441b7d49edb05153bb28ec。阅读报告。
3.一次核心CToken智能合约的重构,目的是为标的代币有可能在转移代币时抽取手续费(如USDT).这个重构展示在Compound私有仓库的提交版本2535734126c7c26e9bc452f27f45c5408acff71f。这个报告目前未公开。
4.Compound私有仓库的提交版本2535734126c7c26e9bc452f27f45c5408acff71f和公共仓库的提交版本bcf0bc7b00e289f9b661a0ae934626e018188040之间的代码差异。这些变化引入了处理标的ERC20代币的能力,实现了可升级(如DAI)。这个报告目前未公开。
5.Compound公共仓库提交版本bcf0bc7b00e289f9b661a0ae934626e018188040和9ea64ddd166a78b264ba8006f688880085eeed13 之间的差异。审计包含了JumpRateModel智能合约的修改和新加入的两个文件CDaiDelegate.sol以及DAIInterestRateModel.sol。这个报告目前未公开。
6.一份阿尔法版本的Compound治理系统包含了Compound治理代币(COMP)跟核心治理合约一起放在初始提交版本6858417c91921208c0b3ff342b11065c09665b1b,同样还有随后的提交版本f5976a8a1dcf4e14e435e5581bade8ef6b5d38ea。这个报告目前未公开。

高级概览

Compound开放预言机围绕着一团报告者们签发的一系列资产价格数据来处理。这些通过签发的消息包含诸如资产价格和时间戳的信息来获得。预言机校验提交的数据并保存到一个存储映射中,这个映射拥有的结构同时包含资产的价格和时间戳。每个资产的官方参考价格必须是来自每个受信任报告者们价格的中间价格,当这些受信任的报告者的价格发生变化时,将由预言机进行计算。
每个源分别报告的价格通常是可用的并可验证的。价格关联到签发时间的时间戳,如根据当源时间戳表示后来时间时,才会更新到存储。任何人可以提交价格到预言机,然而只有受信任来源的才用于计算资产的中间价格。

接下来按照重要程度展示我们的发现。

严重风险



高风险

[H01]存储数据不一致可能导致不正确的中间价格
DelFiPrice智能合约有个公开的prices映射,有意用来存储每个资产的中间价格。遵循规范,资产通过符号(大写字母的字符串)被引用并且对于的中间价格被存为uint64的值。任何人可以提交价格到链上,但计算中间价格时仅有那些受信任的源签发的数据被带入账户。有两种方式提交资产的价格到链上。
第一种方式通过OpenOraclePriceData智能合约的外部put函数。给定一个签名并关联包含价格和资产符号(除了别的外)的消息,函数首先获取源的消息和签名,然后为这个特殊的源更新资产价格。每个源的价格数据和符号被保留到智能合约存储中的私有data映射。注意通过这种方式的更新的价格不会触发已存在DelFiPrice合约公开prices映射的官方(中间)价格进行重计算。因此,如果这时一个用户从prices映射查询了资产价格,返回的中间价格将不正确。此外,如果用户使用medianPrice函数查询资产价格,返回的中间价格同样不准确,因为它不会与锚定价格进行比较来验证它。
另一种方式通过DelFiPrice的外部postPrices函数来提交价格,这允许在单个交易中报告一种或多种资产的批量价格(有关联的签名)。在内部,postPrices调用了put函数。在这种情况下,假如中间数在安全范围内,postPrices函数也正确地重计算传入symbol参数的每个资产的中间价格,更新相应在prices映射中的条目。然而,如有在symbols数组间引用资产不匹配,资产的价格会被更新,但一些中间价格可能不会重新计算。
根据规范,“无论何时提交发生,官方(中间)价格会重计算”。在当前的实现下并没有严格遵循,留了开放的门让注册的价格和他们的中间数存有潜在的数据不一致。带入到账户报告精确中间价格是系统基本的目的,我们理解这个是个高风险的问题。可以考虑进行必要的修改去严格遵循规范。特别是,需要一个单独可靠的方式去提交价格到链上,一个单独可靠的方式去查询资产的中间价格。

中风险

[M01]在突然极端价格变化情况下潜在的不准确的中间价格
开放预言机报告的“官方”价格由一系列受信任源分别报告的集合再计算中间价格而形成。总所周知,大多数加密相关的资产实际价格会高度不稳定性,在短时间内易遭受突然极端,不可预知的波动。
由于开放预言机报告的价格基于一系列单独价格的中间数,因此预言机需要至少50%的单独的价格才能精确计算中间数。在突然极端的资产价格变化情况下,预言机报告的新的中间价格只反映了50%源报告的新价格。因此,开放预言机可能不是反映突然极端变化情况的及时方式。当以太坊网络高度拥塞,这个行为有时变得越来越有问题,因为报告的新价格的交易将要比通常要长的时间去开采。在高度价格不稳定的时候,依赖于这个开放价格预言机喂价的项目必须意识到这相关的风险,实现必要的逻辑去适应预言机报告价格的潜在延迟。
为更好反映价格变化,可以考虑不仅计算给定资产各自价格的中间数,也可以计算他们的标准差。这有利于预言机价格的透明性,帮助项目查询开放预言机理解到实际的价格分布,因此让它们做出更明智的决定。

[M02]缺少最少样品量要求去实现官方价格的计算
为获得一个资产的官方价格,Compound开放预言机从受信任的源获取所有报告的价格数据,使用DelFiPrice智能合约的medianPrice函数去计算中间价格,最后提供这个中间价格作为资产的价格。
然而,在计算资产的中间价格时,此过程不考虑价格的样本量。当样本容量较低时,可能致使预言机遭受价格操纵攻击(查看[N09]考虑流氓源的注意,需要更多详细的说明)。
可以考虑要么实现一个最小样品量要求或返回样品量连同预言机官方价格,以提高对样本量低到危险程度时的潜在风险的认识。

[M03]函数put允许存入无效签名的数据
OpenOraclePriceData的put函数被用于提交签名的价格数据。它首先通过继承的源函数从给定的签名和消息中获取签名地址,然后用获取的地址(例如源)在相应的源地址和资产符号下去更新价格和时间戳。
继承的source函数使用ecrecover预编译去获取签名地址。如果在获取过程中发生错误,ecrecover将返回零地址(如文档所说的)。因为返回的地址未被put函数校验,这将导致在成功的交易中提交了无效数据。虽然这不会暴露安全风险,因为数据被写入到零地址对应的条目中,但函数应该更早地失败,以便采用更可预测的行为。
可以考虑在继续执行put函数前确保通过source函数返回的地址不为零地址。

[M04]如锚定价格更新不够快导致预言机失败
DelFiPrice智能合约实现了一个安全机制去无视报告,即使来源于受信任的源,可能导致剧烈的预言机报告的官方中间价格变化。这个机制是基于一个锚定价格(由信任的anchor地址报告),去计算中间价格的上下限。如果新的中间价格与锚定价格比率超出了限制,官方的资产中间价格不会被更新。报告锚定价格的地址和锚定价同中间价之间的允许的差值在部署期设置了。
在高度价格不稳定的场景下,受信任的源可以报告资产价格剧烈的变化。因此,新计算的中间价容易掉出锚定价定义可接受的范围。除非锚定价格被受信任anchor地址迅速更新,否则预言机将不会接受新的中间价格,导致预言机失败。
负责报告锚定价格的地址在开放预言机系统扮演了基础的角色,尤其是在价格波动剧烈的时期。虽然这种管理方式的地址超出了本次审计的范围,我们还是强烈建议密切关注价格浮动和动向,因而能快速更新锚定价格记录并确保预言机价格一直能正常更新。

低风险

[L01]注释掉的代码
OpenOracleData智能合约包含了注释掉的代码行,没有给开发者足够原因知道这些行为什么被废弃,因此没有明显给他们提供任何有用的价值。
这些注释行的目的不清晰可能会困扰今后的开发者和外部贡献者,可以考虑从基础代码删除掉它们。如果它们要提供某种类型的接口文档,可以考虑提取它们到分开的文档中,让其包含更深入透彻的解释。

[L02]事件中缺乏索引的参数
DelFiPrice智能合约定义的事件没有参数做了索引。可以考虑索引事件参数避免阻碍指定事件链下服务搜索和过滤的任务。

[L03]未完善的文档字符串
* DelFiPrice智能合约postPrices函数缺失symbols参数的文档。它们应该注明符号期望提交的格式(例如规范中的大写).
* OpenOracleData智能合约应包含提到的开放预言机标准规范的链接文档。
* OpenOraclePriceData智能合约的put函数认为消息参数要指定的方式格式化,但并没有在参数文档字符串中描述。尤其是它没提到message必须包含prices字符串才有效。

[L04]魔幻常量的使用
DelFiPrice智能合约出现了几处魔幻的常量。例如38,39,40和65行,用到了100e16。
可以考虑为每个魔幻常量定义一个固定状态的变量,给它一个清晰,无需说明的名称。对于复杂的值,可以考虑也增加行内注释说明它们是怎么计算的或它们是如何选择的。所有这些将增加可读性,减轻代码的维护。

注意&额外信息

[N01]函数参数编码风格不一致
函数参数的名称中使用了不一致的下划线。虽然有些参数包括下划线和它们名称的结尾,但其他参数不包括。为了提高整体基础代码的可读性,可以考虑遵循一致的编码风格。

[N02]排序价格数组冗余分配
DelFiPrice智能合约medianPrice函数调用了私有sort函数去排序内存中数组postedPrices,分配操作的结果成一个新的sortedPrices数组。在Solidity中,数组是引用类型,因此postedPrices数组在调用了sort函数后被排序。因此为了更高效gas燃料的执行,可以考虑同时移除sort函数的返回语句和不必要分配的sortedPrices数组。

[N03]关于无信任的价格来源的无文档化的系统设计
开放预言机的设计是让任何人通过调用DelFiPrice合约的postPrices函数或OpenOraclePriceData合约的put函数来提交资产价格。然而,只有定义在sources数据,这些受信任来源报告的价格才被带入账户去计算资产的官方中间价格。尽管同时允许其他源价格,但系统的设计放弃不受信任来源无用的报价。虽然这看起来像是系统内在的意图,但一份清晰的说明进行强调(在代码中或外部文档)可以给用户,开发者及审计者能带来益处。

[N04]缺乏状态变量的明确可见性
DelFiPrice智能合约的状态变量anchor,upperBoundAnchorRatio 和 lowerBoundAnchorRatio 含蓄使用默认的可见性。为提高可读性,可以考虑明确声明所有状态变量的可见性。

[N05]命名
* OpenOraclePriceData合约的data映射应重命名为更不需说明的名字,写明什么类型的数据映射存储。类似地,get和put函数可以从无需说明的命名受益。
* OpenOracleView合约的data地址应重命名清晰表示它所引用的合约地址。
* OpenOracleData的source函数应重命名成getSource,getSourceFromMessage或类似的。
* DelFiPrice合约的PriceUpdated事件应重命名为MedianPriceUpdated。
* DelFiPrice合约的prices应该命名为medianPrices。

[N06]声明uint作为uint256
项目代码经常使用uint作为变量类型。例如DelFiPrice.sol合约的36,53,58行。为更显性,所有uint的实例应声明为uint256。

[N07]不必要的转型操作
DelFiPrice.sol合约54行,data状态变量首先被转型到address,再到OpenOraclePriceData类型。然而,data已经存储为OpenOraclePriceData类型。因此,可以考虑移除不必要的转型操作。

[N08]未使用的命名返回变量
DelFiPrice合约medianPrice函数声明一个命名返回变量叫median。已知的它没使用,可以考虑移除这个变量。

[N09]对无赖来源的考虑
根据规范,Compound开放预言机依赖于一伙受信任的源在来产生资产价格放在已知https端。任何人可以查询这些公共的网页端,获取到签名数据用于后续被提交给以太坊智能合约。任何人可以提交链上数据,但仅受信任源签名的数据被考虑用作计算资产的中间价格。
每个源有关联注册链上价格的以太坊地址。在智能合约层没有强制任何方式的源数量。更有趣的是,掌控所有受信任源地址的数组开部署时设置而且后来不能修改。
资产的官方预言机价格是一系列受信任源报告价格的中间数。因此,只要控制50%的源就可无赖地摆布预言机的价格。即使剩下50%的源报错报告资产的真实价格,但中心化源将完全控制预言机的官方价格。不清楚开放预言机信任多少源,但是越低的源数量,越容易导致这种攻击。虽然首先一群恶意的源听起来不太可能,应该考虑的是随着完全依赖预言机项目数量的增加,操控一组源的动机也会增加。如果在Compound注意到前初始受信任的源变无赖去操控价格,所有依赖于这个开放预言机的链上金融系统将遭受严重的金融损失。
Compound团队已意识到无赖来源的潜在风险,根据规范“联合运作基于信任但可验证的原则,如果报告者使用太未来的时间戳或报告太脱离正常的价格,他们会被投票清除到Compound参考价格范围视图外”。在本次审计时没有治理机制用于“投票出局”受信任源。因此,仅有更好源的方式是使用一系列新的源来重部署DelFiPrice智能合约。
为便于快速检测和减轻任何恶意行为,我们强烈建议密切关注链上提交的价格和时间戳,在每次价格更新后跟踪Write事件的触发。此外,可以考虑列出一个响应恢复计划以便在检测到试图破坏开放预言机的情况下迅速采取行动。

总结

没有严重风险但有个高风险问题被发现。一些修改已提出以遵循最佳实践和减少潜在的攻击面。

原文链接:https://blog.openzeppelin.com/compound-open-oracle-audit

最后提醒一下,市场有风险,本文只是个研究,不作为投资建议,请合理控制风险。

点赞就是对传教士最大的鼓励,谢谢支持。

—-

编译者/作者:DeFi传教士

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...