Uniswap V2 Oracle和鏈上累計價格儲存證明的結合使用

買賣虛擬貨幣

Uniswap V2釋出了許多新功能,包括:

令牌:令牌流動性對(代替ETH/DAI和ETH/MKR,Uniswap V2現在原生支援MKR/DAI)

內建多跳路由(ETH->DAI->MKR->USDT,如果這是ETH->USDT的價格)

ERC777相容性

累積價格Oracle

在本文中,我們將討論此“累加價格Oracle”其工作方式,如何使用它,並介紹一個Solidity庫,可以用來將這個Oracle整合到您自己的以太坊專案中。本文將假設您對Uniswap等defi產品市場有深入的瞭解。

如果您已經瞭解本文主要的想法,你可以在這裡找到程式碼示例和solidity庫:https://github.com/Keydonix/uniswap-oracle。

雖然我們通常認為oracle是一個透過來自受信任/繫結方的交易(例如Maker Price Feed,ChainLink)將鏈下資訊饋入區塊鏈的系統,但Uniswap V2 oracle不需要任何特定的互動即可提供此資料。取而代之的是,每個交換事務都向該oracle提供資訊。

為了說明Uniswap V2透過這一新的Oracle功能解決的問題,讓我們首先探討Uniswap V1的問題。

不要將Uniswap V1用作Oracle

Uniswap團隊從未將Uniswap V1推廣為可行的鏈上Oracle。正是由於Uniswap簡單、無許可、鏈上、面向市場的功能,才吸引了有創造力的人將其作為一個整體來使用。Uniswap V1 oracle的警報器很簡單:

uint256tokenPrice=token.balanceOf(uniswapMarket)/address(uniswapMarket).balance

由於目前Uniswap V1市場的“價格”只是代幣和以太餘額的比率,因此計算這些專案的gas效率非常高而且非常簡單。但是問題在於它非常不安全。有許多攻擊與使用Uniswap V1作為Oracle的專案有關,但最引人注目的攻擊可能是bZx/Fulcrum/Compound攻擊,它在24小時內淨賺近100萬美元。

Uniswap V1的問題在於價格反饋是即時的,並且很容易在很短的時間內進行操作。讓我們看看下面的psuedo程式碼示例:

//send100etherandreceivesomenumberoftokensuniswapMarket.ethToTokenSwapInput.value(100ether)(100ether);exploitTarget.doSomethingThatUsesUniswapV1AsOracle();//sendallthetokenswereceivedabovebackuniswapMarket.tokenToEthSwapInput(token.balanceOf(address(this));

在上面的攻擊中,您將向流動性提供者支付非常少量的以太幣(約0.6 ETH)的費用(雙向為0.3%)。但是當呼叫exploitTarget時,它會認為令牌比實際值錢得多。如果exploitTarget使用Uniswap V1 oracle來確保您存入的抵押品價值足以提取其他代幣,那麼該系統將允許您提取比存款憑單多得多的借出代幣。

Uniswap V2如何像Oracle一樣工作

在上面的例子中,Uniswap V1價格讀取是有問題的,因為它們是即時的。V2部署了一個聰明的系統,用於將價格時間資料記錄在鏈上,這種方式在短時間內操作成本很高,並且不可能在單個交易中進行操作。透過使用“累計”價格時間值,可以將價格的可用時間加權為一個特殊值,每次代幣掉期都會花費少量的天然氣來保持這些值的同步。

以下是Uniswap市場程式碼片段:

注意:與V1不同,V2是兩個代幣之間的市場。在內部,這些令牌中的一個需要表示為token0,另一個表示為token1。他們的餘額由相應的reserve0和reserve1跟蹤。Uniswap Docs具有有關令牌排序的更多資訊。

