買比特幣 買比特幣
Ctrl+D 買比特幣
ads
首頁 > Fil > Info

ICE:價格預言機的使用總結(二):UniswapV2篇_PRI

Author:

Time:1900/1/1 0:00:00

前言

該系列的前一篇文章介紹了Chainlink價格預言機的使用,其目前也被大部分DeFi應用所使用,但依然存在局限性。首先是所支持的Token的覆蓋率還不全,尤其是長尾資產,大多還未支持,比如SHIB,目前只在BSC主網有SHIB/USD的PriceFeed,而其它網絡的都還沒有,連Ethereum的都還沒支持。其次,有些資產的偏差閾值較大,價格更新也比較慢,可能長達十幾二十個小時才會更新價格,比如BNT。

這時候就需要考慮其它價格預言機了,而UniswapV2和UniswapV3都是不錯的選擇。

本篇先來聊聊如何使用UniswapV2作為價格預言機。

UniswapV2價格預言機

UniswapV2使用的價格預言機稱為TWAP,即時間加權平均價格。不同于鏈下聚合的Chainlink取自多個不同交易所的數據作為數據源,TWAP的數據源來自于Uniswap自身的交易數據,價格的計算也都是在鏈上執行的,因此,TWAP屬于鏈上預言機。

TWAP的原理比較簡單,首先,在UniswapV2Pair合約中,會存儲兩個變量price0CumulativeLast和price1CumulativeLast,在_update()函數中會更新這兩個變量,其相關代碼如下:

contract?UniswapV2Pair?{

??...

??uint32?private?blockTimestampLast;

??uint?public?price0CumulativeLast;

??uint?public?price1CumulativeLast;

??...

??//?update?reserves?and,?on?the?first?call?per?block,?price?accumulators

??function?_update(uint?balance0,?uint?balance1,?uint112?_reserve0,?uint112?_reserve1)?private?{

????...

????uint32?blockTimestamp?=?uint32(block.timestamp?%?2**32);

????uint32?timeElapsed?=?blockTimestamp?-?blockTimestampLast;

????if?(timeElapsed?&ampgt;?0?&ampamp;&ampamp;?_reserve0?!=?0?&ampamp;&ampamp;?_reserve1?!=?0)?{

??????//?*?never?overflows,?and?+?overflow?is?desired

??????price0CumulativeLast?+=?uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0))?*?timeElapsed;

??????price1CumulativeLast?+=?uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1))?*?timeElapsed;

????}

????blockTimestampLast?=?blockTimestamp;

????...

??}

}

price0CumulativeLast和price1CumulativeLast分別記錄了token0和token1的累計價格。所謂累計價格,其代表的是整個合約歷史中每一秒的Uniswap價格總和。且只會在每個區塊第一筆交易時執行累加計算,累加的值不是當前區塊的第一筆交易的價格,而是在這之前的最后一筆交易的價格,所以至少也是上個區塊的價格。取自之前區塊的價格,可以大大提高操控價格的成本,所以自然也提高了安全性。

歐易Web3錢包與去中心化價格預言機Nest Protocol達成合作:據官方消息,歐易Web3錢包宣布與去中心化價格預言機Nest Protocol達成合作。歐易Web3錢包用戶可以通過Discover板塊搜索并進入Nest Protocol,進行期權、期貨、Swap、NFT等交易。

據悉,歐易Web3錢包是歐易交易所研發的Web3新產品,旨在為用戶提供便捷、安全、易操作的一站式Web3入口,降低用戶Web3學習成本。[2023/1/4 9:52:20]

如上圖所示,合約的第一個區塊為Block122,這時候,價格和時間差都為0,所以累計價格也為?0。到了下一個區塊Block123,這時候取自上個區塊的最后一口價格10.2,且經過的時間差為7,因此就可以計算出累計價格priceCumulative=10.2*7=71.4。再到下個區塊Block124,取自上一口價格10.3,兩個區塊間的時間差為8,那此時的累計價格就變成了71.4+(10.3*8)=153.8。Block125的時候也同理,上口價格為10.5,區塊時間差為5,所以最新的累計價格就變成了153.8+(10.5*5)=206.3。

有了這個基礎之后,就可以計算TWAP了。

固定時間窗口TWAP

計算TWAP的原理也是非常簡單,如上圖所示,這是計算時間間隔為1小時的TWAP,取自開始和結束時的累計價格和兩區塊當時的時間戳,兩者的累計價格相減,再除以兩者之間的時間差,就算出這1小時內的TWAP價格了。

