Chainlink 数据反馈安全

  • Ackee
  • 发布于 2023-08-08 10:37
  • 阅读 16

本文全面介绍了Chainlink数据Feed,重点关注安全性,主要面向EVM和Solidity。文章分为三个部分:Feed的架构和链下聚合协议、信任模型以及与数据Feed相关的常见问题。详细介绍了Chainlink如何通过链下聚合协议解决预言机问题,并深入探讨了数据Feed的信任模型以及各种潜在问题,并提供了避免这些问题的建议和代码示例。

本文全面地介绍了 Chainlink 数据feed, 重点关注安全性。本文主要针对 EVMSolidity,并包含 三个主要部分:

  1. Data feed和链下聚合协议的架构
  2. 信任模型
  3. 常见的数据feed相关问题

首先,我们介绍 Chainlink 解决的问题 以及 如何 解决这些问题:解释链下聚合协议的核心思想,并描述核心合约。然后,我们讨论 data feed的信任模型。这将帮助你评估将data feed集成到你的协议中的风险程度。最后,也是最重要的一点,我们讨论 与data feed相关的问题:我们提供解释、代码示例以及关于如何避免这些问题的建议。

架构 & 链下聚合

动机

在智能合约执行期间,我们无法调用外部 API,因为我们需要所有节点执行的确定性。因此,从智能合约访问链下数据是有问题的。因此,我们需要 EOA 将数据发布到链上。

我们需要三件事才能将链下数据发布到链上:

  1. 链下数据feed的独立性和质量
  2. 将数据发布到链上的实体的去中心化
  3. 数据的新鲜度

Chainlink 试图通过其链下聚合协议(OCR)来实现这一点。

链下报告协议

OCR 是围绕 去中心化预言机网络 构建的协议,该网络是(希望)独立的 节点网络。这些节点监控 多个链下feed 以获取有关当前价格的信息。作为 OCR 的一部分,会选择一个领导者。领导者聚合 来自其他参与者的 报告,然后向链上发送交易。

如果领导者 行为不端他将被罚没(slashed)。该报告被发送到 Chainlink 合约。在那里,读取报告,验证签名,并取价格的中位数:

int192 median = r.observations[r.observations.length/2];
require(minAnswer <= median && median <= maxAnswer, "median is out of min-max range");
r.hotVars.latestAggregatorRoundId++;
s_transmissions[r.hotVars.latestAggregatorRoundId] =
Transmission(median, uint64(block.timestamp));

Data Feed 合约的架构

每个feed(例如,BTC/ETH、ETH/USD,...)都有两个主要的对应合约——一个 代理(proxy) 和一个 聚合器(aggregator)。如果你从 Chainlink dashboard 复制了一个data feed地址,你将获得 Proxy 的地址。然后,Proxy 指向实现——聚合器。

这是来自 文档 的图表:

Proxy 允许协议 可升级。然后,聚合器处理所有报告逻辑。

Proxy (代理)

对于消费者合约,Proxy 公开了 AggregatorV3Interface。这允许消费者 检索价格其他元数据

它还允许 升级聚合器合约(访问控制在信任模型中提到)。在每次升级期间,phaseId 变量会递增。使用 phase,我们甚至可以查询历史聚合器,从而查询历史数据。

Aggregator (聚合器)

AccessControlledOffchainAggregator 是处理价格更新的合约。在两种情况下更新价格:

  • 心跳阈值触发(定期更新数据feed)
  • 偏差阈值触发(链下值偏离至少阈值,因此必须更新)

一次价格更新定义一个轮次(round)。每次更新时,我们都会递增 roundId 变量。每次更新都会获取 block.timestamp 并将其设置为feed的更新时间。此时间戳是在调用 latestRoundData() 时作为 startedAt、updatedAt 返回的值。

AggregatorV3Interface

大多数协议将使用 AggregatorV3Interface 与 Data Feed 交互。API 在 文档 中有详细描述。我们将专门关注 latestRoundData() 函数。

function latestRoundData() external view
returns (
  uint80 roundId,
  int256 answer,
  uint256 startedAt,
  uint256 updatedAt,
  uint80 answeredInRound
)

