Uniswapの毒プールの罠、29時間で13万ドルを徴収

BlockbeatsBlockbeats

原文タイトル:《見積もり0%、実行12.8%:Uniswap v4「毒プール」動的料率の罠を振り返る》

原文著者:王子豪、BitsLab

2026年8月22日から23日にかけて、BNB Chain上のUniswap v4 USDC/WBNB動的料率プールが、アグリゲーターの見積もりでは0%の料率を表示しながら、実際の取引では12.8%のLP手数料を徴収していた。約29時間の間に、このプールは21,086件の取引を成立させ、累計手数料は131,888ドルに達し、その後閉鎖された。被害に遭った取引はすべて成功していた。コントラクトは侵害されておらず、出力はユーザーが設定した下限を上回り、異常なシグナルは一切なかった。差額は静かに手数料へと変わっていた。

本稿では、そのうちの1件のサンプル取引を入口として分析する。

(0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0:ユーザーは5,926.90 USDTを投入し、受け取った額は予想より757.43 USDT少なかった)このプールがどのようにして「見積もり0%、実行12.8%」を実現したのかを分解する。料率関数の3層の判定、骨抜きにされたスリッページ保護、他のLPを締め出すホワイトリスト。すべての結論はオンチェーンデータとローカルでの制御された検証に基づく。

01 見積もりから実行までの経路

ユーザーがアグリゲーターのフロントエンドでスワップを開始した後、全体の経路は次のようになる。

この経路には2つの背景事実があり、以降の各層の判定はすべてこの2点に基づいている。

まずv4側を見てみよう。v4ではHookが取引ごとに料率を上書きできる。PoolKey.fee = 0x800000は動的料率プールを宣言し、HookはbeforeSwapの3番目の戻り値に0x400000 | feeを付与する。v4-core内でこの戻り値を受け取るコード(Pool.sol:303-305)は以下の通り。

isOverride()は上書きフラグをチェックし、removeOverrideFlagAndValidate()は0x400000を除去してMAX_LP_FEE = 1,000,000(100%)を超えないことを検証し、その料率でこのスワップを実行してSwapイベントに書き込む。つまり、Hookが返したものがそのままユーザーに請求される。出力がamountOutMinを上回っている限り。これは正式な機能であり、ボラティリティや在庫に応じて料率を調整することは正当な用途である。

次にこのHook自体を見てみよう。宣言されている権限は2つだけである。v4は権限をコントラクトアドレスの下位14ビットにエンコードし(Hooks.sol:29-47)、デプロイ時に検証され、後から変更することはできない。v4-core側のフラグ定義とアドレスデコードの対応は以下の通り。

オンチェーンで実測したgetHookPermissions()の戻り値はビットマップと一致し、この2ビットのみが真である。

Hookにはソースコードがない。本稿ではバイトコードから料率関数の全文を逆コンパイルした。以下は、すでにオンチェーンで実行されたこのロジックの動作分析である。

次に各層を順に見ていく。最初の2層は「これはシミュレーションかどうか」を判定し、第3層が実際の取引でいくら支払うかを決定する。

02 料率関数の3層の判定

判定1:呼び出し元の区別

料率関数が呼び出されると、まずtx.originを比較する。3つの定数アドレスのいずれかに等しい場合、直接フォールバック値を返す。フォールバック値は現在0%に設定されている。

この3つのアドレスは無作為に選ばれたものではない。eth_callでfromが指定されていない場合、トランザクションのoriginはゼロアドレスになる。アグリゲーターのバルク見積もりでは通常fromが指定されない。ローカルフォークで直接検証した。

第1層はこの3つのアドレスのみを認識する。次の層では、通常のアドレスが指定された後、Hookがさらにどのように区別できるかを見ていく。

判定2:ガスのチェック

tx.originを通常のアドレスに変更して再試行すると、戻り値は依然として0%だった。第1層は回避されたが、料率は変わらなかった。これは第1層が唯一の判定ではないことを示しており、第2層を特定した。gasleft() * 10100 / 10000 >= gateである。

アグリゲーターの見積もりでは、候補ルートがシミュレーション中にガス不足で失敗しないように、通常高いガスを設定する。実際の取引がHookに入る前には、ガスはRouter、承認、前置ホップによって消費されている。同じコードで、3種類のガスを試した。

境界線は実測で16.85M~16.9Mガスの間にある。30Mと2.59Mの差が、「見積もり」と「実行」の差である。

判定3:取引ごとの擬似乱数