這是TWAP最簡單的計算方式,也稱為固定時間窗口的TWAP。下面來講講具體如何實現。

Uniswap官方也有提供了一個示例代碼來計算固定時間窗口的TWAP,其代碼放在v2-periphery項目中:

https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol

該示例代碼也比較簡單,我們直接貼上代碼看看:

pragma?solidity?=0.6.6;

import?'

????function?update()?external?{

????????(uint?price0Cumulative,?uint?price1Cumulative,?uint32?blockTimestamp)?=

????????????UniswapV2OracleLibrary.currentCumulativePrices(address(pair));

????????uint32?timeElapsed?=?blockTimestamp?-?blockTimestampLast;?//?overflow?is?desired

????????//?ensure?that?at?least?one?full?period?has?passed?since?the?last?update

????????require(timeElapsed?&ampgt;=?PERIOD,?'ExampleOracleSimple:?PERIOD_NOT_ELAPSED');

????????//?overflow?is?desired,?casting?never?truncates

MetaMask Portfolio dApp推出由NFT Bank支持的NFT價格預估功能:11月18日消息,MetaMask宣布在MetaMask Portfolio dApp中推出由NFT Bank支持的NFT價格預估功能,目前為以太坊上大部分NFT系列提供NFT價格預估服務,之后會擴展到其他網絡。[2022/11/18 13:20:00]

????????//?cumulative?price?is?in?(uq112x112?price?*?seconds)?units?so?we?simply?wrap?it?after?division?by?time?elapsed

????????price0Average?=?FixedPoint.uq112x112(uint224((price0Cumulative?-?price0CumulativeLast)?/?timeElapsed));

????????price1Average?=?FixedPoint.uq112x112(uint224((price1Cumulative?-?price1CumulativeLast)?/?timeElapsed));

????????price0CumulativeLast?=?price0Cumulative;

????????price1CumulativeLast?=?price1Cumulative;

????????blockTimestampLast?=?blockTimestamp;

????}

????//?note?this?will?always?return?0?before?update?has?been?called?successfully?for?the?first?time.

????function?consult(address?token,?uint?amountIn)?external?view?returns?(uint?amountOut)?{

????????if?(token?==?token0)?{

????????????amountOut?=?price0Average.mul(amountIn).decode144();

????????}?else?{

????????????require(token?==?token1,?'ExampleOracleSimple:?INVALID_TOKEN');

????????????amountOut?=?price1Average.mul(amountIn).decode144();

????????}

????}

}

PERIOD指定為了24小時,說明這個示例計算TWAP的固定時間窗口為24小時,即每隔24小時才更新一次價格。

該示例也只保存一個交易對的價格,即token0-token1的價格。price0Average和price1Average分別就是token0和token1的TWAP價格。比如,token0為WETH,token1為USDC,那price0Average就是WETH對USDC的價格,而price1Average則是USDC對WETH的價格。

update()函數就是更新TWAP價格的函數,這一般需要鏈下程序的定時任務來觸發,按照這個示例的話,就是鏈下的定時任務需要每隔24小時就定時觸發調用update()函數。

update()函數的實現邏輯也和上面所述的公式一致:

讀取出當前最新的累計價格和當前的時間戳;

John McAfee三年前關于BTC的價格預測未能實現:三年前,加密貨幣倡導者、殺軟件之父John McAfee在推特預測BTC將在三年內價值超過50萬美元。不過這一預言未能實現,目前比特幣交易價格仍低于1萬美元,這是過去三個月來非常強勁的阻力位。或許人們可以質疑之前反彈的有效性,因為比特幣現在比以往任何時候都更具流動性,越來越多的機構投資者蜂擁而入,需求穩步上升,而山寨幣似乎也在做出積極反應。比特幣在市場的主導地位正在被山寨幣市場動搖,在預期的反彈之后,比特幣可能會進一步下跌。

有一些潛在的基本面不利于比特幣及其預期的下一波走勢。首先,新冠病大流行給企業和人們的日常活動帶來壓力。因此,看到更多人投資高度波動的市場和被認為有風險的市場(如加密資產)的可能性非常小。其次,比特幣是基于PoW機制開采,在監管機構推動清潔能源項目之際,比特幣因高能耗而受到影響。另一個可能壓制比特幣價格的主要原因是交易成本高,以及交易活動大量涌入導致處理速度慢。

McAfee的案例表明,價格預測,尤其是加密行業的價格預測無法得到保證,因為沒有人能單獨控制市場。幣安CEO評論稱,“這就是為什么我不做價格預測。”(Zycrypto)[2020/7/18]