roundIdansweredInRound 包含相同的值,即先前描述的最新轮次。answer 是价格,startedAtupdatedAt 再次包含相同的值,并对应于先前描述的轮次的时间戳。

变量包含相同的值可能令人惊讶,请参阅 代码 以获取证明。返回相同的值是因为聚合器已升级,但 Chainlink 保留了 API(即,以前的版本行为不同)。

我们甚至编写了一个测试来验证这一点:

#eth mainnet addrs

feeds = {
"usdt/eth" : Address("0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46"),
"usdc/eth" : Address("0x986b5E1e1755e3C2440e960477f25201B0a8bbD4"),
"btc/eth" : Address("0xdeb288F737066589598e9214E782fa5A8eD689e8"),
"eth/btc" : Address("0xAc559F25B1619171CbC396a50854A3240b6A4e99"),
"eth/usd" : Address("0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"),
"ampl/usd" : Address("0xe20CA8D7546932360e37E9D72c1a47334af57706"),
"matic/usd" : Address("0x7bAC85A8a13A4BcD8abb3eB7d6b4d632c5a57676"),
"ftm/eth" : Address("0x2DE7E4a9488488e0058B95854CC2f7955B35dC9b"),
}

@default_chain.connect(fork=" https://eth-mainnet.g.alchemy.com/v2/top_secret")
def test_get_data():
  for type, addr in feeds.items():
    roundId, answer, startedAt, updatedAt, answeredInRound = AggregatorV3Interface(addr).latestRoundData()
    assert roundId == answeredInRound
    assert startedAt == updatedAt

这里要讨论的最后一个重要的事情是 小数位数(decimals)。每对都具有表示价格的小数位数(正如我们从例如 ERC20 中所知的小数位数)。有些feed有 8 个小数位,有些有 18 个。每个数据feed有多少个小数位没有明确的规则;请参阅我们从主网检索的对的小数位数列表:

    feed usdt/eth, decimals: 18

    feed usdc/eth, decimals: 18

    feed btc/eth, decimals: 18

    feed eth/btc, decimals: 8

    feed eth/usd, decimals: 8

    feed ampl/usd, decimals: 18

    feed matic/usd, decimals: 8

    feed ftm/eth, decimals: 18

以 ETH 计价的feed似乎有 18 个小数位,而非以 ETH 计价的feed有 8 个小数位。但是,与此同时,我们有 AMPL/USD feed,它有 18 个小数位,这打破了规则。

信任模型

本节将讨论协议和安全研究人员在与 Data Feed 交互时必须考虑的 信任假设

首先,Data Feed 是 可升级的。Chainlink 运营的多重签名拥有每个feed的 Proxy 合约。他们使用 Safe 多重签名,我们通过从 Proxy 中提取所有者地址并在 Etherscan 上 检查 它来找到它。多重签名有 9 个所有者,其阈值为 4,请参阅 evm.storage 上的 合约。这意味着只需要 4 个签名就可以任意更新任何feed。

每次升级都可能失败,或者可能包含对特定地址的审查。

此外,还必须信任 OCR 协议的节点。据推测,它们是去中心化和独立的,但它们的数量远低于以太坊节点的数量,因此可能更容易操纵。

此外,即使节点是诚实的,Data Feed 也可能提供不正确的值。过去发生过这种情况,请参阅 事后分析

问题

在本部分中,我们将描述 与 Data Feed 交互时 可能出现的所有 问题。我们将主要关注最新的 V3 版本。

1. 过时的 API

Data Feed 合约公开的某些函数已弃用,不应再使用。请参阅完整的 列表。这些函数可能会在未来的升级中删除,因此使用它们是不安全的。

这些函数是:

  • getAnswer,
  • getTimestamp,
  • 重要的是 latestAnswer,
  • latestRound,
  • latestTimestamp.

2. 接收过时的数据

每个 Data Feed 都可以返回过时的数据,例如,由于升级中的错误、OCR 节点无法达成共识等。但是,AggregatorV3Interface 提供了足够的信息来检查数据是否新鲜。

latestRoundData 函数返回的值之一是 updatedAt。此外,我们可以访问 block.timestamp。我们通过减去这两个值来获得自上次更新以来的时间。我们可以设置此差异的最大阈值。如果大于此值,则数据已过时,我们回滚。

