2023年9月15日更新:Balancerは、このインシデントの全体像、経験、教訓について詳細な説明を提供する公式の事後分析を公開しました。この事後分析は、その複雑で優れた記述により、説得力があり、確かにあなたの時間を費やす価値があります。
セキュリティの観点から、この事後分析は2つのバグが存在することを示しています。最初のバグは、我々のレポートで議論した丸め誤差であり、2番目のバグは、我々のレポートで説明されているように、攻撃ステップ3.6および3.7で発生した「0供給時のレートリセット」です。Balancerのレポートでは、2番目のバグを最も重大な問題と見なし、最初のバグは寄与的なものであると考えています。しかし、我々は、利益を得るためには両方のバグが等しく重要であると考えています。
-
最初のバグは、トークンレートを急騰させるために使用され、利益の根本原因となります。これなしでは、利益を生み出すことは不可能でした。
-
2番目のバグは、bb-a-トークンの債務をバランスさせることで、エクスプロイトを可能にします。これなしでは、攻撃者はこれらのトークンを取得する他のソースがないため(攻撃者が何らかの方法でそれらを入手することに成功しない限り)、bb-a-トークンの流動性が低いため、攻撃は失敗します。
2023年8月22日、Balancerは複数のブースト済みプールに影響を与える重大な脆弱性の存在を公に発表し、ユーザーに影響を受けるプールからのLPの即時引き出しを促しました。Balancerは、TVLの大部分を確保するために緊急緩和措置を開始しましたが、一部の資金はリスクにさらされたまま残りました。残念ながら、8月27日、5日後に、我々は複数の攻撃が発生していることに気づきました。それ以来、212万ドルを超える資産が盗まれています。
本レポートの執筆時点(発表から3週間以上経過し、安全であると判断した時点)で、Balancerはこの脆弱性に関する詳細な分析を公開していません。攻撃トランザクションの1つに基づき、本レポートでは包括的な分析を提供することを目指します。
主要なポイント (TL;DR)
- 我々の調査によると、根本原因は、
linearプールの丸め誤差ロジックによる価格操作に起因します。これにより、対応するboostedプールで使用されるキャッシュされたトークンレートが不適切に影響されます。 - このインシデントは、脆弱なソースからフォークしたプロジェクトへの迅速な通知の重要な必要性を強調しており、これはコミュニティ全体にとって大きな課題となっています。
- 多数の継続的な攻撃は、将来の損失を軽減するのに役立つ、プロアクティブな脅威防止の必要性を強調しています。
以下のセクションでは、まずBalancerに関する基本的な背景情報を提供します。その後、脆弱性と関連する攻撃の包括的な分析を行います。最後に、これまでに観察された攻撃の簡単な要約とその利益を提示します。
0x1 Balancerの背景
Balancer V2 [1]は、プログラム可能な流動性の柔軟なビルディングブロックを表す分散型自動マーケットメーカー(AMM)プロトコルです。トークン会計がプールロジックとペアになっている他のAMMとは異なり、Balancerはトークン会計と管理をプールロジックから分離しており、これにより多くのトークン転送を削減してスワップ効率を向上させることができます。
Balancerはさまざまな種類のプールをサポートしています。各プールは、BPT(Balancer Pool Token)というLPトークンに関連付けられています。基本的に、BPTの価値は、すべての基盤トークンの総価値に基づいて計算されます。
Balancerは、Vaultに登録されているすべてのプールから最良の価格を活用するマルチホップスワップ、またはbatch swapをサポートしています。具体的には、Vaultはマルチホップスワップを容易にするためにbatchSwap関数を提供します。
Balancerのプールにおけるflash swapは、スワップを実行するために従来必要とされる入力トークンを保持する必要をなくします。代わりに、不均衡を特定すると、Vaultにスワップを実行するように指示し、その後報酬を受け取ることができます。
0x1.1 Balancerのさまざまなプール
以下では、この脆弱性に関連するプールの概念を簡単に紹介します。
-
Linear Pools:
Linearプール [2]は、既知の交換レートで資産とそのラップされた利回り生成型カウンターパートの交換を促進するBalancerプールです。名前が示すように、Linearプールは線形数学を使用します。linearプールは3つのトークンを保持します。- 2つの資産、つまり同じ価値の基盤トークンを持つ
mainトークンとwrappedトークン - 対応する
BPT(Balancer Pool Token)。BPTはERC-20トークンです。
- 2つの資産、つまり同じ価値の基盤トークンを持つ
-
Nesting Linear Pools: Linear Pool BPTは、別のプール内にネストできます。これにより、スイッパーが
BPTから外部プール内の基盤トークンの1つにスワップできるため、基盤資産と外部プール内のトークンとの間に簡単なbatchSwapパスが作成されます。 -
Composable Stable Pools: Composable Stable Pools [3]は、ほぼ同等または既知の交換レートで一貫してスワップされることが期待される資産向けに設計されています。Composable Stable Poolsは、大幅な価格影響に遭遇する前にかなりのサイズの交換を可能にするStable Mathを使用しており、同種および相関種のスワップの資本効率を大幅に向上させます。
プールがcomposableであるとは、そのLPトークンとの間でスワップを許可することです。そのLPトークンを他のプール(または「ネスト」)に入れると、ネストされたプールトークンから外部プール内のトークンへの簡単な
batchSwapが可能になります。 -
Boosted Pools:
Boostedプール [4]は、大規模プールのアイドル流動性の資本効率を向上させるように設計されています。Boostedプールは、実際には他のプールのサブクラスです。たとえば、boostedプールは、linearプールの上に構築される可能性があります。Boosted Poolsは、ユーザーが一般的なトークンのスワップ流動性を提供できるようにしながら、アイドルトークンを外部プロトコルに転送することにより、高い資本効率を提供するように設計されています。これにより、流動性プロバイダーは、スワップから収集したスワップ手数料に加えて、Aaveのようなプロトコルのメリットを享受できます。
0x1.2 脆弱なブースト済みプールの具体的な例:Balancer Boosted Aave USD
Balancer Boosted Aave USD(シンボル:bb-a-USD)は、3つのステーブルコイン(USDC、USDT、DAI)間のスワップを促進し、アイドル流動性をAaveに転送するComposable Stable Poolです。基盤となるlinearプールは次のとおりです。
bb-a-USDC(USDCとラップされたaUSDCで構成)bb-a-USDT(USDTとラップされたaUSDTで構成)bb-a-DAI(DAIとラップされたaDAIで構成)
具体的には、bb-a-USDは、3つの異なるlinearプールのプールトークンを含むComposable Stable Poolのコレクションであり、これらのlinearプールのそれぞれは、DAI、USDC、USDTの対応するステーブルコインに関連付けられています。以下の公式ドキュメント[5]から提供された図は、bb-a-USDの構造を示しています。

0x1.3 BPTの価格の計算方法
特定の量のBPT(amountIn)を特定量の他のトークン(amountOut)とスワップする場合、BPTの価格をどのように決定するかという重要な問題が自然に発生します。
Balancerは、さまざまなプールで採用されている数学的公式[6, 7]について詳細な説明を提供しています。簡単にするために、最も関連性の高い概念を抽象化して要約します。
linearプールを例にとると、BPTの価格はLinearPoolコントラクトのonSwap関数で計算されます。

計算は次のように要約できます。

ここで、tokenRateは次の式で計算されます。

は定数です:。
上記の式では、分子はmainトークンの残高とwrappedトークンの残高の合計として単純化できます。一方、分母は定義済みの値(_INITIAL_BPT_SUPPLY)とBPTの残高の差です。
計算を実行する前に、すべての関連トークンの残高を名目化する必要があることに注意してください。これは、異なるトークンが異なる小数を持つ可能性があるためです。具体的には、指定されたトークンの生の残高は、_scalingFactors関数によって決定される対応するアップスケールファクターと乗算されます。
(1) Linearプールのスケーリングファクター
BPTとmainトークンは、両方とも定数で一定のスケーリングファクターを持っています。

(2) bb-a-USDのようなBoostedプールのスケーリングファクター
boostedプールの計算は少し複雑です。具体的には、返されるスケーリングファクターは、生のスケールファクター(例:1e18)とトークンレートの積であり、キャッシュされたトークンレートから取得されます(存在する場合)。

キャッシュされたトークンレートはどこから来るのでしょうか? _updateTokenRateCacheという名前のプライベート関数が存在します。明らかに、この関数はまずgetRate関数を呼び出してレートを取得し、それをキャッシュします。

繰り返しになりますが、bb-a-USDCを例にとると、対応するgetRate関数のコアロジックは、前述の計算式に従います。

onSwap関数を経由するパスのために更新を実行する際に、有効期限チェックが配置されています。

0x2 脆弱性分析
根本原因は、linearプールのonSwap関数の丸め誤差ロジックによって引き起こされる価格操作にあります。これは、結果としてboostedプールによって使用されるキャッシュされたトークンレートに不適切に影響します。
具体的には、_downscaleDown関数が呼び出されるときにamountOutは切り捨てられます。したがって、amountOutとscalingFactors[indexOut]の間に大きな差がある場合、_downscaleDown関数の戻り値はゼロになる可能性があります。

たとえば、bb-a-USDC(BPTとして)を使用してbb-a-USDCプールでUSDC(mainトークンとして)をスワップする場合、amountOutが1,000,000,000未満の場合、戻り値は常にゼロに切り捨てられます。これにより、bb-a-USDCの流動性を一方的に追加していると見なされるため、bb-a-USDCの残高が増加します。
結果として、BPTがスワップに使用されるトークンである場合、そのレートは上昇します。これは、分子は同じままで分母が減少することを考えると、レートを計算する式に沿っています。このバグは、(巨大な)価格差につながるように悪用される可能性があります。
0x3 攻撃分析
攻撃トランザクションは、次の攻撃ステップで構成されています。
- AaveからFlashloanで300,000 USDCを借入。
- bb-a-USDCプールで1.067753 USDCを0.970495 aUSDCにスワップ。
- bb-a-USDCおよびbb-a-USDプールで
batchSwapを実行。つまり、42,203 USDCで15,628 bb-a-USDC、139,431 bb-a-DAI、および248,868 bb-a-USDTを獲得。詳細なステップを次の表にまとめます(小数を含む)。

- LPトークンを対応する基盤ステーブルコインにスワップ:
- bb-a-DAIプールで139,431 bb-a-DAI → 141,127 DAI
- bb-a-USDCプールで15,628 bb-a-USDC → 15,685 USDC
- bb-a-USDTプールで248,868 bb-a-USDT → 253,461 USDT
- フラッシュローンを返済し、最終的な利益は次のとおりです。
- 114,324 DAI
- 253,461 USDT
- 0.970495 aUSDC
攻撃者はステップ2でbb-a-USDCプールからUSDCとaUSDCをドレインしたことに注意する価値があります。これにより、ステップ3での価格操作がはるかに容易になります。つまり、攻撃者はUSDCとbb-a-USDCにのみ焦点を当てる必要がありました。
ここでステップ3が重要な役割を果たします。なぜ攻撃者が利益を上げることができたのかを理解するために、このステップの詳細を掘り下げてみましょう。具体的には、
- ステップ3.1は、bb-a-USDCプールからUSDCとbb-a-USDCをドレインするために使用されます。
- ステップ3.3および3.4は、bb-a-USDCをbb-a-DAIにスワップするために使用され、ステップ3.5はbb-a-USDCをbb-a-USDTにスワップするために使用されます。
- ステップ3.7は、bb-a-USDCプールからUSDCをbb-a-USDCにスワップするために使用されます。
ここでのステップ3.2および3.6では、前述の丸め誤差のため、ターゲットトークン(USDC)のバックアップは行われません。したがって、スワップ後のターゲットトークンの残高は変更されず、bb-a-USDCの追加流動性をbb-a-USDCプールに追加していると見なすことができます。
明らかに、異常なスワップは主にステップ3.4、3.5、および3.7で発生します。以下では、これらの各ステップの詳細を順に見ていきます。
(1) bb-a-USDC → bb-a-DAI
ステップ3.3では、bb-a-USDCとbb-a-DAIの交換レートはほぼ1ですが、ステップ3.4では、交換レートは19になります。
- ステップ3.3:1,000,339,378,515,783,699 / 1,000,000,000,000,000,000 = 1.00
- ステップ3.4:139,430,482,942,020,211,267,110 / 7,300,000,000,000,000,000,000 = 19.10
前述のコードロジックを思い出すと、ステップ3.3では、以前にキャッシュされたトークンレートを返してスケーリングファクター(1,012,181,365,780,643,700)を計算した後、レートを更新して新しい値(40,240,000,000,000,000,000)を計算します。この更新された値は、ステップ3.4で新しいスケーリングファクターとして使用されます。生のスケールファクターは変更されないため(つまり1e18)、これは新しいレートが古いレートの約40倍であることを意味します。

