Crypto OS
Technical Crypto OS第4段階 · DeFi 開発

T20 · DEX アーキテクチャ

一回の Swap は、実際に何層を通るか?

この章を 3D で体験する →
練習する能力
BuilderFinancial Literacy
実践
2 ホップのルーターを実装し、直接交換した場合と中間資産を経由した場合の最終約定価格を比べる。
AI Lab
AI にルーティングアルゴリズムを設計させ、実際のプールデータで最適経路か検証する。

現実の問題

T19 のプールをフロントエンドへ接続しました。

両準備金から見積もり、「1 ETH は約 3000 USDC」と表示し、確認後に swap へ計算済み出力を渡します。

ローカルでは正常でも、翌日の苦情は二種類でした。

  • 取引の失敗。署名後に revert し、Gas は消費します。
  • 成功したが表示額と入金額が違う。3000 と表示して、2890 しか届きません。

攻撃もコントラクトのバグも見積もり誤算もないのに、110 はどこへ行きましたか?

見積もりを見てから、実際にチェーンで実行するまで、どれだけ時間があるかに答えがあります。

十数秒から数分です。その間、他ユーザーの通常取引や、見た人の先回りで準備金が変わります。

見積もり、経路選択、実行を一体化し、最も弱い見積もりをオフチェーンへ置いていました。本章で三つを分けます。

思考実験

空港の両替所を考えます。

三工程を別の役が担います。

  1. 入口の相場表示:定期更新し、参考値と明示します。
  2. 窓口担当:直接か、ドル経由か、二つへ分割するか選びます。
  3. 金庫:実際に数えて渡します。

空港では一分以内なので、分かれているとは感じません。

チェーンでは、異なる三時点です。

t0   ブラウザーが準備金を読み、見積もって表示       (オフチェーン、読み取り)
t1   署名してメモリプールへ入る                   (オフチェーン、公開)
t2   ブロック生成者が収録して実行                 (チェーン上、状態は変わり得る)

t0〜t2 は誰でも変えられ、特に t1〜t2 は、取引が公開されています。金額と対象を見て先に約定できます。

窓口で相場表示に従うと言われても、歩く間に表示が変われば、どの価格になりますか?

お金を渡す時に、「これ未満なら交換しない」と言うしかありません。

それが本章の最重要パラメータです。

あなたが決める

表示と違う金額を受け取らせないため、どう変えますか?

結果を観察する

三パラメータは別の問題を防ぎます。

パラメータ防ぐこと決める人欠落の結果
最低受取量先回り、サンドイッチ、変動の金額ずれ発信者任意価格で約定
有効期限長い滞留の時間ずれ発信者古い意図を数時間後に実行
受取アドレス資産の行き先発信者中間コントラクトへ残る

共通するのは、三つとも発信者が署名することです。

ユーザーが制約を付ける唯一の場所です。収録時には、フロントエンド、バックエンド、RPC、Router はユーザーの制御外にあります。

直感に反する点もあります。

許容スリッページは、自分で公開する懸賞額です。

5% を許せば、その範囲の先回り利益を取られても正常に成功し、限界で約定するだけです。

許容幅失敗率奪われ得る上限
0.1%高い、少しの変動で失敗0.1%
0.5%中0.5%
5%低い5%
50%ほぼ失敗しない50%

失敗を減らすため大きな既定値を付ける製品もあります。体験の設定でなく、リスク予算です。

モデルを作る

責任、場所、費用の違う三層です。

  1. Quote:見積もり
  2. Route:経路選択
  3. Execute:実行
前二層はオフチェーンで複雑でもよく、第三はチェーン上で単純、決定的、検証可能にします。
層場所仕事信頼できるか
Quoteオフチェーン複数準備金で経路別結果を予測不可、数秒前の値
Routeオフチェーン候補選択、分割不可、提案にすぎない
Executeチェーン経路を進み、最終下限を確認可、算術と検証だけ

経路は外で計算し、チェーンは結果だけ検証します。

全プールを列挙して最適を探すのは、Gas が高く、さらに読む準備金自体を操作できます。同取引内で状態を変え、攻撃者に有利な経路を選ばせられます。

経路を署名へ固定すれば、残るのはその経路で実際にいくら受け取ったかという検証です。

Router の骨格です。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

function swapExactTokensForTokens(
    uint256 amountIn,
    uint256 amountOutMin,     // 発信者の署名に含む下限
    address[] calldata path,  // オフチェーンで計算した経路
    address to,               // 発信者が指定する受取人
    uint256 deadline          // 発信者が指定する有効期限
) external returns (uint256 amountOut) {
    require(block.timestamp <= deadline, "EXPIRED");
    require(path.length >= 2, "BAD_PATH");

    _transferFrom(path[0], msg.sender, _pairFor(path[0], path[1]), amountIn);

    uint256 amount = amountIn;
    for (uint256 i = 0; i + 1 < path.length; i++) {
        amount = _swapOneHop(path[i], path[i + 1], amount, i + 2 == path.length ? to : _pairFor(path[i + 1], path[i + 2]));
    }

    amountOut = amount;
    require(amountOut >= amountOutMin, "INSUFFICIENT_OUTPUT");
}