3. 过宽的新鲜度间隔

正如我们在上一个问题中解释的那样,我们需要 新鲜度检查。但是,如果我们使用过宽的新鲜度阈值,那么我们将无法及时检测到过时的价格。

间隔不应过宽。理想情况下,它应该与给定feed的心跳间隔相关。

4. 过窄的新鲜度间隔

此外,我们不能决定使用 非常窄 的新鲜度间隔。如果决定使用比心跳周期更短的新鲜度间隔,那么对feed的一些查询将 错误地回滚。因此,用户将受到 DoS 攻击。

5. 在 try/catch 块中读取价格

正如我们所解释的,合约是可升级的,因此由于 错误审查,调用可能会回滚。在这种情况下,协议可能会卡住,用户会受到 DoS 攻击。

如果我们使用 try/catch 读取价格,我们可以从回滚中恢复,例如,尝试其他预言机,例如 TWAP。

6. L2 排序器停机

在某些 L2(如 Arbitrum 或 Optimism)上,我们有一个称为 排序器(sequencer) 的实体。排序器是一个节点,它接收用户的交易并将其批量发布到 L1。目前,几乎没有协议提供去中心化的排序,因此它们的停机可能性相对较高。

与此同时,这些 L2 提供了一个选项,可以通过 L1 合约直接与它们交互,而无需排序器的干预。

Chainlink 提供了一个feed来 检查排序器的停机时间,该feed应该通过 L1 进行更新;请参阅 文档

此外,Chainlink 通过排序器更新 L2 Data Feed,因此如果排序器关闭,则价格不会更新。

我们没有找到确凿的证据来证明这一点,但 Chainlink 本身在 文档 中建议在查询数据feed时检查排序器的正常运行时间,这强烈暗示了这一点。如果 data feed 不是通过排序器更新的,则不会出现此建议。

假设排序器已离线,但用户可以绕过它并通过 L1 直接发送他们的交易。此外,假设一个协议集成了 ETH/USD feed,并且 ETH 的价格大幅下跌。恶意用户看到了这一点,并通过 L1 合约发送交易。该交易是在尚未更新的价格上下文中处理的,因此,用户受益于仍然较高的价格。

请参阅有关此问题的更详细的 博客文章

7. 硬编码 Data Feed 地址

使用 硬编码的数据feed地址是危险的,因为现在合约已部署在多个链上。在不同的链上,feed可以有不同的地址。

如果未针对每次部署修改源代码,则协议可以使用无效的feed地址。

8. 错误的小数位数解释

有些feed有 8 个小数位,有些有 18 个,并且每个feed有多少个小数位没有明确的规则。有必要查询给定feed的 decimals 函数,或者在部署之前检索信息。

不应在未首先验证的情况下假设给定的feed具有 x 个小数位。使用关于小数位数的错误假设可能导致严重的会计错误。

9. 备用预言机

如果对feed的调用回滚(例如,在升级失败后),拥有备用预言机(如 TWAP)将允许协议继续运行,而不会对用户造成 DoS 攻击。

作为审计员,我们应该仔细验证备用预言机,因为相应的 逻辑可能未经过充分测试,因为它们的使用可能被认为是低概率的情况。

10. 低质量的feed

并非所有feed都是相同的。有些质量可能较低,或者可能具有已弃用的状态。幸运的是,Chainlink 为每个feed提供了 状态。建议在使用前验证feed的状态。

11. 基本价格验证

大量协议使用以下 require 来验证价格:require(answer > 0, “无效价格”);。这样,可以实现对价格的基本健全性检查。协议可以添加其他要求,即价格位于一些 合理的 <min, max> 区间内,这可用于避免在价格突然崩溃期间(例如,脱钩)使用该协议。

结论

Chainlink 预言机是 使用最广泛的预言机。它们表现出极大的 可靠性,但该协议 并非完全去中心化,并且具有一些 信任假设

集成预言机的协议可能会遇到各种问题,例如使用 已弃用的 API价格数据验证不足

在此处阅读更多我们的研究 这里

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

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation