值得信赖的区块链资讯!
价格预言机的使用总结(一):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 价格预言机的局限性,并无法适用所有场景的应用。
比推快讯
更多 >>- 某鲸鱼从币安提取 1320 枚 BTC,价值近 1.2 亿美元
- CZ:超级周期即将到来,但也可能判断错误
- 某交易员开设 10 倍杠杆 FARTCOIN 和 PUMP 多单,现整体仓位价值超 1200 万美元
- 安全机构:Arbitrum 链上 FutureSwapX 合约被盗损失近 40 万美元
- 首条“比特币”推文发布 17 周年,社区纪念 Hal Finney 早期贡献
- STBL 发布 Q1 路线图:USST 主网部署并启动借贷与 RWA 扩展
- 非农数据提前外泄?特朗普社交媒体手滑曝光关键就业报告
- 过去 7 日 CEX 净流出 6,317.80 枚 BTC,净流出 5.96 万枚 ETH
- 某鲸鱼三天前清仓 LINK 后再次增持,近两日已从币安买入超 40 万枚 LINK
- 数据:“800 万美元做多 11 款山寨”巨鲸总浮盈收窄至 104 万美元
- 下周宏观展望:CPI 硬撼美联储炮火,地缘火力迎战指数抛售潮
- F2Pool 联创王纯标记地址将 4000 枚 ETH 转入币安
- 数据:3000 枚 ETH 从 Chun Wang 转入 Binance,价值约 928 万美元
- 某投资者 27 天前买入 3036 万枚 WHITEWHALE,现价值已达 448 万美元
- 机构:市场定价美联储 1 月不降息,最早或 6 月开启降息
- 数据:ETH 突破 3100 美元
- 何一币安广场资料显示“你们均交易了币安人生”标识,特定情况下或可触发
- 我踏马来了市值短时突破 2900 万美元,再创历史新高
- 麻吉持仓多单浮亏扩大至 61 万美元,以太坊多单爆仓价 3,000.16 美元
- CZ 转帖富国银行买入比特币,称美国银行在散户恐慌抛售时持续加仓
- Galaxy 研究主管解读加密结构法案投票前景:通过将成重大利好,受阻或压制情绪
- Vitalik:“Corposlop”是无灵魂、邪恶且蹩脚的同质化产物
- 数据:16977 枚 SOL 从 pump.fun 转出,价值约 230 万美元
- 某沉寂 1 年的巨鲸 5 小时前从币安提取 8 万枚 SOL,价值 1087 万美元
- 若比特币跌破 8.9 万美元,主流 CEX 累计多单清算强度将达 8.94 亿
- 数据:过去 24 小时全网爆仓 2.06 亿美元,多单爆仓 1.35 亿美元,空单爆仓 7,044.06 万美元
- CZ:抱怨别人不够去中心化是自己思维中心化的表现
- 陈茂波:虚拟货币是金融创新的一部分,香港理应拥抱但须谨慎处理
- Solana Status:建议所有主网测试版验证节点使用 v3.0.14 版本
- 价值 2310 万美元的 ONDO 代币今晨被分配至 4 个钱包,历史行为显示或将流向交易所
- Bitdeer 本周出售 137.9 枚 BTC,比特币总持仓量降至 1,990.9 枚
- Zama:已开放公售注册
- 数据:271 万枚 USD1 从 World Liberty Fi 转入 Jump Crypto,价值约 271 万美元
- Tim Beiko:将担任以太坊协议顾问,从一级研发转向前沿应用场景探索
- 某巨鲸向 Hyperliquid 存入 1206 万美元 USDC 并买入 48 万枚 HYPE
- “Strategy 对手盘”新开 20 倍杠杆 XRP 多单,整体仓位现浮亏约 225 万美元
- BSC Meme 币“川普”短时暴涨超 560 倍,市值一度触及 500 万美元
- 疑似 WLFI Wallet 团队成员称 USD1 将在 2 个月内跻身前三大稳定币
- 多链钱包 Zerion 已集成 TRON network
- 数据:ACA 24 小时跌超 13%,POLYX 涨超 5%
- 机构对 2026 年持谨慎乐观态度,《加密市场结构法案》为关注焦点
- 法国发生一起加密货币抢劫案,三名男子持枪闯入民宅抢走加密 U 盘
- XRP 现货 ETF 已累计吸纳超 10 亿美元资金,部分机构投资者仍不看好并将其视为 Meme 币
- RootData:ERA 将于一周后解锁价值约 365 万美元的代币
- 数据:519.99 万枚 EIGEN 从匿名地址转出,经中转后流入 Uniswap
- 数据:过去 24h Binance 净流出 12.59 亿 USDT
- 比特币 OG 多单浮亏超 900 万美元,已支付资金费 556 万美元
- 数据,美国 XRP 现货 ETF 单日净流入 493 万美元
- 数据:Hyperliquid 平台鲸鱼当前持仓 65.98 亿美元,多空持仓比为 0.93
- 数据:1447.32 万枚 POL 从 BitGo 转入 Polygon,价值约 231.62 万美元
比推专栏
更多 >>观点
比推热门文章
- 某鲸鱼从币安提取 1320 枚 BTC,价值近 1.2 亿美元
- CZ:超级周期即将到来,但也可能判断错误
- 某交易员开设 10 倍杠杆 FARTCOIN 和 PUMP 多单,现整体仓位价值超 1200 万美元
- 安全机构:Arbitrum 链上 FutureSwapX 合约被盗损失近 40 万美元
- 首条“比特币”推文发布 17 周年,社区纪念 Hal Finney 早期贡献
- STBL 发布 Q1 路线图:USST 主网部署并启动借贷与 RWA 扩展
- 非农数据提前外泄?特朗普社交媒体手滑曝光关键就业报告
- 过去 7 日 CEX 净流出 6,317.80 枚 BTC,净流出 5.96 万枚 ETH
- 某鲸鱼三天前清仓 LINK 后再次增持,近两日已从币安买入超 40 万枚 LINK
- 数据:“800 万美元做多 11 款山寨”巨鲸总浮盈收窄至 104 万美元
比推 APP



