Foundry を極める — すでに Rust で考えるエンジニアのための Solidity テスト規律
問い
rethlab の openhl 系コースで身についた Rust の規律 — pure-compute プリミティブ / debug_assert! + saturating_arithmetic で守る state machine / proptest! による保存則検証 / byte-for-byte 答え合わせ — を Solidity contract に 1:1 で移植する。Foundry はその移植を機械的に回す実行環境。なぜ Foundry が標準になり、Hardhat / Truffle ではダメで、規律はどう transfer するか?
原理(最小モデル)
- Foundry が標準になった理由 3 つ. ① 速度(REVM を in-process embed、IPC なし)、② Fuzzing first-class(shrinking / corpus persistence built-in)、③ Cheatcodes-as-precompiles(
vm.warp/vm.deal/vm.prankが precompile call)。 - Hardhat との 20-30× 速度差は最適化の積み上げでない. Process boundary を外したアーキテクチャ差。
hardhat node別プロセス + IPC round-trip vs Foundry が REVM を直接 in-process embed。 - 規律の transfer 表.
cargo test↔forge test/proptest!(single-input) ↔forge fuzz/proptest!(sequenced) ↔forge invariant/debug_assert!↔require/vm.expectRevert/ saturating_add ↔ Solidity 0.8 unchecked / conservation laws ↔ invariant assertions / byte-for-byte ↔ reference contract + forge corpus。 - Capstone (レッスン 6) で証明する. openhl-liquidation Stage 10b の
InsuranceFundを Rust → Solidity へ port、レッスン 9 の保存則 invariant を Foundry で書き、同じ定理を 2 言語で mechanical に証明。 - コース構造 7 lessons / 4 modules. Orientation (L0) → Test discipline (L1-L3) → CLI + state-aware testing (L4-L5) → Capstone (L6)。
- 前提知識. ① 基本 Solidity syntax(
function/mapping/struct)、② Rust crate でのcargo test、③ openhl-liquidation コースを レッスン 9 まで読了(WithdrawOutcomeproptest の登場点、capstone がそのセマンティクスを前提)。 - 含まれないもの. Gas optimization deep dive / Slither / Mythril / formal verification / Frontend (ethers.js / viem) / Foundry script の full deployment workflow。
具体例 + ステップで組み立てる
Foundry を極める — すでに Rust で考えるエンジニアのための Solidity テスト規律
このコースで得るもの
rethlab の openhl 系コース(Consensus、CLOB、Funding、Liquidation、ADL)を通過していれば、次の規律は身についている。
- pure-compute プリミティブ
debug_assert!+saturating_arithmeticで守る state machineproptest!による保存則の検証- byte-for-byte の答え合わせ
このコースの目的は、その規律を Solidity contract に 1:1 で移植することにある。Foundry はその移植を機械的に回す実行環境である。
完走後の到達点:
forge initで最小構成を立ち上げる。 build / test / fuzz のループをローカルで高速に回せる。forge fuzzを Rust のproptest!と同じ感覚で使う。 shrinking、失敗入力の最小化、再実行まで一通り実践する。forge invariantで multi-call 保存則を検証する。Handlerを介してランダムな呼び出し列を作り、各ステップで不変条件を確認する。castを日常的に使える状態にする。 storage 読み取り、view 呼び出し、ABI decode を手癖化する。anvil --fork-url+ cheatcodes で state-aware testing を行う。vm.deal/vm.warp/vm.prankを使い、実チェーン状態に近い検証を行う。- Capstone: openhl-liquidation Stage 10b の
InsuranceFundを Rust から Solidity に移植し、同じ定理を 2 言語で検証する。
このコースで理解すること:
- なぜ Foundry が標準になったか。 Rust 製の single binary で、REVM を同一プロセスで直接扱えるため。
- なぜ JS 系ツールだけでは不足するか。 IPC/JSON-RPC 経由の間接実行が増え、重い検証ほど遅延が効くため。
forge fuzz/forge invariantの正体。 openhl のcrates/evmと同種の REVM 駆動パターンを、Solidity テストとして表面化したもの。- なぜ cheatcodes が precompile なのか。 テストから EVM 状態へ直接アクセスでき、検証ループを短く保てるため。
このコースが存在する理由
ほとんどの Foundry チュートリアルは「このツールをどう使うか」に答える。本コースが答えるのは別の問いだ: 「Rust で学んだ厳格テストの規律を、どうやって Solidity contract に持ち込むか。」
すべての openhl 系コースで繰り返されてきた、その規律の形:
┌──────────────────────────────────────────────────────────────┐
│ rethlab の Rust 規律 ←→ Foundry Solidity 等価物 │
├──────────────────────────────────────────────────────────────┤
│ cargo test forge test │
│ proptest! (single-input) forge fuzz │
│ proptest! (sequenced ops) forge invariant │
│ debug_assert! require / vm.expectRevert│
│ saturating_add (consensus) Solidity 0.8 unchecked │
│ conservation laws invariant assertions │
│ byte-for-byte 答え合わせ reference contract + │
│ vs openhl SHA forge test corpus │
└──────────────────────────────────────────────────────────────┘
右列のすべての行を、レッスン6までにあなたは自分の手で書く。Capstone こそが、左列と右列が同じことを言っている 証明 になる。
なぜ Hardhat / Truffle / Brownie ではなく Foundry なのか
背景を短く整理する。
- 2022-2024 年にかけて、Foundry は JS ベースの主要ツールを置き換えた。
- Truffle は終了し、Hardhat は主にデプロイやフロント連携で使われる。
- L1 / contract / engine 開発では、Foundry が事実上の標準になった。
このコースの対象(L1 / infra エンジニア)にとって、Foundry は競争優位ではなく前提知識である。本コースは新しい作法を教えるのではなく、既存の Rust 規律を Solidity 側へ移すことに集中する。Foundry が選ばれた理由は次の 3 点だ。
-
速度。 Foundry の test runner は REVM を直接 in-process で embed する。JS test runner と別プロセスの
ganache/hardhat nodeをつなぐ IPC round-trip がない。Hardhat で 60 秒かかる 1000-test スイートが、forge testなら 2-3 秒で終わる。アーキテクチャ的な違い:┌─────────────────────────────────────────────────────────┐ │ Hardhat / Truffle (out-of-process — 遅い) │ ├─────────────────────────────────────────────────────────┤ │ ┌────────────┐ JSON-RPC over ┌────────────────┐ │ │ │ JS test │ ◄── IPC / TCP ──► │ hardhat node │ │ │ │ runner │ (eth_sendRaw..., │ (別プロセス) │ │ │ │ (mocha) │ eth_call, ...) │ EVM を embed │ │ │ └────────────┘ └────────────────┘ │ │ ↑ 呼び出しごとに ~1ms × test あたり数千回 │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Foundry (in-process — 速い) │ ├─────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────┐ │ │ │ forge test (単一の Rust binary) │ │ │ │ ┌──────────────┐ 直接の関数呼び出し │ │ │ │ │ Solidity │ ───────────────► │ │ │ │ │ test runner │ REVM の実行 │ │ │ │ └──────────────┘ (同一プロセス) │ │ │ └─────────────────────────────────────────────────┘ │ │ ↑ 呼び出しごとに ~µs、IPC なし、serialize なし│ └─────────────────────────────────────────────────────────┘この 20-30× の差は、最適化の積み上げではない。process boundary を外したアーキテクチャ差によるものだ。
-
Fuzzing が first-class primitive。 Hardhat では property-based testing は plugin 扱いだった。Foundry は built-in で出荷した — shrinking、corpus persistence、sequenced call 用の invariant testing 込みで。最も近い JS 等価物 (
fast-check+ Hardhat) は非自明な配線を要求する。 -
Cheatcodes-as-precompiles。 Hardhat の
evm_snapshot/evm_increaseTimeは JSON-RPC method — リモートノードに state を変えるよう依頼する。Foundry のvm.warp/vm.deal/vm.prankはアドレス0x7109709ECfa91a80626fF3989D68f67F5b1DD12Dの magic precompile への Solidity 呼び出し。これが REVM の state を内側から hack する — 同一プロセス、IPC なし、リモートノードへの信頼も不要。openhl Precompiles コース (Stage 9) を通った読者には、これは Rust で学んだ「precompile-as-EVM-superpower」パターン が Solidity 経由でテスト用に露出されたものだと分かる。速く、composable、何より contract と同じ Solidity ファイル内で testable。
L1 エンジニアにとっての戦略的含意。 Reth / REVM / Alloy code を書いたり読んだりするなら(rethlab の既存フォーカス)、Foundry は別言語の wrapper を被った同じ toolchain だ。生態系を切り替えるのではない。同じ execution engine に第 2 の言語を足すだけだ。
規律の transfer — port する 3 つの具体的不変条件
レッスン 2、レッスン 3、レッスン6で歩く内容のプレビュー。
Liquidation レッスン9から(Rust proptest):
$$\text{amount} + \text{unfilled} = \text{shortfall}$$
Fund は WithdrawOutcome { amount, unfilled } を返す。両者の和は caller が渡した shortfall と必ず一致する。Rust proptest はこう書く。
proptest! {
#[test]
fn withdraw_amount_plus_unfilled_equals_shortfall(
initial in 0_i64..1_000_000,
shortfall in 1_i64..1_000_000,
) {
// ... fund setup, withdraw call ...
prop_assert_eq!(amount + unfilled, shortfall);
}
}
Foundry へ(forge fuzz) — レッスン2で教える内容。
function testFuzz_AmountPlusUnfilledEqualsShortfall(
uint64 initial,
uint64 shortfall
) public {
vm.assume(shortfall > 0 && shortfall < 1_000_000);
vm.assume(initial < 1_000_000);
InsuranceFund f = new InsuranceFund(initial);
(uint64 amount, uint64 unfilled) = f.withdrawShortfall(shortfall);
assertEq(uint256(amount) + uint256(unfilled), uint256(shortfall));
}
同じ定理、違う syntax。Rust の shrinker と Foundry の shrinker は反例に対して同じ挙動をする。レッスン 6 が終わる頃には InsuranceFund 全体と レッスン9の 4 つの invariant をすべて port し終えている。同じ定理、2 言語、両方とも mechanical に証明済みだ。
7 つのレッスン
Module 0 — Orientation
- レッスン 0 (本レッスン) — なぜ Foundry、discipline-transfer の thesis、7-lesson ロードマップ。
Module 1 — Test discipline (レッスン 1〜3) — コア
- レッスン 1 —
forge test— first invariants、basic assertions、assertEq/vm.expectRevert、-vvvでの実行。Solidity 版のcargo test。 - レッスン 2 —
forge fuzz— Solidity のproptest!。Single-parameter fuzzing、shrinking、corpus persistence。Liquidation レッスン9を cross-reference する。 - レッスン 3 —
forge invariant—Handlercontract とtargetContractで回す multi-call invariant testing。Liquidation レッスン13の scanner proptest(per-scan な保存則)を cross-reference する。
Module 2 — CLI + state-aware testing (レッスン 4〜5)
- レッスン 4 —
cast— chain CLI の deep dive。call/send/storage/abi-decode/4byte。ethereum.reth.rs/rpc経由のメインネット例。 - レッスン 5 —
anvil --fork-url+ cheatcodes —vm.deal/vm.warp/vm.prankで回す state-aware testing。Cheatcodes-as-precompiles の framing(openhl Precompiles コースを cross-reference)。
Module 3 — Capstone (レッスン6)
- レッスン 6 — InsuranceFund.sol + forge invariants — openhl-liquidation Stage 10b の
InsuranceFundを Rust から Solidity に port し、レッスン9の保存則 invariant を Foundry で書き、10K iteration を回し、同じ定理を 2 言語で mechanical に証明する。答え合わせの contract + tests は rethlab のexamples/foundry-capstone/内。
このコースに 含まれない もの
- Gas optimization の deep dive —
forge inspectの gas snapshot 自体は real な topic だが、optimization は規律ではない。スコープ外。(将来コース候補: 「L1 エンジニアのための Solidity — gas、storage layout、bytecode」) - Slither / Mythril / formal verification — Foundry 隣接だが別の tooling family。扱わない。
- Frontend / ethers.js / viem — dApp stack の JS 側。rethlab の読者は L1 / contract / engine エンジニアであり、UI は別の関心事だ。
- Foundry script (
forge scriptでの deployment) — レッスン4のcast sendセクションで軽く触れる程度。Deployment story は本コースが教える testing 規律とは別のスキルだ。
License / asset discipline
本コースの reference asset — レッスン6の InsuranceFund.sol capstone + forge test corpus — は rethlab の examples/foundry-capstone/ に in-repo で住む。レッスンが ship される rethlab git SHA に pin される。読者は git checkout <sha> で byte-for-byte に動く copy を手に入れる。
Foundry 自体は頻繁に update される。コースは foundry-rs/foundry rev に pin する(コースが ship する時点での foundryup デフォルト)。将来の Foundry version がレッスンを壊したら rethlab issue を立てる — コースは current stable Foundry を追随する設計だ。
前提知識
以下に慣れていること:
- 基本的な Solidity 構文 (
function定義が読め、mappingとstructを区別できる)。 - Rust crate での
cargo testの実行 (rethlab の openhl 系コース全体で使うパターン)。 - rethlab の openhl-liquidation コースを少なくとも レッスン9まで読了している (最初の
WithdrawOutcomeproptest が登場するレッスン)。レッスン 6 capstone は、そのコースからInsuranceFundのセマンティクスを内面化していることを前提にする。
どれか心許なくても問題ない。openhl-liquidation コースが自然な前提知識で、基本 Solidity は solidity-by-example.org で 1 日で拾える。
合格基準
- Foundry が選ばれた 3 理由(速度 + Fuzzing first-class + cheatcodes-as-precompiles)を即答できる。
- Hardhat との 20-30× 速度差の根拠(process boundary 削除)を 1 文で説明できる。
- 規律の transfer 表(
cargo test→forge test、proptest!→forge fuzz/forge invariant、debug_assert!→vm.expectRevert/require、保存則 → invariant assertion)を即答できる。 - コース 7 レッスン構造(Orientation / Test discipline 3 / CLI + state-aware 2 / Capstone)を即答できる。
- 前提知識(Solidity syntax + cargo test + openhl-liquidation L9 まで)を即答できる。
まとめ(3行)
- Foundry = Rust 製 single binary、REVM を in-process embed = JS 系より 20-30× 速い、Fuzzing first-class + cheatcodes-as-precompiles で
proptest!/prop_assume!/ precompile-as-superpower パターンが Solidity に 1:1 移植。 - 本コースは「ツールの使い方」ではなく「Rust 規律の Solidity への transfer」が thesis、規律の transfer 表(cargo test / proptest! / debug_assert! / 保存則)が縦糸、Capstone (L6) で
InsuranceFund2 言語証明が終点。 - 7 lessons / 4 modules(Orientation / Test discipline 3 / CLI + state-aware 2 / Capstone)、前提 = openhl-liquidation L9 まで読了、Foundry install (
foundryup) 済み。