L2 Sequencer 与过时预言机价格漏洞

  • lopotras
  • 发布于 2023-06-17 14:15
  • 阅读 15

本文介绍了在Layer-2网络中使用Chainlink预言机价格时,可能由于Sequencer宕机导致的价格过时漏洞。文章详细解释了Optimistic Rollup,Sequencer和Delayed Inbox的概念,以及漏洞产生的原理。最后,文章提供了一个简单的解决方案:在获取预言机数据之前,实施Sequencer Uptime检查。

最近几个月,一个漏洞在针对使用 Chainlink Oracle 价格的协议的审计竞赛中变得非常流行,这些协议将其合约部署在 L2 上——缺少 sequencer 正常运行时间检查。

让我们一步一步地分解它,以确保它非常清楚!

Layer-2s 和 Optimistic Rollups

Layer-2 (L2) 是构建在现有区块链网络(layer-1)之上的解决方案的名称,旨在增强底层协议的功能。layer-2 可以带来的一些潜在改进是更高的交易吞吐量和更小的费用。

为了进一步解释,让我们关注现有的 Ethereum L2 之一——Arbitrum。

为了扩展 Ethereum,Arbitrum 使用所谓的 Optimistic Rollups。这意味着在 Arbitrum 上,交易独立于 L1 发生,但是它们被批量处理并放在 Mainnet 上,假设网络上的一切都按照规则运行。放在 Ethereum 上的交易批次被称为 rollups,你可以把它们想象成检查点。

如果发生违规行为,可以在 L1 上提出异议,并且在欺诈被证实后,无效的声明将被忽略,恶意方将受到经济上的惩罚。

Sequencer

在正常情况下,负责对传入的交易进行排序、向用户发出即时(软)收据并将交易批次提交给底层 L1 的一方被称为 Sequencer

Sequencer 本质上是一个 Arbitrum 全节点,在物理上运行在某个地方,因此它可能会离线。

但不用担心,有一种紧急方式可以将交易提交到 L2 网络,专门为这种情况准备:一个延迟的 Inbox。

延迟 Inbox

为了在 Sequencer 停机的情况下将其交易强制添加到 Arbitrum,用户可以通过 L1 将其 L2 消息提交到延迟的 Inbox 中。这将要求消息在那里停留大约 24 小时,然后才能通过使用 SequencerInboxforceInclusion 方法手动将其包含到 L2 网络中。

现在我们有了必要的背景知识,让我们深入研究这个问题!

Oracle 价格和漏洞

对于在 L2 上使用 Oracle 价格数据而不检查 sequencer 正常运行时间 feed 的协议,可能存在潜在的漏洞,因为通常 建议 这样做。

由于区块链上的 Oracle 是需要通过交易更新的合约,如果 Sequencer 不可用,则特定 L2 上的 Oracle feed 将停止更新并变得陈旧。

如果 Sequencer 的停机时间超过了在紧急程序中提交交易到网络所实施的延迟时间,则从理论上讲,用户可以使用陈旧的价格来执行交易。

下图显示了这种情况:

让我们以 ETH/USD Chainlink Oracle 为例。它有两个触发参数:Deviation threshold(偏差阈值) 和 Heartbeat(心跳)。这意味着如果价格变动至少 0.5% 或最晚每小时(ETH/USD 价格 feed 的心跳),价格 feed 将会更新。

在图中所示的情况下,在 Oracle 更新到版本 x 后,Sequencer 停止工作。ETH/USD 货币对开始经历快速的价格波动,从而触发了价格 feed 更新,但它们没有进入 L2 网络——那里的最新版本仍然是 x

Alice 看到了她的机会,并通过延迟的 inbox 发送了一个交易到 lending protocol(借贷协议),使用 ETH 作为抵押品。她的 ETH 仍将以与 Oracle 版本 x 相关的价格进行估值,而当前版本为 x+2

延迟时间过去了,Sequencer 仍然停止工作,Alice 的交易通过 forceInclusion 被接受,允许她借入比当前 ETH 价格应该借入的更多的资产。

解决方案

幸运的是,解决方案非常简单:在获取 Oracle 数据之前,实施 Sequencer 正常运行时间检查。 Chainlink 在他们的文档中提供了这种检查的示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
// 这是一个示例合约,为了清楚起见,使用硬编码的值。
// 这是一个使用未经审计的代码的示例合约。
// 请勿在生产中使用此代码。

contract DataConsumerWithSequencerCheck {
    AggregatorV2V3Interface internal dataFeed;
    AggregatorV2V3Interface internal sequencerUptimeFeed;

    uint256 private constant GRACE_PERIOD_TIME = 3600;

    error SequencerDown();
    error GracePeriodNotOver();

    /**
     * Network: Optimism Goerli testnet
     * Data Feed: BTC/USD
     * Data Feed address: 0xC16679B963CeB52089aD2d95312A5b85E318e9d2
     * Uptime Feed address: 0x4C4814aa04433e0FB31310379a4D6946D5e1D353
     * For a list of available Sequencer Uptime Feed proxy addresses, see:
     * https://docs.chain.link/docs/data-feeds/l2-sequencer-feeds
     */
    // 网络:Optimism Goerli 测试网
    // 数据 Feed:BTC/USD
    // 数据 Feed 地址:0xC16679B963CeB52089aD2d95312A5b85E318e9d2
    // 正常运行时间 Feed 地址:0x4C4814aa04433e0FB31310379a4D6946D5e1D353
    // 有关可用 Sequencer 正常运行时间 Feed 代理地址的列表,请参见:
    // https://docs.chain.link/docs/data-feeds/l2-sequencer-feeds
    constructor() {
        dataFeed = AggregatorV2V3Interface(
            0xC16679B963CeB52089aD2d95312A5b85E318e9d2
        );
        sequencerUptimeFeed = AggregatorV2V3Interface(
            0x4C4814aa04433e0FB31310379a4D6946D5e1D353
        );
    }

    // Check the sequencer status and return the latest data
    // 检查 sequencer 的状态并返回最新数据
    function getLatestData() public view returns (int) {
        // prettier-ignore
        (
            /*uint80 roundID*/,
            int256 answer,
            uint256 startedAt,
            /*uint256 updatedAt*/,
            /*uint80 answeredInRound*/
        ) = sequencerUptimeFeed.latestRoundData();

        // Answer == 0: Sequencer is up
        // Answer == 1: Sequencer is down
        // Answer == 0:Sequencer 启动
        // Answer == 1:Sequencer 停止
        bool isSequencerUp = answer == 0;
        if (!isSequencerUp) {
            revert SequencerDown();
        }

        // Make sure the grace period has passed after the
        // sequencer is back up.
        // 确保在 sequencer 恢复后,宽限期已过。
        uint256 timeSinceUp = block.timestamp - startedAt;
        if (timeSinceUp <= GRACE_PERIOD_TIME) {
            revert GracePeriodNotOver();
        }

        // prettier-ignore
        (
            /*uint80 roundID*/,
            int data,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = dataFeed.latestRoundData();

        return data;
    }
}

让我们分解一下!

我们可以看到,首先在 constructor 中定义了 2 个数据源:

constructor() {
    dataFeed = AggregatorV2V3Interface(
        0xC16679B963CeB52089aD2d95312A5b85E318e9d2
    );
    sequencerUptimeFeed = AggregatorV2V3Interface(
        0x4C4814aa04433e0FB31310379a4D6946D5e1D353
    );
}

接下来在 getLatestData() 中,首先调用 Sequencer 正常运行时间 feed:

// Check the sequencer status and return the latest data
// 检查 sequencer 的状态并返回最新数据
function getLatestData() public view returns (int) {
    // prettier-ignore
    (
        /*uint80 roundID*/,
        int256 answer,
        uint256 startedAt,
        /*uint256 updatedAt*/,
        /*uint80 answeredInRound*/
    ) = sequencerUptimeFeed.latestRoundData();

如果 Sequencer 停止工作,交易将恢复:

// Answer == 0: Sequencer is up
// Answer == 1: Sequencer is down
// Answer == 0:Sequencer 启动
// Answer == 1:Sequencer 停止
bool isSequencerUp = answer == 0;
if (!isSequencerUp) {
    revert SequencerDown();
}

之后,会检查以确保自从 Sequencer 恢复以来,已经过去了足够的时间来认为 Oracle feed 是最新的:

// Make sure the grace period has passed after the
// sequencer is back up.
// 确保在 sequencer 恢复后,宽限期已过。
uint256 timeSinceUp = block.timestamp - startedAt;
if (timeSinceUp <= GRACE_PERIOD_TIME) {
    revert GracePeriodNotOver();
}

最后,在所有检查都通过后,调用价格 feed 并返回收到的数据:

(
    /*uint80 roundID*/,
    int data,
    /*uint startedAt*/,
    /*uint timeStamp*/,
    /*uint80 answeredInRound*/
) = dataFeed.latestRoundData();

return data;

实施此过程将确保用户无法在 L2 上使用陈旧的 Oracle 价格执行交易,因为它们将在 Sequencer 停机期间恢复。

今天的总结就是这样!

相关资源:

如果你喜欢这个内容,请查看我在 TwitterMedium 上为你准备的其他内容。

下次见!😉

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

0 条评论

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