如何安全的使用 Chainlink 预言机价格

  • Ashton
  • 更新于 2023-04-18 19:59
  • 阅读 2272

价格数据的可靠性是很多DeFi协议可靠运行的基石,Chainlink作为预言机头部平台,一般来说它提供的价格数据是非常可靠的。但是在我们这个行业从来都不缺二般情况...

价格数据的可靠性是很多 DeFi 协议可靠运行的基石,Chainlink 作为预言机头部平台,一般来说它提供的价格数据是非常可靠的。但是在我们这个行业从来都不缺二般情况...

0x01 直接从 Chainlink 读取价格数据

官方给的示例是非常简单直接的。下面的代码就是原封不动的从官方文档拷贝过来的。

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

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

contract PriceConsumerV3 {
    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Sepolia
     * Aggregator: BTC/USD
     * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
     */
    constructor() {
        priceFeed = AggregatorV3Interface(
            0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
        );
    }

    /**
     * Returns the latest price.
     */
    function getLatestPrice() public view returns (int) {
        // prettier-ignore
        (
            /* uint80 roundID */,
            int price,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = priceFeed.latestRoundData();
        return price;
    }
}

这个代码直接通过调用 AggregatorV3Interface 的 latestRoundData 方法来获取价格数据,并且完全忽略掉了 latestRoundData 方法返回的其它数据。

估计很多在正式环境部署的 DeFi 协议也是这样使用 Chainlink 价格数据的,这样会有什么问题呢?最大的问题就是过于依赖 Chainlink 单一平台,并假定 Chainlink 永远不会有问题。当初币安链上最大的借贷应用 Venus 估计也是这样假设的,于是在上次 Luna 事件中出现了安全事故,我在该文也做过相应分析。Luna 事件暴露出来的预言机问题 Chainlink 很快就修复掉了,但谁又能保证没有其它 Chainlink 没有完全考虑到的情况呢?Chainlink 那边价格出现的一个小问题,对于依赖的 DeFi 应用可能都会带来灭顶之灾。

0x02 为 Chainlink 预言机加上容错机制

  1. 对 Chainlink 返回的数据进行完整性检查 Chainlink 返回的数据可不是只有价格,还有与价格相关的上下文信息:

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

    roundId: Chainlink 每次更新价格都是一个 round(轮次),round id 会加 1,正常情况下 roundId 是个大于 0 的数字。 answer: 价格数据,对于币种价格来说,正常情况下是个大于 0 的数字 startedAt: 该轮报价开始时间 updatedAt: 该轮报价信息更新时间 answeredInRound: 现在已经不推荐使用 我们应该对这些返回值做基本的完整性检查:

    if (
    roundId != 0 &&
    answer > 0 &&
    updatedAt != 0 && 
    updatedAt <= block.timestamp
    ) {
    // 通过检查
    } else {
    // 未通过检查
    }
  2. 对数据的实效性检查

    if (block.timestamp - updatedAt <= TIMEOUT) {
    // 通过检查
    } else {
    // 未通过检查
    }

    TIMEOUT 是我们根据应用对数据的实时性要求自定义的一个时间,比如 30 分钟

  3. 记下来上次通过检查的报价并计算价格偏差 lastGoodPrice = answer; if (abs(answer - lastGoodPrice) / lastGoodPrice <= ACCETABLE_DEVIATON) { // 通过检查 } else { // 未通过检查 }

  4. 如果价格未通过检查,启动容错机制,这里可能需要准备一个备用预言机,当 Chainlink 预言机不可用时,可以切换到备用预言机来读取价格。 对于备用预言机,可以用 Uniswap TWAP,可以用另一个可靠性还可以的第三方预言机,也可以用自建预言机。如果备用预言机也不好使了,可以返回上次报价数据并通知应用预言机处于不可用状态,这样对应用来说,相当于价格停止更新了,可以根据场景做针对性的处理。比如对于借贷应用来说,价格停更一段时间后再重新更新时,对清算操作做一定的缓冲处理,避免预言机价格剧烈波动带来的不合理清算。

0x03 在 L2 上使用 Chainlink 价格

今年开始基于 rollup 的 L2 开始变得非常火爆,L2 极大的提高了以太坊的交易处理能力。但有一点儿我们要注意,就是 L2 排序器当机的概率要远远高于以太坊网络。

假设我们在 L2 上部署了一个借贷协议,有一天该 L2 排序器出现故障,交易无法处理,预言机价格无法更新。若干小时之后,当排序器重新上线并且预言机更新它们的价格时,停机期间发生的所有价格变动都会立即起作用。如果这些价格变动幅度很大,就可能会造成非常大的混乱。借款人会急于保住头寸,而清算人会急于清算借款人。由于清算主要由机器人处理,借款人可能会面临被大规模清算的风险。

这对借款人是不公平的,因为如果不是因为 L2 故障,借款人可以有足够的时间来还款或补充抵押物。这时候如果我们的借贷协议能够检测到 L2 故障,并在 L2 恢复后给借款人一个处理借款头寸的缓冲期,就可以避免此类问题了。

Chainlink 专门提供了相关服务 来检测 L2 的状态。

AAVE V3 也做了专门处理 可以作为实现参考。

点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ashton
Ashton
0x53b3...c54F
专注于 EVM 和比特币生态的区块链开发者