2025年11月6日更新:Balancer は公式予備報告書 [6] を公開し、当社の分析で特定された根本原因を確認しました。
2025年11月3日、Balancer V2 の Composable Stable Pools は、複数のチェーンにわたる いくつかのフォークプロジェクト と共に、連携されたエクスプロイトの被害に遭い、総額1億2500万ドル以上の損失が発生しました。BlockSec は最も早い時期にアラートを発し [1]、その後初期分析を発表しました [2]。
これは非常に高度な攻撃でした。当社の調査によると、根本原因は、不変量計算における精度損失に起因する価格操作であり、それがBPT(Balancer Pool Token)の価格計算を歪めたことが明らかになりました。この不変量の操作により、攻撃者は単一のバッチスワップを通じて特定のステーブルプールから利益を得ることができました。一部の研究者は洞察に満ちた分析を提供していますが、特定の解釈は誤解を招くものであり、根本原因と攻撃プロセスはまだ完全に解明されていません。このブログは、インシデントに関する包括的かつ正確な技術的分析を提示することを目的としています。
主要なポイント (TL;DR)
根本原因:丸め処理の不整合と精度損失
- アップスケーリング操作は単方向丸め(切り捨て)を使用しますが、ダウン・スケーリング操作は双方向丸め(切り上げおよび切り捨て)を使用します。
- この不整合により精度損失が発生し、慎重に作成されたスワップパスを通じて悪用されると、丸め処理が常にプロトコルに有利になるという標準原則に違反します。
エクスプロイト実行
- 攻撃者は、精度損失の効果を最大化するために、反復回数や入力値を含むパラメータを意図的に作成しました。
- 攻撃者は、検出を回避するために2段階のアプローチを使用しました。まず、即時の利益なしに単一トランザクション内でコアエクスプロイトを実行し、その後、別のトランザクションで資産を引き出すことで利益を実現しました。
運用上の影響と増幅
- 特定の制約により、プロトコルを一時停止できませんでした [3]。この運用停止能力の欠如は、エクスプロイトの影響を悪化させ、多数の subsequent または copycat 攻撃を可能にしました。
次のセクションでは、まず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 は Vault [5] 内でのマルチホップスワップを可能にする batchSwap() 関数を提供します。この関数に渡されるパラメータによって決定されるスワップタイプが2つあります。
- GIVEN_IN(「指定イン」):呼び出し元が正確な入力トークン量を指定し、プールが対応する出力量を計算します。
- GIVEN_OUT(「指定アウト」):呼び出し元が希望する出力量を指定し、プールが必要な入力量を計算します。
通常、batchSwap() は onSwap() 関数を介して実行される複数のトークン間スワップで構成されます。以下は、SwapRequest が GIVEN_OUT スワップタイプに割り当てられたときの実行パスの概要です(ComposableStablePool は BaseGeneralPool から継承していることに注意してください)。
以下は、GIVEN_OUT スワップタイプの amount_in の計算を示しており、これには不変量 D が含まれます。
スケーリングと丸め処理
異なるトークン残高間での計算を正規化するために、Balancer は以下の2つの操作を実行します。
- アップスケーリング:計算を実行する前に、残高と金額を統合された内部精度にスケールアップします。
- ダウン・スケーリング:結果をネイティブ精度に戻し、方向性のある丸め処理を適用します(例:プールが過少請求しないように、入力金額は通常切り上げられ、出力金額はしばしば切り捨てられます)。
この不整合の理由は不明です。_upscale() 関数のコメントによると、開発者は単一方向の丸め処理の影響は最小限であると考えています。
// Upscale rounding wouldn't necessarily always go in the same direction: in a swap for example the balance of // token in should be rounded up, and that of token out rounded down. This is the only place where we round in // the same direction for all amounts, as the impact of this rounding is expected to be minimal (and there's no // rounding error unless
_scalingFactor()is overriden).
0x2 脆弱性分析
根本的な問題は、BaseGeneralPool._swapGivenOut() 関数のアップスケーリング中の丸め切り捨て操作に起因します。特に、_swapGivenOut() は _upscale() 関数を通じて swapRequest.amount を誤って切り捨てます。結果として丸められた値は、_onSwapGivenOut() を介して amountIn を計算する際に amountOut として使用されます。この動作は、丸め処理がプロトコルに有利な方法で適用されるべきであるという標準的な慣行と矛盾します。
したがって、与えられたプール(wstETH/rETH/cbETH)について、計算された amountIn は実際の必要入力量を過小評価します。これにより、ユーザーは一方の基盤資産(例:wstETH)をもう一方(例:cbETH)よりも少ない量で交換でき、結果として実効流動性の低下により不変量 D が減少します。その結果、対応する BPT(wstETH/rETH/cbETH)の価格は、BPT 価格 = D / totalSupply であるため、人工的に希薄化されます。
0x3 攻撃分析
攻撃者は、検出リスクを最小限に抑えるために、2段階の攻撃を実行しました。
- 第1段階では、単一トランザクション内でコアエクスプロイトが実行され、即時の利益は得られませんでした。
- 第2段階では、攻撃者は別のトランザクションで資産を引き出すことで利益を実現しました。
第1段階はさらに2つのフェーズに分けることができます。パラメータ計算とバッチスワップです。以下に、Arbitrum 上の 攻撃トランザクション (TX) の例を使用してこれらのフェーズを説明します。
パラメータ計算フェーズ
このフェーズでは、攻撃者はオフチェーン計算とオンチェーンシミュレーションを組み合わせて、composable Stable Pool の現在の状態(スケーリング係数、増幅係数、BPT レート、スワップ手数料、その他のパラメータを含む)に基づいて、次の(バッチスワップ)フェーズの各ホップのパラメータを正確に調整しました。興味深いことに、攻撃者はこれらの計算を支援するために補助コントラクトもデプロイしており、これはフロントランニングへの暴露を減らすことを意図していた可能性があります。
まず、攻撃者はターゲットプールの基本情報(各トークンのスケーリング係数、増幅パラメータ、BPT レート、スワップ手数料率)を収集します。次に、精度損失を誘発するために使用されるターゲットトークンの操作された量である trickAmt というキー値を計算します。
ターゲットトークンのスケーリング係数を sF とすると、計算は次のようになります。
次の(バッチスワップ)フェーズのステップ2で使用されるパラメータを決定するために、攻撃者は補助コントラクトの 0x524c9e20 関数に以下のコールデータを使用して、後続のシミュレーション呼び出しを行いました。
uint256[] balances; // プールトークンの残高(BPTを除く) uint256[] scalingFactors; // 各プールトークンのスケーリング係数 uint tokenIn; // このホップのシミュレーションの入力トークンのインデックス uint tokenOut; // このホップのシミュレーションの出力トークンのインデックス uint256 amountOut; // 希望する出力トークン量 uint256 amp; // プールの増幅パラメータ uint256 fee; // プールスワップ手数料率
そして、返されるデータは次のとおりです。
uint256[] balances; // スワップ後のプールトークン残高(BPTを除く)
具体的には、初期残高と反復ループ回数はオフチェーンで計算され、攻撃者のコントラクトにパラメータとして渡されました(それぞれ 100,000,000,000 と 25 と報告されています)。各反復は3つのスワップを実行します。
- スワップ 1:スワップ方向が 0 → 1 であると仮定して、ターゲットトークンの量を trickAmt + 1 にプッシュします。
- スワップ 2:trickAmt でターゲットトークンをスワップアウトし続け、_upscale() の呼び出しで丸め切り捨てをトリガーします。
- スワップ 3:逆スワップ操作(1 → 0)を実行します。スワップされる量は、プールの現在のトークン残高から、上位2桁の小数部分を切り捨てることによって得られます。つまり、rd が小数桁数である場合、10^d-2 の最も近い倍数に切り捨てられます。例:324,816 → 320,000。
- このステップは、StableMath 計算で使用されるニュートン・ラフソン法のために、時折失敗する可能性があることに注意してください。これを軽減するために、攻撃者は元の値の 9/10 のフォールバックを使用した2回の再試行を実装しています。 攻撃者の補助コントラクトは、"BAL"-style のカスタムエラーメッセージが含まれていることから、Balancer V2 の StableMath ライブラリから派生しています。
バッチスワップフェーズ
次に、batchSwap() 操作は3つのステップに分解できます。
-
ステップ 1:攻撃者は BPT(wstETH/rETH/cbETH)を基盤資産にスワップし、1つのトークン(cbETH)の残高を丸め境界の端(amount = 9)に正確に調整します。これにより、次のステップでの精度損失の条件が設定されます。
-
ステップ 2:攻撃者は、作成された量(= 8)を使用して、別の基盤資産(wstETH)と cbETH の間でスワップします。トークン量をスケールアップする際の丸め切り捨てにより、計算された Δx はわずかに小さくなり(8.918 から 8 に)、過小評価された Δy とその結果として不変量(Curve の StableSwap モデルの D)が減少します。BPT 価格 = D / totalSupply であるため、BPT 価格は人工的に希薄化されます。
- ステップ 3:攻撃者は基盤資産を BPT に逆スワップし、バランスを回復しながら希薄化された BPT 価格から利益を得ます。
0x4 攻撃と損失
以下の表に、攻撃とその対応する損失をまとめました。総損失は 1億2500万ドル を超えています。
0x5 結論
このインシデントは、Balancer V2 プロトコルとそのフォークプロジェクトを標的とした一連の攻撃トランザクションであり、重大な財務的損失をもたらしました。最初の攻撃の後、複数のチェーンで多数の subsequent および copycat トランザクションが観測されました。このイベントは、DeFi プロトコルの設計とセキュリティに関するいくつかの重要な教訓を浮き彫りにしています。
-
丸め処理の動作と精度損失:アップスケーリング操作で使用される単方向丸め(切り捨て)は、ダウン・スケーリング操作で使用される双方向丸め(切り上げおよび切り捨て)とは異なります。同様の脆弱性を防ぐために、プロトコルはより高精度の算術演算を使用し、堅牢な検証チェックを実装する必要があります。丸め処理が常にプロトコルに有利になるという標準原則を維持することが不可欠です。
-
搾取の進化:攻撃者は、検出を回避するために設計された洗練された2段階のエクスプロイトを実行しました。第1段階では、攻撃者は即時の利益なしに単一トランザクション内でコアエクスプロイトを実行しました。第2段階では、攻撃者は別のトランザクションで資産を引き出すことで利益を実現しました。このインシデントは、セキュリティ研究者と攻撃者の間の継続的な軍拡競争を再び浮き彫りにしています。
-
運用上の認識と脅威対応:このインシデントは、初期化と運用状況に関するタイムリーなアラートの重要性、および継続的または copycat 攻撃による潜在的な損失を軽減するための積極的な脅威検出および防止メカニズムの重要性を強調しています。
運用およびビジネスの継続性を維持しながら、業界参加者は 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