最初の2層を通過した実際の取引は第3層に入る。ブロック環境のフィンガープリントをハッシュ化して10,000で割った余りを取り、その結果が属する区間に応じて対応する料率を返す。現在の設定は8% / 10% / 10%の3段階で、該当しない場合は0%にフォールバックする。

この層には注目すべき設計が2つある。フィンガープリントにはcalldataの3つのフィールドが混入されている。アグリゲーターの見積もりのcalldataと実際のルーティングのcalldataは元々異なるため、フィンガープリントも当然異なる。また、取引ごとの擬似乱数により料率に分布が生じ、数件だけを見ても規則性を察知しにくく、単純なルールの列挙では防げない。

全21,086件のオンチェーンイベントには19段階の過去の料率(6.8%~28%)が出現しており、料率は管理者関数によって必要に応じて再設定されていたことがわかる。サンプル取引が成立した時点で有効だったのは12.8%である。

3層を統合した全体像

ここまでで「毒プール」を定義できる。以下の3つの条件を同時に満たすv4プールである。PoolKey.feeが動的料率フラグを持つこと、料率の決定が公開市場の状態ではなく実行環境のシグナル(ガス、origin、ブロック環境)を読み取ること、見積もりシミュレーションとオンチェーン実行の料率に体系的な分岐が生じ、受益者が毒プールのデプロイヤーであること。分岐点は料率が何を読むかにある。どの呼び出し元にも同じ料率を返すものはプログラマブルな市場設計であり、「誰が問い合わせているか」に応じて異なる料率を返すものはルーティングシステムに対する欺瞞である。

ここまでで、料率分岐のメカニズムは完全に解明された。しかし、料率が徴収された後、なぜ取引が成功するのかが次の問題である。

03 スリッページ保護が機能しなかった理由

まずこの取引の完全な経路と金額を見てみよう。

該当するSwapイベントは有効な料率をfeeフィールドに記録している。128000である。v4の料率単位は1,000,000を100%とするため、128000は12.8%となる。この取引の損失は2つの角度から測定できる。プールから見ると、料率はWBNBの入力に対して課される。ルート全体から見ると、ステーブルコインの出入り差額は757.43 USDTで、損失率は12.7794%であり、これには前置ホップの手数料とペッグ乖離も含まれる。2つの数字の差はわずか0.02ポイントであり、損失の主な原因がこのプールのLP手数料であることを示している。

amountOutMinは最終出力の下限のみを検証し、中間の各ホップでいくら手数料が取られたかは見ない。

12.8%の料率は20.32%のバッファ内に完全に収まり、出力は依然として下限を上回っていた。取引は成功し、ロールバックされなかった。主流のステーブルコインルートの通常のデフォルトは0.1%~1%だが、このルートは20倍以上の余裕を与えていた。ユーザーは広いバッファを「より安全」だと思い込むが、実際には経路上のすべてのコントラクトを相手側の自己規律に委ねていることになる。この計算を図にすると以下のようになる。

04 毒プールデプロイヤーの操作

ホワイトリスト:他のLPを締め出す

ホワイトリストのロジックはbeforeAddLiquidity内にあり、逆コンパイルの結果は以下の通り。

流動性を追加するには3つの関門を通過する必要がある。まず、すべてのv4 Hookに存在するPoolManager呼び出しチェック、次に公式PositionManagerを経由する必要があり、最後にポジション保有者のホワイトリストを照合する。リストにないアドレスはここでコントラクトによってロールバックされ、他のLPは参加できず、手数料収入は希薄化されず、すべてデプロイヤーのものになる。リスト自体は管理者関数(セレクタ0xc4452e52)によってアドレス単位で管理されている。

0%レートと出来高の水増し

21,086件の取引をまとめて見ると、分布には明確なパターンが現れる。0%レートは6,946件、取引額は12,947,751ドルで、1件あたり平均1,864ドル。手数料がかかるレートは14,140件、取引額は1,120,106ドルで、1件あたり平均79ドル。大口取引は0%レートに集中し、手数料は小口取引に集中している。大口の0%の最も合理的な説明は、デプロイヤー自身が高いガスで自己取引を行ったことである。高いガスはアグリゲーターの見積もりと同じ判定に該当し、自己取引の手数料コストはほぼゼロになる。水増しされた出来高がプールを相場サイトのランキングに押し上げ(スナップショットでは24時間出来高約1,082万ドル、16,671件)、アグリゲーターの目には、これは深い流動性と低い料率のプールとして映る。

料率の必要に応じた再設定

