Mastering Foundry — Solidity testing discipline for engineers who already think in Rust
Question
The Rust discipline you internalised in the openhl tracks — pure-compute primitives, state machines guarded by debug_assert! + saturating arithmetic, conservation laws checked by proptest!, byte-for-byte answer keys — needs to port to Solidity contracts. Foundry is the runtime that runs that discipline mechanically. Why has Foundry become the standard, and what cannot Hardhat/Truffle do?
Principle (minimum model)
- Three reasons Foundry became the standard. (1) Speed (REVM embedded in-process — no IPC). (2) Fuzzing as a first-class primitive (shrinking + corpus persistence built-in). (3) Cheatcodes-as-precompiles (
vm.warp/vm.deal/vm.prankare precompile calls). - The 20–30× speed gap vs Hardhat is architectural, not micro-optimisations.
hardhat noderuns in a separate process; Hardhat tests talk to it over IPC. Foundry embeds REVM directly into the test runner process — no IPC, no serialisation. - Discipline transfer table.
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 answer keys ↔ reference contract + forge corpus. - The Capstone (Lesson 6) is the proof. Port openhl-liquidation Stage 10b's
InsuranceFundfrom Rust to Solidity, write the four conservation-law invariants in Foundry, run 10 K iterations — same theorem proved twice in two languages. - 7-lesson, 4-module structure. Orientation (L0) → Test discipline (L1–L3) → CLI + state-aware testing (L4–L5) → Capstone (L6).
- Prerequisites. (1) Basic Solidity syntax (read a
functiondefinition, distinguishmappingfromstruct). (2)cargo testin a Rust crate. (3) Read the openhl-liquidation track at least through Lesson 9 (the lesson whereWithdrawOutcomeproptest appears — the Capstone assumes you have internalised those semantics). - Out of scope. Gas-optimisation deep dives / Slither + Mythril + formal verification / frontend (ethers.js / viem) / full
forge scriptdeployment workflow.
Worked example + steps
Mastering Foundry — Solidity testing discipline for engineers who already think in Rust
What you'll build
If you've been through any of rethlab's openhl courses (Consensus, CLOB, Funding, Liquidation, ADL), you've learned a discipline: pure-compute primitives, state machines defended by debug_assert! + saturating_arithmetic, conservation laws proven by proptest!, byte-for-byte answer keys. That discipline transfers to Solidity contracts almost 1-to-1 — and Foundry is the tool that makes the transfer mechanical.
By the end of this course, you'll have:
- A Solidity project initialized with
forge initthat builds, tests, and fuzzes locally with sub-second feedback loops. - First-hand experience with
forge fuzz— Solidity'sproptest!equivalent. Same shrinking, same input distribution, same "find the minimal failing input" workflow you learned in Lesson 9 of the Liquidation course. forge invariantmulti-call testing — the closest Solidity primitive to per-scan conservation laws (Liquidation Lesson 13). Define aHandler, run thousands of random method-call sequences, assert a property holds at every step.castmuscle memory — the chain CLI most production traders/engineers run dozens of times daily. Read storage slots, call view functions, decode ABIs.anvil --fork-url+ cheatcodes — local mainnet replication withvm.deal/vm.warp/vm.prankfor state-aware testing. Cheatcodes are precompiles in disguise (see the openhl Precompiles course); Foundry exposes them via Solidity instead of Rust.- A capstone: port openhl-liquidation Stage 10b's
InsuranceFundfrom Rust to Solidity, write the Lesson 9 conservation-law invariants in Foundry, and prove the same theorem mechanically in two languages.
You'll understand:
- Why Foundry won the Solidity tooling war: because it's Rust-built, single-binary, sub-second-feedback, and embeds REVM directly — the same REVM you've been peering into across the openhl courses.
- Why Hardhat / Truffle / Brownie lost ground: JS-based, slower, indirect EVM access via remote forks rather than embedded execution.
- What
forge fuzzandforge invariantactually do under the hood — they're orchestrating REVM via the same patterns rethlab teaches incrates/evmof openhl, just exposed as Solidity-side tests. - Why cheatcodes are precompiles — and why that's the design choice that makes Foundry's test environment so much faster than JS-based alternatives.
Why this course exists
Most Foundry tutorials answer "how do I use this tool?" This course answers a different question: "how do I take the rigorous-testing discipline I learned in Rust and apply it to Solidity contracts?"
The shape of that discipline, repeated across every openhl course:
┌──────────────────────────────────────────────────────────────┐
│ rethlab Rust discipline ←→ Foundry Solidity │
│ equivalent │
├──────────────────────────────────────────────────────────────┤
│ 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 answer key reference contract + │
│ vs openhl SHA forge test corpus │
└──────────────────────────────────────────────────────────────┘
Every row in the right column is what you'll be writing by Lesson 6. The capstone is the proof that the left column and the right column are saying the same thing.
Why Foundry, not Hardhat / Truffle / Brownie
A one-paragraph history: Foundry replaced the JS-based stack between 2022 and 2024 for serious Ethereum engineering — Truffle is end-of-life, Hardhat survives mostly for deploy scripts and frontend integration, and for L1 / contract / engine work Foundry is now the de facto standard. For the audience of this course (L1 / infra engineers), Foundry fluency is a commodity prerequisite — not a competitive advantage. This course teaches it so the discipline you already have transfers. Three reasons Foundry won:
-
Speed. Foundry's test runner embeds REVM directly in-process. There's no IPC round-trip between a JS test runner and a separate
ganache/hardhat node. A 1,000-test suite that takes 60 seconds in Hardhat finishes in 2-3 seconds withforge test. The architecture difference:┌─────────────────────────────────────────────────────────┐ │ Hardhat / Truffle (out-of-process — slow) │ ├─────────────────────────────────────────────────────────┤ │ ┌────────────┐ JSON-RPC over ┌────────────────┐│ │ │ JS test │ ◄── IPC / TCP ──► │ hardhat node ││ │ │ runner │ (eth_sendRaw..., │ (separate proc)││ │ │ (mocha) │ eth_call, ...) │ embeds EVM ││ │ └────────────┘ └────────────────┘│ │ ↑ ~1ms per call, ×1000s of calls per test │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Foundry (in-process — fast) │ ├─────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────┐ │ │ │ forge test (single Rust binary) │ │ │ │ ┌──────────────┐ direct fn calls │ │ │ │ │ Solidity │ ───────────────► │ │ │ │ │ test runner │ REVM execution │ │ │ │ └──────────────┘ (same process) │ │ │ └─────────────────────────────────────────────────┘ │ │ ↑ ~µs per call, no IPC, no serialization │ └─────────────────────────────────────────────────────────┘The 20-30× speedup isn't an optimization — it's an architectural consequence of removing the process boundary.
-
Fuzzing as a first-class primitive. Hardhat had property-based testing as a plugin. Foundry shipped it built-in, with shrinking, with corpus persistence, with invariant testing for sequenced calls. The closest JS equivalent (
fast-check+ Hardhat) requires non-trivial wiring. -
Cheatcodes-as-precompiles. Hardhat's
evm_snapshot/evm_increaseTimeare JSON-RPC methods that ask a remote node to change its state. Foundry'svm.warp/vm.deal/vm.prankare Solidity calls to a magic precompile at address0x7109709ECfa91a80626fF3989D68f67F5b1DD12Dthat hacks REVM's state from the inside — same process, no IPC, no remote-node trust. For readers who came through the openhl Precompiles course (Stage 9), this is the same precompile-as-EVM-superpower pattern you learned in Rust, exposed via Solidity for testing. Faster, more composable, and (importantly) testable inside the same Solidity file as the contracts they test.
The strategic implication for an L1 engineer: if you write or read Reth/REVM/Alloy code (rethlab's existing focus), Foundry is the same toolchain in a different language wrapper. Learning it is not switching ecosystems — it's adding a second language to the same execution engine.
The discipline transfer — three concrete invariants you'll port
A preview of what Lesson 2, Lesson 3, and Lesson 6 walk through:
From Liquidation Lesson 9 (Rust proptest):
$$\text{amount} + \text{unfilled} = \text{shortfall}$$
The fund returns WithdrawOutcome { amount, unfilled }; their sum equals the shortfall the caller passed in. 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);
}
}
To Foundry (forge fuzz) — what Lesson 2 teaches:
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));
}
Same theorem. Different syntax. The Rust shrinker and the Foundry shrinker behave identically on a counter-example. By Lesson 6 you'll have ported the whole InsuranceFund and all four Lesson 9 invariants. Same theorem, two languages, both proven mechanically.
The 7 lessons
Module 0 — Orientation
- Lesson 0 (this lesson) — Why Foundry, the discipline-transfer thesis, the 7-lesson roadmap.
Module 1 — Test discipline (Lesson 1–Lesson 3) — the core
- Lesson 1 —
forge test— first invariants, basic assertions,assertEq/vm.expectRevert, run with-vvv. The Solidity equivalent ofcargo test. - Lesson 2 —
forge fuzz— Solidity'sproptest!. Single-parameter fuzzing, shrinking, corpus persistence. Cross-references Liquidation Lesson 9. - Lesson 3 —
forge invariant— multi-call invariant testing withHandlercontracts andtargetContract. Cross-references Liquidation Lesson 13's scanner proptests (per-scan conservation laws).
Module 2 — CLI + state-aware testing (Lesson 4–Lesson 5)
- Lesson 4 —
cast— chain CLI deep dive.call/send/storage/abi-decode/4byte. Mainnet examples viaethereum.reth.rs/rpc. - Lesson 5 —
anvil --fork-url+ cheatcodes — state-aware testing withvm.deal/vm.warp/vm.prank. Cheatcodes-as-precompiles framing (cross-reference openhl Precompiles course).
Module 3 — Capstone (Lesson 6)
- Lesson 6 — InsuranceFund.sol + forge invariants — port openhl-liquidation Stage 10b's
InsuranceFundfrom Rust to Solidity, write the Lesson 9 conservation-law invariants in Foundry, run 10K iterations, prove the same theorem mechanically in two languages. Answer-key contract + tests sit in-repo atexamples/foundry-capstone/.
What's NOT in this course
- Gas optimization deep dive —
forge inspectfor gas snapshots is a real topic, but it's optimization, not discipline. Out of scope. (Future course candidate: "Solidity for L1 engineers — gas, storage layouts, bytecode.") - Slither / Mythril / formal verification — Foundry-adjacent but a different tooling family. Not covered.
- Frontend / ethers.js / viem — the JS side of the dApp stack. rethlab's audience is L1 / contract / engine engineers; UI is its own concern.
- Foundry script (
forge scriptfor deployments) — covered briefly in Lesson 4'scast sendsection. The deployment story is a separate skill from the testing discipline this course teaches.
License / asset discipline
This course's reference assets — the Lesson 6 InsuranceFund.sol capstone + the forge test corpus — live in-repo at rethlab/examples/foundry-capstone/. Pinned to whatever rethlab git SHA the lesson ships at; the reader can git checkout <sha> to get a byte-for-byte working copy.
Foundry itself is updated frequently. The course pins to foundry-rs/foundry rev (as listed in foundryup defaults at the time the course ships). If a future Foundry version breaks any lesson, file a rethlab issue — the course is intended to track current stable Foundry.
Audience prerequisites
You should already be comfortable with:
- Basic Solidity syntax (you can read a
functiondefinition and understandmappingvsstruct). - Running
cargo testagainst a Rust crate (rethlab's openhl courses use this pattern throughout). - Reading rethlab's openhl-liquidation course at least through Lesson 9 (where the first
WithdrawOutcomeproptest appears). Lesson 6's capstone assumes you've internalized theInsuranceFundsemantics from that course.
If any of those feel shaky, no problem — the openhl-liquidation course is the natural pre-req, and basic Solidity can be picked up from solidity-by-example.org in an afternoon.
Summary (3 lines)
- Foundry = a Rust-native single binary that embeds REVM in-process → 20–30× faster than JS toolchains. Fuzzing is first-class; cheatcodes are precompiles. The
proptest!/prop_assume!/ precompile-as-superpower patterns port 1:1 to Solidity. - This course is not "how to use Foundry". It is "how to transfer the Rust discipline from openhl into Solidity contracts". The discipline-transfer table is the spine; the Capstone is the proof.
- 7 lessons / 4 modules. Prerequisite: openhl-liquidation through L9. Setup:
foundryupinstalled before Lesson 1.