# 关于Price Oracle的原理及实现 **Published by:** [Mobius](https://paragraph.com/@0xmobius/) **Published on:** 2022-01-25 **URL:** https://paragraph.com/@0xmobius/price-oracle ## Content 广义的Oracle包括链上链下各种繁杂信息的转译和传递,要能实现无障碍的信息交互还是道阻且长,但在细分场景下,对Price Oracle的落地应用已经有较为成熟的解决方案。链下预言机(eg. Chainlink)Chainlink是去中心化网络,其中的节点将链下数据和信息通过预言机发送给智能合约。Link token用于支付节点提供的数据服务。 1. oracle作用:作为链上链下数据的“翻译机” 2. oracle提供数据服务process: (1)Requesting Contract发出数据请求 (2)Chainlink协议将请求注册为event,监听event创建SLA Contract(Chainlink Service Level Agreement Contract) (3)SLA合约生成三个sub-contracts:Chainlink Reputation Contract:验证oracle provider的历史记录,拒绝历史记录不良Node的服务;Chainlink Order-Matching Contract:转发Requesting Contract的请求到Chainlink Node,接收Node对请求的报价后选择满足需求的Node响应请求;Chainlink Aggregating Contract:接收预言机提供的数据,并验证整合后得到一个准确值。(4)Chainlink Node数据验证的可靠性:Chainlink Node将请求数据通过“Chainlink Core”软件将链上程序语言翻译为链下服务可理解的编程语言。通过外部API获取数据,再将数据翻译后发送给Aggregating ContractAggregating通过比对Node的数据,验证单个API数据源。重复该过程即可验证多个数据源。3. Link Token用途 (1)Requesting Contract所有者使用Link支付Chainlink Node运营商提供的服务。 (2)Chainlink Node运营商将Link质押,作为数据真实性的信用凭证。质押量作为Reputation Contract评价的参考指标。Node运营商的不良行为会被惩罚,即被Chainlink协议征从staked Link中收惩罚税。链上预言机(eg. Dex)由于直接获取Dex的价格数据容易遭受三明治攻击等闪电贷攻击,因此,TWAP(Time Weight Average Price)被用于创建有效防止价格操作的链上价格预言机。 1. UniswapV2Pair实现过程:计算当前区块时间与上一次更新的时间差timeElapsed更新累加价格和记录本次更新的区块时间2. 如何防止价格操纵:每次计算是在每个区块的第一笔交易对价格影响生效前进行Chainlink Price Oracle实现从API获取数据,提交上链// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; contract Link is ChainlinkClient { using Chainlink for Chainlink.Request; // 构造函数参数:_links是link token address, _oracle和_specId是Node地址、该预言机下的任务Id constructor(address _link, address _oracle, bytes32 _specId) { setChainlinkToken(_link); setChainlinkOracle(_oracle); specId = _specId; } bytes32 internal specId; bytes32 public currentPrice; event RequestFulfilled( bytes32 indexed requestId, // User-defined ID bytes32 indexed price ); function requestEthereumPrice(string memory _currency, uint256 _payment) public { requestEthereumPriceByCallback(_currency, _payment, address(this)); } function requestEthereumPriceByCallback( string memory _currency, uint256 _payment, address _callback ) public { Chainlink.Request memory req = buildChainlinkRequest(specId, _callback, this.fulfill.selector); req.add("get", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY"); string[] memory path = new stringUnsupported embed; path[0] = _currency; req.addStringArray("path", path); sendChainlinkRequest(req, _payment); } function fulfill(bytes32 _requestId, bytes32 _price) public recordChainlinkFulfillment(_requestId) { emit RequestFulfilled(_requestId, _price); currentPrice = _price; } } 从Aggregator合约获取报价// 设置合约地址 AggregatorInterface internal ref; constructor(address _aggregator) public { ref = AggregatorInterface(_aggregator); } // 调用接口获取报价信息 interface AggregatorInterface { // 最新聚合价格 function latestAnswer() external view returns (int256); // 最新聚合时间戳 function latestTimestamp() external view returns (uint256); // 最新聚合轮次号 function latestRound() external view returns (uint256); // 最新聚合结果 function getAnswer(uint256 roundId) external view returns (int256); // 轮次号对应的时间戳 function getTimestamp(uint256 roundId) external view returns (uint256); event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); } 案例分析:以Compound为例:使用 Chainlink 进行喂价并加入 Uniswap TWAP 进行边界校验,防止价格波动太大时,交易受异常极值影响询价接口// 询价接口 function priceInternal(TokenConfig memory config) internal view returns (uint) { if (config.priceSource == PriceSource.REPORTER) return prices[config.symbolHash]; if (config.priceSource == PriceSource.FIXED_USD) return config.fixedPrice; if (config.priceSource == PriceSource.FIXED_ETH) { uint usdPerEth = prices[ethHash]; require(usdPerEth > 0, "ETH price not set, cannot convert to dollars"); return mul(usdPerEth, config.fixedPrice) / ethBaseUnit; } } 报价接口自建报价服务function putInternal(address source, uint64 timestamp, string memory key, uint64 value) internal returns (string memory) { // Only update if newer than stored, according to source Datum storage prior = data[source][key]; if (timestamp > prior.timestamp && timestamp < block.timestamp + 60 minutes && source != address(0)) { data[source][key] = Datum(timestamp, value); emit Write(source, key, timestamp, value); } else { emit NotWritten(prior.timestamp, timestamp, block.timestamp); } return key; } 报价数据源1 - 获取锚点价格(从Dex询价)// Fetches the current token/usd price from uniswap, with 6 decimals of precision. function fetchAnchorPrice(string memory symbol, TokenConfig memory config, uint conversionFactor) internal virtual returns (uint) { (uint nowCumulativePrice, uint oldCumulativePrice, uint oldTimestamp) = pokeWindowValues(config); // This should be impossible, but better safe than sorry require(block.timestamp > oldTimestamp, "now must come after before"); uint timeElapsed = block.timestamp - oldTimestamp; // Calculate uniswap time-weighted average price // Underflow is a property of the accumulators: https://uniswap.org/audit.html#orgc9b3190 FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(uint224((nowCumulativePrice - oldCumulativePrice) / timeElapsed)); uint rawUniswapPriceMantissa = priceAverage.decode112with18(); uint unscaledPriceMantissa = mul(rawUniswapPriceMantissa, conversionFactor); uint anchorPrice; anchorPrice = mul(unscaledPriceMantissa, config.baseUnit) / ethBaseUnit / expScale; emit AnchorPriceUpdated(symbol, anchorPrice, oldTimestamp, block.timestamp); return anchorPrice; } 报价数据源2-Chainlink喂价// Each ValidatorProxy is the only valid reporter for the underlying asset price function putInternal(address source, uint64 timestamp, string memory key, uint64 value) internal returns (string memory) { // Only update if newer than stored, according to source Datum storage prior = data[source][key]; if (timestamp > prior.timestamp && timestamp < block.timestamp + 60 minutes && source != address(0)) { data[source][key] = Datum(timestamp, value); emit Write(source, key, timestamp, value); } else { emit NotWritten(prior.timestamp, timestamp, block.timestamp); } return key; } 整合报价信息function postPriceInternal(string memory symbol, uint ethPrice) internal { TokenConfig memory config = getTokenConfigBySymbol(symbol); require(config.priceSource == PriceSource.REPORTER, "only reporter prices get posted"); bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); uint reporterPrice = priceData.getPrice(reporter, symbol); uint anchorPrice; if (symbolHash == ethHash) { anchorPrice = ethPrice; } else { anchorPrice = fetchAnchorPrice(symbol, config, ethPrice); } if (reporterInvalidated) { prices[symbolHash] = anchorPrice; emit PriceUpdated(symbol, anchorPrice); } else if (isWithinAnchor(reporterPrice, anchorPrice)) { prices[symbolHash] = reporterPrice; emit PriceUpdated(symbol, reporterPrice); } else { emit PriceGuarded(symbol, reporterPrice, anchorPrice); } } Reference:https://www.gemini.com/cryptopedia/what-is-chainlink-and-how-does-it-workhttps://soliditydeveloper.com/uniswap-oraclehttps://compound.finance/docs/priceshttps://learnblockchain.cn/article/1056 ## Publication Information - [Mobius](https://paragraph.com/@0xmobius/): Publication homepage - [All Posts](https://paragraph.com/@0xmobius/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@0xmobius): Subscribe to updates - [Twitter](https://twitter.com/___Mobius___): Follow on Twitter