背景
在實現 LP池子時,需要計算 LP Token 的代幣價值,從而方便的給用戶提示 LP Token 當前准確的價格,來計算收益率。所以對 LP Token 的價值計算做了一點深入的研究,並且還翻閱到 Alpha Finance 團隊的關於安全獲取 LP 價格的方法。 本位將這些學習筆記分享給大家。
一般 LP Token 價格的獲取方法
我們知道對於一般 Token 的價格,在 Cex 中其實是市場上交易撮合的成交價。在 Dex 中,由於 AMM 做市商模型通過一組 LP 來構建價格的錨定。所以如果我們想獲取到一個 Token 的價格,都是通過對於穩定幣 USDT、USDC 或者 BUSD 的幣對關系,從而反映現實世界的價格。
我們知道 LP Token 是不具有流動性池的,如果有那就是套娃了。那么我們應該如何去計算價格呢?其實我們只需要用總增發量和貨幣價格反推即可。
任意一個 Token X 的總市值是 Capx,是用當前的價格 Px 和當前總鑄造數量Tx相乘可得。對於 LP Token,我們可以用這個公式來反推幣價。因為在 LP Token 中,總市值是可以通過兩種幣的數量和對應價格求得,並且總的制造數量也是已知的。
所以我們可以如此計算 LP Token 總價格:
其中,r0和 r1就是 LP Token 合約中兩種代幣的存量,price0和 price1分別代表 r0和 r1 對應 Token 的價格。市面上無論 BSC、ETH 還是 Polygon 還是 Heco 鏈等,其 LP 代幣基本都是 fork Uniswap 的,所以 r0和 r1、price0和 price1 都是能拿到的。
上面的公式我們其實可以看出,是通過市值反推價格,也沒有什么巨大的邏輯問題。當我們需要訪問其幣價的時候已經可以滿足需求。在 Web3.js 前端中,我們就可以照此拿到結果。
export const getLpTokenPrice = async ( lpAddress: string, lib: any, price0: BigNumber, price1: BigNumber ) => { const lpToken = getPancakeLp(lib, lpAddress); let [r0, r1] = (await lpToken.getReserves()).map((n) => bignumberToBN(n)); let totalSupply = bignumberToBN(await lpToken.totalSupply()); return r0 .multipliedBy(price0) .plus(r1.multipliedBy(price1)) .dividedBy(totalSupply); };
至此,我的需求完成。
延時喂價漏洞
對於上文公式:
其實乍一看是不存在問題的。但是如果我們所做的需求,不僅僅是一個價格展示,而是一個借貸系統,用這種方式來獲取清算系數,就會存在被閃電貸的風險。雖然 price0和 price1不能被操控,但是 r0和 r1是可以的。黑客可以通過操作 r0 和 r1,從而對價格實現控制。
之前漫霧團隊寫過一篇「Warp Finance 被黑詳解」的分析,采用了如下攻擊流程:
- 通過 dydx 與 Uniswap 閃電貸借出 DAI 和 WETH;
- 用小部分 DAI 和 WETH 在 Uniswap 的 WETH-DAI LP 中添加流動性,獲得 LP Token;
- 將 LP Token 抵押到 Wrap Finance 中;
- 用巨量的 WETH 兌換成 DAI,因為 WETH 迅速進入了 WETH-DAI 流動池,總數量大增。但是由於價格使用的是 Uniswap 的預言機,訪問的是 Uniswap 的 LP 池,所以 WETH 的價格並未發生變化。從而導致 Wrap Finance 中的 WETH-DAI LP Token 價格迅速提高;
- 由於 LP Token 單價變高,導致黑客抵押的 LP Token 可以借出更多的穩定幣來獲息。
這里,我們發現漏洞的關鍵地方,其實是 price 計算對於借貸項目中,使用的是他人的 LP 合約,還未等套利者來平衡價格,從而終究留出了時間差。
為了解決這個問題,如果我們可以找到一種方式,從而規避價格查詢,就能大概率防止上述漏洞。這里,Alpha Finance 給出了另外一個推導公式。
獲取公平 LP 價格方法
首先我們在一個 LP 池中,我們能保證的是恆定乘積 KK 值的大小,我們定義價格比值是 PP,那么我們會有以下關系式:
因為 r0 和 r1 在舊方法中是可以被操縱的,所以我們用 K 和 P 來反解真實的 r0′ 和 r1′ :
如此,我們在帶入一開始計算 pricelp的公式中: