值得信赖的区块链资讯!
价格预言机的使用总结(一):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 价格预言机的局限性,并无法适用所有场景的应用。
比推快讯
更多 >>- 威廉姆斯:通胀将在上半年达到 2.75%-3% 峰值
- 威廉姆斯:2026 年经济前景“相当有利”,货币政策接近中性
- 美联储威廉姆斯:2026 年经济增长率或在 2.5% 至 2.75% 之间
- 美联储威廉姆斯,预计回购操作将被积极使用
- 数据:3141.41 枚 ETH 从匿名地址转入 Coinbase,价值约 973 万美元
- 华尔街日报:白宫权衡伊朗核提议,建议特朗普“先礼后兵”
- 特朗普:任何与伊朗开展业务的国家都将被征收 25%关税
- 贝森特警告:美联储调查引发市场震荡,或令鲍威尔拒绝交权
- 数据:5.12 万枚 SOL 从 Coinbase 转出,经中转后流入 Binance
- 美股小盘股创七年来最长连胜纪录
- 美联储 1 月维持利率不变概率高达 95%
- 美两党议员联合提交《区块链监管确定性法案》,推动DeFi软件开发者豁免监管
- 数据:3100 枚 ETH 从 Stake.com 转出,经中转后流入 Kraken
- 道琼斯指数收盘上涨 86.13 点,标普 500 和纳斯达克均上涨
- 美股收盘,金龙指数涨逾 4%,阿里巴巴大涨 10%
- SOL 跌破 140 USDT,24H 涨幅收窄至 1.27%
- 美元指数下跌 0.27%,收于 98.862
- Rick Rieder:美联储必须将利率降至 3%
- Uniswap在以太坊主网的累计交易量逼近3万亿美元,接近意大利全年GDP水平
- 数据:BTC 突破 92000 美元
- 美CFTC新任主席设立创新咨询委员会,拟加强对预测市场与数字资产监管
- 标普 500 盘中创历史新高,最新上涨 0.2%
- 数据:若 BTC 跌破 87,336 美元,主流 CEX 累计多单清算强度将达 17.9 亿美元
- AlphaTON Capital签署4600万美元计算协议,部署英伟达B300芯片扩容TON生态AI网络
- BTC OG 内幕巨鲸再度接近扭亏为盈,SOL 多单浮盈 689 万美元
- 美国参议院银行业委员会议员:鲍威尔并非罪犯,应尽快结束美联储调查
- 比特币回升突破 92,000 美元
- 现货金银持续上扬,续创新高
- World Liberty Finance 将 5 亿枚 WLFI 转入 Jump Trading
- Eigen 基金会:将于未来几日内部转移 EIGEN 代币,并非用于出售
- 惠誉:比特币支持的证券风险极高
- 阿里巴巴涨幅扩大至 10%,纳斯达克中国金龙指数涨近 4%
- 谷歌母公司 Alphabet 今年迄今已涨 4.8%,“七巨头”中表现最好
- 数据:5860.07 枚 ETH 转入 eToro,价值约 1.83 亿美元
- 数据:4795.94 万枚 TRX 从匿名地址转出,价值约 1427 万美元
- Tom Lee:若股东不批准 BitMine 增发新股授权,将被迫放缓 ETH 增持速度
- Web3AI 代理平台 Neuramint 完成 500 万美元种子轮融资
- Alphabet(GOOG.O)市值首次突破 4 万亿美元
- 苹果选择谷歌旗下 Gemini 驱动新版 Siri
- 伯南克、格林斯潘等数位前美联储主席声援鲍威尔
- 美联储主席“四人名单”浮出水面,特朗普 1 月做最终选择
- 比特币核心开发团队提拔 TheCharlatan 担任 Trusted Keys 维护者,系三年来首次
- 耶伦:鲍威尔的调查令人极度担忧,需关注联储独立性
- BSC Meme 币“人生 K 线”市值跌破 800 万美元,24H 跌幅 52.36%
- 美股跌幅收窄,纳指转涨,AMD 涨 1.8%
- 今日美国比特币 ETF 净流出 3,734 枚 BTC,以太坊 ETF 净流出 42,299 枚 ETH
- 数据:1780 万枚 POL 从 Polygon 转入 Upbit,价值约 271.62 万美元
- 数据:USDC Treasury 在以太坊链上销毁 1 亿枚 USDC
- Strategy 现浮盈 105.5 亿美元,Bitmine 现浮亏 32.25 亿美元
- 数据:上周全球上市公司净买入 12.5 亿美元 BTC,Strategy 投入 12.5 亿美元购买 13,627 枚比特币
比推专栏
更多 >>观点
比推热门文章
- 威廉姆斯:通胀将在上半年达到 2.75%-3% 峰值
- 威廉姆斯:2026 年经济前景“相当有利”,货币政策接近中性
- 美联储威廉姆斯:2026 年经济增长率或在 2.5% 至 2.75% 之间
- 美联储威廉姆斯,预计回购操作将被积极使用
- 数据:3141.41 枚 ETH 从匿名地址转入 Coinbase,价值约 973 万美元
- 华尔街日报:白宫权衡伊朗核提议,建议特朗普“先礼后兵”
- 特朗普:任何与伊朗开展业务的国家都将被征收 25%关税
- 贝森特警告:美联储调查引发市场震荡,或令鲍威尔拒绝交权
- 数据:5.12 万枚 SOL 从 Coinbase 转出,经中转后流入 Binance
- 美股小盘股创七年来最长连胜纪录
比推 APP