contractUniswapV2Pair{//ContractStorageVariables:uintpublicprice0CumulativeLast;uintpublicprice1CumulativeLast;...//Theonlyplacethesestoragevariablesareupdated:function_update(uintbalance0,uintbalance1,uint112_reserve0,uint112_reserve1)private{uint32timeElapsed=blockTimestamp-blockTimestampLast;if(timeElapsed>0&&_reserve0!=0&&_reserve1!=0){price0CumulativeLast+=uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0))*timeElapsed;price1CumulativeLast+=uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1))*timeElapsed;}blockTimestampLast=blockTimestamp;}}

price(0 | 1)CumulativeLast是獨立的儲存變數,它們累積“ price-time”。UQ112x112使程式碼有點難以閱讀,但在概念上並不重要;它只充當高精度除法的包裝器。這些累積彈性值的“0”和“1”版本之間的唯一區別是價格的方向。

price0CumulativeLast是“以token1計價的token0的價格”

price1CumulativeLast是“以token0計價的token1的價格”

由於在進行這種累加時數學運算的方式,price0CumulativeLast不是price1CumulativeLast的倒數。對於本文件的其餘部分,我們將僅引用price0CumulativeLast,但同樣適用於這兩個值。另外,price0CumulativeLast不一定在每個區塊上都是最新的,因此您要麼需要在市場上執行sync(),要麼自己調整值。

price0CumulativeLast是一個值,它只更新區塊上的第一個事務,取最後一個已知的reserve0和reserve1值(token0和token1的令牌餘額),計算它們的比率(price),並按price0CumulativeLast上次更新後的秒數進行縮放。price0CumulativeLast是一個每秒鐘按兩個儲量的比率遞增的值。要將此值轉換回價格,需要兩個時間點值price0CumulativeLast,使用以下公式:

(price0CumulativeLATEST—price0CumulativeFIRST)/(timestampOfLATEST—timestampOfFIRST)

透過將兩個樣本中價格累計的差值除以這兩個樣本之間的秒數,過程被逆轉,結果就是該時段的時間加權價格。您選擇的視窗是一個重要的安全考慮因素:

兩個樣本之間的秒數越少,更新的時間就越多,但更易於操作。

兩個樣本之間的秒數越長,更新的時間越少,但操作起來就越困難。

在防篡改和最新之間找到正確的平衡點應該仔細考慮您的專案。

現在我們已經有了計算這個價格的公式,但仍然存在一個問題:如何檢索鏈上的歷史價格累積資訊?

使用智慧合約檢索歷史累計值

利用V2作為鏈上的Oracle,需要“證明”以下各項的先驗值:price0CumulativeLast及其對應的塊時間戳。

檢索每個值的當前值都很簡單(block.timstamp和uniswapMarket.price0CumulativeLast()),但是如何檢索舊值呢?最直接的方法是部署一個智慧合約,該合約將price0CumulativeLast和時間戳的當前值記錄到其自己的儲存中,以供以後呼叫以作為歷史值。儘管這可行,但它有一些缺點:

必須定期呼叫以儲存快照值,如果您希望該價格饋送在將來持續可用。

如果不定期呼叫,則必須提前計劃事務,首先儲存當前值,等待一段時間,然後啟動使用該歷史值的事務。

你總是在某種程度上激勵機器人不斷更新儲存值(機器人費用來自系統中其他地方的利潤),或者要求使用者傳送兩個事務,一個用來獲取累計值,將他們希望執行的事務延遲一些非常重要的時間,以達到價格饋送平均值所需的秒數。

如果你對為機器人設計經濟系統不感興趣,並且你懷疑使用者是否願意等待傳送兩個交易,有一個更好的方法來利用Uniswap V2作為價格來源:Merkle Patricia Proof!

利用儲存證明檢索歷史累計值

以太坊合約狀態儲存在“ Merkle Trie”中,這是一種特殊的資料結構,它允許一個32位元組的雜湊值代表每個以太坊合約中的每個儲存值(具有單獨的收據和交易資料嘗試)。這個32位元組的值稱為stateRoot,是每個以太坊塊(以及您可能更熟悉的塊,例如塊號,塊雜湊和時間戳)的屬性。

使用以太坊節點的JSON-RPC介面,您可以呼叫eth_getProof來檢索有效負載,當與該stateRoot值結合使用時,可以證明儲存插槽B上的地址A的值為C。

使用鏈上邏輯,可以將stateRoot和儲存證明結合起來以驗證儲存插槽的值。如果我們針對Uniswap V2市場和price0CumulativeLast的儲存插槽,則可以實現所需的基於證明的歷史查詢。

但是stateRoot查詢不能作為EVM操作碼使用。唯一相關的操作碼是BLOCKHASH,它採用blockNumber並返回32位元組的區塊雜湊。塊的blockhash是一個簡單的Keccak256雜湊,它具有rlp編碼的所有各種屬性。透過提供一個區塊的所有屬性(包括stateRoot),我們可以透過雜湊並將其與鏈上blockHash查詢進行比較,從而驗證原始塊資料是否有效。驗證之後,我們便可以使用區塊的必需屬性(時間戳和stateRoot)。

//NOTE:Non-functionalpseudocodefunctionverifyBlock(parentBlock,stateRoot,blockNumber,timestamp,...)returns(bool){bytes32_realBlockHash=blockhash(blockNumber);bytes32_proposedBlockHash=keccek256(rlpEncode(parentBlock,stateRoot,blockNumber,timestamp,...));return_proposedBlockHash==_realBlockHash;}

1. 像上面這樣的函式可以驗證完整塊的詳細資訊,並確認該塊的所有欄位都是正確的

2. 使用stateRoot(上面已驗證)解析提供的證明(來自JSON-RPC getProof呼叫)從該塊檢索歷史儲存值

3. 從Uniswap market獲取當前價格0累積最新值

4. 透過將price0CumulativeLast中的增量除以自驗證時間戳以來的秒數,計算所提供塊(來自驗證的時間戳)和現在之間的平均價格。

此時,您可以在純粹可根據市場動態從完全去中心化的系統中,在一段可配置的時間內平均獲得價格。為了讓這個價格反饋被操縱,攻擊者不僅需要向一個方向推動這個價格,他們還需要在區塊之間長時間保持價格不變,讓任何買家都有機會購買價格偏低的資產,這反過來又會糾正價格上漲。

注意:鏈上BLOCKHASH查詢僅適用於過去256個區塊,可用於儲存證明的最舊塊必須在事務落入鏈上時位於最後256個區塊之內。

介紹Uniswap Oracle庫

以上策略由少量客戶端程式碼(用於處理證明)和大量相當複雜的Solidity組成,包括YUL/assembly和Merkle Trie驗證。作為Keydonix開發團隊的一員,Micah Zoltu和我開發併發布了Uniswap-Oracle,這是一個Solidity庫,使其他智慧合約可以利用此oracle功能。

要與您自己的智慧合約整合,只需簡單地從基本合約UniswapOracle.sol(contract HelloWorld is UniswapOracle)繼承,您的合約將繼承getPrice函式:

functiongetPrice(IUniswapV2PairuniswapV2Pair,addressdenominationToken,uint8minBlocksBack,uint8maxBlocksBack,ProofDatamemoryproofData)publicviewreturns(uint256price,uint256blockNumber)

需要訪問此Uniswap price的您自己的函式將需要接收此證明資料作為引數傳遞給此內部getPrice函式呼叫。請參閱Uniswap Oracle自述檔案.me整合文件。

Uniswap-Oracle庫未經稽覈。任何對主網有價值的應用程式都應該進行全面審計;請確保您的應用程式的審計也包括Uniswap的Oracle程式碼。

------------------------------------

原文作者:Scott Bigelow

原文連結:https://medium.com/@epheph/using-uniswap-v2-oracle-with-storage-proofs-3530e699e1d3

譯者:鏈三豐

譯文出處:http://bitoken.world

------------------------------------------

描下方二維碼新增我,拉您進入技術交流群

掃碼

關注我們

獲得

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;