
价格预言机的使用总结(一):Chainlink篇
前言
价格预言机已经成为了 DeFi 中不可获取的基础设施,很多 DeFi 应用都需要从价格预言机来获取稳定可信的价格数据,包括借贷协议 Compound、AAVE、Liquity,也包括衍生品交易所 dYdX、PERP 等等。
目前最主流的价格预言机主要有 Chainlink、UniswapV2、UniswapV3,这几种价格预言机的接入方式和适用场景都不太一样,可以单独使用,也可以结合使用。鉴于不少同学还不知道这些预言机具体有哪些接入方式,也不了解背后的机制,更不清楚如何才能做到保证安全性的同时又能以最小的成本接入。下面,我将分享下我的经验总结,以供参考。
Chainlink
先从 Chainlink 的价格预言机开始聊起,这应该是使用最广泛的价格预言机了。
其实,Chainlink 提供的产品不只是价格预言机,还有其他产品,包括 Verifiable Random Numbers (VRF)、Call External APIs、Chainlink Keepers。当然,使用最广泛的还是价格预言机,叫 Data Feeds。
Chainlink Data Feeds 目前已经支持了多条链,主要还是 EVM 链,包括 Ethereum、BSC、Heco、Avalanche 等,也包括 Arbitrum、Optimism、Polygon 等 L2 的链。另外,也支持了非 EVM 链,目前支持了 Solana 和 Terra。不过,我对非 EVM 链并不熟悉,所以只讲 EVM 链的使用。
DeFi 应用接入使用 Chainlink Data Feeds 其实很简单,而且还有不同的使用方式,下面就来看看最常用的使用方式。
Price Feed
第一种使用方式,官方给的示例代码是这样的:
// SPDX-License-Identifier: MITpragma solidity ^0.8.7;import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";contract PriceConsumerV3 { AggregatorV3Interface internal priceFeed; /** * Network: Kovan * Aggregator: ETH/USD * Address: 0x9326BFA02ADD2366b30bacB125260Af641031331 */ constructor() { priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331); } /** * Returns the latest price */ function getLatestPrice() public view returns (int) { ( uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound ) = priceFeed.latestRoundData(); return price; }}
首先,每个交易对都有一个单独的 Price Feed,也叫 Aggregator,其实就是一个个 AggregatorProxy,像下面这样:
可以看到,每个 Pair 都有一个对应的 Proxy,读取价格其实就是从 Proxy 提供的方法读取的。Proxy 的具体实现稍微有点复杂,但 DeFi 应用要接入的话,只要知道 Interface 就够了,这个 Interface 则很简单,就是示例代码中所引入的 AggregatorV3Interface,其代码如下:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); // getRoundData and latestRoundData should both raise "No data present" // if they do not have data to report, instead of returning unset values // which could be misinterpreted as actual reported values. function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound );}
就 5 个查询方法而已,简单介绍下这几个方法:
-
decimals():返回的价格数据的精度位数,一般为 8 或 18
-
description():一般为交易对名称,比如 ETH / USD
-
version():主要用来标识 Proxy 所指向的 Aggregator 类型
-
getRoundData(_roundId):根据 round ID 获取当时的价格数据
-
latestRoundData():获取最新的价格数据
大部分应用场景可能只需要读取最新价格,即调用最后一个方法,其返回参数中,answer 就是最新价格。
另外,大部分应用读取 token 的价格都是统一以 USD 为计价单位的,若如此,你会发现,以 USD 为计价单位的 Pair,精度位数都是统一为 8 位的,所以一般情况下也无需根据不同 token 处理不同精度的问题。
当然,在实际应用中,肯定不会只读取一个固定 Token 的价格,更多场景是根据 Token 读取该 Token 的 USD 价格,因此可以将前面的示例合约升级为如下:
// SPDX-License-Identifier: MITpragma solidity ^0.8.7;import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract PriceConsumerV3 is Ownable { mapping(address => AggregatorV3Interface) internal priceFeedMap; function setPriceFeed(address token, address priceFeed) external onlyOwner { priceFeedMap[token] = AggregatorV3Interface(priceFeed); } /** * Returns the latest price */ function getLatestPrice(address token) public view returns (int) { ( uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound ) = priceFeedMap[token].latestRoundData(); return price; }}
原先的示例只能读取 ETH/USD 一个 Pair 的价格,而现在则可以设置和读取多个不同 token 的价格。比如,现在想要读取 UNI 的 USD 价格,就可以先查出 UNI/USD 的 priceFeed,查出其 Proxy 为 0x553303d460EE0afB37EdFf9bE42922D8FF63220e ,而 UNI token 地址为 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984,那就可以调用:
setPriceFeed("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", "0x553303d460EE0afB37EdFf9bE42922D8FF63220e");
如此,UNI 所使用的 priceFeed 就设置好了,想读取 UNI 的最新价格时,调用 getLatestPrice() 就可读取到结果了,如下:
int price = getLatestPrice("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984");
另外,mapping 所使用的 key 也可以不用 token address,改用 token symbol 或其它具有唯一标识性的属性也是可以的。
虽然该示例比较简单,很多实际应用中,可能比这复杂,但基本核心功能是差不多的了。
Feed Registry
第一种接入方式虽然已经很简单,但每个 token 都需要 owner 执行 setPriceFeed,治理成本其实有点高,对某些场景来说就不太灵活。这时候,就可以考虑使用第二种方式来接入 Chainlink Data Feeds 了,通过使用 Feed Registry 的方式来接入。
Feed Registry 可以简单理解为 PriceFeeds 的聚合器,已经聚合了多个 priceFeed,有了它,使用者就无需自己去设置 priceFeed 了,可直接通过 Feed Registry 读取价格数据,如下图:
官方给的使用示例代码则如下:
// SPDX-License-Identifier: MITpragma solidity ^0.8.7;import "@chainlink/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol";import "@chainlink/contracts/src/v0.8/Denominations.sol";contract PriceConsumer { FeedRegistryInterface internal registry; /** * Network: Ethereum Kovan * Feed Registry: 0xAa7F6f7f507457a1EE157fE97F6c7DB2BEec5cD0 */ constructor(address _registry) { registry = FeedRegistryInterface(_registry); } /** * Returns the ETH / USD price */ function getEthUsdPrice() public view returns (int) { ( uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound ) = registry.latestRoundData(Denominations.ETH, Denominations.USD); return price; } /** * Returns the latest price */ function getPrice(address base, address quote) public view returns (int) { ( uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound ) = registry.latestRoundData(base, quote); return price; }}
可看到,开头引入了两个 sol 文件,FeedRegistryInterface 和 Denominations。Denominations
是一个很简单的 library,主要定义了各种货币的地址,如下:
// SPDX-License-Identifier: MITpragma solidity ^0.8.7;library Denominations { address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant BTC = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB; // Fiat currencies follow https://en.wikipedia.org/wiki/ISO_4217 address public constant USD = address(840); address public constant GBP = address(826); address public constant EUR = address(978); // … other fiat currencies}
FeedRegistryInterface
则定义了不少函数,包括和 AggregatorV3Interface 一样的几个函数,只是每个函数相比 AggregatorV3Interface 多了两个参数:base 和 quote,如下:
interface FeedRegistryInterface { // V3 AggregatorV3Interface function decimals(address base, address quote) external view returns (uint8); function description(address base, address quote) external view returns (string memory); function version(address base, address quote) external view returns (uint256); function latestRoundData(address base, address quote) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function getRoundData(address base, address quote, uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); // … other functions}
假设交易对为 UNI/USD,那 base 为 UNI 的 token 地址,quote 则为 USD 的地址,即为 Denominations.USD;假设交易对为 ETH/BTC,那 base 则为 Denominations.ETH,quote 为 Denominations.BTC。
另外,也可以通过 getFeed(address base, address quote) 直接读取到 priceFeed。
可以发现,使用 Feed Registry 的方式,主要都是用 base/quote 的方式进行查询。
FeedRegistry 里的每个 priceFeed 则是通过先后调用 proposeFeed() 和 confirmFeed() 两个函数设置的,不过这两个函数只有 FeedRegistry 的 owner 才可以调用。
喂价机制
至此,我们已经知道如何接入 Chainlink Data Feeds 来获取价格信息了,但还不够,我们还要了解背后的喂价机制,也要了解价格数据多久更新一次的,如此才能更好地判定 Chainlink 的价格预言机是否能满足具体的场景需求。
首先,Price Feed 的价格是通过多个层级的数据聚合得到的。实际上有三个数据聚合层:数据源聚合、节点运营商聚合、预言机网络聚合。
最原始的价格数据主要来源于币安、火币、Coinbase 等中心化交易平台,以及 Uniswap、Sushi 等去中心化交易平台。存在一些专门做数据聚合的服务商(比如 amberdata、CoinGecko),会从这些交易平台收集原始的价格数据,并对这些数据源进行加工整合,比如根据交易量、流动性和时差等进行加权计算。
这就是第一个层面的聚合,对数据源的聚合。拥有可靠的价格数据源的关键是要有全面的市场覆盖,才能保证一个价格点能代表所有交易环境的精确聚合,而不是单个交易所或少数交易所的价格,以防止数据被人为操纵和出现价格偏差。也因此,为了确保数据具有高度的防篡改和可靠性,Chainlink Data Feeds 只会从优质的数据聚合服务商获取数据,这意味着每个数据源都代表一个从所有中心化和去中心化交易所聚合的经过交易量调整的精细价格点,也因此可以有效抵抗闪电贷或价格异常偏差等攻击。
第二层则是 Chainlink Node Operators 所做的聚合。每个 Chainlink Node Operator 主要负责运行用于在区块链上获取和广播外部市场数据的 Chainlink 核心软件。Node Operators 会从多个独立的数据聚合服务商获取价格数据,并获取它们之间的中值,剔除掉异常值和 API 停机时间。比如,从 A 数据聚合服务商获取到价格点为 7.0,从 B 服务商获取到价格点为 7.2,那取中值后的价格点为 7.1。这意味着不仅每个单独的数据源反映了来自所有交易环境的聚合价格点,而且每个单独的节点的响应代表了来自多个数据源的聚合,进一步防止任何单一来源成为故障点,即避免了单点故障。
最后一层则是整个预言机网络的聚合,其聚合的方式有多种,但最常见的聚合方式是当响应节点数量达到预设值时对数据取中值。比如总共有 31 个节点,预设值为 21,即收到了 21 个节点的响应后,就取这些节点的价格数据的中值作为最终的价格。不过,并非每一轮的价格结果都会更新到链上,只有满足两个触发参数之一的时候才会更新:偏差阈值(Deviation Threshold)和心跳阈值(Heartbeat Threshold)。而且,不同 PriceFeed 的这两个参数的值可能会不一样。
比如,ETH/USD 的偏差阈值为 0.5%,即表示新一轮的价格点跟上一次更新的价格偏差超过 0.5% 的时候才会更新链上价格;而心跳阈值为 3600 秒,即表示上一次价格更新后过了 1 小时后才会更新链上价格。另外,因为每一轮的数据聚合都不是实时的,也需要时间,再加上偏差阈值的限制,所以,有时候,要隔几十分钟才会有价格更新,这点比较关键,需要清楚。有些 Price Feed 的偏差阈值比较大,会高达十几个小时才会有价格更新,比如下面这个:
可看到,其偏差阈值高达 5%,且已经长达 11 个小时没有价格更新了,而它的心跳阈值其实也比较高,长达 24 小时。
高达 5% 的偏差阈值,且这么长时间都没有更新价格,这如果是应用到一些高杠杆的交易产品,可能就不太合适了。
总结
总而言之,Chainlink 价格预言机接入方便,且安全性还是比较高的,但因为其价格更新机制存在偏差阈值,导致价格更新比较慢,短则几分钟或几十分钟更新一次,长则可能达 24 小时才更新一次,因此,一般只适用于对价格更新不太敏感的应用。这也是 Chainlink 价格预言机的局限性,并无法适用所有场景的应用。
比推快讯
更多 >>- Farcaster 验证功能即将上线,将成为每周获得奖励和参与空投的必要条件
- Jupiter 宣布已聚合时间代币化平台 time.fun 交易
- Ledn 联创:比特币出现前,我最成功的投资是做空玻利瓦尔
- 分析:比特币期货数据预示其或将再创新高
- 美国司法部介入调查 Coinbase 客户数据被盗案,涉及印度员工受贿
- 赵长鹏:投资加密货币有风险,不投资加密货币同样有风险
- 彭博社:Genesis 起诉母公司 DCG,要求追回申请破产前一年内 10 亿美元转账
- 消息人士:美国财政部预计在本周的 G7 财长会议上不会宣布任何贸易协议
- 某巨鲸/机构再次从 Binance 提出 1350 枚 BTC,约 1.4191 亿美元
- 美 SEC 任命 Katherine Reilly 为代理监察长
- 美联储洛根:美联储应强化利率控制机制
- Solana Labs 分拆公司 Anza 提出 Alpenglow 计划,被称为Solana 核心协议的最大变革
- 彭博社:美国司法部对 Coinbase 网络攻击展开调查
- Aave TVL升至约 300 亿美元,年初至今上涨 50%
- Semafor:美国参议院本周可能无法完成有关稳定币的加密法案
- 美联储博斯蒂克:预计今年将降息一次
- 某 10 倍做空主流币巨鲸比特币空单已浮亏 171 万美元,已再次补充保证金和空头仓位
- Fortune: 稳定币发行商 Circle正在以至少50亿美元的估值寻找买家,出售谈判涉及Coinbase和Ripple
- 现货比特币 ETF 的资产管理规模已达 1090 亿美元
- 克宫官员:特朗普谈及美俄关系前景时情绪十分激动
- 美 SEC 推迟对 21Shares 和 Bitwise Solana ETF 的决议,并开启公众评论期
- 普京和特朗普同意未来举行会晤,但未指定地点或时间
- 美联储卡什卡利:目前经济存在很大的不确定性
- 特朗普:俄罗斯、乌克兰将立即启动停火谈判
- 美 SEC 推迟对 VANECK SPOT SOLANA ETF 的审查决定
- 普京:俄罗斯已准备好与乌克兰就和平谈判的备忘录展开合作
- 普京:特朗普认为俄罗斯支持以和平方式解决乌克兰危机
- 微软将马斯克的 Grok 3 等 AI 模型引入其云端
- ALPHA 24 小时涨超 15%,市值升至 3088 万美元
- 摩根大通 CEO 戴蒙:将允许客户购买比特币,但不会为其提供托管服务
- 摩根大通 CEO:无意涉足加密资产托管业务
- 比特币波动率小幅回升至 1.90%
- 芝商所现已上线 XRP 期货
- 巨鲸 James Wynn 通过 40 倍杠杆开多 BTC,仓位价值达 5.7 亿美元
- 摩根大通:5 月前两周比特币网络算力微升
- 美股三大股指低开高走,道指率先翻红
- Paul Atkins:考虑将 FinHub 功能整合到其他部门,加深对新技术专业知识的理解
- 美联储副主席:在制定政策时,穆迪的评级下调将被视为普通数据
- Bybit 宣布支持 USDT 交易 78 只全球优质股票资产,打通加密与传统金融边界
- Berachain 联创:HONEY 代币在 Honey 官网上可见的保险库地址完全担保
- 莱维特:特朗普不认同穆迪的评估
- 美联储威廉姆斯:并未立即担心外国投资者抛售美国资产
- Sei Labs 发布 Sei Giga 白皮书,拟成为支持多提案者的 EVM L1 区块链
- BounceBit 试行比特币交易策略,抵押品为贝莱德 BUIDL
- 疑似 ether.fi 团队向 CEX 存入 500 万枚 ETHFI,约合 620 万美元
- Circle:索尼新加坡在线商店已接受 USDC 支付
- Cardano 创始人否认挪用 6 亿美元 ADA 的指控,并承诺发布审计报告
- 美联储威廉姆斯:6、7 月以后前景会更加明朗
- 美副总统:意识到乌克兰冲突谈判陷入僵局
- 美 SEC 主席反思加密监管:从“鸵鸟政策”到主动适配,将整合 FinHub 职能
比推专栏
更多 >>观点
比推热门文章
- Farcaster 验证功能即将上线,将成为每周获得奖励和参与空投的必要条件
- Jupiter 宣布已聚合时间代币化平台 time.fun 交易
- Ledn 联创:比特币出现前,我最成功的投资是做空玻利瓦尔
- 分析:比特币期货数据预示其或将再创新高
- 美国司法部介入调查 Coinbase 客户数据被盗案,涉及印度员工受贿
- 赵长鹏:投资加密货币有风险,不投资加密货币同样有风险
- 【比推每日新闻精选】摩根大通 CEO :将允许客户购买比特币,但不会为其提供托管服务;美 SEC 推迟对 21Shares 和 Bitwise Solana ETF 的决议,并开启公众评论期;Fortune: 稳定币发行商 Circle正在以至少50亿美元的估值寻找买家,出售谈判涉及Coinbase和Ripple
- 彭博社:Genesis 起诉母公司 DCG,要求追回申请破产前一年内 10 亿美元转账
- 消息人士:美国财政部预计在本周的 G7 财长会议上不会宣布任何贸易协议
- 某巨鲸/机构再次从 Binance 提出 1350 枚 BTC,约 1.4191 亿美元