しかし、この大幅な増加はどこから来るのでしょうか? tokenRateの計算式に戻ってみましょう。ステップ2でaUSDCの残高が枯渇したため、tokenRateの計算は次のように単純化できます。


ここで、nominalMainBalanceの実際の値は、ステップ3.2で発生した丸め誤差によるものです。
(2) bb-a-USDC → bb-a-USDT
ステップ3.5は同じトリックを使用して、より多くのbb-a-USDTを獲得し、bb-a-USDCとbb-a-USDTの交換レートは12倍以上になります。
- 248,868,905,733,352,246,491,156 / 20,000,000,000,000,000,000,000 = 12.44
(3) USDC → bb-a-USDC
さらに、ステップ3.6でbptBalanceが増加し、ステップ3.7でbptSupplyがゼロになります。これにより、ほぼ1:1の交換レートでUSDCをbb-a-USDCにスワップすることが可能になります。

0x4 攻撃と利益の概要
執筆時点までに、我々は野良で数十件の攻撃を観測しており、212万ドルを超える損失が発生しています。要約すると、これらの攻撃は、次の3つの異なるアカウントによって実行されました。

Balancerはこの脆弱性により、合計で約100万ドルの損失を被りました。Balancerへの最初の攻撃から12時間未満で、そのフォークされたプロトコルであるBeethoven Xも同様の攻撃に屈し、推定約110万ドルの損失が発生しました。Beethoven Xは、Balancerよりもさらに大きな損失を被りました! このセキュリティインシデントによる累積損失は、約212万ドルに達しました。
これらの攻撃トランザクションの完全なリストは、作成したドキュメントにまとめられています。詳細については、そちらを参照してください。
攻撃者に関するいくつかの観察事項
各ネットワークによって開始されたトランザクションを分析した結果、Fantomの攻撃トランザクションのトレースと、EthereumおよびOptimismのトレースとの間に、顕著な違いがあることがわかりました。
具体的には、主要な関数の注目すべき違いに加えて、Fantomの攻撃者はMEVボットによるフロントランニングを回避するために2つのユニークなトリックを利用しました。さらに、Fantomでの攻撃に使用された資金は、攻撃の163日前に準備されていました。
上記で詳細に説明した観察から、次のことが推測できます。
-
少なくとも2人の異なる攻撃者が関与しました。
-
Fantomの攻撃者は経験豊富な常習犯です。
0x5 結論
要約すると、これは丸め誤差ロジックに起因する巧妙な脆弱性です。しかし、この脆弱性を悪用することは簡単ではありません。具体的には、攻撃者はlinearプールでの丸め誤差の問題を悪用してキャッシュされたトークンレートをインフレさせ、それによって対応するboostedプールでのトークン価格を操作することができました。
このインシデントはまた、脆弱なソースからフォークしたプロジェクトへのタイムリーな通知の重要性を強調しています。Balancerの警告にもかかわらず、フォークされたプロトコルを標的とした攻撃は続いており、これらのフォークされたプロジェクトがソースプロジェクトからのセキュリティアップデートに関する情報を入手し続ける必要性が浮き彫りになっています。しかし、これらのフォークされたプロジェクトが迅速な通知を受け取ることを保証することは、コミュニティにとって継続的な課題となっています。
さらに、継続的な一連の攻撃は、潜在的な損失を効果的に軽減するのに役立つ、プロアクティブな脅威防止の重要性を強調しています。
参考文献
- [1] https://docs.balancer.fi/concepts/overview/basics.html
- [2] Linear Pools: https://docs.balancer.fi/concepts/pools/linear.html
- [3] Composable Stable Pools
- [4] Boosted Pools
- [5] https://docs.balancer.fi/concepts/pools/boosted.html#example
- [6] https://docs.balancer.fi/reference/math/linear-math.html
- [7] https://docs.balancer.fi/reference/math/stable-math.html