四点に注意します。

  1. deadline は最初に確認します。 期限切れなら何もしません。
  2. 途中の受取先は次プールです。 Router に留めず、転送回数も減らします。
  3. amountOutMin は最後に確認します。 最終額が達すれば、途中一段の大きなずれがあっても成功すべきです。
  4. Router 自体は資金を持ちません。 取引間の残高はゼロが適切です。

二段が一段より良い場合の数学です。

直接:A -> C
  深度 D1 の一プールが全量を担う

二段:A -> B -> C
  深度 D2、D3 で、それぞれのずれが小さい
  代償:手数料が約 0.3% から 0.6%、Gas も増える

有利な条件:
  A/C が浅く、A/B と B/C が深い
  直接の余分なスリッページ > 追加 0.3% と Gas

経路アルゴリズムの問いはこれです。 最短でなく、ずれと手数料を比較します。

何と呼ぶか

Pool流動性プール

二資産を保有して曲線で値付けし、外の世界を知りません。

T19 のコントラクトです。Router を知る必要はありません。

Routerルーターコントラクト

経路を受け、各 Pool を呼び、最後にスリッページを確認します。

資金を持たず、価格判断をせず、全制約を呼び出し元から受けます。 違反すれば道具からリスクになります。

Quote見積もり

オフチェーンの予測結果です。

常に過去の値です。 読んだ時の状態で、実行時ではありません。約束と見なすのが事故の原因です。

Slippage Protectionスリッページ保護

署名パラメータで結果の境界を設けます。

最低受取量と期限の価値は、ユーザーの署名にあることから生まれます。

Aggregatorアグリゲーター

複数 DEX で比較、分割するシステムです。

経路選択を広げますが、任意の対象を呼ぶ実行コントラクトの権限は、設計を誤れば承認額を使う穴になります。

Slippage Tolerance許容スリッページ

受け入れる最大のずれで、最低量の計算を決めます。

体験の切り替えでなく、リスク予算です。その範囲を取られることを許します。

実践

実践二段経路を作り、直接と中間資産経由の最終価格を比較するSolidity + 任意のコントラクトテストフレームワーク0 円、すべてローカルネットワーク

三プールをデプロイします。 T19 のコードで A、B、C を作り、深度を変えます。

A/C  片側 10 万      (浅い)
A/B  片側 500 万     (深い)
B/C  片側 500 万     (深い)

二関数を書きます。各段結果を読む getAmountsOut(amountIn, path) と、実行する swapExactTokensForTokens です。

getAmountsOut は view とし、同じ見積もり式を共有します。近似と厳密を混在させません。

二経路を比較します。 1000、10000、100000 を、A/C と A/B/C へ通します。

金額直接の受取二段の受取追加手数料有利な経路
1000
10000
100000

境目を探します。どれほど小さいと追加 0.3% が割に合わないかが、実際の計算対象です。

保護を三テストで検証します。

  1. amountOutMin を予測より 0.5% 低くし、成功を確認。
  2. 別アドレスの大口取引で準備金を変え、INSUFFICIENT_OUTPUT を確認。
  3. 過去の deadline で EXPIRED を確認。

第二が中心です。戻らなければ、確認位置が誤っています。途中の結果を下限と比べていないか調べます。

攻撃費用を逆側から計算します。 定量演習です。

攻撃者の必要額だけでなく、設定が残す利益幅を計算します。

取引額 V、許容幅 s、片側深度 D

1. 許容境界で約定するまで価格を動かす
2. 元本は T19 から、D * (sqrt(1+s) - 1) の程度
3. 粗利は約 V * s
4. 費用は二取引手数料(元本の約 0.6%)、二回 Gas、先回りの入札
5. 純利益 = 粗利 - 費用。正なら実行する

V = 10000、s = 0.5% と 5% で全項を求めます。

どれほどの取引から攻撃する価値が出ますか? それ未満は対象にならず、リスク開始点を知る重要な数値です。

T24 で攻撃側コードも含めて計算します。

ローカルのみで実資金は接続しません。 卒業課題でも、第五段階の安全性確認後に使います。

AI Lab

AI LabAI に経路選択を設計させ、実プールの値で最適性を検証するLevel 2 · AI Copilot

第二が重要な二段階です。

第 1 段階:
準備金と手数料の分かる定積プール群から、
入力、出力資産と入力額に対し、最大受取の経路を求めてください。
重み関数と、最短経路が使えない理由を示してください。

