Rust-based Solidity dev framework: `forge` (build/test/fuzz/deploy), `anvil` (local node + fork), `cast` (CLI for any RPC call), and `chisel` (Solidity REPL). Tests are written in Solidity for native fuzzing and invariant testing.
- 01fast Solidity tests and fuzzing
- 02invariant and differential testing
- 03deterministic forge scripts for deployments
- 04local mainnet forks via anvil
- 05ad-hoc chain interaction with cast
- curl -L https://foundry.paradigm.xyz | bash
- foundryup
- forge init my-project
- forge install OpenZeppelin/openzeppelin-contracts
| Variable | Scope | Description |
|---|---|---|
| FOUNDRY_ETH_RPC_URL | Server | Default RPC URL for `cast` and `forge script` (overridable per-command with `--rpc-url`). |
| FOUNDRY_PRIVATE_KEY | Server | Deployer private key for `forge script --broadcast`. Prefer a hardware wallet (`--ledger`/`--trezor`) or `cast wallet` keystore in production. |
| ETHERSCAN_API_KEY | Server | Etherscan (or compatible explorer) API key used by `forge verify-contract`. |
Use Foundry as the primary Solidity toolchain. Initialize with `forge init`, write contracts in `src/`, write Solidity tests in `test/` extending `forge-std/Test.sol` and run with `forge test -vvv`. Use fuzz tests (`function testFuzz_X(uint256 amount) public`) and invariant tests (`function invariant_Y() public`) for deeper coverage. Deploy via `forge script script/Deploy.s.sol --rpc-url $FOUNDRY_ETH_RPC_URL --broadcast --verify` rather than `forge create` for reproducibility. Use `anvil --fork-url $MAINNET_RPC` for local mainnet forks and `cast call/send/storage` for ad-hoc reads/writes. Pin the toolchain version with `foundry.toml` and a `.foundry-toolchain` file.
- ⚑Default Solidity compiler runs the legacy pipeline; enabling `via_ir = true` in `foundry.toml` changes gas costs and bytecode — measure gas BOTH ways and pick one for the whole project. Mixing pipelines between local and CI gives different addresses for the same source.
- ⚑`forge install` uses git submodules, not npm — re-running CI without `--recursive` clones leaves dependencies missing. Always `forge install` (not `npm install`) and commit `lib/` lockfile-style via submodule SHAs.
- ⚑Anvil's default chain ID is 31337 and accounts are deterministic from the same mnemonic — never reuse anvil keys on a public testnet, and never sign mainnet txs with them by mistake.
- ⚑Fuzz tests use a configurable seed; flaky fuzz failures are real but reproducible only with the same `FOUNDRY_FUZZ_SEED` — log the seed in CI output before treating a failure as a flake.
- ⚑`forge verify-contract` requires the EXACT compiler settings (version, optimizer runs, via-ir flag) used at deploy time; mismatch produces an opaque 'bytecode does not match' error.
- ⚑Cheatcodes (`vm.prank`, `vm.warp`, `vm.deal`) only work in tests — calling them in production contracts compiles fine but reverts on-chain.