Foundry Documentation
Foundry is a blazing-fast, modular toolkit for building, testing and deploying smart contracts. It is written in Rust, uses Solidity for tests, and is optimized for speed, fuzzing, and advanced testing workflows.
What is Foundry
Foundry is a smart contract development toolkit focused on speed and testability. It is built around three main tools:
- forge — build, test, format, and manage Solidity projects.
- cast — Swiss-army knife for chain interactions (RPC calls, encodings, tx decoding).
- anvil — local Ethereum node with fast forking and advanced debugging features.
Foundry uses Solidity as the testing language. You write tests in the same language as your contracts, while the Rust engine handles compilation, execution, fuzzing, and invariant checking.
Why use Foundry
Foundry is widely used by protocol teams and security auditors because it focuses on:
- Speed — compilation and tests are extremely fast, even for large codebases.
- Testing power — first-class fuzzing, invariant testing, and cheatcodes.
- Low friction — no Node.js required, minimal boilerplate, simple CLI.
- Compatibility — works well with existing Solidity projects, OpenZeppelin, Hardhat, and more.
Many teams use Foundry for testing and Hardhat for deployment, plugins, and JS/TS integration. You don’t have to pick one — you can run both in the same repository.
Installation
The recommended way to install Foundry is via the official installation script. It can also install the Rust toolchain if you don’t have it yet.
1. Install Foundry (Unix/macOS)
curl -L https://foundry.paradigm.xyz | bash
foundryup
The foundryup command ensures the latest Foundry version is installed and available on your PATH.
2. Verify the installation
forge --version
cast --version
anvil --version
Run foundryup again to update Foundry to the latest release. This is useful when new Solidity versions or Foundry features are released.
Creating a project
New Foundry projects are initialized via forge init.
1. Create and initialize
mkdir foundry-demo
cd foundry-demo
forge init
This creates a template with sample contracts, tests, scripts, and configuration. You can also initialize in an existing directory:
forge init .
2. Build and run tests
forge build
forge test
If everything is installed correctly, you will see compilation output and passing tests for the sample contracts.
Forge / Cast / Anvil
Foundry revolves around three main binaries:
Forge
- forge build — compile contracts.
- forge test — run test suite.
- forge fmt — format Solidity code.
- forge coverage — generate coverage report.
- forge inspect — inspect ABIs, bytecode, storage layout, and more.
Cast
- cast call — read data from contracts.
- cast send — send transactions.
- cast abi-encode / abi-decode — encode/decode calldata.
- cast to-hex, to-wei, from-wei — utility conversions.
Anvil
- Fast local Ethereum node with a JSON-RPC interface.
- Prefunded local accounts with exposed private keys.
- Built-in support for forking mainnet or other networks.
anvil --port 8545 --chain-id 31337
You can point other tools (Hardhat, Foundry scripts, frontends) to Anvil via http://127.0.0.1:8545.
Project structure
After forge init, a typical layout looks like this:
.
├─ src/
│ └─ Counter.sol
├─ test/
│ └─ Counter.t.sol
├─ script/
│ └─ Counter.s.sol
├─ lib/
│ └─ (dependencies, e.g. openzeppelin-contracts)
├─ foundry.toml
└─ remappings.txt
- src/ — main contracts.
- test/ — Solidity test contracts ending with .t.sol.
- script/ — Solidity scripts ending with .s.sol (deployments, admin actions).
- lib/ — external libraries installed via forge install.
- foundry.toml — main configuration file.
- remappings.txt — import remappings (similar to Node.js path aliases).
Libraries such as OpenZeppelin are usually installed into lib/. Remappings let you write clean imports like import "openzeppelin/token/ERC20/ERC20.sol"; instead of long relative paths.
Configuration: foundry.toml
The foundry.toml file controls compiler versions, optimization settings, directories, RPC endpoints, profiles, and more.
Basic config
[profile.default]
src = "src"
test = "test"
script = "script"
out = "out"
libs = ["lib"]
solc_version = "0.8.28"
optimizer = true
optimizer_runs = 200
ffi = false
Common keys you’ll use:
- src, test, script, out, libs — paths.
- solc_version, optimizer, optimizer_runs — compiler settings.
- via_ir — enable IR pipeline for advanced optimization.
- ffi — enable calling external programs from tests (off by default for safety).
Profiles & build flags
Foundry supports profiles so you can tweak behavior for testing, production builds, or CI.
Example with multiple profiles
[profile.default]
optimizer = true
optimizer_runs = 200
[profile.ci]
verbosity = 3
fuzz_runs = 256
[profile.production]
optimizer_runs = 10000
via_ir = true
Use profiles with the --profile flag:
forge build --profile production
forge test --profile ci
You can keep quick tests as the default profile and run heavy fuzzing or long-running invariant tests only in a separate CI profile.
Local node (Anvil)
anvil is a fast local Ethereum node, similar to Hardhat Network or Ganache, but focused on speed and good UX.
Start a fresh local chain
anvil --port 8545 --chain-id 31337
By default, Anvil prints out a list of accounts and private keys you can use from scripts, tests, or MetaMask.
Forking mainnet with Anvil
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 19000000
This lets you interact with real mainnet state locally, which is perfect for testing integrations with existing protocols.
Unit tests
In Foundry, tests are Solidity contracts placed inside the test/ directory with filenames ending in .t.sol.
Example test
// test/Counter.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter internal counter;
function setUp() public {
counter = new Counter();
}
function test_Increment() public {
assertEq(counter.count(), 0);
counter.increment();
assertEq(counter.count(), 1);
}
function testFuzz_IncrementMany(uint256 x) public {
// Fuzz test: increment x times
x = bound(x, 0, 1000);
for (uint256 i; i < x; i++) {
counter.increment();
}
assertEq(counter.count(), x);
}
}
Run tests with:
forge test
Most projects use forge-std (Foundry standard library) that provides Test, console2, and many helper utilities.
Fuzz testing
Fuzzing is built into Foundry. Any test function that takes arguments becomes a fuzz test. Foundry will generate random inputs trying to break your assumptions.
Basic fuzz test
function testFuzz_AdditionIsCommutative(uint256 a, uint256 b) public {
uint256 x = a + b;
uint256 y = b + a;
assertEq(x, y);
}
Control fuzzing parameters in foundry.toml:
[profile.default]
fuzz_runs = 256
max_test_rejects = 65536
Use the bound helper from Test to keep fuzz inputs in realistic ranges (e.g. token amounts, time ranges).
Cheatcodes
Cheatcodes are special helpers exposed via the vm interface in forge-std. They let you manipulate the EVM during tests: change block timestamps, prank msg.sender, deal tokens, etc.
Common cheatcodes
- vm.warp(timestamp) — set the block timestamp.
- vm.roll(blockNumber) — set the block number.
- vm.prank(addr) — make the next call execute as addr.
- vm.startPrank(addr) / vm.stopPrank() — multiple calls as addr.
- vm.deal(addr, amount) — set ETH balance for an address.
- vm.expectRevert() — expect a revert in the next call.
Example using cheatcodes
function testOnlyOwnerCanCall() public {
address attacker = address(0xBEEF);
vm.prank(attacker);
vm.expectRevert();
counter.onlyOwnerFunction();
vm.prank(counter.owner());
counter.onlyOwnerFunction(); // no revert
}
Invariant testing
Invariant tests check that certain properties always hold, even after many random sequences of actions. They are powerful for complex protocols (AMMs, lending, vaults, etc.).
Basic invariant setup
// test/InvariantExample.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract InvariantTest is Test {
Token internal token;
function setUp() public {
token = new Token();
}
function invariant_TotalSupplyConstant() public view {
assertEq(token.totalSupply(), 1_000_000 ether);
}
}
Run invariants with:
forge test --match-path test/InvariantExample.t.sol --invariant
Good invariants express the core guarantees of your protocol: conservation of value, bounds on debt, no unauthorized state changes, etc. They often catch bugs that unit tests miss.
Gas optimization
Foundry includes tools to measure and optimize gas usage.
Gas reports
forge test --gas-report
This prints gas usage per function and per test, helping you identify hot paths.
Inspecting sizes and layouts
forge inspect MyContract size
forge inspect MyContract storage-layout
You can enable via_ir and high optimizer runs in a dedicated production profile, while keeping tests fast with a lighter profile.
Scripts & broadcasting
Foundry uses Solidity scripts (in the script/ folder) to perform deployments and admin actions. Scripts end with .s.sol.
Example deployment script
// script/DeployCounter.s.sol
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/Counter.sol";
contract DeployCounterScript is Script {
function run() public {
uint256 deployerKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerKey);
Counter counter = new Counter();
vm.stopBroadcast();
console2.log("Counter deployed at", address(counter));
}
}
Simulate vs broadcast
# Simulate (no on-chain writes)
forge script script/DeployCounter.s.sol
# Broadcast transactions on a network
forge script script/DeployCounter.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify
Using scripts, you keep deployment logic versioned and testable, instead of relying on ad-hoc manual steps.
Mainnet forking
Foundry has first-class support for mainnet forking via Anvil. You can test against real contract state without touching production funds.
Run Anvil with a fork
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 19000000
Then point forge test or forge script at this RPC:
forge test --fork-url http://127.0.0.1:8545
forge script script/MyScript.s.sol --fork-url http://127.0.0.1:8545
With a forked Anvil node, you can impersonate whales, protocol contracts, or governance addresses using cheatcodes, which is extremely useful for testing integrations and governance flows.
Using Foundry with Hardhat
You can combine Hardhat and Foundry in the same project: Foundry for tests and scripts, Hardhat for plugins, TypeScript tooling, and some deployment pipelines.
Typical integration pattern
- Keep Solidity contracts in a shared contracts/ or src/ directory.
- Use Foundry for fast, aggressive testing (fuzzing, invariants, gas reports).
- Use Hardhat for JS/TS tooling, frontend integration, or specific plugins (like advanced verification).
- Point both toolchains at the same RPC endpoints for testnets and forks.
Many teams also run both forge test and npx hardhat test in CI to cover different test styles (Solidity-level + TypeScript-level).
CI & automation
Foundry is easy to integrate into CI systems (GitHub Actions, GitLab CI, CircleCI, etc.) because it’s a single binary toolchain with simple CLI commands.
Example GitHub Actions snippet
name: Foundry
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Foundry
run: |
curl -L https://foundry.paradigm.xyz | bash
echo "$HOME/.foundry/bin" >> $GITHUB_PATH
foundryup
- name: Run tests
run: forge test --profile ci
Run quick unit tests on every push, and reserve heavy fuzz / invariant suites for nightly runs or specific branches to keep feedback loops fast.
Resources
- Official Foundry book and reference documentation.
- forge-std repository with standard testing utilities.
- OpenZeppelin Contracts for standard tokens, governance, and access control.
- Security blogs and audits that publish example Foundry test suites.
- Guides on combining Foundry with Hardhat, Viem, and modern frontend stacks.