第 2 段階:
最適でない経路を出す三場面の、構造、誤り、代償を示してください。
分割して複数経路を使うなら、アルゴリズムをどう変えますか?
追加 Gas が、どの金額以下で割に合わなくなりますか?

第二は境界を認めさせます。難しさは探索でなく、流す額がスリッページを変え、辺の重みを変えることです。100 と 100 万で同経路の最適性が変わり、多くの回答が漏らします。

実数値で検算します。表を作ったので、手元に正解があります。それが検証できる理由です。

AI の回答後、自分で必ず検証すること

  • 深度順だけでなく、重みに手数料を含めている
  • 最短経路を最良約定と誤解していない
  • Lab の三プールで、手計算の最適経路と一致する
  • 経路長の上限と、無限に増やせない理由がある
  • 経路も署名へ含め、バックエンドが実行時に決めない
  • インターフェース、関数、引数順が自分のコントラクトに存在する

実例

期限が無効になった継続的に存在

deadline に type(uint256).max を渡すか、現在の時刻へ巨大な値を足します。

第一は永久有効で、何か月後の有利な条件まで待って収録できます。

さらに、deadline をチェーン上で block.timestamp から計算すれば、実行時の block.timestamp は自分自身を超えず、いつも成立します。

署名前に値が固定されたかで判断します。そうでなければ保護しません。

既定の許容幅が大きい複数ウォレットと画面

失敗を減らすため広げると、ユーザーは成功を見て、境界の損失に気付きません。

期待値を知らないので苦情も少なく、合法でログも正常です。

現在の価格インパクトへ結び付け、0.3% なら 0.5% などにし、一律にしません。数値を見せ、予測額より最低受取を中心に表示します。

受取人を誤った統合事故

to を Router や中間コントラクトにし、後で取得するつもりでした。

取り出す処理がなければ、最初に見つけた人が取ることになります。残高を探す専用ボットもあります。

受取先は、確実に取り出せるアドレスにします。 取引終了後の中間残高はゼロにします。

アグリゲーターの任意呼び出し権限複数の事故

多様なプロトコルを使うため、任意アドレスへの任意呼び出しを持ちます。

ユーザーの承認があり、攻撃者が transferFrom を呼ばせれば、全承認者の残高を取れます。

許可リスト、引数検証、必要額だけの承認で防ぎます。承認は取引と独立した持続リスクです。 不要なものを定期的に取り消します。

変数を一つ変える

もしバックエンドが最低量を埋め、ユーザーは金額だけ署名する

新しいデータで近い下限を出し、失敗率が減ります。

しかし署名外なら、誠実さ、侵害、バグに依存します。署名は、その後誰も変えられない境界です。外へ出せば境界を失います。

関与するなら、バックエンドは提案し、ユーザーが署名で確認します。

もし最大経路を 3 から 8 段へ広げる

理論価格は改善しても、三点が悪化します。

Gas が線形増加し、失敗率が増し、攻撃面が増します。各段の呼び出し、変化、異常が全体へ影響します。

3〜4 段が多いのは技術上限でなく、逓減する効果です。第四段の改善が Gas より小さくなります。

もし公開メモリプールを通さず、生成者へ直接送る

公開の隙間が消え、先回りやサンドイッチの機会が減ります。

代わりに特定中継者が内容を見て、収録を決め、他へ機会を渡せるという信頼を置きます。

全員が見えるリスクを、一人が見えるリスクへ変えます。 T24 で代償を計算します。

もし0.1% の許容幅で頻繁に失敗する

Gas を失い、価格が動き、再試行します。

失敗率も利用され得ます。即再試行すると知られれば、条件を作って広い許容へ追い込めます。

金額と深度で動的に変え、大きければ分割を勧めます。価格を大きく動かす取引は、許容幅だけで解決できません。

持ち帰る問い

1
どんな問題を解決するか?

何を解決しますか?外での意図をチェーンで安全に実現します。どの制約を署名へ含めたかで、取引画面を判断します。

4
ユーザーは誰か?

ユーザーは誰ですか?一般取引者は下限で約定し、裁定者やボットは広すぎる下限を探します。同 Router の意味が、二者で逆です。

5
誰が支払っているか?

誰が支払いますか?取引者が手数料、Gas、スリッページを払います。第三は通常大きく、請求書にないので漏れます。

9
誰がリスクを負うか?

誰がリスクを負いますか?署名者です。誤見積もり、悪い経路、広い許容の損失を負い、チェーンは正常に見えます。保護を署名に入れる理由です。

章末チェック

要点

価格提示、経路選択、実行を分けなければ、スリッページも先回り取引も防げない。

この章の実践課題を終えたらチェックしてください進捗をすべて見る

このページの内容