
价格预言机的使用总结(一):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 价格预言机的局限性,并无法适用所有场景的应用。
比推快讯
更多 >>- Boundless 将推出原生代币 ZKC,约 6%用于社区代币销售和空投
- JPYC 旗下日元稳定币将在以太坊、Avalanche、Polygon 上部署
- 旧金山联储提供新方法监测美国经济衰退情况
- 区块链借贷公司 Figure 公开递交 IPO 申请,将以纳斯达克代码 FIGR 交易
- Strategy 放宽股权出售融资限制,允许股价低于比特币持有量 2.5 倍时发行普通股
- The ETF Store 总裁:SEC 不应继续拖延批准现货以太坊 ETF 质押功能
- Chamath Palihapitiya 重返 SPAC 市场,拟募资 2.5 亿美元
- 某新建钱包地址建仓 15,752 枚 ETH,价值 6824 万美元
- 特朗普:开始安排普泽会,之后将举行三边会谈
- 华泰证券维持中长期美元贬值压力观点
- 美联储降息决策将保持谨慎,不会大幅降息
- 美国财长:稳定币将扩大全球美元使用渠道,促进美国国债需求激增
- Chamath 旗下AMERICAN EXCEPTIONALISMSPAC 计划筹资 2.5 亿美元,投资目标聚焦于 AI 与 DeFi 等领域
- 英特尔盘后涨超 3%,与软银签署 20 亿美元投资协议
- 日本稳定币发行商JPYC获得牌照,或在10月推出日元稳定币
- KindlyMD完成2亿美元可转债融资拟增持比特币
- 美元指数上涨 0.32%,收于 98.167
- 美财政部呼吁公众就加密货币非法活动发表评论
- 数据:过去 24 小时全网爆仓 5.31 亿美元,多单爆仓 4.39 亿美元,空单爆仓 9158.99 万美元
- 美股收盘:爱奇艺大涨 17%,英特尔跌 3.6%
- 瑞银:将到2026年3月底黄金的目标价上调100美元,至每盎司3600美元
- 特朗普:认为今天会有解决方案,决议可能包括安全措施
- 特朗普:将尝试达成美乌俄三方协议,俄乌不必非得有停火协议
- 美国三大股指全部转涨,市场情绪回暖
- 美 SEC 推迟对多支加密 ETF 的申请做出决议
- 美联储逆回购操作接纳 382.4 亿美元对手方
- 特朗普政府称将讨论收购英特尔10%的股份
- 数据:ETH 当前全网 8 小时平均资金费率为 0.0052%
- 隆领资本关联地址过去 3 日将 1.3 万枚 ETH 存入 Binance
- Google 持有比特币矿企 TeraWulf 股份增至 14%
- Starknet v0.14.0 升级提案获社区批准,将于 9 月 1 日上线主网
- Circle 收购高性能共识引擎 Malachite 以支持其 Arc 网络
- 美股以太坊策略股涨跌不一,BTCS 涨 7.48%,BMNR 跌 3.82%
- Space and Time 与 a16z crypto 达成合作,加速 Jolt 基于 Dory 多项式承诺方案实现
- Gauntlet 联合 FalconX 推出杠杆化 RWA 投资策略
- 某鲸鱼地址买入 359 万枚 AERO,均价 1.4 美元
- Injective 推出链上英伟达 H100 GPU 租赁费率市场
- 数据:机构上周累计买入逾 47 亿美元 ETH,稳定币市值增长 67 亿美元
- Tom Lee:历史性机遇正在以太坊生态上演,未来将见证前所未有的创新浪潮
- 数据:Cumberland 向 Coinbase 转入逾 2.48 万枚 ETH,约合 1.07 亿美元
- Sky Protocol 上周花费 139 万美元回购 1732 万枚 SKY
- 据比推数据,ETH现报4310.49美元,24小时跌幅为5.3%,价格波动较大,请谨慎交易,控制风险。
- 币安完成 HBAR 和 SUI 在 BSC 网络集成,现已开放充提服务
- 某独立矿工成功挖出比特币区块,获得 37.1 万美元奖励
- 数据:SOL Strategies 验证节点质押钱包突破 6000 个,创历史新高
- Virtuals Protocol 推出 Genesis 三级质押机制,以实现需求与公平性间平衡
- Jupiter 上线 Jupiter Send 功能,支持二维码发送任意代币
- 美股开盘持稳,市场关注央行年会与零售财报潮
- 数据:某巨鲸此前于 2740 美元做空 ETH,当前浮亏逾 2150 万美元
- 美股上市公司 AEHL 与 BitGo 签署战略协议推进比特币收购和托管
比推专栏
更多 >>观点
比推热门文章
- Boundless 将推出原生代币 ZKC,约 6%用于社区代币销售和空投
- JPYC 旗下日元稳定币将在以太坊、Avalanche、Polygon 上部署
- 旧金山联储提供新方法监测美国经济衰退情况
- 区块链借贷公司 Figure 公开递交 IPO 申请,将以纳斯达克代码 FIGR 交易
- Strategy 放宽股权出售融资限制,允许股价低于比特币持有量 2.5 倍时发行普通股
- The ETF Store 总裁:SEC 不应继续拖延批准现货以太坊 ETF 质押功能
- Chamath Palihapitiya 重返 SPAC 市场,拟募资 2.5 亿美元
- 某新建钱包地址建仓 15,752 枚 ETH,价值 6824 万美元
- 特朗普:开始安排普泽会,之后将举行三边会谈
- 华泰证券维持中长期美元贬值压力观点