
Foundry
- 4 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Build, test, fuzz, and deploy EVM smart contracts with Foundry - Forge, Cast, Anvil, and Chisel.
About
A Rust-based Ethereum toolkit covering Forge build/test/fuzz, Cast interaction, Anvil local node, cheatcodes, scripting, and the linter. A developer uses it to write, test, debug, and deploy Solidity contracts.
- Cheatcodes, invariant/fork testing, coverage, and forge verify-contract
- Cast, Anvil local node, Chisel REPL, and forge lint
Foundry by the numbers
- 4 all-time installs (skills.sh)
- Ranked #347 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill foundryAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Build, test, fuzz, and deploy EVM smart contracts with Foundry - Forge, Cast, Anvil, and Chisel.
Files
Skill based on Foundry (foundry-rs/foundry), generated 2026-02-09. User docs: https://getfoundry.sh, book: https://book.getfoundry.sh
Foundry is a fast, portable Ethereum dev toolkit (Rust): Forge (build, test, fuzz, deploy), Cast (EVM interaction), Anvil (local node), Chisel (Solidity REPL). This skill focuses on agent capabilities — architecture, cheatcodes, scripting, debugging, custom networks, and the linter — from the in-repo dev docs.
Core References
| Topic | Description | Reference |
|---|---|---|
| Architecture | evm, config, cli crates; where cheatcodes and CLI live | core-architecture |
| Cheatcodes | Vm address, Inspector, adding cheatcodes, Cheatcode trait, JSON spec | core-cheatcodes |
| Config | foundry.toml, profiles, compiler, paths, remappings | core-config |
| Project layout | forge init, src, test, script, lib | core-project-layout |
Features
| Topic | Description | Reference |
|---|---|---|
| Forge build & test | forge build, forge test, snapshot, gas report, fuzz | features-forge-build-test |
| Forge fmt | forge fmt, [fmt] config, --check | features-forge-fmt |
| Contract size | forge build --sizes, 24KB limit | features-contract-size |
| Forge install | forge install, remappings, lib layout | features-forge-install |
| Scripting | forge script flow, broadcast, resume, nonce, multi-chain | features-scripting |
| Testing patterns | vm.prank, expectRevert, expectEmit, forge-std Test | features-testing-patterns |
| Coverage & verify | forge coverage, forge verify-contract | features-coverage-verify |
| Invariant testing | invariant_* functions, runs, depth, stateful fuzz | features-invariant |
| Fork testing | createSelectFork, selectFork, activeFork, multi-fork | features-fork-testing |
| FFI & signing | vm.ffi, vm.sign, EIP-712 helpers | features-ffi-signatures |
| State cheatcodes | vm.deal, vm.mockCall, vm.etch | features-state-cheatcodes |
| Cast | cast call, send, ABI encode/decode, chain queries | features-cast |
| Anvil | Local node, fork, pre-funded accounts, block time | features-anvil |
| Chisel | Solidity REPL for snippets and quick checks | features-chisel |
| Debugging | RUST_LOG, tracing filters for forge/cast/anvil | features-debugging |
| Custom Networks | Custom precompiles, evm-networks crate | features-networks |
| Lint (forge lint) | Early/late passes, adding lint rules, testing | features-lint |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Testing | Test layout, naming, isolation, CI patterns | best-practices-testing |
| Scripting | Broadcast, verify, resume, keys, multi-chain | best-practices-scripting |
External Links
Generation Info
- Source:
sources/foundry(https://github.com/foundry-rs/foundry) - Git SHA:
0847fed786bb32d77851fc2fcd5734867111eff7 - Generated: 2026-02-09
- Docs used: docs/dev/README.md, architecture.md, cheatcodes.md, scripting.md, debugging.md, networks.md, lintrules.md; README.md (overview)
Testing Best Practices
Structure and run Forge tests so they stay fast, deterministic, and easy to maintain.
Naming and layout
- Prefix test functions with
test(e.g.testTransferRevertsWhenBalanceLow); prefix fuzz withtestFuzz_or use parameters; prefix invariants withinvariant_. - One test file per contract under test (e.g.
Token.t.solforToken.sol) or group by feature. - Put tests in
test/; Forge discovers any*.t.solthere. UsesetUp()for shared state so each test gets a fresh EVM.
Isolation
- Use
setUp()to deploy contracts and set initial state; avoid relying on test order. - For fork tests, create the fork in
setUp()or at the start of the test and pin block for reproducibility. - Clear mocks (
vm.clearMockedCalls()) or use new fork when tests depend on clean state. - Prefer
vm.prankanddealover transferring from a shared account so tests don’t interfere.
CI and performance
- Run
forge testwith a fixed fuzz run count (e.g. infoundry.toml) so CI is predictable. - Use
--gas-reportin CI to track regressions; useforge snapshot --checkto fail on gas changes. - Fork tests: use a cached RPC or limited block range to avoid flakiness and rate limits.
- Disable FFI in CI unless required; enable only for the job that needs it.
Key points
- Determinism: pin forks, avoid
block.timestamp/block-dependent data unless fuzzed, use fixed seeds if needed. - Keep tests focused: one behavior per test; use descriptive names and
assertmessages. - Balance speed and coverage: more fuzz runs and invariant depth improve coverage but slow CI; tune per repo.
<!-- Source references:
- https://book.getfoundry.sh/forge/tests
- https://getfoundry.sh/forge/
-->
Foundry Architecture
Foundry is a Cargo workspace. High-level layout:
- `evm/` — EVM tooling built around revm. Implements cheatcodes (Solidity calls that manipulate execution environment for tests).
- `config/` — All Foundry settings and how to load them (e.g.
foundry.toml). - `cli/` — Core
forgeandcastCLI implementation and subcommands.
Key Points
- Cheatcodes are the main testing hook; they are implemented in the EVM layer and invoked at a fixed address.
- Config is centralized in
config/; CLIs consume it for forge/cast/anvil/chisel. - For agent tasks: use
configfor understanding options,clifor subcommand behavior,evmfor test/script execution and cheatcode semantics.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/architecture.md
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/README.md
-->
Foundry Cheatcodes
Cheatcodes are Solidity calls that manipulate the EVM during tests/scripts. They are invoked at a fixed address and intercepted by Foundry's EVM inspector.
Vm interface and address
- Cheatcode handler address:
address(uint160(uint256(keccak256("hevm cheat code"))))→0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. - In Solidity:
Vm constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);or inherit fromforge-std/Test.sol.
Implementation (for contributors)
- revm::Inspector — Callbacks (e.g.
Inspector::call) notify when the EVM is about to execute a call; the cheatcode inspector listens for the cheatcode address and decodes calldata. - Rust bindings — Generated via Alloy
sol!macro from theVminterface incheatcodes/spec/src/vm.rs. Each cheatcode is a function onVmwith attributes: #[cheatcode(group = <ident>)](required)#[cheatcode(status = Stable|Experimental)]#[cheatcode(safety = Safe|...)]for script safety.- Cheatcode trait — Implement exactly one of:
apply(no EVM data),apply_stateful(needs EVM state),apply_full(needs executor for recursive calls). - JSON spec — Run
cargo cheatsto regeneratecheatcodes.json/cheatcodes.schema.jsonfrom thesol!definition; first run after adding a cheatcode updates the files (CI may fail until second run).
Adding a new cheatcode
1. Add Solidity definition(s) in cheatcodes/spec/src/vm.rs (documented, named params). Compilation will fail until step 2. 2. Implement the Cheatcode trait for the generated call struct in the appropriate module under crates/cheatcodes. 3. If you added structs/enums/errors/events to Vm, update spec::Cheatcodes::new. 4. Run cargo cheats twice to refresh JSON (first run may fail CI). 5. Add an integration test in testdata/default/cheats/.
Key Points
- All cheatcodes are defined in one
sol! { interface Vm { ... } }and dispatched via a single match on decodedVmCalls. - Use the Foundry Book cheatcodes reference for user-facing list; this skill is for implementation and extending.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/cheatcodes.md
- https://github.com/foundry-rs/foundry/blob/master/crates/cheatcodes/README.md
-->
Foundry Config (foundry.toml)
Foundry uses foundry.toml at the project root for compiler, test, and tool settings. Agents need to read or write this file when setting up projects, CI, or deployment.
Minimal config
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.28"Profiles
Use profiles to switch between dev, CI, and production:
[profile.default]
solc_version = "0.8.28"
[profile.ci]
fuzz = { runs = 256 }
optimizer = true
optimizer_runs = 200
[profile.release]
optimizer = true
optimizer_runs = 10000Key options
| Option | Purpose |
|---|---|
src, out, libs | Source dir, output dir, library dirs |
solc_version | Solidity compiler version |
evm_version | Target EVM hardfork (e.g. cancun, shanghai) |
optimizer, optimizer_runs | Optimizer on/off and run count (deploy vs runtime gas trade-off) |
remappings | Import path → path (e.g. @openzeppelin/=lib/openzeppelin-contracts/) |
rpc_endpoints | Named RPC URLs for scripts (e.g. mainnet, localhost) |
Resolving config
Run forge config to print the resolved configuration (including defaults and profile merge). Use when debugging or scripting.
Key points
- One concept per profile;
[profile.default]is the default. evm_versionshould match deployment chain (opcode compatibility).- Remappings are required for dependencies (e.g.
forge installthen add remapping). - Scripts and tests can use
--rpc-urlor configrpc_endpointskeys.
<!-- Source references:
- https://book.getfoundry.sh/config/overview
- https://getfoundry.sh/forge/reference/config/
-->
Project Layout
Forge expects a standard layout; forge init creates it. Agents use this when scaffolding or navigating a Foundry project.
forge init
forge init [PATH]
forge init my-project --no-git --empty- Default: creates
src/,test/,script/,lib/,foundry.toml, and example Counter contract + test + script. --empty: no example files; only dirs and config.--no-git: skipgit init.--vscode: add VS Code settings andremappings.txt.--force: create even if directory is not empty.--template <repo>: start from a template repo.
Default structure
.
├── foundry.toml
├── src/ # Contract source
├── test/ # Test contracts (*.t.sol)
├── script/ # Deployment scripts (*.s.sol)
└── lib/ # Dependencies (git submodules, forge install)Paths are configurable in foundry.toml (src, out, libs, etc.). Tests are any contract in the test dir with test-prefixed functions; scripts are contracts with run() (or entrypoint specified in forge script).
Key points
- New projects: run
forge initthenforge install forge-stdif not already present. - Existing repos: add
foundry.tomland matchsrc/test/script/libsto the repo layout. - Scripts live in
script/and are run withforge script script/Name.s.sol:ContractName.
<!-- Source references:
- https://getfoundry.sh/reference/forge/forge-init
- https://getfoundry.sh/guides/project-setup/project-layout/
- https://book.getfoundry.sh/reference/forge/forge-init
-->
Anvil
Anvil is Foundry's local Ethereum node. Use it for fast local tests, scripting against a chain, or forking mainnet.
Start
anvil
anvil --port 8546
anvil --fork-url $RPC_URL
anvil --fork-url $RPC_URL --fork-block-number 18000000- Default port 8545; default chain id 31337.
--fork-url: run as fork of given RPC; optional--fork-block-numberfor a fixed block.
Pre-funded accounts
Anvil starts with 10 deterministic accounts (same keys every run). Check anvil --help for the list; typically used via --account or in scripts with known private keys. Useful for forge script and tests without loading keys.
Block time
anvil --block-time 2Default is instant (mine on demand). --block-time N mines a new block every N seconds.
Key points
- Start Anvil before
forge scriptor tests that need a local RPC; point them athttp://127.0.0.1:8545. - Fork mode: use for scripts that depend on mainnet state; pin block for reproducibility.
- Pre-funded accounts are for dev only; never use those keys on mainnet.
<!-- Source references:
- https://book.getfoundry.sh/reference/anvil/
- https://getfoundry.sh/anvil/
-->
Cast
Cast is the Swiss-army CLI for talking to the EVM: read contract state, send transactions, and query chain data. Use it in scripts or when debugging.
Read (call)
cast call <CONTRACT> <SIG> [ARGS...] --rpc-url <RPC>
# Example: view function
cast call 0x... "balanceOf(address)(uint256)" 0x... --rpc-url $RPCUse for view/pure calls; no transaction is sent.
Send transaction
cast send <CONTRACT> <SIG> [ARGS...] --rpc-url <RPC> --private-key <KEY>
# Or from env
cast send 0x... "transfer(address,uint256)" 0x... 1e18 --private-key $PKSigns and broadcasts a transaction. Use --value for ETH, --gas-limit if needed.
ABI encoding / decoding
cast abi-encode "f(address,uint256)" 0x... 100
cast abi-decode "f(address,uint256)" <HEX>
cast calldata "transfer(address,uint256)" 0x... 1e18Useful for building calldata or inspecting revert data.
Chain and account queries
cast block-number --rpc-url $RPC
cast balance <ADDRESS> --rpc-url $RPC
cast nonce <ADDRESS> --rpc-url $RPC
cast gas-price --rpc-url $RPCKey points
- Always pass
--rpc-url(or setETH_RPC_URL) for chain state. - For private key, use
--private-keyorCAST_PRIVATE_KEY; avoid committing keys. - Use
cast callfor reads;cast sendfor state-changing txs. - ABI helpers use Solidity-style function signatures.
<!-- Source references:
- https://book.getfoundry.sh/reference/cast/
- https://getfoundry.sh/cast/
-->
Chisel
Chisel is Foundry's Solidity REPL. Use it for one-off expressions, quick contract checks, or exploring EVM behavior without writing full tests.
Usage
chisel
npx --yes @foundry-rs/chisel@nightlyRuns inside or outside a Foundry project. At the prompt you can type Solidity snippets and see results (e.g. uint x = 1 + 2;).
When to use
- Try small Solidity snippets without creating a test file.
- Inspect return values or reverts interactively.
- Quick sanity checks (hashing, ABI encoding, etc.) when scripting or debugging.
Key points
- Chisel is separate from Forge/Cast/Anvil; no
foundry.tomlrequired for one-off runs. - For project-specific code, run from the project dir so remappings and dependencies apply if Chisel loads them.
- Prefer
forge testfor reproducible, versioned tests; Chisel for ad-hoc exploration.
<!-- Source references:
- https://getfoundry.sh/chisel
- sources/foundry/npm/@foundry-rs/chisel/README.md
-->
Contract Size
Ethereum enforces a 24KB (24,576 bytes) limit on deployed contract size. Forge can report sizes and fail the build if the limit is exceeded.
Usage
forge build --sizesBuilds the project and prints a table of contract sizes (non-test, non-script). Shows size and remaining margin to 24KB. Exits with code 1 if any contract exceeds the limit.
Exclusions
Test and script contracts are excluded from the size check (they are not deployed). Contracts that are only used by tests or scripts may still appear; mark script helpers with bool public IS_SCRIPT = true; if they should be excluded from the limit check.
Key points
- Run
forge build --sizesbefore deployment or in CI to catch size regressions. - Reduce size by: enabling optimizer, increasing optimizer runs (trade deploy cost for size), splitting logic into libraries or multiple contracts, removing unused code.
- Via-IR and some configs can affect which contracts appear in the report; check the output for the contracts you deploy.
<!-- Source references:
- https://book.getfoundry.sh/reference/forge/forge-build
- https://github.com/foundry-rs/foundry/issues/4615
-->
Coverage and Verification
Forge can report test coverage and verify contract source on block explorers (Etherscan and compatibles).
Coverage
forge coverage
forge coverage --report lcovRuns the test suite and reports which lines/branches are covered. Use --report lcov to emit lcov output for external tools or CI. Coverage is based on execution during forge test; invariant and fuzz runs contribute.
Verify contract
After deployment, verify the contract so the explorer shows source and ABI:
forge verify-contract <ADDRESS> <CONTRACT> --chain <CHAIN> --etherscan-api-key $KEY
# Example
forge verify-contract 0x... src/Token.sol:Token --chain mainnetContract is specified as path:ContractName. For constructor args:
--constructor-args $(cast abi-encode "constructor(uint256)" 42)--constructor-args-path args.txt--guess-constructor-args(extract from creation code)
Use --compiler-version and --num-of-optimizations if they differ from default. --watch waits for verification result; --retries and --delay help with rate limits.
Verify from script
When deploying with forge script, add --verify to verify in the same run:
forge script script/Deploy.s.sol --broadcast --verify --rpc-url $RPCKey points
- Coverage is best interpreted with lcov or a coverage dashboard; aim to cover critical paths and edge cases.
- Verification requires an API key for the explorer (e.g. Etherscan); chain must be supported.
- Constructor args must match deployment exactly; use same compiler version and optimizer runs as deploy.
<!-- Source references:
- https://getfoundry.sh/forge/reference/forge-coverage.html
- https://book.getfoundry.sh/reference/cli/forge/verify-contract
- https://getfoundry.sh/guides/deploying-contracts
-->
Debugging Foundry
Foundry binaries use the tracing crate. A console formatter is installed for forge, cast, and anvil.
RUST_LOG
Set RUST_LOG=<filter> to increase verbosity. Examples:
RUST_LOG=forge— all logs from theforgecrateRUST_LOG=cast— all logs from thecastcrate
Valid log levels: error, warn, info, debug, trace. Filter syntax is described in tracing-subscriber.
Key Points
- Use
RUST_LOG=debugorRUST_LOG=tracefor troubleshooting CLI or execution; scope by crate name (e.g.forge,cast) to reduce noise. - For Rust code,
dbg!from the standard library is also available. - When helping users: suggest enabling
RUST_LOGand reproducing the issue to capture internal state.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/debugging.md
- https://github.com/foundry-rs/foundry/blob/master/crates/cli/README.md
-->
FFI and Signing Cheatcodes
Foundry provides vm.ffi to run external binaries in tests and vm.sign / EIP-712 helpers for signature-based logic.
vm.ffi
Execute a command and use its stdout as return data:
string[] memory inputs = new string[](3);
inputs[0] = "node";
inputs[1] = "script.js";
inputs[2] = "arg";
bytes memory result = vm.ffi(inputs);Use for proofs, hashing, or any off-chain computation that must match in tests. Enable with ffi = true in foundry.toml under the test profile; CI may restrict or disable FFI.
vm.sign
Sign a digest with a private key; returns (v, r, s):
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
// use in signature-based logic, e.g. permit, meta-txsUse for testing ecrecover, permit, or other raw signature checks. For EIP-712, use the typed-data helpers and then sign the final digest.
EIP-712 helpers
vm.eip712HashType(typeDefinition)— typeHash from struct definition.vm.eip712HashStruct(typeName, structData)— structHash.vm.eip712HashTypedData(jsonTypedData)— full EIP-712 digest to sign.
Then pass the digest to vm.sign. Use forge eip712 and forge bind-json to get canonical type strings and Solidity bindings for structs.
Key points
- FFI is opt-in and can be disabled in CI for security; use only when necessary.
- For EIP-712, build the typed data (domain + struct), hash with the helpers, then sign.
vm.signis for raw digests; pair with EIP-712 hashing for typed structured data.
<!-- Source references:
- https://getfoundry.sh/reference/cheatcodes/sign/
- https://getfoundry.sh/guides/eip712
- https://book.getfoundry.sh/reference/cheatcodes/signing
- https://book.getfoundry.sh/tutorials/testing-eip712
-->
Forge Build and Test
Forge is the build and test CLI. Agents use it to compile contracts, run tests, and inspect gas.
Build
forge buildCompiles src/ (or configured src), writes artifacts to out/. Use --force to recompile. Libraries and remappings from foundry.toml apply.
Test
forge test
forge test --match-test testFork
forge test --match-contract MyContract
forge test --gas-report
forge test --fork-url $RPC_URL--match-test,--match-contract: filter tests by name or contract.--gas-report: print gas per test.--fork-url: run tests against a fork (e.g. mainnet); use with--fork-block-numberfor determinism.-vvv/-vvvv: extra traces for debugging.
Fuzz testing
Tests that take parameters are fuzzed by default; Foundry generates random inputs. Use vm.assume in tests to constrain inputs. Configure runs in foundry.toml (e.g. fuzz.runs).
Snapshot (gas)
forge snapshot
forge snapshot --diff
forge snapshot --checkCreates a gas snapshot from the test run. --diff compares to existing snapshot; --check fails if gas changed beyond tolerance.
Key points
forge buildmust succeed beforeforge testorforge script.- Use
--gas-reportin CI or before optimizations. - Fork tests with
--fork-urlfor integration-style tests; pin block for reproducibility. - Snapshots help avoid accidental gas regressions.
<!-- Source references:
- https://book.getfoundry.sh/reference/forge/forge-test
- https://book.getfoundry.sh/reference/forge/forge-snapshot
- https://book.getfoundry.sh/introduction/getting-started
-->
Forge Fmt
Forge includes a Solidity formatter. Use forge fmt to normalize style and --check in CI to enforce it.
Usage
forge fmt
forge fmt --check
forge fmt -w- No flags: format files in place (respects
foundry.toml[fmt]). --check: exit non-zero if any file would change; use in CI.-w: watch mode; re-run formatter on file changes.-r: print formatted output to stdout (raw).
Config ([fmt] in foundry.toml)
[fmt]
line_length = 120
tab_width = 4
style = "space"
bracket_spacing = false
int_types = "long"
multiline_func_header = "attributes_first"
quote_style = "double"
number_underscore = "preserve"Common options: line_length, tab_width, style (space vs tab), quote_style. Use int_types = "short" for uint/int instead of uint256/int256.
Key points
- Run
forge fmtbefore commits or in a pre-commit hook; useforge fmt --checkin CI. - Formatter applies to Solidity files under the project; paths and excludes follow config.
- Editor integration: point the editor’s Solidity formatter at
forge fmtor use the Foundry VS Code extension for format-on-save.
<!-- Source references:
- https://getfoundry.sh/config/reference/formatter/
- https://book.getfoundry.sh/reference/cli/forge/fmt
-->
Fork Testing
Foundry can fork a live chain in tests so you run against real contract state. Use RPC URLs (or aliases from foundry.toml rpc_endpoints) and optional block or tx pinning for determinism.
Create and select a fork
uint256 forkId = vm.createSelectFork(MAINNET_RPC_URL);
// or pin block
uint256 forkId = vm.createSelectFork(MAINNET_RPC_URL, 18_000_000);
// or fork at a specific tx (replays up to that tx)
uint256 forkId = vm.createSelectFork(MAINNET_RPC_URL, txHash);createSelectFork creates the fork and makes it active. Returns a fork id for switching later. Use block number or tx hash for reproducible tests.
Multiple forks
uint256 mainnet = vm.createSelectFork(MAINNET_RPC);
uint256 arb = vm.createSelectFork(ARB_RPC);
vm.selectFork(mainnet);
// ... use mainnet ...
vm.selectFork(arb);
// ... use arb ...
assertEq(vm.activeFork(), arb);Use selectFork(forkId) to switch; activeFork() returns the current fork id. Roll state with vm.rollFork(blockNumber) on the active fork.
Key points
- Fork tests need a reachable RPC; use env vars or
rpc_endpointsin config. - Pin block (or tx) so tests don't depend on latest state.
createFork+selectForkseparately if you need to create without switching;createSelectForkdoes both in one step.- Roll fork to a later block when testing time-dependent logic.
<!-- Source references:
- https://getfoundry.sh/reference/cheatcodes/create-select-fork/
- https://book.getfoundry.sh/reference/cheatcodes/forking
- https://getfoundry.sh/forge/fork-testing
-->
Invariant Testing
Invariant tests assert that specified invariants hold across many randomized sequences of function calls. Use them to find bugs in stateful logic that unit tests might miss.
Defining invariants
Name functions invariant_*; they are run after each call in a generated sequence:
function invariant_totalSupplyEqualsBalances() public {
uint256 sum;
for (uint i = 0; i < users.length; i++) sum += token.balanceOf(users[i]);
assertEq(sum, token.totalSupply());
}The fuzzer generates sequences of calls (e.g. transfer, approve, transferFrom); after each call all invariant_* functions are executed. If one fails, Foundry reports the failing call sequence.
Runs and depth
- Runs: number of sequences (campaigns).
- Depth: number of calls per sequence.
Configure in foundry.toml or per-test. More runs/depth improve coverage but take longer. Alternatively set a timeout in seconds.
afterInvariant
Use afterInvariant() to run logic at the end of each run (e.g. reset state, log metrics). Each invariant_* function runs in its own executor; to assert multiple invariants on the same state, put them in one function.
Key points
- Invariants should be properties that always hold (e.g. sum of balances = totalSupply, xy = k).
- Combine with handlers that call contract functions with fuzzed inputs; Foundry can use storage-aware fuzz when enabled.
- Start with small runs/depth to get fast feedback; increase for CI or pre-release.
- Group related assertions in a single
invariant_*so they see the same state.
<!-- Source references:
- https://getfoundry.sh/forge/advanced-testing/invariant-testing/
- https://book.getfoundry.sh/forge/invariant-testing
-->
Foundry Linter (forge lint)
Solidity linter for potential errors, vulnerabilities, gas optimizations, and style. Two-pass: AST (early) and HIR (late).
Architecture
1. Parsing — Solidity → AST via solar. 2. HIR — AST lowered to HIR with types and semantics. 3. Early lint passes — EarlyLintVisitor runs EarlyLintPass on the AST (syntax, naming, simple patterns). 4. Late lint passes — LateLintVisitor runs LateLintPass on the HIR (semantic, cross-reference, type-aware). 5. Diagnostics — Lint context emits warnings/notes; optional Suggestion for fixes (machine-applicable or example).
Key types: Linter, SolidityLinter, Lint / SolLint, EarlyLintPass, LateLintPass, LintContext, Suggestion.
Adding a lint rule
1. Add test Solidity in crates/lint/testdata/<RuleName>.sol (and auxiliary/ for imports). Use solar -Zdump=ast or -Zdump=hir to inspect patterns. 2. Declare metadata with declare_forge_lint!(ID, Severity, "kebab-id", "description");. 3. Register in mod.rs: register_lints!((PassStruct, early|late, (LINT_ID)));. 4. Implement EarlyLintPass or LateLintPass on the pass struct. Use cx.emit_with_suggestion for fixes; set Applicability (e.g. MachineApplicable, MaybeIncorrect). 5. Add tests: annotate expected diagnostics with //~WARN: message or //~NOTE: message; run cargo bless-lints to refresh .stderr; run cargo test -p forge --test ui (or nextest) to verify.
Choosing early vs late
- Early: syntax-only, naming, formatting, no type info. Use when the rule can be decided from the AST.
- Late: needs types, cross-references, or semantic checks; use to avoid false positives.
Key Points
- One pass struct can handle multiple lints; register all in
register_lints!. - Suggestions integrate with solar's diagnostics and applicability levels.
- UI tests compare linter output to blessed
.stderrfiles.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/lintrules.md
- https://github.com/foundry-rs/foundry/blob/master/crates/lint/README.md
-->
Custom Network Features
Foundry's anvil, forge, and cast can be extended with network-specific behavior. Currently supported: custom precompiles; custom transaction types are planned.
Implementation
- Custom features are implemented in the `evm-networks` crate (
crates/evm/networks). - Use this when you need to emulate or support a chain that has non-standard precompiles or other EVM behavior.
- Documentation and examples live inside the
evm/networkscrate.
Key Points
- For standard Ethereum/mainnet forks, no change is needed.
- When an agent needs to support a custom L2 or sidechain that differs by precompiles or tx types, point to
evm-networksand the Foundry book/config for network selection.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/networks.md
-->
Foundry Scripting
forge script compiles and runs a Solidity script, optionally broadcasting transactions and resuming deployments.
Execution flow (high level)
1. Compile — Script and dependencies are compiled. 2. Execute — Runner spawns backend (fork or local), deploys libraries and script contract, runs setUp() then run(). Broadcastable transactions are collected from vm.broadcast / vm.startBroadcast during run(). 3. Broadcast — If --broadcast, transactions are sent (single- or multi-chain). RPCs can be collected from config or CLI. 4. Resume — --resume only re-sends previously generated transactions; it does not re-run the script. Use after a partial broadcast or to retry. 5. Verify — Contracts can be verified after deployment.
Resume and verify can run without --broadcast (e.g. verify after a past run).
Nonce management
During script execution, Foundry adjusts the sender nonce so that execution and state match on-chain: setUp() and run() are called with the correct msg.sender, and each vm.broadcast-created contract decrements the nonce. If no user sender is set and the default sender is used, this nonce correction can be skipped.
Key Points
- Script execution and on-chain simulation are separate:
ScriptArgs::executeruns the script;ScriptArgs::onchain_simulation(when not skipping) runs only the collected broadcastable transactions. - Multi-chain: multiple script sequences can be created and deployed via
MultiChainSequence; resume works per chain. - For agents: use
forge script --helpandfoundry.tomlfor RPC/sender; document--broadcastvs--resumevs verify-only flows.
<!-- Source references:
- https://github.com/foundry-rs/foundry/blob/master/docs/dev/scripting.md
- https://github.com/foundry-rs/foundry/blob/master/README.md
-->