設計經濟模型時,應對邊界條件進行充分測試並嚴格判斷流動性與價格。

作者:九九,Kong & Lisa

背景

據慢霧安全團隊情報,2023 年 11 月 23 日,去中心化交易平臺 KyberSwap 遭到攻擊,攻擊者獲利約 5470 萬美元。 慢霧安全團隊第一時間介入分析,並將結果分享如下:

根本原因

由於 KyberSwap Elastic 的 Reinvestment Curve(再投資曲線特性),在基礎流動性與再投資流動性作為實際流動性參與計算的情況下,使得池子通過 calcReachAmount 函數在刻度邊界計算兌換所需的代幣數量大於預期,造成下一價格 sqrtP 超過邊界刻度的 sqrtP,且池子使用不等號對 sqrtP 進行檢查, 導致協定未按預期的通過 _updateLiquidityAndCrossTick 更新流動性。

前置知識

在分析開始前,我們需要了解關於 KyberSwap 一些關鍵性知識以便理解本次分析內容。

KyberSwap 是一個鏈上去中心化交易平臺,其具有一種新型的流動性優化模型——KyberSwap Elastic。 該模型採用的集中流動性做市商機制使 LP 能夠將流動性分配給定製的價格區間,並且引入再投資曲線,自動為 LP 複利在池子中閑置的流動性費用。

首先,何謂集中流動性做市商(CLMM)? 與 Uniswap v3 類似,流動性提供者可以將其資金在自定義價格區間內提供流動性,只有當價格落在此區間內,其流動性才會被使用。 Uniswap v3(https://blog.uniswap.org/uniswap-v3)與 KyberSwap(https://docs.kyberswap.com/liquidity-solutions/kyberswap-elastic)都提供了詳細的文檔進行解釋說明,我們這裡以一個簡易的 ETH/USDC 池子圖例來說明閱讀此文章所需瞭解的知識:

From: KyberSwap Elastic Liquidity Concept ( https://docs.kyberswap.com/liquidity-solutions/kyberswap-elastic/concepts/concentrated-liquidity#liquidity-tracking-lp-contributions-at-a-specific-price)

以上是具有三個相同流動性倉位組成的 ETH/USDC 池子,當前價格為 1995。 在 CLMM 中,價格範圍被成為 tick-range。 倉位 1 流動性所在的 tick 範圍為 1960-2020,倉位 2 流動性所在的 tick 範圍為 1980-2000,倉位 3 流動性所在的 tick 範圍為 1990-2000。

由圖可得當前範圍所在位置是流動性最好的範圍,三個倉位流動性在 tick 1990-2000 上重合。 當 ETH 價格下跌至 1985 時,其價格將向左移動越過 tick1990 的範圍,此時其將要離開倉位 3 的流動性範圍,但仍在倉位 1 和 2 的流動性範圍中,因此將會更新新範圍內的流動性,將倉位 3 的流動性排除。 而當 ETH 價格上漲至 2005 時,其價格將向右移動越過 tick 2000 的範圍,此時將排除倉位 2 和 3 的流動性,但其仍在倉位 1 範圍里。 即當價格跨過流動性邊界時必將更新流動性,或增加或減少。

與 Uniswap v3 不同的是,KyberSwap Elastic 創新的引入了一個新特性 —— Reinvestment Curve(再投資曲線)。 這是一個額外的 AMM 池,其將使用者在池子中兌換收取的費用累積到其中,其曲線支援從 0 到無窮的價格範圍。 KyberSwap Elastic 通過過將再投資曲線與原始價格曲線進行聚合(即曲線分離,但資金仍在同一個池中),使得 LP 的費用及時在價格超出其倉位範圍時也能複利賺取收益。

簡單瞭解 KyberSwap Elastic 的機制後,我們對攻擊步驟進行分析。

攻擊步驟分析

此處以攻擊交易 0x485... 0f3 為例進行分析:

1. 攻擊者首先從 AAVE 中閃電貸出 2000 枚 WETH,並在 KyberSwap 的池子中用 6.8496 枚 WETH 兌換成 frxETH,使 frxETH 的價格越過流動性提供者的所有倉位範圍。 此時當前的價格數值 sqrtP(當前價格乘以 2^96 的平方根表示)被拉升至 20282409603651670423947251286016,位於 tick 110909 上。

2. 接下來攻擊者在指定的價格區間 [110909,111310] 添加了 0.006948 枚 frxETH 和 0.1078 枚 WETH 作為流動性,隨後又移除了部分的流動性,最終將該價格區間里的流動性數值控制在 74692747583654757908 以使得流動性符合後續攻擊計算時所需的數額。 此時 tick [110909,111310] 區間只有攻擊者一人擁有流動性,tick 111310 的價格數值 sqrtP 為 20693058119558072255662180724088。

3. 之後攻擊者在當前價格刻度 110909 用 387.17 枚 WETH 兌換出 0.005789 枚 frxETH。 此次大額兌換將當前價格數值 sqrtP 拉升至 20693058119558072255665971001964,超過了邊界刻度 111310 上的 sqrtP。

4. 最後攻擊者從略大於價格刻度 111310 的 sqrtP 處,使用 0.005868 枚 frxETH 反向兌換出 396.2 枚 WETH,兌換后價格落回 [110909,111310] 刻度範圍內。 此時攻擊者已經獲利,其反向兌換比正向兌換多換出了約 9 枚 WETH。

為何如此樸實無華的攻擊步驟卻能換出多於預期的資金呢? 這與 KyberSwap Elastic 的再投資曲線有著極大的關聯,我們接下來通過詳細剖析來揭秘其獲利方式。

攻擊原理剖析

通過上述步驟我們知道在進行最後一步反向兌換時換出了多於預期的資金,在進行兌換時當前 sqrtP 為 20693058119558072255665971001964,這大於攻擊者添加流動性時的 tickUpper 111310 所在的價格,我們用刻度圖示意其所在的位置。

由於超過了攻擊者添加的流動性刻度 [110909,111310] 範圍,因此當前 sqrtP 所在的位置理論上是沒有流動性的,其在兌換過程中只能向左跨過 111310 刻度才能獲得有效的流動性進行兌換,我們跟進查看其是否如預期進行兌換。

如下圖所示,當我們查看當前 sqrtP 所在刻度的流動性時,我們發現不考慮重投資曲線的情況下,這裡本該是 0 流動性的範圍內卻非預期的出現了大量流動性且遠大於重投資曲線的流動性,並且流動性數額與刻度 [110909,111310] 範圍內一致。

這使得在進行兌換時,將在刻度 111310 進行有效的代幣兌換,如下圖所示。

在刻度 111310 進行有效兌換后,sqrtP 將跨過此刻度進入 [110909,111310] 範圍兌換剩餘的代幣。 回顧前置知識我們知道在跨越流動性倉位範圍時將進行流動性更新,在 KyberSwap Elastic Pool 中會通過 _updateLiquidityAndCrossTick 函數將 [110909,111310] 範圍內的流動性加入曲線中以參與代幣兌換,如下圖所示。

這將使得刻度 [110909,111310] 範圍內的有效流動性會與刻度 111310 右邊多出的虛假流動性相加,導致在刻度 [110909,111310] 範圍內進行兌換時的總有效流動性遠大於預期,如下圖所示,有效流動性比預期增加了一倍。

而由於當前刻度範圍內流動性的增加,使得池子的深度比預期更好,因此攻擊者可以獲得比預期多的資金,這些額外的資金來自於池子中其他刻度範圍的流動性。

而為什麼在刻度 111310 右邊會多出非預期的流動性,且流動性數量還與 [110909,111310] 範圍的流動性相同呢? 唯一的解釋只能是在前一次兌換過程中池子並未按照預期進行流動性更新操作。 如下圖所示,理論上在進行前一次兌換跨過 tick 111310 時,也應該調用 _updateLiquidityAndCrossTick 函數,更新 sqrtP 進入 tick 111310 右邊后的流動性。

我們實際分析此兌換過程,在進行兌換時 Pool 將通過 computeSwapStep 函數計算用於兌換的實際數額,以及兌換費用和新的 sqrtP 價格數值。 理論上,在跨越流動性範圍時,計算的 sqrtP 結果將會是落在範圍邊界的刻度 111310 的 sqrtP。 但實際上新的 sqrtP 已經超過了刻度 111310 的 sqrtP。 如下圖所示,刻度 111310 的 sqrtP 為 20693058119558072255662180724088,但實際 sqrtP 卻是 20693058119558072255665971001964。

由於新的 sqrtP 並未落在邊界刻度 111310 的 sqrtP 上,即 swapData.sqrtP != swapData.nextSqrtP,這將使得池子認為當前 sqrtP 還在 [110909,111310] 範圍內,因此將跳出流動性檢查操作,不會觸發 _updateLiquidityAndCrossTick 函數進行流動性更新!

但為什麼會出現新的 nextSqrtP 未落在邊界上的情況呢? 通過分析 calcReachAmount 計算,我們可以發現攻擊兌換的數額 387170294533119999999 正好小於當前範圍內的流動性數量 387170294533120000000。

這使得 nextSqrtP 不會被賦值為 targetSqrtP,而是仍然為 0,因此其將直接通過 calcFinalPrice 函數進行最後的 sqrtP 計算,這使得其計算結果大於刻度 111310 的 sqrtP。

因此 calcReachAmount 函數是關鍵,其用於計算從 currentSqrtP 到達 targetSqrtP 的兌換中所需的代幣數量。 通過分析其計算公式,我們可以知道其計算結果主要取決於當前流動性 L,而當前流動性 L 是基礎流動性和再投資流動性的總和。

我們都知道在 Uniswap v3 中並沒有再投資曲線的特性,因此是否是因為加入了再投資流動性導致 calcReachAmount 的計算結果比預期的大呢?

通過測試,在不包含再投資流動性的情況下,calcReachAmount 計算結果為 387160697969657129472,這小於攻擊所兌換的數量 swapQty 387170294533119999999。

而在不包含再投資流動性的情況下 computeSwapStep 計算的 sqrtP 則剛好落在刻度 111310 上:

因此真相水落石出,由於 KyberSwap Elastic 的 Reinvestment Curve(再投資曲線)特性,在使用基礎流動性與再投資流動性計算從當前 sqrtP 到刻度邊界 sqrtP 的兌換中,所需的代幣數量將大於預期,這導致了兌換后的 sqrtP 超過刻度邊界的 sqrtP,使得協定認為當前刻度範圍內的流動性已經滿足了兌換所需, 進而停止對越過邊界刻度進行流動性更新的操作。

MistTrack 分析

KyberSwap Exploiter 1:0x50275e0b7261559ce1644014d4b78d4aa63be836

KyberSwap Exploiter 2:0xc9b826bad20872eb29f9b1d8af4befe8460b50c6

KyberSwap Exploiter 3:0xae7e16cAa7a4d572FfF09924Bf077a89485850Cb

KyberSwap Exploiter 4:0xd01896e3D4F130Ffd6f6a5A9d6780bbd7008d71d

據 MistTrack 分析,KyberSwap 攻擊者共獲利超 5470 萬美元,涉及 Ethereum、BSC、Arbitrum、Optimism、Polygon、BASE、Scroll、Avalanche 鏈。

在 Ethereum 上,KyberSwap Exploiter 1 的初始資金來自 Tornado Cash 轉入的 20 ETH。 其中 0.1 ETH 被轉移到 KyberSwap Exploiter 2,2 ETH 被轉移到 FixedFloat,6.5 ETH 被分別跨鏈到 Arbitrum, Optimism, Scroll, Base 鏈。 而 KyberSwap Exploiter 2 則獲利價值超 758 萬美元的 Token,包含 USDC, WETH, KNC 等,暫未轉移。

在 BSC 上,KyberSwap Exploiter 1 收到了 FixedFloat 轉入的 4.2678 BNB,作為餘額暫未轉移。

在 Arbitrum 上,KyberSwap Exploiter 2 獲利價值超 2029 萬美元的 Token,包含 WBTC, WETH, ARB, DAI 等,其中 500 WETH 轉移到 0x98d69d3ea5f7e03098400a5bedfbe49f2b0b88d3,該位址將 300 WETH 跨鏈到乙太坊,暫未轉移。 值得注意的是,KyberSwap Exploiter 2 將 1,000 WETH 轉移到 Indexed Finance Exploiter 的位址 0x84e66f86c28502c0fc8613e1d9cbbed806f7adb4。

在 Optimism 上,KyberSwapExploiter 2 獲利超 1564 萬美元的 Token,包含 wstETH, WETH, OP, DAI 等,暫未轉移。

在 Polygon 上,KyberSwap Exploiter 1 的初始資金來自 FixedFloat 轉入的 2,666.1243 MATIC,接著將 100 MATIC 轉到 KyberSwap Exploiter 2,目前 Exploiter 1 餘額為 2,564.0016 MATIC; 而 KyberSwap Exploiter 2 獲利超 293 萬美元的 Token,包含 WBTC, WETH, DAI 等,暫未轉移; KyberSwap Exploiter 3 獲利超 575 萬美元的 Token,包含 wstETH, USDT, USDC 等,並將大部分 Token 轉移到位址 0xa4c92d7482066878bb1e2c0510f42b20d79a7ea9。

在 BASE 上,KyberSwap Exploiter 2 獲利超 195 萬美元的 Token,包含 USDC, WETH 等,暫未轉移。

在 Avalanche 上,KyberSwap Exploiter 1 的初始資金來自 FixedFloat 轉入的 49 AVAX; 而 KyberSwap Exploiter 2 獲利超 2.35 萬美元的 Token,包含 293.0756 WAVAX, 17,316.0305 USDC,暫未轉移; KyberSwap Exploiter 4 獲利超 56.5 萬美元的 Token,包含 WAVAX, USDC 等,並將 USDC 轉移到位址 0x9296fa3246f478e32b05d4dde35176d927be703f。

慢霧安全團隊已拉黑相關位址,大部分資金仍未轉移,我們將持續監控資金異動。

結論

此次攻擊事件的根本原因在於計算當前價格到邊界刻度價格的兌換中,所需的代幣數量會因為 KyberSwap Elastic 的再投資曲線而將流動性多加上手續費複利的部分,從而造成其計算結果比預期大,可以覆蓋用戶兌換所需,但實際價格已經越過了邊界刻度,使得協定認為當前刻度範圍內的流動性已經滿足了兌換所需,故而未進行流動性更新。 最終導致反向兌換跨過邊界刻度時流動性增加了兩次,使得攻擊者獲得了多於預期的代幣。

慢霧安全團隊建議在設計經濟模型時,應對邊界條件進行充分測試,並且嚴格判斷流動性與價格而不是使用不等號進行檢查。

參考

攻擊者位址:0x50275e0b7261559ce1644014d4b78d4aa63be836

攻擊合約:0xaf2acf3d4ab78e4c702256d214a3189a874cdc13

相關攻擊交易:

0x485e08dc2b6a4b3aeadcb89c3d18a37666dc7d9424961a2091d6b3696792f0f3

0x09a3a12d58b0bb80e33e3fb8e282728551dc430c65d1e520fe0009ec519d75e8

0x396a83df7361519416a6dc960d394e689dd0f158095cbc6a6c387640716f5475

免責聲明:作為區塊鏈資訊平臺,本站所發佈文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。 文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。