計算出當前時間和上一次更新價格時的時間差timeElapsed,要求該時間差需要達24小時;

根據公式TWAP=(priceCumulative-priceCumulativeLast)/timeElapsed計算得到最新的TWAP,即priceAverage;

更新priceCumulativeLast和blockTimestampLast為當前最新的累計價格和時間戳。

不過,有一點需要注意,因為priceCumulative本身計算存儲時是做了左移112位的操作的,所以計算所得的priceAverage也是左移了112位的。

consult()函數則可查詢出用TWAP價格計算可兌換的數量。比如,token0為WETH,token1為USDC,假設WETH的價格為3000USDC,查詢consult()時,若傳入的參數token為token0的地址,amountIn為2,那輸出的amountOut則為3000*2=6000,可理解為若支付2WETH,就可根據價格換算成6000USDC。

滑動時間窗口TWAP

固定時間窗口TWAP的原理和實現,比較簡單,但其最大的不足就是價格變化不夠平滑,時間窗口越長,價格變化就可能會越陡峭。因此,在實際應用中,更多其實是用滑動時間窗口的TWAP。

所謂滑動時間窗口TWAP,就是說,計算TWAP的時間窗口并非固定的,而是滑動的。這種算法的主要原理就是將時間窗口劃分為多個時間片段,每過一個時間片段,時間窗口就會往右滑動一格,如下圖所示:

上圖所示的時間窗口為1小時,劃分為了6個時間片段,每個時間片段則為10分鐘。那每過10分鐘,整個時間窗口就會往右滑動一格。而計算TWAP時的公式則沒有變,依然還是取自時間窗口的起點和終點。如果時間窗口為24小時,按照固定時間窗口算法,每隔24小時TWAP價格才會更新,但使用滑動時間窗口算法后,假設時間片段為1小時,則TWAP價格是每隔1小時就會更新。

Uniswap官方也同樣提供了這種滑動時間窗口TWAP實現的示例代碼,其Github地址為:

https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleSlidingWindowOracle.sol

我們也貼上代碼看看:

pragma?solidity?=0.6.6;

以太坊混幣平臺Tornado.cash集成Chainlink以太坊Gas價格預言機:以太坊混幣平臺Tornado.cash已在主網集成Chainlink提供的以太坊Gas價格預言機,如果Tornado.cash無法獲得鏈下Gas費用數據,其前端頁面將會調用Chainlink的Gas價格預言機,為用戶提交交易時的Gas費用預測提供數據支持。

目前該預言機已集成了四個鏈下的Gas價格來源,分別是EthGasStation、Gas Oracle、Etherchain和POA的GasPrice,Chainlink會將這些數據持續提交到以太坊鏈上,其他應用即可參考調用。[2020/6/6]

import?'

????address?public?immutable?factory;

????//?the?desired?amount?of?time?over?which?the?moving?average?should?be?computed,?e.g.?24?hours

????uint?public?immutable?windowSize;

????//?the?number?of?observations?stored?for?each?pair,?i.e.?how?many?price?observations?are?stored?for?the?window.

????//?as?granularity?increases?from?1,?more?frequent?updates?are?needed,?but?moving?averages?become?more?precise.

????//?averages?are?computed?over?intervals?with?sizes?in?the?range:

????//???

????//?e.g.?if?the?window?size?is?24?hours,?and?the?granularity?is?24,?the?oracle?will?return?the?average?price?for

????//???the?period:

????//???,?now]

????uint8?public?immutable?granularity;

????//?this?is?redundant?with?granularity?and?windowSize,?but?stored?for?gas?savings?&ampamp;?informational?purposes.

????uint?public?immutable?periodSize;

????//?mapping?from?pair?address?to?a?list?of?price?observations?of?that?pair

????mapping(address?=&ampgt;?Observation)?public?pairObservations;

????constructor(address?factory_,?uint?windowSize_,?uint8?granularity_)?public?{

????????require(granularity_?&ampgt;?1,?'SlidingWindowOracle:?GRANULARITY');

????????require(

????????????(periodSize?=?windowSize_?/?granularity_)?*?granularity_?==?windowSize_,

聲音 | John McAfee:仍堅信比特幣5萬美元中期價格預測,也堅信2020年底將達100萬美元:殺軟件之父、加密貨幣支持者John McAfee發推稱:“忽略掉比特幣價格的下跌吧。我堅定地支持Peter Brandt對5萬美元中期價格的預測。我對2020年底前100萬美元的價格也很堅定。”[2019/9/30]

????????????'SlidingWindowOracle:?WINDOW_NOT_EVENLY_DIVISIBLE'

????????);

????????factory?=?factory_;

????????windowSize?=?windowSize_;

????????granularity?=?granularity_;

????}

