Back to Blog

深入剖析:Balancer V2 漏洞利用事件

Code Auditing
November 5, 2025
8 min read

2025 年 11 月 6 日更新:Balancer 已發布官方初步報告 [6],確認了我們在分析中指出的根本原因。

2025 年 11 月 3 日,Balancer V2 的 Composable Stable Pools 以及跨多條鏈的多個分叉項目遭到協同攻擊,導致總損失超過 1.25 億美元。BlockSec 在第一時間發布了警報 [1],隨後發布了初步分析報告 [2]。

這是一次高度複雜的攻擊。我們的調查顯示,根本原因是縮放運算中因精度損失導致的價格操縱,進而扭曲了 BPT(Balancer Pool Token)的價格計算。這種不變量(invariant)操縱使得攻擊者能夠通過單次批量交易(batch swap)從特定的穩定池中獲利。儘管一些研究人員提供了深入見解,但某些解讀存在誤導性,且事件的根本原因和攻擊過程尚未完全釐清。本部落格旨在提供本次事件全面且準確的技術分析。

重點摘要 (TL;DR)

根本原因:四捨五入不一致與精度損失

  • 放大(upscaling)運算採用單向四捨五入(向下取整),而縮小(downscaling)運算採用雙向四捨五入(向上及向下取整)。
  • 這種不一致導致了精度損失,當通過精心設計的交易路徑利用時,它違背了「四捨五入應始終有利於協議」的標準原則。

漏洞執行

  • 攻擊者刻意編造參數,包括迭代次數和輸入值,以最大化精度損失的效果。
  • 攻擊者採用了兩階段方法以規避檢測:首先在單筆交易中執行核心攻擊且不立即獲利,隨後在另一筆交易中提取資產實現獲利。

營運影響與連鎖反應

  • 由於特定限制,該協議無法暫停 [3]。這種無法停止營運的狀況加劇了攻擊的影響,並促成了隨後的多次模仿攻擊。

在接下來的章節中,我們將首先提供關於 Balancer V2 的關鍵背景資訊,隨後對所識別的問題及相關攻擊進行深入分析。

0x1 背景

Balancer V2 的 Composable Stable Pool

本次攻擊中受影響的元件是 Balancer V2 協議的 Composable Stable Pool [4]。這些資金池專為預期保持近 1:1 兌換比例(或以已知匯率交易)的資產而設計,並允許以極低的價格滑點進行大額交易,從而顯著提高同類或相關資產之間的資本效率。每個資金池都有自己的 Balancer Pool Token (BPT),代表流動性提供者在池中的份額以及相應的底層資產。

  • 該資金池採用 Stable Math(基於 Curve 的 StableSwap 模型),其中不變量 D 代表資金池的虛擬總價值。
  • BPT 價格可近似表示為:

從上述公式可以看出,如果 D 在賬面上變得更小(即使實際資金並未流失),BPT 價格看起來就會變得更便宜。

batchSwap() 與 onSwap()

Balancer V2 提供了 batchSwap() 函數,允許在 Vault 內進行多跳交易 [5]。傳遞給此函數的參數決定了兩種交易類型:

  • GIVEN_IN ("Given In"):調用者指定確切的輸入代幣數量,資金池計算相應的輸出數量。
  • GIVEN_OUT ("Given Out"):調用者指定期望的輸出數量,資金池計算所需的輸入數量。

通常,batchSwap() 由透過 onSwap() 函數執行的多個代幣間交易組成。以下概述了當 SwapRequest 被指定為 GIVEN_OUT 交易類型時的執行路徑(請注意,ComposableStablePool 繼承自 BaseGeneralPool):

下圖顯示了 GIVEN_OUT 交易類型的 amount_in 計算方式,其中涉及不變量 D。

縮放與四捨五入