判定3で19段階の過去の料率に言及したが、これらは逆コンパイルされた料率設定関数(セレクタ0x4d909a45、公開署名には未収録)に由来する。

4つの料率と閾値がまとめてパッケージ化され、keccak(poolId, 2)というストレージスロットに書き込まれる。オンチェーンで実測したこのスロットの読み取り値は0x0138800186a00186a0であり、関数のパッケージ形式とセグメントごとに対応している。料率はここではいつでも調整可能なパラメータであり、デプロイ時に固定されたものではない。

ライフサイクル

このプールは08-22 06:57に作成され、07:13に最初のスワップが発生し、21:39に本稿のサンプル取引が行われた。08-23 12:25に最後の取引が成立し、その後閉鎖された。活動期間は約29時間、総取引額は14,067,857ドル、手数料収入は131,888ドル(デプロイヤーのコストは差し引かれていない)。再確認時には登録フラグが再び有効に戻っていた。プールはまだ存在し、いつでも再び有効化できる。

05 完全な経路

攻撃チェーン全体をつなげると以下のようになる。

チェーン全体の因果関係は上の図にすべて示されている。3つの条件がすべて揃わなければならない。シミュレーションが最初の2層に該当すること、実際の取引が第3層に該当すること、バッファが料率より大きいこと。どれか1つでも成立しなければ、この手法は機能しない。

06 修正と提案

1つ目はアグリゲーターの見積もり方法である。根本原因は、シミュレーションと実行が同じ経路を通らないことにある。見積もりでは理想化されたプールレベルのクエリを使用し、ゼロアドレスと高いガスを設定する。実際の取引では、実際の身元と消費済みのガスが伴う。Hookはこれらの違いを読み取ることができ、料率が分岐する。対策は、シミュレーションを実行に近づけることである。実際に送信するRouterのcalldata、実際のfrom/to、オンチェーン取引に近いガスを使用してシミュレーションを行う。取引成立後はSwapイベントをデコードしてfeeを照合し、見積もりと一致しない場合は重みを下げるか、ルートから除外する。

2つ目はユーザーのスリッページ設定である。根本原因はmin_outが緩すぎることにある。20.32%のバッファが12.8%の料率を完全に吸収し、取引がそのまま成功してしまう。主流のステーブルコインルートでは0.1%~1%で日常使用には十分であり、取引を開始する前にこの数字を確認するべきである。ウォレットやフロントエンドがデフォルト値を厳しくすることで、ユーザーのリスクの大部分を防ぐことができる。

3つ目はルーティングの承認基準である。根本原因は、どの動的料率プールも直接見積もり競争に参加できることにある。オープンソースでもなく、監査記録もないHookでも、水増しされた出来高によって推奨ルートに入ることができる。信頼できるプロファイルを持たない動的料率プールは、デフォルトでルーティングに含めないか、大幅に重みを下げることが最も確実な対処である。

データと説明

・サンプル取引:

https://bscscan.com/tx/0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0(ブロック117497524)

・プール初期化取引:

https://bscscan.com/tx/0x8fa72ef72d77b61f715dc9ce90548249ab045c6d78716078cd5ab425bbe69a05

・PoolManager:

0x28e2ea090877bf75740558f6bfb36a5ffee9e9df

・プールID:

0x36e5540e9dedc02229fe8a82aa5b10c0bf07d1fa74e4f2ffe0efd00fa1a36aea

・Hook:

0xd111b3ddd92e627f1864520c770e913ec04e0880(ソースコードなし、疑似コードは本稿が独自に逆コンパイル。管理者0x08b03e1a5444d469f4dc954e74d3f662c94a6b13)

・取引と手数料:このプールの全21,086件のSwapイベントを1件ずつ集計(eth_getLogs)、WBNBは同じ取引の実行価格で換算。収入は総額であり、デプロイヤーのコストは差し引かれていない

この内容は情報提供および教育目的であり、BTCCに関連する投資助言ではありません。BTCCは信頼性・正確性・独自性に努めていますが、これらを完全に保証するものではありません。

おすすめ

Robinhoodはハエに投機、Solanaは猫に投機コインベースCEO、ビットコインは2030年までに40万ドル到達の可能性と発言Solanaエコシステム再興へ、Pump.funが株式ミームペアリングに参入BTCC デイリー(9.11)|米10年国債利回りが5%に接近、ブレント原油は110ドル目前から急反落BTCC デイリー(9.14)|CLARITY法案「最終版」草案を公表、FRBの9月利上げ確率は87%へ