FABRKNT
Step 3. Precompiles — EVM 拡張による CLOB ステートのスマートコントラクト連携
Orientation
レッスン 1 / 12·CONTENT15 分50 XP
コース
Step 3. Precompiles — EVM 拡張による CLOB ステートのスマートコントラクト連携
レッスンの役割
CONTENT
順序
1 / 12

レッスン0 — OpenHL Precompile を作る(CLOB state をスマートコントラクトに接続する)

問い

前コース(CLOB)は bridge が matching engine を所有する地点で終わった。だが 約定はまだ並列リストにすぎず、同じ Reth node 上で動くスマートコントラクトからは見えない。CLOB の状態と EVM の状態は別世界にある。どうやってスマートコントラクトが CLOB を read/write できるようにするか?

注: OpenHL コースのコードブロックは原則として手元で実行可能な形で示す。ただし <file> などのプレースホルダや答え合わせ用コマンドは、各レッスンの指示に従って置換してから実行すること。

原理(最小モデル)

  • custom EVM precompile = EVM 内の固定 address で native Rust を実行する。 間に Solidity bytecode は挟まらない。caller からは固定 address への external call に見えるが、実装は自分が選んだ state にフルアクセスできる native Rust。「もう 1 つのスマートコントラクト」でなく「EVM から呼べる native 関数」
  • custom range は 0x0c00 以降。 Ethereum は 0x010x0a を標準 precompile(ECDSA recovery / SHA-256 等)に予約済み。CLOB precompile は衝突しない並列 range に置く。
  • read + write の 2 パス。 0x...0c1b で best bid を read、0x...0c1c で order を発注。これで CLOB は EVM の横の独立構造から、EVM が対話できる state 拡張 になる。
  • precompile は bridge が所有するのと同じ CLOB instance に触れる。 これがチェーンを「Hyperliquid 型」にする本質 — perp matching engine が同じチェーン上のコントラクトから呼び出せる。

具体例

Solidity contract ──call(0x...0c1b)──► clob_read_best_bid  → (price, qty) を 64-byte で返す
Solidity contract ──call(0x...0c1c, calldata)──► clob_place_order → decode → book.submit → 約定要約

ゴールは cargo test -p openhl-evm bridge_against_custom_evm_node_shares_clob_with_precompile --release — コントラクトの call が precompile 経由で order を発注し、既存 book とマッチし、約定が bridge へ流れる。

失敗例(誤解)

「Rust の HTTP サービスを並列に立てて、コントラクトから call() で best_bid を読めばいい」は誤り — consensus パスで破綻する。EVM 実行は決定的でなければならず、全 validator が同じ入力から同じ結果を計算する必要がある。外部 HTTP read は非決定的(ネットワーク遅延・サービス状態で結果が変わる)で、validator 間の合意を壊す。read は EVM 実行内で決定的に完結する native precompile でなければならない。「precompile を Solidity 関数のように考える」も誤り(precompile は固定 address の native Rust)。


ここまでで「precompile は EVM 内の決定的 native 関数」は着地した。ここから先はスコープ・前提・12レッスンのロードマップに入る。L1 以降は実際に Rust を書く。

🛑 予測。 Solidity から CLOB の best_bid を読みたい。素朴な「Rust の HTTP サービスを並列に立てて call()」がなぜ consensus パスで破綻するか — そこから read メカニズムはどんな形でなければならないと結論できるか?(答え: EVM 実行は決定的でないと validator が合意できない。外部 read は非決定的。→ read は EVM 内で決定的に完結する native precompile であるべき。)

終了時に手にするもの

新規 crates/evm/src/precompiles/ モジュール:

  • 既知 EVM address に登録された custom precompile 2 個: clob_read_best_bid(read、(price,qty) を 64-byte で返す)/ clob_place_order(write、calldata から order を decode → CLOB submit → 約定要約を返す)
  • custom EVM machinery(openhl_evm.rs)— Reth executor に precompile を組み込む EvmFactory + ExecutorBuilder
  • Bridge 統合 — LiveRethEvmBridge が custom EVM 付き Reth node を spawn し、precompile への call が bridge 所有の同じ CLOB instance に触れる

end-to-end test は ~3 秒(Reth bootstrap → precompile trigger → 約定 assert)。

終了時にも手にしないもの(意図的な scope cut)

  • 約定を実 EVM tx として block body に encode(約定は payload に attach された並列リストのまま。read/write から 見える が block body の一部ではない)
  • Funding state machine(= Step 4)
  • Liquidation / oracle / perp 固有 math
  • Multi-market precompile(Stage 9 は CLOB 1 つ。production は market ごと 1 precompile か market-id calldata)

「チェーンのどこかに orderbook がある」→「チェーンそのものが orderbook + EVM」への大きな capability ジャンプ。ループを完全に閉じる(約定を tx として block body に戻す)のは下流。

前提

  • CLOB(Step 2)完了LiveRethEvmBridge<P>clob/pending_fills/submit_order/payload_fills/pending_fill_count が揃っていること。
  • Rust 1.95+。
  • REVM に trait レベルで慣れていることPrecompile/PrecompileFn/Precompiles 型。未見なら revm-precompile docs を一読)。
  • スレッド境界を越えた共有 state に Arc<Mutex<T>>

不要: EvmFactory/ExecutorBuilder 予備知識(L1-2 で説明)/ Solidity(raw calldata でテスト)。

セットアップ確認(今やる)

cd ~/code/openhl-reference
git fetch origin
git log --oneline | grep -E "(1761d4d|b635ef7|a8823a1|d19ba1b|2f796c3|2ba97c6)"
# Stage 9a〜9e の SHA が見えるはず

cd ~/code/my-openhl
cargo test -p openhl-evm clob_fills_flow_into_payload --release 2>&1 | tail -5
# 期待: Step 2 のマイルストーンテストが pass

12レッスンのロードマップ

#build するもの終了時テスト
0Orientation(本レッスン)セットアップ確認
1openhl_evm.rs — EvmFactory パターン + 依存cargo check -p openhl-evm
2precompiles/mod.rs — hardcoded read precompile + registryprecompile がコンパイル
3OpenHlExecutorBuilder + NodeBuilder 統合; call する smoke testprecompile_is_callable_via_registry
4install_clob() — Arc-shared CLOB statebridge が shared state でコンパイル
5read precompile を live CLOB state に接続precompile が実 best_bid を返す
6end-to-end: read が bridge.submit_order の結果を反映integration test pass
7clob_place_order signature + calldata decodeprecompile が正しく decode
8book.submit + 約定要約を返すprecompile が正しく write
9install_fill_sink() — precompile 約定が bridge の pending_fills に流れるprecompile 約定が bridge に届く
10bridge が custom-EVM Reth node に spawnfull pipeline test pass
11Capstone(recap)

マイルストーンは レッスン10 — live Reth node 上で EVM から呼べる CLOB(コントラクトが precompile を call → matching → 約定が bridge を経由して payload に現れる)。レッスン11 で「まだ何が足りないか(約定はまだ EVM tx でない)」を名指す。

答え合わせの規律

LessonsStageSHA
L1〜39a + 9e1761d4d / 2ba97c6
L4〜69bb635ef7
L7〜89ca8823a1
L99c+d19ba1b
L109d2f796c3
# レッスン範囲に応じて checkout:
# 1761d4d / b635ef7 / a8823a1 / d19ba1b / 2f796c3 / 2ba97c6
cd ~/code/openhl-reference && git checkout 1761d4d
diff -u ~/code/my-openhl/crates/evm/src/precompiles/mod.rs ./crates/evm/src/precompiles/mod.rs

本質(型・制御フロー)が一致していればよい。

合格基準

  • cargo test ... bridge_against_custom_evm_node_shares_clob_with_precompile を通せる(コース完走時)。
  • precompile が「EVM から呼べる native 関数」であることを説明できる。
  • なぜ外部 HTTP read が consensus で破綻するか(非決定性)を 1 文で言える。

まとめ(3行)

  • custom EVM precompile は固定 address(0x0c00+)で native Rust を決定的に実行する — Solidity bytecode なし、external call と同じ呼び出し shape。
  • read(clob_read_best_bid)+ write(clob_place_order)で CLOB を EVM の state 拡張にする — これがチェーンを「Hyperliquid 型」にする本質。
  • precompile は bridge 所有の同じ CLOB instance に触れる。約定の EVM-tx 化(block body への encode)はまだ scope 外。