為了標準化不同代幣餘額之間的計算,Balancer 執行以下兩種操作:

  • 放大(Upscaling):在進行計算之前,將餘額和金額放大到統一的內部精度。
  • 縮小(Downscaling):將結果轉回其原始精度,應用方向性四捨五入(例如,輸入金額通常向上取整以確保池子不會少收,而輸出金額通常向下取整)。
顯然,放大和縮小在理論上是成對的乘法和除法運算。然而,這兩個運算的實現存在不一致性。具體而言,縮小運算有兩種變體或方向:divUp 和 divDown。相比之下,放大運算只有一個方向,即 mulDown。

這種不一致的原因尚不明確。根據 _upscale() 函數中的註釋,開發者認為單向四捨五入的影響微乎其微。

// 放大時的四捨五入不一定總是在同一個方向:例如在交易中,輸入代幣的餘額應向上取整,而輸出代幣的餘額應向下取整。這是唯一一個所有金額都朝同一方向捨入的地方,因為預計這種捨入的影響極小(除非覆蓋了 _scalingFactor(),否則不會出現捨入誤差)。

0x2 漏洞分析

根本問題源於 BaseGeneralPool._swapGivenOut() 函數在放大過程中執行的向下取整操作。特別是,_swapGivenOut() 在透過 _upscale() 函數處理時錯誤地將 swapRequest.amount 向下取整。所得的捨入值隨後在計算 _onSwapGivenOut() 中的 amountIn 時被用作 amountOut。這種做法與「應以有利於協議的方式應用捨入」的標準原則相悖。

因此,對於一個給定的資金池(wstETH/rETH/cbETH),計算出的 amountIn 低估了實際所需的輸入。這允許用戶以少量的一種底層資產(例如 wstETH)換取另一種(例如 cbETH),從而因為有效流動性的減少而導致不變量 D 下降。因此,相應 BPT (wstETH/rETH/cbETH) 的價格就會被壓低,因為 BPT 價格 = D / 總供應量。

0x3 攻擊分析

攻擊者執行了兩階段攻擊,很可能是為了降低被發現的風險:

  • 在第一階段,核心攻擊在單筆交易中完成,沒有產生即時利潤。
  • 在第二階段,攻擊者透過在單獨的交易中提取資產來實現利潤。

第一階段可進一步分為兩個階段:參數計算和批量交易。以下我們以Arbitrum 上的攻擊交易 (TX) 為例說明這些階段。

參數計算階段

在此階段,攻擊者結合鏈下計算和鏈上模擬,根據 Composable Stable Pool 的當前狀態(包括縮放因子、放大係數、BPT 匯率、交易費用和其他參數),精確調整下一個(批量交易)階段中每一跳的參數。有趣的是,攻擊者還部署了一個輔助合約來協助這些計算,這可能是為了減少被搶先交易(front-running)的風險。

開始時,攻擊者收集了目標資金池的基本資訊,包括每個代幣的縮放因子、放大參數、BPT 匯率和交易費用百分比。然後,他們計算一個稱為 trickAmt 的關鍵值,它是用於誘發精度損失的目標代幣操縱金額。

將目標代幣的縮放因子記為 sF,其計算方式為:

為了確定下一個(批量交易)階段第 2 步中使用的參數,攻擊者針對輔助合約的 0x524c9e20 函數進行了後續模擬調用,傳遞了以下 calldata:

uint256[] balances; // 資金池代幣餘額(不含 BPT)
uint256[] scalingFactors; // 每個資金池代幣的縮放因子
uint tokenIn; // 此跳模擬的輸入代幣索引
uint tokenOut; // 此跳模擬的輸出代幣索引
uint256 amountOut; // 期望的輸出代幣金額
uint256 amp; // 資金池的放大參數
uint256 fee; // 資金池交易費百分比

返回數據為:

uint256[] balances; // 交易後資金池代幣的餘額(不含 BPT)

