什麼是價格#
Uniswap 作為鏈上的去中心化交易所,承載著價值發現的功能,即用戶或其他鏈上合約可以通過 Uniswap 來獲取代幣的價格,Uniswap 在這其中承擔鏈上預言機的功能。
假設當前交易池中有 1 Ether 和 2000 USDC,那麼以太幣的價格就是 2000/1=2000 USDC,反之就是 USDC 的價格,因此價格就是一個比率,由於智能合約尚不支持小數,所以在 Uniswap 的代碼中拓展了新的數據類型來存儲價格,關於新的數據類型後面會專門做介紹。
TWAP 價格機制#
然而僅僅使用瞬時的代幣數目之比作為價格是不安全的,存在人為操縱價格預言機的風險,由於 Uniswap 提供了閃電貸功能,因此在某個閃電貸交易的瞬間,交易對內的代幣餘額會產生劇烈波動。在 UniswapV2 中為了解決這個問題,採用了 TWAP(Time Weighted Average Price)即時間加權的價格預言機機制。
具體工作原理如下:
- 假設過去一天,資產在前 20 個小時的價格為 20$,最近 4 小時的價格為 10$,那麼 TWAP=($20*20+10$*4)/24 = 18.33$
- 假設過去一天,資產在第一個小時的價格為 10$,最近 23 小時的價格為 15$,那麼 TWAP=($10*1+15$*23)/24 = 14.79$
總結下來,TWAP 的公式如下,這裡的 T 是時間段,P 是對應時間段的價格
在 UniswapV2 的合約中,只會記錄分子部分,即記錄每個時間段乘以單價的求和,而分母部分則需要使用方自行維護,交易對內有兩個代幣,所有有兩個值來記錄
通常我們只需關心某一時間區間內的代幣價格,這是 TWAP 公式的歷史價格公式:
假設我們的計價從 T4 開始,那麼實際的計價公式應該如下:
前面已經提到,在合約內有一個變量會追蹤分子的求和值,以 token0 的追蹤計價變量 price0Cumulativelast 為例:
這個變量是記錄了歷史以來所有時間段的求和,那麼我們只需要從 T4 開始的部分即可,計算方式也很簡單,在 T3 時間點我們獲取一個 price0Cumulativelast 變量的快照,在最新即 T6 時間點再獲取一次,兩次的差值即是 T4 - 最新時間段內 token0 的計價和
我們自己也維護了最近的窗口持續時間和,即:T4+T5+T6
那麼這段時間內的 TWAP 價格即可計算得出:(price0Cumulativelast-UpToTime3)/(T4+T5+T6)
UniswapV2 的實現#
具體到 Uniswap 的實現中,對於每個交易對都維護了兩個變量 price0Cumulativelast 和 price1Cumulativelast,在之前提到的_update
方法中進行求和,具體的代碼如下:
- 首先獲取當前的區塊時間戳
blockTimestamp
- 通過與 blockTimestampLast 相減,計算出距離上次更新過去了多少時間
timeElapsed
- 只有在
timeElapsed
>0 時,即進入下一個區塊時,才會累加價格 * 時間段 - 注意這裡的價格計算用到了
UQ112x112
的特殊格式,了解它是為了記錄小數即可,後面會專門講解這裡的優化 - 在處理完累加後,才會更新
blockTimestampLast
到最新的區塊時間戳
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
...
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && _reserve0 != 0 && _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;
...
}
TWAP 的潛在問題#
-
基於時間加權的價格計算,會提高攻擊者的操縱成本,因為需要連續控制多個區塊,這樣的攻擊成本是很高的。
當價格產生劇烈波動時,由於有時間作為加權因素,預言機的價格無法在較短時間內反映出價格的波動,反而提供出過時的價格,尤其是在市場發生劇烈動盪時,這樣的情況會導致 Uniswap 中的價格與外部市場產生較大的差異。 -
使用 TWAP 預言機仍然依賴鏈下的定時觸發,存在維護成本與中心化問題。