????//?returns?the?index?of?the?observation?corresponding?to?the?given?timestamp

????function?observationIndexOf(uint?timestamp)?public?view?returns?(uint8?index)?{

????????uint?epochPeriod?=?timestamp?/?periodSize;

????????return?uint8(epochPeriod?%?granularity);

????}

????//?returns?the?observation?from?the?oldest?epoch?(at?the?beginning?of?the?window)?relative?to?the?current?time

????function?getFirstObservationInWindow(address?pair)?private?view?returns?(Observation?storage?firstObservation)?{

????????uint8?observationIndex?=?observationIndexOf(block.timestamp);

????????//?no?overflow?issue.?if?observationIndex?+?1?overflows,?result?is?still?zero.

????????uint8?firstObservationIndex?=?(observationIndex?+?1)?%?granularity;

????????firstObservation?=?pairObservations;

????}

????//?update?the?cumulative?price?for?the?observation?at?the?current?timestamp.?each?observation?is?updated?at?most

????//?once?per?epoch?period.

????function?update(address?tokenA,?address?tokenB)?external?{

????????address?pair?=?UniswapV2Library.pairFor(factory,?tokenA,?tokenB);

????????//?populate?the?array?with?empty?observations?(first?call?only)

????????for?(uint?i?=?pairObservations.length;?i?&amplt;?granularity;?i++)?{

????????????pairObservations.push();

????????}

????????//?get?the?observation?for?the?current?period

????????uint8?observationIndex?=?observationIndexOf(block.timestamp);

????????Observation?storage?observation?=?pairObservations;

????????//?we?only?want?to?commit?updates?once?per?period?(i.e.?windowSize?/?granularity)

????????uint?timeElapsed?=?block.timestamp?-?observation.timestamp;

????????if?(timeElapsed?&ampgt;?periodSize)?{

????????????(uint?price0Cumulative,?uint?price1Cumulative,)?=?UniswapV2OracleLibrary.currentCumulativePrices(pair);

????????????observation.timestamp?=?block.timestamp;

????????????observation.price0Cumulative?=?price0Cumulative;

????????????observation.price1Cumulative?=?price1Cumulative;

????????}

????}

????//?given?the?cumulative?prices?of?the?start?and?end?of?a?period,?and?the?length?of?the?period,?compute?the?average

????//?price?in?terms?of?how?much?amount?out?is?received?for?the?amount?in

????function?computeAmountOut(

????????uint?priceCumulativeStart,?uint?priceCumulativeEnd,

????????uint?timeElapsed,?uint?amountIn

????)?private?pure?returns?(uint?amountOut)?{

????????//?overflow?is?desired.

????????FixedPoint.uq112x112?memory?priceAverage?=?FixedPoint.uq112x112(

????????????uint224((priceCumulativeEnd?-?priceCumulativeStart)?/?timeElapsed)

????????);

????????amountOut?=?priceAverage.mul(amountIn).decode144();

????}

????//?returns?the?amount?out?corresponding?to?the?amount?in?for?a?given?token?using?the?moving?average?over?the?time

????//?range?,?now]

????//?update?must?have?been?called?for?the?bucket?corresponding?to?timestamp?`now?-?windowSize`

????function?consult(address?tokenIn,?uint?amountIn,?address?tokenOut)?external?view?returns?(uint?amountOut)?{

????????address?pair?=?UniswapV2Library.pairFor(factory,?tokenIn,?tokenOut);

????????Observation?storage?firstObservation?=?getFirstObservationInWindow(pair);

????????uint?timeElapsed?=?block.timestamp?-?firstObservation.timestamp;

????????require(timeElapsed?&amplt;=?windowSize,?'SlidingWindowOracle:?MISSING_HISTORICAL_OBSERVATION');

????????//?should?never?happen.

????????require(timeElapsed?&ampgt;=?windowSize?-?periodSize?*?2,?'SlidingWindowOracle:?UNEXPECTED_TIME_ELAPSED');

????????(uint?price0Cumulative,?uint?price1Cumulative,)?=?UniswapV2OracleLibrary.currentCumulativePrices(pair);

????????(address?token0,)?=?UniswapV2Library.sortTokens(tokenIn,?tokenOut);

????????if?(token0?==?tokenIn)?{

????????????return?computeAmountOut(firstObservation.price0Cumulative,?price0Cumulative,?timeElapsed,?amountIn);

????????}?else?{

????????????return?computeAmountOut(firstObservation.price1Cumulative,?price1Cumulative,?timeElapsed,?amountIn);

????????}

????}

}

要實現滑動時間窗口算法,就需要將時間分段,還需要保存每個時間段的priceCumulative。在這實現的示例代碼中,定義了結構體Observation,用來保存每個時間片段的數據,包括兩個token的priceCumulative和記錄的時間點timestamp。還定義了pairObservations用來存儲每個pair的Observation數組,而數組實際的長度取決于將整個時間窗口劃分為多少個時間片段。

windowSize表示時間窗口大小,比如24小時,granularity是劃分的時間片段數量,比如24段,periodSize則是每時間片段的大小,比如1小時,是由windowSize/granularity計算所得。這幾個值都在構造函數中進行了初始化。

觸發update()函數則更新存儲最新時間片段的observation,如時間片段大小為1小時,即每隔1小時就要觸發update()函數一次。因為這個示例中是支持多個pair的,所以update()時需要指定所要更新的兩個token。

而查詢當前TWAP價格的計算就在consult()函數里實現了。首先,先獲取到當前時間窗口里的第一個時間片段的observation,也算出當前時間與第一個observation時間的時間差,且讀取出當前最新的priceCumulative,之后就在computeAmountOut()函數里計算得到最新的TWAP價格priceAverage,且根據amountIn算出了amountOut并返回。

總結

本文我們主要介紹了被廣泛使用的一種鏈上預言機TWAP,且介紹了固定時間窗口和滑點時間窗口兩種算法的TWAP。雖然,TWAP是由Uniswap推出的,但因為很多其他DEX也采用了和Uniswap一樣的底層實現,如SushiSwap、PancakeSwap等,所以這些DEX也可以用同樣的算法計算出對應的TWAP。

但使用UniswapV2的TWAP,其主要缺陷就是需要鏈下程序定時觸發update()函數,存在維護成本。UniswapV3的TWAP則解決了這個問題,下一篇會來聊聊其具體是如何實現的。

文章首發于「Keegan小鋼」公眾號:

https://mp.weixin.qq.com/s?__biz=MzA5OTI1NDE0Mw==&mid=2652494441&idx=1&sn=57a97690390b93770c5a906dce4157c8&chksm=8b685079bc1fd96f9ab60cc1b41b8642abf807a13a37c12f05a280be2e03f3a9288a047b5739&token=1584634265&lang=zh_CN#rd

Tags:AMPPRIRICEICEDREAMPAD價格PRIMATErice幣還會漲SLICE幣

Fil
DOT:加密領域第一季度回顧和市場展望_dot幣總量多少

快覽: 盡管市場低迷,但加密投資在第一季度仍然非常活躍。在基礎設施方面,我們在跨鏈解決方案和DAO工具中看到了很多的動向。新的layer-1仍在孵化中.

1900/1/1 0:00:00
KEY:NFT 防盜指南:如何保護資產安全?_ruff幣區塊鏈最新消息

隨著NFT用戶數、交易量和市值的不斷攀升,釣魚者、黑客等不法分子也開始瞄準這個市場,進一步威脅NFT生態的安全.

1900/1/1 0:00:00
ETA:扎克伯格噩夢難醒:Meta增速拉垮 元宇宙難當重任_MET

繼兩個月前發布首個令人大跌眼鏡的“元宇宙”財報之后,META在美東時間4月27日盤后再次公布了一份令市場喜憂參半的財報.

1900/1/1 0:00:00
比特幣:金色午報 | 4月23日午間重要動態一覽_區塊鏈運用的技術不包括

7:00-12:00關鍵詞:浙江、Avalanche、RSS3、Infura1.浙江省上線首個數字藏品規范化交易平臺;2.Avalanche鏈上USDC發行量突破13億美元.

1900/1/1 0:00:00
加密貨幣:金色觀察丨加密熊市又來?但這里有三種“穩贏”模式_比特幣是穩定幣嗎為什么

金色財經?區塊鏈5月2日訊?對于去年比特幣猶如過山車般的表現,相信整個加密市場還記憶猶新。在經歷了一年的起起落落之后,如今的加密貨幣市場似乎仍在熊市邊緣徘徊.

1900/1/1 0:00:00
以太坊:金色晨訊 | 4月23日隔夜重要動態一覽_比特幣和萊特幣哪個好

21:00-7:00關鍵詞:SkyBridge、美國財政部、耶倫、Infura、Polygon1.SkyBridgeCapital啟動比特幣挖礦基金.

1900/1/1 0:00:00
ads