具體而言,初始餘額和迭代循環次數是在鏈下計算並作為參數傳遞給攻擊者合約的(報告數值分別為 100,000,000,000 和 25)。每次迭代執行三次交易:

  • 交易 1:假設交易方向為 0 → 1,將目標代幣金額推至 trickAmt + 1。
  • 交易 2:繼續使用 trickAmt 交易出目標代幣,這會觸發 _upscale() 調用中的向下取整。
  • 交易 3:執行回調操作(1 → 0),其中要交易的金額是透過截斷資金池當前代幣餘額的最後兩位有效數字得出的,即向下取整至 10d210^{d-2} 的最近倍數,其中 d 是小數位數。例如,324,816 -> 320,000。
    • 注意,此步驟有時可能因為 StableMath 計算中使用的牛頓拉夫遜法(Newton–Raphson method)而失敗。為緩解此狀況,攻擊者實施了兩次重試嘗試,每次使用原始值的 9/10 作為後備。 攻擊者的輔助合約衍生自 Balancer V2 的 StableMath 庫,這從隨附的「BAL」風格自定義錯誤訊息中可以明顯看出。

批量交易階段

隨後,batchSwap() 操作可以分解為三個步驟:

  • 步驟 1:攻擊者將 BPT (wstETH/rETH/cbETH) 換成底層資產,以精確將一個代幣 (cbETH) 的餘額調整至四捨五入邊界(金額 = 9)。這為下一步的精度損失設定了條件。

  • 步驟 2:攻击者隨後使用精心構造的金額(= 8)在另一個底層資產 (wstETH) 和 cbETH 之間進行交易。由於在縮放代幣金額時向下取整,計算出的 Δx 變得略小(8.918 變為 8),導致 Δy 被低估,從而產生更小的不變量 D(基於 Curve 的 StableSwap 模型)。由於 BPT 價格 = D / 總供應量,BPT 價格被人工壓低。

  • 步驟 3:攻擊者將底層資產反向交易回 BPT,在恢復餘額的同時,從被壓低的 BPT 價格中獲利。

0x4 攻擊與損失

我們將攻擊及其對應的損失總結在下表中,總損失超過 1.25 億美元

0x5 結論

本次事件涉及一系列針對 Balancer V2 協議及其分叉項目的攻擊交易,導致了重大的財務損失。在初次攻擊後,跨多條鏈觀察到了無數次的後續與模仿交易。這一事件為 DeFi 協議的設計與安全帶來了幾個關鍵教訓:

  • 四捨五入行為與精度損失:放大運算中使用的單向四捨五入(向下取整)與縮小運算中使用的雙向四捨五入(向上與向下取整)不同。為防止類似漏洞,協議應採用更高精度的算術運算並實施穩健的驗證檢查。堅持「四捨五入應始終有利於協議」的標準原則至關重要。

  • 攻擊模式的演進:攻擊者執行了一次旨在規避檢測的高度複雜的兩階段攻擊。在第一階段,攻擊者在單筆交易中執行核心攻擊且不即時獲利。在第二階段,透過單獨交易提取資產實現獲利。此事件再次凸顯了安全研究人員與攻擊者之間持續進行的軍備競賽。

  • 營運意識與威脅對應:此事件強調了關於初始化狀態、營運狀態的及時警報,以及採取主動威脅檢測與預防機制以減輕持續或模仿攻擊造成潛在損失的重要性。

在維持營運與業務連續性的同時,業界參與者可以利用 BlockSec Phalcon 作為保護資產的最後一道防線。BlockSec 專家團隊隨時準備為您的專案進行全面的安全評估。

參考文獻

[1] https://x.com/Phalcon_xyz/status/1985262010347696312

[2] https://x.com/Phalcon_xyz/status/1985302779263643915

[3] https://x.com/Balancer/status/1985390307245244573

[4] https://docs-v2.balancer.fi/concepts/pools/composable-stable.html

[5] https://docs-v2.balancer.fi/reference/swaps/batch-swaps.html

[6] https://x.com/balancer/status/1986104426667401241

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit