# foundry - Ethereum Development Framework Use Foundry's Forge, Cast, Anvil, and Chisel tools to build, test, fuzz, debug, deploy, and inspect Ethereum smart contracts. ## Installation Foundry is installed using **foundryup**, the official installer and version manager. :::steps ### Install foundryup ```bash $ curl -L https://getfoundry.sh/install | bash ``` ### Restart your terminal Or run `source ~/.bashrc` / `source ~/.zshrc`. ### Install Foundry ```bash $ foundryup ``` ::: This installs the latest stable versions of `forge`, `cast`, `anvil`, and `chisel`. :::tip By default, if neither `FOUNDRY_DIR` nor `XDG_CONFIG_HOME` is set, Foundry is installed to `~/.foundry`. If `XDG_CONFIG_HOME` is set, it defaults to `$XDG_CONFIG_HOME/.foundry`. You can override both defaults by setting the `FOUNDRY_DIR` environment variable before running `foundryup`. For details on the directory layout and environment variables, see the [Config Reference Overview](/config/reference/overview#directory-layout). ::: :::warning[Windows] Foundryup requires [Git Bash](https://gitforwindows.org/) or [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). PowerShell and Command Prompt are not supported. ::: :::note If installation fails, see [Troubleshooting](/help/troubleshooting) for common fixes. ::: ## Updating Run `foundryup` anytime to update to the latest stable release: ```bash $ foundryup ``` ## Installing specific versions ```bash [Install the nightly build] $ foundryup --install nightly ``` ```bash [Install a specific version] $ foundryup --install 1.0.0 ``` ```bash [Install a specific nightly build] $ foundryup --install nightly-abc1234 ``` ```bash [Install from a branch] $ foundryup --branch master ``` ## Tempo support Tempo support ships in the main Foundry release as of v1.7.0. Install the normal toolchain: ```bash $ foundryup ``` The old `foundryup -n tempo` / `foundryup --network tempo` flow is deprecated and ignored. See the [Tempo guide](/guides/tempo) for project setup and [MPP-backed RPC endpoints](/guides/mpp) for paid RPC configuration. ## Binary verification Foundry binaries are attested using [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds). When installing via `foundryup`, binary hashes are automatically verified against the GitHub attestation. To manually verify an installed binary: ```bash $ gh attestation verify --owner foundry-rs $(which forge) ``` Use `foundryup --force` to skip verification and force a fresh install. ## Alternative installation methods :::note Foundry no longer publishes npm packages for `forge`, `cast`, `anvil`, or `chisel`. Use `foundryup`, GitHub releases, Docker, or build from source instead. ::: ### Precompiled binaries Download binaries directly from the [GitHub releases page](https://github.com/foundry-rs/foundry/releases). Extract and add them to your `PATH`. ### Building from source Requires [Rust](https://rustup.rs/) (latest stable). On Windows, also requires [Visual Studio](https://visualstudio.microsoft.com/downloads/) with the "Desktop Development With C++" workload. ```bash [Update Rust] $ rustup update stable ``` ```bash [Install from GitHub] $ cargo install --git https://github.com/foundry-rs/foundry --profile release --locked forge cast chisel anvil solar ``` Or build from a local clone: ```bash $ git clone https://github.com/foundry-rs/foundry.git $ cd foundry $ cargo install --path ./crates/forge --profile release --locked $ cargo install --path ./crates/cast --profile release --locked $ cargo install --path ./crates/anvil --profile release --locked $ cargo install --path ./crates/chisel --profile release --locked $ cargo install --path ./crates/solar --profile release --locked ``` The `solar` package in the Foundry workspace builds the Solar compiler distributed with Foundry. The compiler's source is maintained in the separate [Solar repository](https://github.com/paradigmxyz/solar). You can also use foundryup to build from source: ```bash $ foundryup --branch master $ foundryup --path /path/to/foundry ``` ### Docker ```bash $ docker pull ghcr.io/foundry-rs/foundry:latest ``` Or build locally from the [repository](https://github.com/foundry-rs/foundry): ```bash $ docker build -t foundry . ``` :::note Some systems (including Apple Silicon) may have issues building the Docker image locally. ::: ### CI/CD See the [CI integration guide](/config/ci) for GitHub Actions and other CI platforms. ## Uninstalling Foundry stores all files in `~/.foundry`. To uninstall: :::steps ### Back up keystores The `.foundry` directory may contain keystores with private keys. ### Remove the directory ```bash $ rm -rf ~/.foundry ``` ### Remove PATH entry Edit your shell config (`.bashrc`, `.zshrc`, etc.) and remove the Foundry PATH line. ::: ## Getting Started Foundry is a fast, portable, and modular toolkit for Ethereum development. After [installing Foundry](/introduction/installation), you have access to four tools: | Tool | Purpose | Reference | |------|---------|-----------| | `forge` | Build, test, debug, deploy, and verify smart contracts | [Reference](/reference/forge/forge) | | `cast` | Interact with contracts, send transactions, and query chain data | [Reference](/reference/cast/cast) | | `anvil` | Run a local Ethereum node with forking capabilities | [Reference](/reference/anvil/anvil) | | `chisel` | Solidity REPL for rapid prototyping | [Reference](/reference/chisel/chisel) | :::tip Run any command with `--help` for detailed usage information. ::: :::note See the [CLI reference](/reference/forge/forge) for every command and flag. ::: ## Quick start with Forge Create and test a smart contract in under 30 seconds: :::steps ### Create a new project ```bash $ forge init hello_foundry $ cd hello_foundry ``` ### Build contracts ```bash $ forge build ``` ### Run tests ```bash $ forge test ``` ::: The generated project includes a `Counter` contract and test: :::terminal ```bash // [!include ~/snippets/output/hello_foundry/forge-test:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-test:output] ``` ::: Deploy using a Forge script: ```bash $ forge script script/Counter.s.sol ``` ## Local development with Anvil Start a local Ethereum node: ```bash $ anvil ``` This creates 10 pre-funded test accounts. Fork mainnet state for realistic testing: ```bash $ anvil --fork-url https://eth.merkle.io ``` ## Interact with chains using Cast Query blockchain data: ```bash [Check an address balance] $ cast balance vitalik.eth --ether --rpc-url https://eth.merkle.io ``` ```bash [Get the latest block number] $ cast block-number --rpc-url https://eth.merkle.io ``` ```bash [Call a contract function] $ cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 "totalSupply()" --rpc-url https://eth.merkle.io ``` Send transactions to your local Anvil node: ```bash [Send ETH using an Anvil test account] $ cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ --value 1ether \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ``` ## Prototype with Chisel Start the Solidity REPL: ```bash $ chisel ``` Write and execute Solidity interactively: ```solidity ➜ uint256 x = 42; ➜ x * 2 Type: uint256 ├ Hex: 0x54 └ Decimal: 84 ➜ function double(uint256 n) public pure returns (uint256) { return n * 2; } ➜ double(21) Type: uint256 └ Decimal: 42 ``` Type `!help` to see available commands. ## Next steps * [Write your first tests](/forge/testing) * [Test against mainnet state](/guides/fork-testing) * [Deploy and verify a contract](/guides/deploying-contracts) * [Track gas usage](/forge/gas-tracking) ## Prompting You can speed up smart contract development by using AI to draft boilerplate, testing patterns, and documentation. The best results come from structured prompts with clear constraints and examples. :::warning Always review and test AI-generated code before using it in production. ::: ## Use a structured prompt Copy the template below and replace the `` section with your project details. ```txt [Prompt template] You are an expert Solidity engineer using Foundry. Project context: - Repository layout: {FOLDERS} - Solidity version: {SOLC_VERSION} - Dependencies: {DEPENDENCIES} - Target chain(s): {CHAINS} Constraints: - Use Foundry tools only (forge, cast, anvil, chisel) - Prefer forge-std testing utilities - Keep functions small and focused - Avoid unsafe patterns and unchecked external calls Testing requirements: - Include unit tests and fuzz tests where applicable - Add revert tests for all failure paths - Use vm.assume or bound to constrain fuzz inputs Style: - Use clear naming and short helper functions - Add comments only when logic is non-obvious Describe the contract or tests you want to generate. ``` ## Example usage ```txt [Example] Create a minimal ERC20 with mint and burn functions. Include a test suite that verifies: - mint increases balance and total supply - burn decreases balance and total supply - unauthorized minting reverts ``` ## Foundry documentation for agents The Foundry documentation is available as rendered HTML, page-level Markdown, compact and full text indexes, and an MCP server. Use the smallest source that answers the task instead of loading the entire book. ### When to use Foundry Use Foundry when a task involves a Solidity project or direct EVM interaction. Its four tools cover the common development loop: * **Forge** compiles, tests, fuzzes, debugs, deploys, verifies, formats, and lints Solidity projects. * **Cast** reads chain data, encodes and decodes ABI values, manages wallets, signs messages, and sends transactions from the command line. * **Anvil** runs a local Ethereum node for deterministic tests, state manipulation, mining control, and forks of live networks. * **Chisel** provides an interactive Solidity REPL for evaluating expressions and prototyping contract logic. Reach for a narrower tool when the task is outside that loop: use a Solidity compiler directly for compiler-only integration, a Rust or JavaScript Ethereum library for application code, and a browser wallet for user-mediated signing. Within a Foundry project, call the installed CLI from the project root so it reads the project's `foundry.toml`, remappings, dependencies, and profiles. Start with read-only commands such as `forge build`, `forge test`, `forge config`, `cast call`, or `cast rpc`. Treat `cast send`, `forge create`, and `forge script --broadcast` as state-changing operations: confirm the target chain, signer, value, and user authorization before running them. ### Retrieval interfaces | Interface | URL pattern | Use it for | | --- | --- | --- | | Page Markdown | `https://getfoundry.sh/.md` | Reading one conceptual, guide, or reference page without navigation and presentation markup | | Compact index | `https://getfoundry.sh/llms.txt` | Finding candidate pages by title, route, and description | | Full corpus | `https://getfoundry.sh/llms-full.txt` | Building an offline index or retrieving across the whole documentation set | | MCP server | `https://getfoundry.sh/api/mcp` | Searching the documentation from an MCP-capable client | | HTML | `https://getfoundry.sh/` | Presenting the result to a person or inspecting rendered tables and navigation | For example, retrieve the testing guide as Markdown from `https://getfoundry.sh/forge/testing.md`, then use the HTML route [`/forge/testing`](/forge/testing) when linking the answer for a person. The full corpus is large because it includes generated CLI, cheatcode, and Forge Standard Library reference pages. Start with `llms.txt`, select one or more page routes, and fetch their `.md` forms. Only retrieve `llms-full.txt` when a bulk local copy is actually needed. ### Route by task | Task | Start here | Follow with | | --- | --- | --- | | Install Foundry or create a project | [Getting started](/introduction/getting-started) | [Project setup](/projects), [project layout](/projects/layout) | | Compile or configure Solidity | [Building contracts](/forge/build) | [Configuration](/config), [configuration reference](/config/reference/default-config) | | Write or diagnose tests | [Testing](/forge/testing) | [Invariant testing](/guides/invariant-testing), [fork testing](/guides/fork-testing), [debugging](/forge/debugging) | | Deploy or verify contracts | [Scripting](/forge/scripting) | [Deploying contracts](/guides/deploying-contracts), [`forge script`](/reference/forge/script) | | Query a chain or send a transaction | [Cast overview](/cast) | [Reading chain data](/cast/reading-chain-data), [sending transactions](/cast/sending-transactions) | | Run or control a local node | [Anvil overview](/anvil) | [Forking](/anvil/forking), [custom methods](/anvil/custom-methods) | | Manipulate Forge test state | [Cheatcode overview](/reference/cheatcodes/overview) | The exact cheatcode reference page | | Look up exact flags or syntax | [`forge` reference](/reference/forge/forge) | The specific `forge`, `cast`, `anvil`, or `chisel` command page | Concept and guide pages explain intent, safe workflows, and how features fit together. Generated reference pages are better for exact signatures, flags, defaults, and accepted values. Retrieve both when an answer needs operational context and precise syntax. ### Agent retrieval workflow 1. Identify the tool and task: Forge, Cast, Anvil, Chisel, configuration, cheatcodes, or Forge Standard Library. 2. Search `llms.txt` or the MCP server for the task and its likely command or API name. 3. Read the narrow conceptual or guide page as Markdown. 4. Read the exact reference page when flags, overloads, parameters, or defaults matter. 5. Confirm version-sensitive or safety-critical behavior against the installed command's `--help` output or the current Foundry source. 6. Link the human-readable HTML pages in the final answer. Prefer local evidence when working inside a Foundry project. Read `foundry.toml`, `remappings.txt`, scripts, tests, generated artifacts, and the installed `forge --version` before assuming the project follows defaults. Avoid network calls, broadcasts, key access, or explorer requests unless the task requires them and the user has authorized them. ### Search terms that map well Search with the exact command, configuration key, cheatcode, error fragment, or output field when known. When it is not known, pair the tool name with the intended outcome, such as: * `forge replay fuzz corpus` * `forge symbolic incomplete` * `cast send hardware wallet` * `anvil manual mining txpool` * `vm record state diff` * `foundry.toml invariant depth` Keep aliases in the query when terminology varies. Useful pairs include `script` and `broadcast`, `fork` and `forking`, `trace` and `debug`, `binding` and `ABI`, and `wallet` and `signer`. ### Report uncertainty Foundry nightly releases can move faster than documentation. State the Foundry version used for a version-sensitive answer. If documentation, installed help, and source disagree, report the disagreement and prefer evidence from the version the user is actually running. ## Project Setup Foundry projects are initialized with `forge init` and follow a standard layout that works out of the box. ### Creating a project :::steps ### Initialize a new project ```bash $ forge init my_project $ cd my_project ``` ### Or initialize in an existing directory ```bash $ cd existing_directory $ forge init ``` ::: The `--force` flag initializes in non-empty directories: ```bash $ forge init --force ``` ### Initialization options | Flag | Description | |------|-------------| | `--template ` | Use a custom template repository (URL or `owner/repo`) | | `--no-git` | Skip git repository initialization | | `--commit` | Create an initial commit | | `--shallow` | Perform shallow dependency clones | | `--offline` | Skip dependency installation | | `--vscode` | Generate VS Code settings | ```bash [Create from template] $ forge init --template https://github.com/PaulRBerg/foundry-template my_project ``` ```bash [Initialize without git] $ forge init --no-git my_project ``` ### What gets created A new project includes: :::file-tree * +my\_project/ * foundry.toml Project configuration * +src/ * Counter.sol Example contract * +test/ * Counter.t.sol Example test * +script/ * Counter.s.sol Example script * +lib/ * +forge-std/ Standard library ::: ### Learn more * [Project layout](/projects/layout) — Directory structure and conventions * [Dependencies](/projects/dependencies) — Managing external libraries * [Soldeer](/projects/soldeer) — Alternative package manager ## Project Layout Foundry uses a conventional directory structure. Configure paths in `foundry.toml` or use the defaults. ### Default structure :::file-tree * +project/ * foundry.toml Project configuration * +src/ Contract source files * +test/ Test files (\*.t.sol) * +script/ Script files (\*.s.sol) * +lib/ Git submodule dependencies * +out/ Compilation artifacts * +cache/ Compiler cache * +broadcast/ Deployment logs ::: ### Source directories | Directory | Purpose | Config key | |-----------|---------|------------| | `src/` | Production contracts | `src` | | `test/` | Test contracts | `test` | | `script/` | Deployment scripts | `script` | | `lib/` | Dependencies | `libs` | Customize in `foundry.toml`: ```toml [profile.default] src = "contracts" test = "tests" script = "scripts" libs = ["lib", "node_modules"] ``` ### Output directories | Directory | Purpose | Config key | |-----------|---------|------------| | `out/` | Compiled artifacts (ABI, bytecode) | `out` | | `cache/` | Compiler cache for incremental builds | `cache_path` | | `broadcast/` | Transaction logs from script broadcasts | `broadcast` | :::tip Add `out/`, `cache/`, and `broadcast/` to `.gitignore`. The default template does this automatically. ::: ### File naming conventions Foundry identifies file types by suffix: | Suffix | Type | Example | |--------|------|---------| | `.sol` | Contract | `Token.sol` | | `.t.sol` | Test | `Token.t.sol` | | `.s.sol` | Script | `Deploy.s.sol` | Tests must also inherit from `Test`: ```solidity import {Test} from "forge-std/Test.sol"; contract TokenTest is Test { // ... } ``` Scripts must inherit from `Script`: ```solidity import {Script} from "forge-std/Script.sol"; contract DeployScript is Script { // ... } ``` ### Monorepo setup For monorepos with multiple Foundry projects, use a root `foundry.toml` or per-project configs. Share dependencies with a root `lib/` directory: ```toml [profile.default] libs = ["lib", "../lib"] ``` Or use workspaces: :::file-tree * +monorepo/ * foundry.toml Root config (optional) * +lib/ Shared dependencies * +packages/ * +token/ * foundry.toml * +src/ * +governance/ * foundry.toml * +src/ ::: ### Remappings Control import paths with remappings. Foundry auto-detects them from `lib/`, but you can customize: ```toml [profile.default] remappings = [ "@openzeppelin/=lib/openzeppelin-contracts/", "@uniswap/=lib/v3-core/contracts/", ] ``` Or use a `remappings.txt` file: ``` @openzeppelin/=lib/openzeppelin-contracts/ @uniswap/=lib/v3-core/contracts/ ``` See [Dependencies](/projects/dependencies) for more on managing imports. ## Dependencies Foundry uses git submodules to manage dependencies. Libraries are stored in `lib/` and imported via remappings. ### Installing dependencies :::code-group ```bash [Basic] $ forge install OpenZeppelin/openzeppelin-contracts ``` ```bash [Specific version] $ forge install OpenZeppelin/openzeppelin-contracts@v5.0.0 ``` ```bash [With commit] $ forge install OpenZeppelin/openzeppelin-contracts --commit ``` ::: The library is cloned to `lib/openzeppelin-contracts/`. ### Locking dependencies For dependencies installed as git submodules, Foundry maintains a `foundry.lock` file in the project root. The lock file complements `.gitmodules`: `.gitmodules` records where each submodule comes from, while `foundry.lock` records whether you selected a branch, tag, or revision and the resolved commit. For example, a dependency installed from a tag produces an entry like this: ```json { "lib/openzeppelin-contracts": { "tag": { "name": "v5.0.0", "rev": "b7954c3e9ce1d487b49489f5800f52f4b77b7351" } } } ``` Commit `foundry.lock` so other contributors and CI use the same dependency state. Let Forge update the file instead of editing it manually: * `forge install` creates or synchronizes the lock file. With no dependency arguments, it also installs existing submodules from the recorded state. * `forge update` updates branch-based dependencies to the latest commit on their recorded branch. Dependencies installed from a tag or revision stay pinned unless you explicitly select a different ref. * `forge remove` removes the dependency's lock-file entry. * `forge build` warns if the lock file is malformed, a dependency is missing, or its checked-out revision does not match the lock file. Dependencies installed with `forge install --no-git` are regular directories rather than git submodules, so Foundry does not add them to `foundry.lock`. ### Using dependencies Import installed libraries in your contracts: ```solidity import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; ``` Foundry automatically creates remappings for libraries in `lib/`. The remapping `openzeppelin-contracts/` points to `lib/openzeppelin-contracts/`. ### Remappings Customize import paths with remappings in `foundry.toml`: ```toml [profile.default] remappings = [ "@openzeppelin/=lib/openzeppelin-contracts/", ] ``` Now you can import with the prefix: ```solidity import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ``` Generate remappings automatically: :::terminal ```bash // [!include ~/snippets/output/hello_foundry/forge-remappings:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-remappings:output] ``` ::: Save to a file for IDE support: ```bash $ forge remappings > remappings.txt ``` #### Nested dependency remappings When a dependency remapping points below an auto-detected package root, Foundry preserves both by scoping the dependency remapping to the directory that declares it. For example, `forge remappings` can output: ```txt lib/outer/:inner/=lib/outer/lib/inner/contracts/ inner/=lib/outer/lib/inner/ ``` The contextual form `:=` applies to imports made by source units whose names begin with ``. Here, the trailing slash in `lib/outer/` creates a directory boundary, so sources inside `lib/outer/` use the dependency's `contracts/` directory while root sources and sibling dependencies use the auto-detected package-root mapping. Foundry preserves explicit root-project and CLI remappings when constructing generated dependency contexts. An equal or broader global alias suppresses the generated context. A narrower global alias is overlaid into that context, so it overrides only its matching import subtree while the dependency mapping handles the remaining imports. Explicit contextual remappings are unchanged. ### Updating dependencies Use `forge install` to add a dependency or initialize the dependencies already recorded in a checkout. Running it without arguments reconciles existing git submodules and `foundry.lock`; it does not intentionally fetch newer dependency revisions. Use `forge update` when you want to move a dependency to a different revision. With no arguments, it fetches the latest commit for every branch-based dependency: ```bash $ forge update ``` Dependencies installed from a tag or commit revision remain pinned during a plain `forge update`. To upgrade one of them, provide the new ref explicitly. For example, to move OpenZeppelin Contracts from `v5.0.0` to `v5.1.0`: ```bash $ forge update openzeppelin/openzeppelin-contracts@tag=v5.1.0 ``` You can also select a branch or an exact commit with `@branch=` or `@rev=`. Forge updates the dependency's git submodule checkout and its `foundry.lock` entry together. Review and commit both the changed dependency path and `foundry.lock` after an update. See the [`forge install`](/reference/forge/install) and [`forge update`](/reference/forge/update) references for all options. ### Removing dependencies ```bash $ forge remove openzeppelin-contracts ``` This removes the submodule from `lib/` and `.gitmodules`. ### Resolving conflicts When two dependencies require different versions of the same library, you'll encounter conflicts. #### Diagnosing conflicts Check dependency trees with: ```bash // [!include ~/snippets/output/forge_tree/forge-tree:command] ```
Example output ```ansi // [!include ~/snippets/output/forge_tree/forge-tree:output] ```
#### Resolution strategies **1. Use a compatible version** Find a version that works for both dependencies: ```bash $ cd lib/conflicting-library $ git checkout v2.0.0 $ cd ../.. $ git add lib/conflicting-library $ git commit -m "Pin conflicting-library to v2.0.0" ``` **2. Create separate remappings** If dependencies need different versions, install both under different names: ```bash $ forge install library-v1=org/library@v1.0.0 $ forge install library-v2=org/library@v2.0.0 ``` Add remappings: ```toml [profile.default] remappings = [ "library-v1/=lib/library-v1/", "library-v2/=lib/library-v2/", ] ``` **3. Patch the dependency** Fork and modify the dependency to use a compatible version: ```bash # In lib/problematic-dependency $ git remote add fork https://github.com/you/fork $ git fetch fork $ git checkout fork/compatible-branch ``` ### Using npm packages Foundry can use packages from `node_modules`: ```toml [profile.default] libs = ["lib", "node_modules"] ``` Install with your preferred package manager: ```bash $ npm install @openzeppelin/contracts ``` Import directly: ```solidity import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ``` :::warning npm packages may not be designed for Foundry. Prefer git submodules for Solidity libraries. ::: ### Hardhat compatibility For projects migrating from Hardhat or using Hardhat-style imports: ```toml [profile.default] libs = ["lib", "node_modules"] remappings = [ "@openzeppelin/=node_modules/@openzeppelin/", "hardhat/=node_modules/hardhat/", ] ``` ## Soldeer [Soldeer](https://soldeer.xyz) is a Solidity-native package manager that provides an alternative to git submodules. It offers versioned dependencies, a package registry, and simpler dependency management. ### Installation Soldeer comes bundled with Foundry. Initialize it in your project: ```bash $ forge soldeer init ``` This creates a `soldeer.toml` configuration file. ### Installing packages :::code-group ```bash [Install from registry] $ forge soldeer install @openzeppelin-contracts~5.0.0 ``` ```bash [Install from git] $ forge soldeer install my-lib~1.0.0 https://github.com/org/repo.git ``` ::: Packages are stored in `dependencies/` by default. ### Configuration Configure Soldeer in `soldeer.toml`: ```toml [soldeer] remappings_generate = true remappings_regenerate = false remappings_version = true remappings_prefix = "@" remappings_location = "config" [dependencies] "@openzeppelin-contracts" = "5.0.0" "@solmate" = "6.7.0" ``` Key options: | Option | Description | |--------|-------------| | `remappings_generate` | Auto-generate remappings | | `remappings_prefix` | Prefix for remappings (e.g., `@`) | | `remappings_location` | Where to store remappings (`config` or `txt`) | ### Using packages Import installed packages: ```solidity import {ERC20} from "@openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; ``` When `remappings_location = "config"`, remappings are added to `foundry.toml`. Otherwise, they go to `remappings.txt`. ### Updating packages :::code-group ```bash [Update all packages] $ forge soldeer update ``` ```bash [Update a specific package] $ forge soldeer update @openzeppelin-contracts ``` ::: ### Publishing packages Publish your own packages to the Soldeer registry: :::steps ### Login to Soldeer Login to your [Soldeer](https://soldeer.xyz) account. ```bash $ forge soldeer login ``` ### Prepare your package Add metadata to `soldeer.toml`: ```toml [package] name = "my-library" version = "1.0.0" description = "My awesome Solidity library" ``` ### Publish Publish your package. ```bash $ forge soldeer push my-library~1.0.0 ``` ::: ### Git submodules vs Soldeer | Feature | Git submodules | Soldeer | |---------|---------------|---------| | Version pinning | Commit hash | Semantic versions | | Registry | GitHub | Soldeer registry + git | | Lock file | No | Yes (`soldeer.lock`) | | Transitive deps | Manual | Automatic | | IDE support | Via remappings | Via remappings | Use git submodules when: * You need a specific commit * The library isn't on the Soldeer registry * Your team is familiar with git submodules Use Soldeer when: * You want semantic versioning * You need reproducible builds (lock file) * You prefer npm-style dependency management ### Migrating from git submodules Convert existing submodule dependencies to Soldeer: ```bash # Remove the submodule $ forge remove openzeppelin-contracts # Install via Soldeer $ forge soldeer install @openzeppelin-contracts~5.0.0 ``` Update your imports to use the new remapping prefix: :::code-group ```solidity [Before] import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; ``` ```solidity [After] import {ERC20} from "@openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; ``` ::: ## Forge Forge compiles, tests, and deploys Solidity smart contracts. It's the core development tool in the Foundry suite. ### Core workflows ### Key capabilities | Feature | Description | |---------|-------------| | **Compilation** | Compile contracts with configurable Solidity versions and optimization | | **Documentation** | Generate a Vocs API site from Solidity source and NatSpec comments | | **Testing** | Write tests in Solidity with fuzzing, forking, and gas reporting | | **Scripting** | Deploy and interact with contracts using Solidity scripts | | **Verification** | Verify source code on Etherscan and other explorers | | **Analysis** | Inspect bytecode, storage layouts, and gas usage | | **Bindings** | Generate typed Alloy crates and Solidity JSON helpers | ### Common workflows :::terminal ```bash [Build your project] // [!include ~/snippets/output/hello_foundry/forge-build:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-build:output] ``` ::: :::terminal ```bash [Run all tests] // [!include ~/snippets/output/hello_foundry/forge-test:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-test:output] ``` ::: :::terminal ```bash [Run tests with verbose output] $ forge test -vvvv ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-vvvv:output] ``` ::: ```bash [Deploy via script] $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL ``` ```bash [Verify a deployed contract] $ forge verify-contract $ADDRESS src/Counter.sol:Counter --etherscan-api-key $KEY ``` ### Learn more * [Building contracts](/forge/build) — Compilation, artifacts, and optimization * [Cloning verified contracts](/forge/cloning) — Recreate a project from an address or proxy implementation * [Contract documentation](/forge/documentation) — NatSpec, generated API pages, and publishing * [Contract bindings](/forge/contract-bindings) — Alloy bindings and Solidity JSON helpers * [Testing](/forge/testing) — Writing and running tests * [Scripting](/forge/scripting) — Deployment and on-chain interactions * [Debugging](/forge/debugging) — Traces, debugger, and troubleshooting * [Gas tracking](/forge/gas-tracking) — Snapshots and reports * [Linting](/forge/linting) — Code style enforcement * [Reference](/reference/forge/forge) — Full CLI reference ## Building contracts Forge compiles all Solidity files in your `src/` directory and outputs artifacts to `out/`. :::terminal ```bash // [!include ~/snippets/output/hello_foundry/forge-build:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-build:output] ``` ::: ### Compiler versions Forge auto-detects the required Solidity version from your contracts' pragma statements and downloads the compiler automatically. To pin a specific version: ```toml [foundry.toml] [profile.default] solc_version = "0.8.28" ``` Or use a version range: ```toml [foundry.toml] [profile.default] solc = ">=0.8.0 <0.9.0" ``` ### Optimization Enable the optimizer for production deployments: ```toml [foundry.toml] [profile.default] optimizer = true optimizer_runs = 200 ``` Higher `optimizer_runs` values optimize for frequent function calls at the cost of larger bytecode. Use lower values (like `1`) for contracts deployed once and rarely called. For maximum optimization with via-IR: ```toml [foundry.toml] [profile.default] optimizer = true optimizer_runs = 200 via_ir = true ``` :::warning via-IR compilation is slower but can produce more optimized bytecode. Enable it only when needed. ::: ### Build profiles Define separate profiles for development and production: ```toml [foundry.toml] [profile.default] optimizer = false [profile.production] optimizer = true optimizer_runs = 200 via_ir = true ``` Build with a specific profile: ```bash $ FOUNDRY_PROFILE=production forge build ``` ### Inspecting artifacts View contract ABI: ```bash $ forge inspect Counter abi ``` View deployed bytecode: ```bash $ forge inspect Counter bytecode ``` View storage layout: ```bash $ forge inspect Counter storage-layout ``` View all available fields: ```bash $ forge inspect Counter --help ``` ### Build cache Forge caches compilation results. To force a full rebuild: ```bash $ forge build --force ``` Clear the cache entirely: ```bash $ forge clean ``` ### Watching for changes Rebuild automatically when files change: ```bash $ forge build --watch ``` ## Cloning verified contracts `forge clone` downloads verified Solidity source and compiler settings from a block explorer, initializes a Forge project, compiles it, and writes deployment metadata to `.clone.meta`. By default, Forge clones the exact address you provide. Add `--implementation` when the address is a proxy and you want the implementation reported by Etherscan. ### Prerequisites * An explorer-verified contract. * An Etherscan API key, supplied through `ETHERSCAN_API_KEY` or `--etherscan-api-key`. * The contract's chain name or chain ID when it is not on Ethereum mainnet. ### Clone the contract at an address Pass the contract address and the directory that Forge should create: ```bash $ forge clone \ --chain mainnet \ 0xC02aB1A5eaA8d1B114EF786D9bde108cD4364359 \ sparklend-usds ``` This clones the verified contract at the supplied address. If the address is a proxy, the generated project contains the proxy source unless you request its implementation explicitly. ### Clone a proxy implementation Add `--implementation` to clone the implementation address reported in the proxy's Etherscan metadata: ```bash $ forge clone \ --implementation \ --chain mainnet \ 0xC02aB1A5eaA8d1B114EF786D9bde108cD4364359 \ sparklend-usds ``` Forge uses the resolved implementation for: * Downloaded source code and compiler settings. * Contract creation data and constructor arguments. * The compiled storage layout. * The address, contract name, and source path recorded in `.clone.meta`. If Etherscan does not mark the supplied address as a proxy, `--implementation` clones the supplied address normally. ### Understand implementation resolution Implementation resolution follows one Etherscan metadata link. Forge fetches the implementation reported for the supplied proxy and stops there, even if that implementation is also marked as a proxy. This avoids following an unrelated delegate-call target reported by the explorer. The resolved target must be a Solidity contract. A Vyper proxy can still resolve to a Solidity implementation because Forge only compiles the selected target. When verified sources remain under `lib`, Forge creates an import-only entry point such as `src/Clone.sol` so the compiler discovers the cloned target. The downloaded library sources remain under `lib`. `--implementation` is supported only with Etherscan. It cannot be combined with `--source sourcify` or `--sourcify-url`, and resolution depends on the proxy metadata returned by the explorer. ### Next steps * See the [`forge clone` reference](/reference/forge/clone) for all command options. * Compare storage layouts when [upgrading contracts](/guides/upgrading-contracts). ## Contract Documentation `forge doc` turns Solidity source and NatSpec comments into MDX API pages and a ready-to-run [Vocs](https://vocs.dev) site. It documents contracts, interfaces, libraries, functions, state variables, events, errors, structs, enums, and user-defined value types. ### Write useful NatSpec Document the contract's purpose and the behavior callers need to know. Name every parameter and return value so generated tables remain searchable and unambiguous. ```solidity [src/ICounter.sol] // [!include ~/snippets/projects/forge_doc/src/ICounter.sol] ``` Use `@inheritdoc` when an implementation should reuse an interface or base-contract description: ```solidity [src/Counter.sol] // [!include ~/snippets/projects/forge_doc/src/Counter.sol] ``` Forge renders `@title`, `@author`, `@notice`, `@dev`, `@param`, `@return`, `@inheritdoc`, and `@custom:` metadata. References such as `{value}` become links when Forge can resolve the target in the generated pages. ### Generate the site Run the generator from the project root: ```bash $ forge doc ``` The default `docs/` output contains: ```text docs/ ├── package.json ├── vocs.config.ts ├── vocs.sidebar.ts └── src/pages/ ├── index.mdx └── src/ ├── contract.Counter.mdx └── interface.ICounter.mdx ``` :::note[Foundry 1.7.1 and earlier] Prior Foundry releases generate an mdBook site with `book.toml`, `book.css`, and Markdown pages under `docs/src/`. The Vocs migration replaces that layout with the scaffold above. When upgrading an existing project, generate into a clean output directory so legacy mdBook files are not mixed into the Vocs site. ::: Use `--out ` to generate elsewhere. By default, Forge documents the project's source directory and excludes external libraries. Pass `--include-libraries` when library APIs belong in the published site. ### Configure generation Keep durable settings in the top-level `[doc]` section of `foundry.toml`: ```toml [foundry.toml] // [!include ~/snippets/projects/forge_doc/foundry.toml] ``` * `out` selects the generated site directory. * `title` becomes the Vocs site title. * `homepage` selects Markdown for the generated landing page. It defaults to `README.md`. * `repository` adds source and edit links. Forge tries to infer it from the Git `origin` when omitted. * `commit` pins source and homepage links to a commit, tag, or branch. When omitted, Forge uses the current Git revision. * `ignore` excludes source files with glob patterns. Relative links from the homepage to documented `.sol` files are rewritten to generated site routes. Other relative links become repository links when `repository` is configured. See the [documentation generator configuration reference](/config/reference/doc-generator) for the complete schema. ### Preview and watch The generated output is a Vocs project. Install its dependencies and start the development server: ```bash $ cd docs $ npm install --legacy-peer-deps $ npm run dev ``` `forge doc --serve` was removed during the Vocs migration. Older instructions that use it no longer apply. Run generation in watch mode in a separate terminal: ```bash $ forge doc --watch ``` With no paths, Forge watches the project's source directory, homepage inputs, `foundry.toml`, the deployments directory when `--deployments` is enabled, and library directories when `--include-libraries` is enabled. It does not watch the test directory by default. Explicit paths after `--watch` replace this default set, so include every path that should trigger regeneration. ### Customize without losing changes Forge separates generated files from user-editable site files: * `vocs.config.ts`, `package.json`, and `.gitignore` are created only when absent, so later customizations remain intact. * `vocs.sidebar.ts` is regenerated to reflect the current API pages. Import it from `vocs.config.ts` instead of editing it. * `src/pages/index.mdx` is regenerated from `homepage` on every run. * Generated API pages are tracked in `src/pages/.forge-doc-manifest`. Forge prunes stale pages from that manifest but leaves unrelated user-authored pages in place. Do not hand-edit generated API pages. Change the Solidity NatSpec and regenerate so the source stays authoritative. ### Add deployment addresses Inject known deployment addresses from `hardhat-deploy` or `forge-deploy` artifacts: ```bash $ forge doc --deployments ``` This reads `/deployments//.json` by default and renders an address table on the matching contract page. Pass a path to use another artifact directory: ```bash $ forge doc --deployments artifacts/deployments ``` Omit `--deployments` entirely when addresses should not be published. Review the generated tables before deployment because unreadable or mismatched artifact entries are skipped. ### Build in CI Generate from a clean checkout, then build the resulting Vocs site: ```bash $ forge doc $ cd docs $ npm install --legacy-peer-deps $ npm run build ``` Pin `doc.commit` to the release tag or commit being published so source links remain stable. Treat generator warnings and a failed Vocs build as documentation failures. For agent workflows, the generated MDX and `.forge-doc-manifest` provide a deterministic inventory of documented Solidity symbols. Search those files instead of parsing rendered HTML, but return to the Solidity source before changing behavior. Regeneration is the only supported way to update generated API content. ## Contract bindings Foundry provides two generators with different outputs. Choose the workflow based on what consumes the generated code. | Goal | Command | Output | | --- | --- | --- | | Call contracts from Rust | [`forge bind`](/reference/forge/bind) | An Alloy crate or Rust module generated from compiled contract artifacts | | Start from a verified deployed contract | [`cast source`](/reference/cast/source), then `forge bind` | Explorer source followed by local Alloy bindings | | Serialize Solidity structs to and from JSON | [`forge bind-json`](/reference/forge/bind-json) | A Solidity helper library for JSON cheatcodes | `forge bind-json` does not generate Rust code. The former `cast bind` workflow has also been removed; fetch verified source with `cast source` and generate bindings from a Forge project with `forge bind` instead. ### Generate Alloy bindings from a project `forge bind` compiles the project and reads the resulting ABIs. Calls, return values, errors, and events all become typed Alloy definitions. For example, start with this contract: ```solidity // [!include ~/snippets/projects/contract_bindings/src/Counter.sol] ``` Generate a standalone Rust crate for only `Counter`: ```bash forge bind \ --bindings-path bindings \ --select '^Counter$' \ --crate-name counter-bindings \ --crate-version 0.1.0 \ --crate-license MIT \ --alloy-version 1.0 ``` The result has this shape: ```text bindings/ ├── Cargo.toml └── src/ ├── counter.rs └── lib.rs ``` Add it to another Rust crate as a path dependency: ```toml [dependencies] counter-bindings = { path = "../contracts/bindings" } ``` Then import the generated contract module: ```rust use alloy::primitives::U256; use counter_bindings::counter::Counter; let counter = Counter::new(address, provider); let pending = counter.increment(U256::from(1)).send().await?; ``` The exact provider and transaction setup depends on your Alloy application. The generated module also contains `Incremented`, `ZeroAmount`, the ABI, call types, and return types. #### Select the intended contracts Use `--select ` more than once to generate a narrow public surface: ```bash forge bind \ --bindings-path bindings \ --select '^Counter$' \ --select '^Treasury$' ``` By default, contracts whose names end in `Test` or `Script` are excluded. `--select-all` explicitly includes every contract and cannot be combined with `--select`. The default output is `out/bindings`. Pass `--bindings-path` to keep generated Rust code somewhere else. Use `--module` when the output should be a module rather than a crate, and `--single-file` when one generated source file is preferable. #### Keep generated bindings reproducible Treat Solidity source and compiled ABIs as the source of truth. Do not edit generated Rust files by hand. During development, regenerate intentionally: ```bash forge bind \ --bindings-path bindings \ --select '^Counter$' \ --alloy-version 1.0 \ --overwrite ``` In CI, omit `--overwrite`. If the directory already exists, `forge bind` compares it with freshly generated output and exits unsuccessfully when it is stale: ```bash forge bind \ --bindings-path bindings \ --select '^Counter$' \ --alloy-version 1.0 cargo check --manifest-path bindings/Cargo.toml --locked ``` Commit the generated crate's `Cargo.lock` when your repository policy permits it, or pin the dependency with `--alloy-rev`. Keep every generation option in a script or CI job so local and automated output cannot drift. Only use `--skip-build` when the artifact directory is known to be current. Otherwise, stale artifacts can produce bindings that do not match the Solidity source under review. ### Generate bindings for a verified contract When you only have a deployed address, retrieve the explorer-verified sources into an existing Forge project, then run the same local generator: ```bash cast source "$ADDRESS" \ --chain mainnet \ --etherscan-api-key "$ETHERSCAN_API_KEY" \ -d src/vendor/verified-contract forge bind \ --bindings-path bindings \ --select '^VerifiedContract$' \ --alloy-version 1.0 ``` Check the chain ID, address, explorer, and downloaded source before compiling it. Prefer existing local source or artifacts when available; this workflow introduces explorer availability and verified-source trust into generation. ### Generate Solidity JSON helpers `forge bind-json` discovers Solidity structs and generates typed wrappers around Foundry's JSON cheatcodes. This is useful when tests or scripts read structured configuration files. Define the struct that represents the JSON schema: ```solidity // [!include ~/snippets/projects/contract_bindings/src/ConfigTypes.sol] ``` Configure the generated file and narrow the source set in `foundry.toml`: ```toml // [!include ~/snippets/projects/contract_bindings/foundry.toml] ``` The include and exclude values are globs matched against compiler source paths. A leading `**/` makes the example work for absolute and project-relative paths. If `include` is omitted, Foundry considers all non-library project files by default. Generate the helper: ```bash forge bind-json ``` By default, the command writes `utils/JsonBindings.sol`. A positional path overrides the configured output: ```bash forge bind-json generated/ProjectJsonBindings.sol ``` The generated library includes `serialize`, `deserializeDeploymentConfig`, path-based overloads, and array deserializers. Import it after generation: ```solidity // [!include ~/snippets/projects/contract_bindings/test/JsonBindings.t.sol] ``` Run `forge bind-json` whenever a struct is added, removed, renamed, or changed. Foundry preprocesses an existing generated file so stale imports usually do not prevent regeneration, but the generated file must exist before source that imports it can compile for the first time. ### Agent workflow When an agent encounters generated bindings: 1. Read `foundry.toml`, generation scripts, and CI before changing output paths or flags. 2. Inspect the Solidity declaration or ABI instead of searching large generated files first. 3. Run `forge bind` without `--overwrite` to detect Rust binding drift. 4. Regenerate with the repository's exact selectors and version options, then review ABI-level changes in calls, events, errors, and return types. 5. Run `forge bind-json` before compiling code that imports a missing or stale JSON helper. This order keeps generated code attributable to a small input change and avoids unnecessary explorer or RPC requests. ### See also * [`forge bind` reference](/reference/forge/bind) * [`forge bind-json` reference](/reference/forge/bind-json) * [`cast source` reference](/reference/cast/source) * [Building contracts](/forge/build) ## Testing Forge runs tests written in Solidity. Test files live in `test/` and test functions are prefixed with `test`. :::terminal ```bash // [!include ~/snippets/output/hello_foundry/forge-test:command] ``` ```ansi // [!include ~/snippets/output/hello_foundry/forge-test:output] ``` ::: ### Writing tests Create a test contract that inherits from `Test`: ```solidity [test/Counter.t.sol] // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {Test} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { Counter counter; function setUp() public { counter = new Counter(); } function test_Increment() public { counter.increment(); assertEq(counter.number(), 1); } function test_SetNumber() public { counter.setNumber(42); assertEq(counter.number(), 42); } } ``` Key conventions: * Test files end with `.t.sol` * Test contracts inherit from `forge-std/Test.sol` * Test functions start with `test_` or `test` * `setUp()` runs before each test ### Call isolation Forge runs tests with call isolation enabled by default. In isolation mode, each top-level external call made by a test is executed as a separate transaction in a separate EVM context. This gives more precise gas accounting and transaction state changes for each call. Because those calls use separate transaction contexts, a test function is not always equivalent to one normal transaction with warmed accounts or storage slots shared across every external call in the function body. For example, repeated calls to the same contract can be charged as cold again under isolation. If a test intentionally asserts behavior that depends on warm accounts or slots carrying across repeated calls in one transaction, run it with `--no-isolate` or set `isolate = false` in `foundry.toml`. ### Traces Traces show a tree of all calls made during a test, helping you understand execution flow and debug failures. #### Stack traces When a test fails, use `-vvv` to see a stack trace showing exactly where the revert occurred. This is the most common way to debug test failures. :::terminal ```bash $ forge test -vvv ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-fail-vvv:output] ``` ::: The trace shows the call hierarchy with the revert bubbling up, and the **Backtrace** pinpoints the exact location in your code. #### Full traces Use `-vvvv` to see traces for all tests, including passing ones. This helps you understand execution flow, verify call order, and check gas usage for individual operations. :::terminal ```bash $ forge test -vvvv ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-vvvv:output] ``` ::: #### Reading traces * **Gas costs** appear in brackets: `[29808]` * **Contract and function names** are color-coded * **Call types** are annotated: `[staticcall]` for view/pure functions * **Return values** show what each call returned: `← [Return] 0` for a value, `← [Stop]` for void * **Indentation** shows the call hierarchy—nested calls are indented under their parent ### Verbosity levels Control how much detail Forge outputs with `-v` flags: | Flag | Shows | |------|-------| | (none) | Pass/fail summary only | | `-v` | Test names | | `-vv` | Logs emitted during tests | | `-vvv` | Traces for failing tests | | `-vvvv` | Traces for all tests, including setup | | `-vvvvv` | Traces with storage changes | Use `-vvv` for debugging failures, `-vvvv` when you need to see successful test execution, and `-vvvvv` when tracking state changes. ### Filtering tests Run specific tests: By name: :::terminal ```bash // [!include ~/snippets/output/test_filters/forge-test-match-test:command] ``` ```ansi // [!include ~/snippets/output/test_filters/forge-test-match-test:output] ``` ::: By contract: :::terminal ```bash // [!include ~/snippets/output/test_filters/forge-test-match-contract:command] ``` ```ansi // [!include ~/snippets/output/test_filters/forge-test-match-contract:output] ``` ::: By path: :::terminal ```bash // [!include ~/snippets/output/test_filters/forge-test-match-path:command] ``` ```ansi // [!include ~/snippets/output/test_filters/forge-test-match-path:output] ``` ::: Combine filters: :::terminal ```bash // [!include ~/snippets/output/test_filters/forge-test-match-contract-and-test:command] ``` ```ansi // [!include ~/snippets/output/test_filters/forge-test-match-contract-and-test:output] ``` ::: Exclude tests with `--no-match-*` variants: ```bash $ forge test --no-match-test test_Skip ``` ### Fuzz testing Forge automatically fuzzes test functions that take parameters: ```solidity function testFuzz_SetNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), x); } ``` Forge generates random inputs and runs the test multiple times (256 by default): :::terminal ```bash // [!include ~/snippets/output/fuzz_testing/forge-test-success-fuzz:command] ``` ```ansi // [!include ~/snippets/output/fuzz_testing/forge-test-success-fuzz:output] ``` ::: Configure fuzzing: ```toml [foundry.toml] [fuzz] runs = 1000 max_test_rejects = 65536 seed = "0x1234" ``` Constrain inputs with `vm.assume(){:solidity}`: ```solidity function testFuzz_Transfer(uint256 amount) public { vm.assume(amount > 0 && amount <= 1000 ether); // Test with constrained amount } ``` Or use `bound(){:solidity}` to clamp values: ```solidity function testFuzz_Transfer(uint256 amount) public { amount = bound(amount, 1, 1000 ether); // Test with bounded amount } ``` ### Table testing Foundry v1.3.0 comes with support for table testing, which enables the definition of a dataset (the "table") and the execution of a test function for each entry in that dataset. This approach helps ensure that certain combinations of inputs and conditions are tested. In forge, table tests are functions named with `table` prefix that accepts datasets as one or multiple arguments: ```solidity function tableSumsTest(TestCase memory sums) public ``` ```solidity function tableSumsTest(TestCase memory sums, bool enable) public ``` The datasets are defined as forge fixtures which can be: * storage arrays prefixed with `fixture` prefix and followed by dataset name * functions named with `fixture` prefix, followed by dataset name. Function should return an (fixed size or dynamic) array of values. #### Single dataset In following example, `tableSumsTest` test will be executed twice, with inputs from `fixtureSums` dataset: once with `TestCase(1, 2, 3)` and once with `TestCase(4, 5, 9)`. ```solidity struct TestCase { uint256 a; uint256 b; uint256 expected; } function fixtureSums() public returns (TestCase[] memory) { TestCase[] memory entries = new TestCase[](2); entries[0] = TestCase(1, 2, 3); entries[1] = TestCase(4, 5, 9); return entries; } function tableSumsTest(TestCase memory sums) public pure { require(sums.a + sums.b == sums.expected, "wrong sum"); } ``` It is required to name the `tableSumsTest`'s `TestCase` parameter `sums` as the parameter name is resolved against the available fixtures (`fixtureSums`). In this example, if the parameter is not named `sums` the following error is raised: `[FAIL: Table test should have fixtures defined]`. #### Multiple datasets `tableSwapTest` test will be executed twice, by using values at the same position from `fixtureWallet` and `fixtureSwap` datasets. ```solidity struct Wallet { address owner; uint256 amount; } struct Swap { bool swap; uint256 amount; } Wallet[] public fixtureWallet; Swap[] public fixtureSwap; function setUp() public { // first table test input fixtureWallet.push(Wallet(address(11), 11)); fixtureSwap.push(Swap(true, 11)); // second table test input fixtureWallet.push(Wallet(address(12), 12)); fixtureSwap.push(Swap(false, 12)); } function tableSwapTest(Wallet memory wallet, Swap memory swap) public pure { require( (wallet.owner == address(11) && swap.swap) || (wallet.owner == address(12) && !swap.swap), "not allowed" ); } ``` The same naming requirement mentioned above is relevant here. ### Mutation testing Mutation testing checks the strength of your test suite by making small changes, or mutants, to your source code and re-running your tests. A mutant is killed when at least one test fails. A mutant survives when the changed code still passes the selected tests. See the [mutation testing guide](/guides/mutation-testing) to select source files and tests, configure parallel workers and operators, interpret reports, and understand current limitations. ### Brutalized testing Brutalized testing checks whether code remains robust when values and memory are dirtier than the assumptions made by clean, ordinary test executions. It is useful for contracts that use inline assembly, narrow integer or fixed-bytes casts, addresses, or low-level memory handling. Run the selected tests against brutalized sources with `forge test --brutalize`: ```bash $ forge test --brutalize ``` Forge copies the project into a temporary workspace, rewrites source files under `src/`, compiles that temporary project, and runs the selected tests there. Test files (`.t.sol`) and scripts (`.s.sol`) are not rewritten. Brutalization applies deterministic source rewrites that: * dirty unused upper bits in casts to `address`, smaller `uint`/`int` types, and fixed-size `bytes` types * fill scratch space (`0x00` through `0x3f`) and memory beyond the free memory pointer before eligible external assembly functions run * misalign the free memory pointer by a small deterministic odd offset If `forge test` passes but `forge test --brutalize` fails, the code under test likely depends on assumptions that are not guaranteed in every caller context, such as clean upper bits, zeroed memory, or word-aligned free memory. Regular test filters still apply: ```bash $ forge test --brutalize --match-contract VaultTest $ forge test --brutalize --match-test testWithdraw ``` `--brutalize` is separate from mutation testing. Mutation testing changes code to evaluate test quality; brutalized testing changes call and memory conditions to evaluate code robustness. Because they answer different questions, `--brutalize` cannot be combined with `--mutate`. ### Symbolic testing Symbolic testing explores your code with symbolic inputs instead of concrete ones, searching feasible execution paths within the current symbolic EVM model and configured bounds for a counterexample that violates a property. When Forge reports a failure, it first replays the concrete input or invariant sequence through the normal executor, so the failure is backed by a concrete example. :::info Symbolic testing is currently an MVP. It is ready for early use and feedback, but the modeled EVM surface, configuration, and reporting are still expected to evolve. ::: Symbolic tests are Solidity functions named `check*` or `prove*`. They are only discovered when symbolic mode is enabled with `--symbolic`: ```solidity contract MathSymbolicTest is Test { function check_average(uint256 a, uint256 b) external pure { uint256 average; unchecked { average = (a + b) / 2; } // Forge should find an overflow counterexample. assertGe(average, a <= b ? a : b); } } ``` Run it with: ```bash $ forge test --symbolic --match-test check_average ``` Symbolic testing requires an SMT solver to be installed. The default solver is `z3`: ```bash $ brew install z3 # macOS $ sudo apt-get install z3 # Ubuntu ``` #### Writing symbolic tests Function parameters become symbolic inputs derived from the ABI, and the executor explores the feasible paths: * `require(...)` and `vm.assume(...)` prune paths when their condition is false. * `assert`, forge-std assertions, and DSTest failure signals are treated as properties to disprove. * User reverts terminate the current path. When `--symbolic` is enabled, `invariant*` and `statefulFuzz*` functions are explored as bounded symbolic call sequences instead of using the normal fuzzer. #### Results Forge reports symbolic outcomes as: * **`PASS`**: every explored path finished without a feasible failure under the currently modeled semantics and configured bounds. * **`FAIL`**: the solver found a failing input or invariant sequence, and Forge replayed it concretely before reporting it. * **`FAIL: incomplete symbolic execution (...)` / `Incomplete`**: Forge could not complete the search or validate a counterexample. Treat this as "not established", not as a proof. A `PASS` is scoped to the current symbolic model and configured bounds; it does not cover skipped dynamic lengths, deeper invariant sequences, larger loop bounds, unmodeled behavior, arbitrary unknown external code, or cryptographic preimage/collision properties. #### Configuration Tune the exploration bounds and solver in `foundry.toml`: ```toml [foundry.toml] [profile.default.symbolic] solver = "z3" timeout = 30 max_depth = 10000 max_paths = 1024 max_solver_queries = 10000 ``` Symbolic exploration is bounded by configuration, including `symbolic.max_depth`, `symbolic.max_paths`, `symbolic.max_solver_queries`, dynamic calldata length settings, and `symbolic.invariant_depth`. Bounds can also be set per test with inline `forge-config` annotations: ```solidity /// forge-config: default.symbolic.invariant_depth = 4 function invariant_counterNeverFive() public view { assertTrue(counter.value() != 5); } ``` #### Limitations The symbolic engine is not a complete revm-equivalent EVM model. Unsupported constructs report `incomplete` rather than a proof, and some supported semantics are bounded or approximate. Notable gaps include gas accounting, Cancun+ `SELFDESTRUCT`, arbitrary unknown external code, and cryptographic preimage or collision properties. The exact unsupported-feature reason is preserved in the test output. For a counterexample-to-fix workflow, including durable JSON artifacts, concrete replay, generated regression tests, fuzz corpus integration, and CI handoffs, see the [symbolic testing workflow](/guides/symbolic-testing). ### Testing reverts Use `vm.expectRevert(){:solidity}` to test that a call reverts: ```solidity function test_RevertWhen_Unauthorized() public { vm.expectRevert("Not authorized"); restricted.doSomething(); } ``` Match a custom error: ```solidity function test_RevertWhen_InsufficientBalance() public { vm.expectRevert(Token.InsufficientBalance.selector); token.transfer(address(0), 1000); } ``` :::terminal ```bash // [!include ~/snippets/output/cheatcodes/forge-test-cheatcodes-expectrevert:command] ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-cheatcodes-expectrevert:output] ``` ::: ### Testing events Use `vm.expectEmit(){:solidity}` to verify events are emitted: ```solidity function test_EmitsTransfer() public { vm.expectEmit(true, true, false, true); emit Transfer(alice, bob, 100); token.transfer(bob, 100); } ``` The four booleans specify which topics and data to check. ### Forking Test against live chain state: ```bash $ forge test --fork-url https://ethereum.reth.rs/rpc ``` Or configure in `foundry.toml`: ```toml [foundry.toml] [profile.default] eth_rpc_url = "https://ethereum.reth.rs/rpc" ``` Pin to a specific block for reproducible tests: ```bash $ forge test --fork-url https://ethereum.reth.rs/rpc --fork-block-number 18000000 ``` ### Cheatcodes Forge provides cheatcodes via the `vm` object to manipulate the test environment: ```solidity // Set block timestamp vm.warp(1700000000); // Set block number vm.roll(18000000); // Impersonate an address vm.prank(alice); contract.doSomething(); // Give ETH to an address vm.deal(alice, 100 ether); // Modify storage vm.store(address(token), bytes32(0), bytes32(uint256(1000))); ``` See the [cheatcodes reference](/reference/cheatcodes/overview) for the full list. ### Watch mode Re-run tests when files change: ```bash $ forge test --watch ``` ## Scripting Forge scripts are Solidity files that deploy contracts and execute transactions on-chain. They replace deployment scripts traditionally written in JavaScript. For sender selection, CREATE2, library linking, nonces, simulation, and resume semantics, see [How scripting works](/forge/scripting-internals). ### Script structure Scripts inherit from `Script` and implement a `run()` function: ```solidity [script/Deploy.s.sol] // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {Script} from "forge-std/Script.sol"; import {Counter} from "../src/Counter.sol"; contract DeployScript is Script { function run() public { vm.startBroadcast(); Counter counter = new Counter(); counter.setNumber(42); vm.stopBroadcast(); } } ``` Key elements: * Inherit from `forge-std/Script.sol` * Script files end with `.s.sol` * Wrap deployment logic in `vm.startBroadcast(){:solidity}` / `vm.stopBroadcast(){:solidity}` ### Running scripts Simulate a deployment (no transactions sent): ```bash $ forge script script/Deploy.s.sol ``` Broadcast transactions to a network: ```bash $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL ``` ### Using RPC aliases Define aliases in `foundry.toml` and pass the alias to `--rpc-url`: ```toml [foundry.toml] [rpc_endpoints] sepolia = "${SEPOLIA_RPC_URL}" ``` ```bash $ forge script script/Deploy.s.sol --broadcast --rpc-url sepolia ``` :::note `--chain` only sets the EVM `block.chainid`. It does not select an RPC endpoint. Use `--rpc-url` (with a URL or an alias from `[rpc_endpoints]`) to choose the network. ::: ### Providing a private key :::code-group ```bash [Keystore (recommended)] $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --account deployer ``` ```bash [Hardware wallet] $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --ledger ``` ```bash [Browser wallet] $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --browser ``` ```bash [Raw key (not recommended)] $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --private-key $PRIVATE_KEY ``` ::: See [Browser Wallet Signing](/guides/browser-wallet) for the local connection flow, network checks, and a simulation-first deployment workflow. ### Broadcasting from a specific address To broadcast from a specific address: ```solidity vm.startBroadcast(deployerAddress); ``` Or derive the sender from a private key read from the environment: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); ``` The address overload selects a transaction sender but does not supply a signing key. Provide a matching wallet when broadcasting, or use `--unlocked` with a node that controls that account. The private-key overload derives the address and adds the key to Forge's script wallets. Starting a broadcast changes the sender of outgoing calls; it does not change `msg.sender` in the script's current frame. Pass the intended owner or deployer explicitly in transaction arguments. Inside a contract called directly by the broadcast transaction, `msg.sender` is the transaction sender. ### Overriding the sender nonce By default the sender's starting nonce is fetched from the RPC endpoint, or set to 1 when no endpoint is configured. Pass `--sender-nonce` to pin it instead: ```bash $ forge script script/Deploy.s.sol --rpc-url $RPC_URL --sender-nonce 7 ``` The override applies to script execution and transaction generation and is kept even when broadcasting switches to a different sender. Because addresses of contracts deployed with `CREATE` depend on the sender's nonce, this keeps simulated deployment addresses consistent with the nonce you plan to broadcast from. See [`forge script`](/reference/forge/script) for the full option reference. ### Verifying deployed contracts Verify on Etherscan during deployment: ```bash $ forge script script/Deploy.s.sol \ --broadcast \ --rpc-url $RPC_URL \ --verify \ --etherscan-api-key $ETHERSCAN_API_KEY ``` ### Resuming failed broadcasts If a broadcast fails partway through, resume from where it left off: ```bash $ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --resume ``` ### Multi-chain deployments Deploy to multiple chains by running the script with different RPC URLs: ```bash $ forge script script/Deploy.s.sol --broadcast --rpc-url $MAINNET_RPC $ forge script script/Deploy.s.sol --broadcast --rpc-url $ARBITRUM_RPC $ forge script script/Deploy.s.sol --broadcast --rpc-url $OPTIMISM_RPC ``` ### Reading deployment artifacts Scripts write transaction receipts to `broadcast/`. Access deployed addresses in subsequent scripts: ```solidity function run() public { string memory json = vm.readFile("broadcast/Deploy.s.sol/1/run-latest.json"); address counter = vm.parseJsonAddress(json, ".transactions[0].contractAddress"); } ``` ### Script cheatcodes Scripts have access to all [cheatcodes](/reference/cheatcodes/overview). Common ones for scripting: ```solidity // Read environment variables string memory rpcUrl = vm.envString("RPC_URL"); uint256 privateKey = vm.envUint("PRIVATE_KEY"); // Read/write files string memory config = vm.readFile("config.json"); vm.writeFile("output.txt", "deployed"); // Parse JSON address addr = vm.parseJsonAddress(json, ".address"); // Console logging console.log("Deploying to:", block.chainid); ``` ### Dry run Test a script without sending transactions: ```bash $ forge script script/Deploy.s.sol --rpc-url $RPC_URL ``` This simulates against the live chain state and shows what would happen. ## How scripting works The [scripting guide](/forge/scripting) introduces deployment scripts. This page explains how an ordinary `forge script` invocation turns Solidity execution into transactions, and which addresses and state each stage uses. Network-specific modes, such as [Tempo transaction batching](/guides/tempo), can change the transaction format and deployment behavior. ### From a script to transactions Forge executes the script contract locally. The script contract itself is not deployed to the target network. Instead, broadcasting cheatcodes identify calls and creations that should become transactions. | Stage | What runs | What carries forward | | --- | --- | --- | | Compile and link | The Solidity compiler and library linker | Script bytecode, contract artifacts, and library addresses | | Execute the script | The script constructor, optional `setUp()`, then `run()` or the function selected by `--sig` | Transactions collected from broadcast calls, plus required library deployments | | Simulate transactions | The collected transactions against separate local EVM state backed by the target RPC | Execution results, gas estimates, and deployment metadata | | Broadcast | Signing and RPC submission, when `--broadcast` is set | Transaction hashes and receipts | | Verify | Explorer verification, when requested | Verification results for deployed contracts | With no RPC URL, Forge can execute the script locally but cannot perform the RPC-backed transaction simulation or send the transactions. With an RPC URL and without `--broadcast`, it executes and simulates without submitting transactions. `--skip-simulation` skips the separate transaction simulation; it does not skip executing the script to collect transactions. ### Script caller, transaction sender, and signer These are distinct pieces of information: * The **script caller** is the address Forge uses to call `setUp()` and the selected script function. It determines `msg.sender` in that script frame. * The **transaction sender** is the address recorded in a collected transaction's `from` field. * The **signer** supplies the signature for that sender. Choosing an address does not supply its private key or unlock an account. For ordinary wallet-backed scripts, Forge initializes the script caller from the configured sender, whose default is `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`. A single raw private key or Turnkey address supplies an inferred caller. Otherwise, when `--sender` is absent, Forge can infer the caller from a sole available wallet address, or from the connected browser wallet. Multiple wallets require an explicit choice if we want a predictable script caller. Network-specific session signers have their own sender resolution. The broadcast cheatcodes choose the transaction sender separately: | Invocation | Transaction sender | Signing material | | --- | --- | --- | | `vm.startBroadcast(address)` | The supplied address | Must be available separately when sending | | `vm.startBroadcast(uint256)` | The address derived from that private key | The key is added to Forge's script wallets | | `vm.startBroadcast()` | Explicit `--sender`, otherwise the sole available signer, otherwise the current transaction caller | Must be available when sending | The same selection applies to the three `vm.broadcast` overloads. `broadcast` records the next eligible call; `startBroadcast` records eligible calls until `stopBroadcast`. The scope is the call depth at which broadcasting starts. Cheatcode calls are excluded. Calls made inside a deployed contract remain part of that contract's transaction rather than becoming additional top-level transactions. `--sender` alone is enough for simulation, but normal broadcasting also needs a matching wallet, such as `--account deployer`. With `--unlocked`, the RPC node is responsible for sending from the selected account. Forge rejects ordinary broadcasts whose transaction sender is the default Foundry address. #### What `msg.sender` means during broadcasting Starting a broadcast changes the sender of outgoing calls. It does not rewrite the caller of the already-running script frame. For example, the script caller can be Foundry's default address while `vm.startBroadcast(deployer)` sends the next call from `deployer`. Inside a contract called directly by that transaction, `msg.sender` is the transaction sender. Inside the script, it is still the script caller. A callback or another nested contract call has its own caller as usual. With the default `script_execution_protection = true`, Forge rejects a `msg.sender` opcode read in the main script's broadcasting frame when that caller differs from the broadcast sender. It also protects against relying on the ephemeral script's `address(this)`. These are runtime opcode checks: they do not track values captured before broadcasting, and compiler optimizations can move a read outside the guarded region. Disabling the protection does not make the addresses equal. Pass the intended owner or deployer explicitly when constructing transaction arguments. This example uses the same address for broadcasting and ownership, without depending on the script frame's caller: ```solidity [script/DeployOwned.s.sol] // [!include ~/snippets/projects/scripting/script/DeployOwned.s.sol] ``` For a local run of the example, choose an address and use it consistently: ```bash $ export DEPLOYER=0x0000000000000000000000000000000000001337 $ forge script script/DeployOwned.s.sol:DeployOwned --sender "$DEPLOYER" ``` For deployment, set `DEPLOYER` to the address controlled by the chosen wallet and also supply the RPC and wallet options. Keep `--sender` and the wallet address aligned; a raw key's inferred script caller and an explicitly different broadcast sender can otherwise disagree. ### CREATE and CREATE2 A direct `new Contract(...)` inside a broadcast produces a contract-creation transaction. Its address depends on the transaction sender and that sender's nonce. The constructor sees the transaction sender as `msg.sender`. A direct `new Contract{salt: salt}(...)` at the broadcast depth is different. Forge routes it through its configured CREATE2 deployer, by default `0x4e59b44847b379578588920ca78fbf26c0b4956c`. The collected transaction calls that deployer with the salt followed by the creation bytecode, including encoded constructor arguments. The factory then performs CREATE2. This has three consequences: * The constructor's `msg.sender` is the **factory**, even though the outer transaction comes from the wallet. * The contract address depends on the factory address, salt, and complete init code. Constructor arguments and linked library addresses are part of that init code. * The outer transaction still consumes the wallet's nonce, although that nonce is not part of the CREATE2 address formula. Forge's automatic routing validates the expected deployer bytecode. A configured address is not sufficient if it has no code or incompatible code. To use another factory protocol, call that factory explicitly with its own ABI. A CREATE2 performed inside an ordinary called contract also uses that contract's execution context; it is not the script-level rewrite described above. When running locally without an RPC, Forge can install its default deployer for simulation. That does not install it on a target chain. Check the target chain's factory state when preparing a deployment. The [CREATE2 guide](/guides/deterministic-deployments-using-create2) explains the address formula and compiler settings that affect determinism. ### External libraries and linking Solidity contracts that use external library functions contain link references. Those references must be replaced with library addresses before the bytecode can run. Internal library functions that the compiler includes in the contract do not require a separate library deployment. Forge first uses explicitly configured library addresses. For unresolved references it attempts CREATE2 linking when the configured deployer is available and linking succeeds, using `create2_library_salt`. Otherwise, it links using addresses calculated from the script sender and consecutive CREATE nonces. Required library deployments are collected before the script's own broadcast transactions. For CREATE2 libraries, Forge skips a deployment when code already exists at the calculated address. For CREATE libraries, their deployment transactions consume the first sender nonces, so they also affect the addresses of contracts created later by the script. If execution discovers a different deployer and no `--sender` was given, Forge can relink and execute again using that sender. When several deployers are candidates, it warns and retains the configured sender for predeployment. Set `--sender` explicitly when library deployment ownership matters. Forge can also keep libraries used only by the local script out of the broadcast sequence. This optimization applies only to eligible scripts and reruns them with those libraries deployed locally, checking the candidate before accepting it. It is not a guarantee that every library absent from the final deployed contracts will be omitted. Inspect the prepared sequence to see which library deployments will actually be sent. Changing a linked library address changes the consuming contract's bytecode. It can therefore change CREATE2 addresses as well as verification inputs, even if the contract's Solidity source is unchanged. ### Nonces and deployment addresses With an RPC, Forge obtains the initial script sender nonce from the resolved fork state. Without an RPC, the initial nonce is `1`. `--sender-nonce` overrides this starting value and is retained if Forge changes the inferred sender during relinking. During script execution, each collected transaction gets a nonce from its sender's simulated account state. Calls and creations consume nonces, and multiple senders have separate nonce sequences. Local script setup must not consume the same nonce as an actual deployment: Forge handles the ephemeral script deployment separately and adjusts local execution bookkeeping accordingly. For example, if a sender begins at nonce 7 and needs two ordinary CREATE library deployments, those transactions use nonces 7 and 8. A subsequent direct contract creation uses nonce 9. Adding another required library before it changes that contract's CREATE address. The nonce override is a planning input; it does not change the account nonce on the network. If other transactions use the account after simulation, the prepared transactions may no longer be usable as planned. During sequential broadcasting, Forge checks the provider nonce against the expected transaction nonce, retries when the provider is behind, and errors when it is ahead. This check does not reserve nonces against other processes. ### What simulation does and does not replay The first execution runs Solidity script logic, including reads, calculations, and cheatcodes, to construct transaction arguments. Operations outside a broadcast can affect this local execution without becoming transactions. The second simulation starts from separate RPC-backed state and runs the **collected transactions**. It does not rerun `run()` to recalculate arguments, and script-only state changes are not carried into it. For example, a local storage modification can make script execution succeed, but the transaction simulation will fail if the collected calls depend on that modification existing on-chain. Transactions are associated with their RPC endpoints and simulated in the corresponding contexts. Forge gathers traces, deployment metadata, and gas usage, then applies the gas-estimate multiplier. `--slow` also advances the simulated block number between transactions. This does not predict the exact block timestamps, ordering with other users' transactions, or state that will exist when a real transaction is mined. When broadcasting, Forge resolves the required signers and fills transaction details such as fees. Some networks and `--skip-simulation` workflows require RPC gas estimation immediately before sending. With `--slow`, or when another condition requires sequential submission, Forge waits for receipts between transactions. An ordinary script containing several transactions is not one atomic operation: an earlier transaction can succeed even if a later one fails. ### Saved sequences and resume Forge saves transaction sequences and receipts under the configured `broadcast` directory, normally grouped by script and chain ID. Dry-run sequences use a `dry-run` subdirectory. These files record the prepared calls and deployments, not just the source code that produced them. `--resume` loads a saved sequence, checks pending transactions, and continues publishing the remaining transactions. It skips the ordinary transaction-simulation stage. If signing keys are only available through script execution, Forge may execute the script again to collect those keys; the saved transaction sequence remains authoritative. Editing the script and passing `--resume` does not regenerate its saved transaction arguments or recreate the original `--broadcast` execution state. To produce a new transaction plan, run the script again without `--resume`. To continue a multi-chain saved sequence, use `--multi` with `--resume`. ### Implementation references The main implementation boundaries are [sender selection and orchestration](https://github.com/foundry-rs/foundry/blob/master/crates/script/src/lib.rs), [broadcast cheatcodes](https://github.com/foundry-rs/foundry/blob/master/crates/cheatcodes/src/script.rs), [linking and resume](https://github.com/foundry-rs/foundry/blob/master/crates/script/src/build.rs), [local execution](https://github.com/foundry-rs/foundry/blob/master/crates/script/src/runner.rs), [transaction simulation](https://github.com/foundry-rs/foundry/blob/master/crates/script/src/simulate.rs), and [transaction submission](https://github.com/foundry-rs/foundry/blob/master/crates/script/src/broadcast.rs). ## Debugging Forge provides detailed traces and an interactive debugger to understand contract execution. ### Traces Run tests with `-vvvv` to see full execution traces: :::terminal ```bash $ forge test -vvvv ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-vvvv:output] ``` ::: The trace shows every call, its inputs, outputs, and gas usage. ### Understanding trace output Each line shows: * **Gas used** in brackets * **Contract::function** being called * **Call type** (staticcall, delegatecall, etc.) * **Return value** or revert reason Indentation indicates call depth. ### Tracing a failed transaction Debug a transaction that failed on-chain: ```bash $ cast run 0x --rpc-url $RPC_URL ``` This replays the transaction and shows the execution trace. Add `--debug` to open the transaction in the interactive debugger instead; `cast call --trace --debug` does the same for a call without sending it. ### Interactive debugger Launch the debugger for a single test: ```bash $ forge test --debug --match-test test_Increment ``` In an interactive terminal, a filter that matches more than one test opens a prompt asking which test to debug, so you can also run `forge test --debug` without a filter and pick from the list. In a non-interactive terminal the filter must match exactly one test. The debugger is a terminal UI with five panes: * **Source** – Source code with the currently executing line highlighted. * **Opcodes** – The opcode list for the current call, with the program counter, address, and gas information in the title. * **Variables** – Parameters, return values, and locals in the current scope, with decoded values where available. When you step through a constructor during contract creation, the constructor arguments are decoded and shown here. Storage reads and writes performed by the current step also appear in this pane. * **Stack** – The current EVM stack. * **Data** – A shared pane showing either a buffer (memory, calldata, or returndata) or the [storage explorer](#storage-explorer). The debugger picks a two-column or single-column layout based on the terminal size. Press `l` to switch layouts, or force one with `--debug-layout `. #### Key bindings | Key | Action | |-----|--------| | `j` / `k` (or arrow keys, mouse scroll) | Step forward / backward one instruction | | `s` / `a` | Move to the next / previous jump | | `C` / `c` | Move to the next / previous call | | `g` / `G` | Go to the beginning / end | | `'` | Jump to the breakpoint set with [`vm.breakpoint`](/reference/cheatcodes/breakpoint) | | `0-9` | Repeat prefix for movement keys, e.g. `10k` steps back 10 instructions | | `b` | Cycle the data pane between memory, calldata, and returndata | | `J` / `K` | Scroll the stack pane | | `Ctrl+j` / `Ctrl+k` | Scroll the data pane | | `t` | Toggle stack labels | | `m` | Toggle UTF-8 decoding of the active buffer | | `p` | Go to a program counter | | `o` | Go to a byte offset in the active buffer, or to a slot when the storage explorer is active | | `/`, then `n` / `N` | Search opcodes in the current call, then repeat the search forward / backward | | `:` | Open the command prompt | | `h` | Toggle the shortcut footer | | `q` | Quit | #### Command prompt Press `:` to run a debugger command. `:help` lists all commands and their aliases. | Command | Action | |---------|--------| | `:pc ` (alias `:continue `) | Jump to a program counter in the current contract | | `:line ` | Jump to the nearest instruction mapped to a source line in the current contract | | `:mem []`, `:calldata []`, `:ret []` | Select a buffer in the data pane, optionally jumping to a byte offset | | `:storage []` | Open the storage explorer, optionally jumping to the nearest access of a slot | | `:transient []` | Open the transient storage explorer, optionally jumping to a slot | | `:source`, `:opcodes`, `:variables`, `:stack`, `:data` | Show or hide a pane; the remaining panes reclaim the space | #### Storage explorer `:storage` switches the data pane to a storage view that lists every slot the current call has accessed up to the current step, showing each slot's most recent operation (`SLOAD` or `SSTORE`) and value. The slot touched by the current step is highlighted. `:storage ` jumps to the nearest access of a specific slot: the access at or after the current step, or the most recent earlier one. `:transient` and `:transient ` provide the same view for transient storage (`TLOAD` and `TSTORE`). Press `b` to switch the data pane back to the buffer view. #### Breakpoints Place breakpoints in code with the [`vm.breakpoint`](/reference/cheatcodes/breakpoint) cheatcode: ```solidity vm.breakpoint("a"); ``` Pressing `'a` in the debugger jumps to the step where the breakpoint was recorded. ### Debugging scripts Debug a script: ```bash $ forge script script/Deploy.s.sol --debug ``` ### Console logging Add logs to your contracts for debugging: ```solidity import {console} from "forge-std/console.sol"; function transfer(address to, uint256 amount) public { console.log("Transfer from:", msg.sender); console.log("Transfer to:", to); console.log("Amount:", amount); // ... } ``` View logs with `-vv` or higher: ```bash $ forge test -vv ``` For structured output, Foundry also supports `console.table`, which can make repeated values easier to scan than a long sequence of `console.log` lines. ### Labeling addresses Make traces more readable by labeling addresses: ```solidity function setUp() public { alice = makeAddr("alice"); bob = makeAddr("bob"); vm.label(address(token), "Token"); vm.label(address(pool), "Pool"); } ``` Traces will show `Token::transfer()` instead of `0x1234...::transfer()`. ### Stack traces When a test fails, use `-vvv` to see a stack trace showing exactly where the revert occurred: :::terminal ```bash $ forge test -vvv ``` ```ansi // [!include ~/snippets/output/cheatcodes/forge-test-fail-vvv:output] ``` ::: The trace shows the call hierarchy with the revert bubbling up, and the **Backtrace** pinpoints the exact location in your code. ### Inspecting inheritance linearization When debugging overrides in a multiple-inheritance hierarchy, inspect the method-resolution order directly: ```bash $ forge inspect src/MyContract.sol:MyContract linearization ``` This shows the order Solidity uses to resolve inherited functions, which is often the fastest way to understand why a particular override or `super` call is being selected. ## Gas tracking Forge tracks gas usage to help optimize contracts and catch regressions. Under EIP-8037 (Amsterdam), gas has two dimensions: regular gas and state gas. See [gas accounting](/forge/gas-accounting) for how reports, snapshots, and [`lastFrameGas`](/reference/cheatcodes/last-frame-gas) relate to them. ### Gas reports Generate a gas report for all tests: ```bash $ forge test --gas-report ``` Output shows gas usage per function: ```ansi ╭----------------------------------+-----------------+-------+--------+-------+---------╮ | src/Counter.sol:Counter Contract | | | | | | +=======================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | 156813 | 509 | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |----------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------+-----------------+-------+--------+-------+---------| | increment | 43482 | 43482 | 43482 | 43482 | 1 | |----------------------------------+-----------------+-------+--------+-------+---------| | number | 2424 | 2424 | 2424 | 2424 | 2 | |----------------------------------+-----------------+-------+--------+-------+---------| | setNumber | 23784 | 23784 | 23784 | 23784 | 1 | ╰----------------------------------+-----------------+-------+--------+-------+---------╯ ``` ### Filter gas reports Report only specific contracts: ```toml [foundry.toml] [profile.default] gas_reports = ["Counter", "Token"] ``` Exclude contracts: ```toml [foundry.toml] [profile.default] gas_reports_ignore = ["Test", "Script"] ``` ### Gas snapshots Create a snapshot file to track gas over time: ```bash $ forge snapshot ``` This creates `.gas-snapshot` with gas usage for each test: ```txt CounterTest:test_Increment() (gas: 31293) CounterTest:testFuzz_SetNumber(uint256) (runs: 256, μ: 31121, ~: 31277) ``` Compare against a previous snapshot: ```bash $ forge snapshot --diff ``` Check that gas hasn't increased: ```bash $ forge snapshot --check ``` This exits with an error if any test uses more gas than recorded. ### Inline gas tracking Use `gasleft()` to measure specific operations: ```solidity function test_MeasureGas() public { uint256 gasBefore = gasleft(); counter.increment(); uint256 gasUsed = gasBefore - gasleft(); console.log("Gas used:", gasUsed); } ``` ### Snapshot cheatcodes Take named snapshots for comparison in tests: ```solidity function test_GasComparison() public { vm.startSnapshotGas("increment"); counter.increment(); uint256 gasIncrement = vm.stopSnapshotGas(); vm.startSnapshotGas("setNumber"); counter.setNumber(1); uint256 gasSetNumber = vm.stopSnapshotGas(); assertLt(gasSetNumber, gasIncrement); } ``` ### Gas in CI Add snapshot checking to your CI pipeline: ```yaml - name: Check gas run: forge snapshot --check ``` Update snapshots when gas changes are intentional: ```bash $ forge snapshot $ git add .gas-snapshot $ git commit -m "Update gas snapshot" ``` ### Pause gas metering Exclude setup code from gas measurements: ```solidity function test_OnlyMeasureTarget() public { // Setup not metered vm.pauseGasMetering(); _complexSetup(); vm.resumeGasMetering(); // Only this is measured target.execute(); } ``` ### Reset gas metering Reset the gas counter mid-test: ```solidity function test_ResetMidway() public { target.setup(); vm.resetGasMetering(); // Gas measurement starts fresh here target.execute(); } ``` ## Gas accounting Use a gas **measurement** to understand an execution, a transaction **receipt** to find the charged gas, and a gas **estimate** to choose the limit for a new transaction. These numbers answer different questions, even when they happen to be equal. [EIP-8037: State Creation Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8037) separates gas into two dimensions. This page explains that model and how Foundry exposes it. The EIP and its [tracing API proposal](https://github.com/ethereum/execution-apis/pull/852) are evolving; select tools and a node that implement the rules of the network and block you are testing. ### Which value should you use? | Your question | Value or API | Important boundary | | --- | --- | --- | | How much regular gas did the last call or creation use? | `vm.lastFrameGas().gasTotalUsed` | Excludes EIP-8037 state gas; includes nested execution. Isolation adds transaction intrinsic gas and the regular calldata floor. | | How much net state gas did that frame use? | `vm.lastFrameGas().gasStateUsed` | Already net of state refills; can be negative. Zero without EIP-8037 or after frame rollback. | | What is the net sum of those frame components? | `int256(uint256(g.gasTotalUsed)) + int256(g.gasStateUsed)` | A signed measurement, not a sufficient gas limit or necessarily receipt gas. | | What ordinary refund did that frame accumulate? | `vm.lastFrameGas().gasRefunded` | Frame counter without isolation; finalized refund for an isolated transaction. Excludes state refills. | | How much total gas was charged for a mined transaction? | `eth_getTransactionReceipt` → `gasUsed`; `cast receipt gasUsed` | Includes both dimensions after refund/floor processing. **Do not add state gas again.** | | What was the transaction's execution/state breakdown? | `debug_traceTransaction` with `stateGasTracer`, if the node supports it | Returns `gasUsed`, `executionGasUsed` (the proposal’s `regularGasUsed`), `stateGasUsed`, and `gasRefund`. The dimension fields use block-accounting rules; receipt gas is `gasUsed`. | | How much gas should I supply for another transaction? | `eth_estimateGas`; `cast estimate`; Alloy `Provider::estimate_gas` | Use the target network, input, sender, value, and state. Estimates require a node implementing its fork rules. | | What gas fee did I pay? | Receipt `gasUsed × effectiveGasPrice` | Covers regular and state gas. Add blob or network-specific fee components separately when applicable. | See [`lastFrameGas`](/reference/cheatcodes/last-frame-gas) for every field, [Cast receipt](/reference/cast/receipt), [Cast estimate](/reference/cast/estimate), and [Alloy's gas trace types](https://docs.rs/alloy-rpc-types-trace/latest/alloy_rpc_types_trace/geth/state_gas/index.html). ### Execution gas and state gas | Term | What it pays for | What it excludes | | --- | --- | --- | | **Regular gas** (the EIP’s *execution gas*) | Computation, memory expansion, state access, existing-state updates, and transaction intrinsic costs. | State creation charges assigned to the EIP-8037 state dimension; blob gas. | | **State gas** | State creation, such as a new storage slot, a new account, or deployed code. Reported net usage already deducts charges undone by state refills or rollback. | The accompanying regular execution costs; blob gas. | | **Combined transaction gas limit** (`tx.gas`) | The single gas budget you supply for intrinsic costs and both execution and state gas. | Blob gas, which is accounted for separately. | | **Receipt gas used** (`gasUsed`) | The transaction's combined charged gas after ordinary refunds and the applicable calldata floor. | Unused gas, blob gas, and separate network-specific fees. | A state-changing opcode can incur **both** regular and state gas. State gas does not mean “all gas spent by storage opcodes.” Before EIP-8037, those operations still cost gas, but their costs use the ordinary single-dimensional schedule. ### How the reservoir works You still supply **one transaction gas limit**. After intrinsic gas, the EVM gives the transaction a regular gas allowance, capped by the protocol, and puts any excess into a **state gas reservoir**. Regular execution spends regular gas only. State creation spends the reservoir first and spills into regular gas once it is empty. State **refills** reverse creation charges and restore the gas pools according to EIP-8037's refill rules. Child calls receive the reservoir in full; their `{gas: amount}` allowance and the 63/64 rule apply to regular gas only. `gasleft()` reports regular gas only. Its delta can miss state gas paid from the reservoir or include state charges spilling into regular gas. A refill can make a later `gasleft()` **larger**, causing checked subtraction to revert. An empty reservoir does not disable state charges, and a large reservoir cannot fund regular execution. See the EIP's [reservoir model](https://eips.ethereum.org/EIPS/eip-8037#transaction-level-gas-accounting-reservoir-model) and [call-frame rules](https://eips.ethereum.org/EIPS/eip-8037#gas-accounting-for-halts-and-reverts) for the full mechanics. ### Two meanings of “refund” **Ordinary gas refunds** accumulate in the EVM refund counter and are settled at the transaction boundary. [EIP-3529](https://eips.ethereum.org/EIPS/eip-3529) caps the applied refund at one fifth of gas spent; the [EIP-7623 calldata floor](https://eips.ethereum.org/EIPS/eip-7623) can further limit the reduction in charged gas. A refund does not replenish gas available to execute instructions. **State gas refills** reverse state creation charges during execution, for example when a slot that was zero at transaction start is set and then cleared. They can replenish gas available during execution and are already deducted from net state gas. They are not the ordinary capped refund counter. A reverted or exceptionally halted frame reports zero net state gas, even if it temporarily needed state gas before failing; its regular execution still costs gas. A successful nested frame can have a **negative state gas delta** when it clears state created by an earlier frame in the same transaction. This is why `Vm.Gas.gasStateUsed` is signed. Transaction-level state gas is nonnegative. Use signed arithmetic for frame deltas and avoid subtracting state refills twice. ### Why used gas is not required gas Your transaction budget must fund both regular and state gas **when they are charged**, before later refills or refunds. Regular execution must also fit its separate protocol cap. A transaction that allocates state and later undoes it can report little net state gas while requiring enough gas for the temporary allocation. Call forwarding and minimum gas checks can require additional headroom as well. For example, if a frame reports 30,000 regular gas and 100,000 net state gas, their sum is 130,000 measured gas units. It is not proof that a transaction with a 130,000 gas limit will succeed: the frame can exclude intrinsic or caller-side costs, and it does not report peak temporary state consumption. Subtracting its ordinary refund makes an estimate even less reliable. For an already executed transaction, read the receipt directly. In the EIP-8037 model, transaction gas before the ordinary refund includes both dimensions and intrinsic costs. The applied ordinary refund is capped, then the calldata floor determines the final charged amount. This settlement cannot generally be reconstructed from `lastFrameGas`, because a child frame is not the whole transaction. ### Transaction gas versus block gas Receipt `gasUsed` is the transaction's combined charge. Under EIP-8037, the block header uses the **larger** accumulated gas dimension instead. With [EIP-7778](https://eips.ethereum.org/EIPS/eip-7778), block accounting counts regular gas before ordinary refunds, while state gas remains net of state refills. See [Alloy's gas accounting documentation](https://docs.rs/alloy-rpc-types-trace/latest/alloy_rpc_types_trace/geth/state_gas/index.html) for the fields and calldata-floor rules. Do not sum parent and child frames to reconstruct a transaction total: parent measurements already include nested execution. Some state charges and rollbacks also occur outside opcode steps. ### Which networks are affected? | Execution environment | Behavior | | --- | --- | | Foundry's Ethereum EVM with `evm_version = "amsterdam"` or a later supported fork | Uses EIP-8037 regular/state gas accounting. This is a local execution setting, not evidence of activation on a live chain. | | Ethereum forks before Amsterdam, or another network that has not activated EIP-8037 | No separate state gas dimension: `Vm.Gas.gasStateUsed` is zero. State creation still costs ordinary gas and is included in `gasTotalUsed`. | | A custom network or an L2 | Follows its own hardfork and gas schedule. EVM compatibility alone does not establish EIP-8037 support, pricing, activation dates, or fee equivalence. | | RPC tracing on any network | Depends on both the block's rules and the node's tracer support. Optional state fields may be absent; an unsupported `stateGasTracer` can return an RPC error. An absent field means “not reported,” not measured zero. | When compiling with Solidity 0.8.36, Amsterdam is experimental and also requires `--experimental` (or `experimental = true` in `foundry.toml`). Pin the [EVM version](/config/reference/solidity-compiler#evm_version), fork block, optimizer settings, and isolation mode when comparing measurements. Use a Foundry version supporting the selected fork and a current forge-std `Vm.Gas` interface containing `gasStateUsed`. For a live network, consult that network's activation schedule rather than assuming Amsterdam rules from a chain name or from Alloy's ability to decode the fields. RPC field names differ between implementations. The [execution-apis proposal](https://github.com/ethereum/execution-apis/pull/852) names the execution dimension `regularGasUsed`; Alloy and reth serialize `executionGasUsed`. Check what your node returns and whether your Alloy version accepts both names when decoding. ## Formatting Forge includes a built-in formatter to enforce consistent code style. ### Format files Format all Solidity files: ```bash $ forge fmt ``` Check formatting without making changes: ```bash $ forge fmt --check ``` This exits with an error if any file needs formatting—useful for CI. ### Configuration Configure the formatter in `foundry.toml`: ```toml [foundry.toml] [fmt] line_length = 120 tab_width = 4 bracket_spacing = true int_types = "long" multiline_func_header = "params_first" quote_style = "double" number_underscore = "thousands" single_line_statement_blocks = "preserve" ``` Common options: | Option | Default | Description | |--------|---------|-------------| | `line_length` | 120 | Maximum line length | | `tab_width` | 4 | Spaces per indentation level | | `bracket_spacing` | false | Space inside brackets: `{ x }` vs `{x}` | | `int_types` | "long" | `uint256` vs `uint` | | `quote_style` | "double" | `"string"` vs `'string'` | | `number_underscore` | "preserve" | `1_000_000` vs `1000000` | See the [formatter reference](/config/reference/formatter) for all options. ### Ignoring files Exclude files from formatting: ```toml [foundry.toml] [fmt] ignore = ["src/legacy/**"] ``` ### Pre-commit integration Add formatting checks to git pre-commit hooks: ```bash [.git/hooks/pre-commit] #!/bin/sh forge fmt --check ``` Or use a tool like `lefthook` or `husky` for more complex workflows. ### CI integration Check formatting in CI: ```yaml - name: Check formatting run: forge fmt --check ``` ## Linting Forge includes a built-in linter to catch common issues and enforce best practices. ### Run the linter ```bash $ forge lint ``` The linter checks for: * Incorrect shift operations * Unchecked external calls * Divide-before-multiply bugs * Incorrect ERC721 interface signatures * Incorrect ERC20 interface definitions * Strict equality on externally-influenced values * Unsafe typecasts * Naming convention violations * Use of tx.origin for authorization * Return bomb risks from gas-limited calls * Unused imports * Gas optimizations ### Configuration Configure linter rules in `foundry.toml`: ```toml [foundry.toml] [lint] severity = ["high", "med", "low"] exclude_lints = ["mixed-case-function", "custom-errors"] ``` #### Severity levels Control which lints run by severity: ```toml [foundry.toml] [lint] severity = ["high", "med"] # Only high and medium severity ``` Valid severity levels: `high`, `med`, `low`, `info`, `gas`, `code-size` Gas and code-size lints skip test and script files. #### Exclude specific lints Disable specific lint rules globally: ```toml [foundry.toml] [lint] exclude_lints = ["mixed-case-variable", "asm-keccak256"] ``` ### Ignoring files Exclude files from linting: ```toml [foundry.toml] [lint] ignore = ["src/legacy/**", "test/**"] ``` ### Inline suppression Disable lints for specific lines or blocks using comment directives. #### Disable on current line ```solidity uint256 Mixed_Case = 1; // forge-lint: disable-line(mixed-case-variable) ``` #### Disable on next line ```solidity // forge-lint: disable-next-line(custom-errors) revert("Use custom errors instead"); ``` #### Disable for next item Disable lints for an entire function, struct, or contract: ```solidity // forge-lint: disable-next-item(mixed-case-function) function non_standard_name() public { // entire function is excluded from the lint } ``` #### Disable a block ```solidity // forge-lint: disable-start(asm-keccak256) bytes32 hash1 = keccak256(abi.encodePacked(a, b)); bytes32 hash2 = keccak256(abi.encodePacked(c, d)); // forge-lint: disable-end(asm-keccak256) ``` #### Disable multiple lints ```solidity // forge-lint: disable-next-line(custom-errors, mixed-case-variable) ``` #### Disable all lints ```solidity // forge-lint: disable-next-line ``` Or explicitly: ```solidity // forge-lint: disable-next-line(all) ``` ### Disable linting on build By default, `forge build` runs the linter. To disable for a single invocation, pass `--no-lint` (alias `--skip-lint`): ```bash forge build --no-lint ``` To disable persistently: ```toml [foundry.toml] [lint] lint_on_build = false ``` ### CI integration Add linting to your CI pipeline with `--deny warnings`, which exits with a non-zero status when warning-level lint diagnostics are emitted: ```yaml - name: Run linter run: forge lint --deny warnings ``` See the [linter reference](/config/reference/linter) for all configuration options. ### Lint reference Every lint emitted by `forge lint` has its own page describing what it flags, why it matters, and how to fix it. Use the index below to jump to a specific lint, or use the navigation on the right to browse by severity. #### High severity * [`arbitrary-send-erc20`](/forge/linting/arbitrary-send-erc20) — Flags ERC20 `transferFrom` and `safeTransferFrom` calls whose `from` argument is not constrained to `msg.sender` or `address(this)`, including SafeERC20 library calls. * [`arbitrary-send-erc20-permit`](/forge/linting/arbitrary-send-erc20-permit) — Flags `transferFrom` and `safeTransferFrom` calls preceded by a `permit` for the same token and owner in the same function, when `from` is not constrained to `msg.sender` or `address(this)`. This includes common SafeERC20 and SafeTransferLib wrappers. * [`arbitrary-send-eth`](/forge/linting/arbitrary-send-eth) — Flags ETH transfers to caller-controlled destinations in functions without a recognized caller restriction. This includes `transfer`, `send`, calls with `{value: ...}`, `selfdestruct`, and common OpenZeppelin and Solady ETH-transfer helpers. * [`controlled-delegatecall`](/forge/linting/controlled-delegatecall) — Flags `delegatecall` targets other than a trusted literal, constant, zero address, or `address(this)`. * [`encode-packed-collision`](/forge/linting/encode-packed-collision) — Encode Packed Collision * [`enumerable-loop-removal`](/forge/linting/enumerable-loop-removal) — Flags `EnumerableSet.remove` inside a loop that also reads the same set with `at` using an increasing index. * [`erc20-unchecked-transfer`](/forge/linting/erc20-unchecked-transfer) — Warns when a function with the same signature as `transfer(address,uint256)` or `transferFrom(address,address,uint256)` and a `bool` return type is invoked but the result is not checked. * [`function-selector-collision`](/forge/linting/function-selector-collision) — Reports different proxy and implementation function signatures with the same four-byte selector. Identical signatures are not reported. * [`incorrect-exp`](/forge/linting/incorrect-exp) — Reports `a ^ b` when both operands are decimal integer literals and `a` is `2` or `10`. In Solidity, `^` is bitwise XOR, so `10 ^ 18` evaluates to `24`, not `10 ** 18`. Hexadecimal and scientific-notation operands are excluded. * [`incorrect-shift`](/forge/linting/incorrect-shift) — Warns when the first argument to a Yul `shl` or `shr` call is dynamic and the second argument is a literal. Yul shift calls take the shift amount first and the value second, so `shr(value, 8)` shifts the literal `8` by `value`; the usual intended expression is `shr(8, value)`. * [`protected-vars`](/forge/linting/protected-vars) — Protected variables * [`reentrancy-balance`](/forge/linting/reentrancy-balance) — Reports public or external functions that save `address(this).balance`, make an external call that permits reentry, and then check the current balance against the saved value. * [`reentrancy-eth`](/forge/linting/reentrancy-eth) — Reports low-level `.call{value: ...}(...)` operations without a concrete gas cap, including `gas: gasleft()`, when a state variable read before the call is written after it. * [`rtlo`](/forge/linting/rtlo) — Detects the right-to-left override codepoint (`U+202E`) and other bidirectional control characters embedded in identifiers, strings, and comments. * [`unchecked-call`](/forge/linting/unchecked-call) — Warns when the boolean returned by a low-level call is discarded — either because the return value is not assigned or because only the `bytes memory` payload is used. * [`unprotected-initializer`](/forge/linting/unprotected-initializer) — Unprotected initializer #### Medium severity * [`assert-state-change`](/forge/linting/assert-state-change) — Warns when an `assert()` argument contains a state-mutating operation: a pre- or post-increment/decrement (`++`/`--`) on a state variable, an assignment (`=`, `+=`,etc.) to a state variable, a `delete` of a state variable, or a call to a function that writes state variables. * [`block-number-across-roll`](/forge/linting/block-number-across-roll) — Warns in tests and scripts when a value derived from `block.number` can be used after `vm.roll`, or when `block.number` is read both before and after a roll in the same call. * [`block-timestamp-across-warp`](/forge/linting/block-timestamp-across-warp) — Warns in tests and scripts when a value derived from `block.timestamp` can be used after `vm.warp`, or when `block.timestamp` is read both before and after a warp in the same call. * [`boolean-cst`](/forge/linting/boolean-cst) — Reports literal boolean conditions in `if`, `for`, and `do while`, `while (false)`, and boolean operators (`&&`, `||`) where one side is a literal `true`/`false`. The idiomatic infinite loop `while (true)` is exempt. * [`dangerous-unary-operator`](/forge/linting/dangerous-unary-operator) — Reports `x =- y` and `x =~ y`, where `=` is written directly beside a unary operator. These are assignments, not compound operations: `x =- 1` means `x = -1`, not `x -= 1`. The intentional spaced forms (`x = -1`, `x = ~y`) and compound operators (`x -= 1`) are not flagged. * [`divide-before-multiply`](/forge/linting/divide-before-multiply) — Warns on expressions of the form `(a / b) * c` (or equivalent shapes), where the integer division truncates before the result is multiplied. * [`ecrecover`](/forge/linting/ecrecover) — Reports direct `ecrecover` calls without a check that `s` is at most `0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0` before the recovered address is used. * [`incorrect-erc20-interface`](/forge/linting/incorrect-erc20-interface) — For each function whose name and parameter types match a canonical ERC20 method (`totalSupply`, `balanceOf`, `transfer`, `transferFrom`, `approve`, `allowance`), the lint checks that the return type matches the spec. A mismatch is reported. * [`incorrect-erc721-interface`](/forge/linting/incorrect-erc721-interface) — For each function whose name and parameter types match a canonical ERC721/ERC165 method (`balanceOf`, `ownerOf`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, `getApproved`, `isApprovedForAll`, `supportsInterface`), the lint checks that the return type matches the spec. A mismatch is reported. * [`incorrect-strict-equality`](/forge/linting/incorrect-strict-equality) — Incorrect Strict Equality * [`locked-ether`](/forge/linting/locked-ether) — Locked Ether * [`mapping-deletion`](/forge/linting/mapping-deletion) — Reports `delete x` when `x` is a struct or array whose type holds a `mapping`, directly or through a nested struct or array. Deleting a whole mapping is not valid Solidity, but deleting a container of one compiles and silently leaves the mapping's entries in place. * [`non-reentrant-not-first`](/forge/linting/non-reentrant-not-first) — Reports a function, fallback, or receive function when `nonReentrant` appears after another modifier, for example `onlyOwner nonReentrant`. * [`reentrancy-no-eth`](/forge/linting/reentrancy-no-eth) — Reports public or external functions that read a state variable, make an external call without sending ETH, and then write the same state variable. * [`tautological-compare`](/forge/linting/tautological-compare) — Reports `a a` where `a` is a side-effect-free expression (an identifier, member access, or indexing) and `` is `<`, `<=`, `>`, `>=`, `==`, or `!=`. Such a comparison has a constant result. Comparisons whose sides could legitimately differ (for example involving a function call) are left untouched, as are comparisons on user-defined value types, whose operators are user-defined (`using {f as ==} for T`) and need not be constant. * [`tx-origin`](/forge/linting/tx-origin) — Reports `tx.origin` reads when they are used as part of a guard condition. Plain reads outside of guard predicates are not reported. * [`type-based-tautology`](/forge/linting/type-based-tautology) — Flags comparisons that are always true or false because of an integer type's range, such as `uint256 x >= 0`. Also reports conditions that cover the entire range, such as `x > 0 || x == 0` for unsigned `x`. * [`uninitialized-local`](/forge/linting/uninitialized-local) — Reports local variables that can be read before being assigned. Parameters and state variables are excluded. * [`uninitialized-state`](/forge/linting/uninitialized-state) — Reports state variables that are read but never assigned in the contract or its base contracts. An assignment at the declaration or in a constructor satisfies the lint. * [`unsafe-oz-erc721-mint`](/forge/linting/unsafe-oz-erc721-mint) — Reports calls to OpenZeppelin's ERC721 `_mint`, including overrides that delegate to it, without a recognized recipient check. * [`unsafe-typecast`](/forge/linting/unsafe-typecast) — Reports casts where the source value's type can exceed the target type (for example, `uint256 → uint128` or `int256 → uint128`). An unsigned value masked to the target width, such as `uint8(value & 0xff)`, is not flagged. A preceding manual range check may still produce a warning; review that check before suppressing the lint. * [`unused-return`](/forge/linting/unused-return) — Detects high-level external calls (member calls on contract-typed variables or interface-cast addresses) that return one or more values when the entire result is discarded or any slot of a tuple return is omitted. ERC20 `transfer` and `transferFrom` are excluded as they are handled by the separate `erc20-unchecked-transfer` lint. * [`weak-prng`](/forge/linting/weak-prng) — Reports direct use of `block.timestamp`, `block.number`, `block.coinbase`, `blockhash(...)`, `block.prevrandao`, or `block.difficulty` in modulo expressions or `keccak256(...)`. `abi.encode*` calls are treated as entropy only when they feed one of those expressions. #### Low severity * [`block-timestamp`](/forge/linting/block-timestamp) — Reports comparison expressions (`<`, `<=`, `>`, `>=`, `==`, `!=`) involving `block.timestamp`. * [`calls-loop`](/forge/linting/calls-loop) — Reports high-level contract calls, low-level `call`/`delegatecall`/`staticcall`, Ether `send`/`transfer`, external self-calls through `this`, and contract creation inside a loop. Internal and private library calls and `super` dispatch are not treated as external calls. * [`delegatecall-loop`](/forge/linting/delegatecall-loop) — Reports `delegatecall` expressions that appear in the body of a `for`, `while`, or `do while` loop when the enclosing function is `public payable` or `external payable`. * [`deprecated-oz-function`](/forge/linting/deprecated-oz-function) — Reports uses of OpenZeppelin's `SafeERC20.safeApprove` and `AccessControl._setupRole`, including their upgradeable variants. * [`empty-block`](/forge/linting/empty-block) — Reports a function whose body is `{}` (a comment does not make a body non-empty). * [`inconsistent-type-names`](/forge/linting/inconsistent-type-names) — Reports shorthand `uint` or `int` declarations when the same contract also uses `uint256` or `int256`, respectively. This includes types within arrays and mappings. * [`incorrect-modifier`](/forge/linting/incorrect-modifier) — Flags modifiers that can finish successfully without reaching the `_` placeholder. A path that reverts before `_` is allowed, but calling a function that might revert does not by itself prevent the modifier from skipping the body. * [`missing-events-access-control`](/forge/linting/missing-events-access-control) — Flags protected public or external functions that change ownership, roles, or other state used in authorization checks without a related event containing the changed value or key. * [`missing-events-arithmetic`](/forge/linting/missing-events-arithmetic) — Flags protected public or external functions that update integer parameters used in arithmetic by an unprotected entry point without emitting an event. Updates include assignments from function input and arithmetic changes. * [`missing-zero-check`](/forge/linting/missing-zero-check) — Reports `address` parameters used in a state write or value transfer by an externally callable state-mutating function or constructor without a check against `address(0)`. * [`msg-value-loop`](/forge/linting/msg-value-loop) — Reports `msg.value` expressions that execute inside a `for`, `while`, or `do while` loop reachable from a `public payable` or `external payable` entry point. * [`reentrancy-events`](/forge/linting/reentrancy-events) — Reports events emitted after an external interaction, such as a state-changing contract call, low-level `call` or `delegatecall`, ETH `send` or `transfer`, or contract creation. Static calls and `view` or `pure` calls are excluded. * [`require-revert-in-loop`](/forge/linting/require-revert-in-loop) — Reports `require` calls and Solidity or Yul `revert` operations inside loops. * [`return-bomb`](/forge/linting/return-bomb) — Detects low-level `call`, `delegatecall`, and `staticcall` expressions that specify `{gas: ...}`. Solidity copies the full returndata for these calls even when the second tuple element is ignored. It also detects high-level external calls with `{gas: ...}` that consume dynamically encoded return values such as `bytes`, `string`, dynamic arrays, or structs containing dynamic fields. * [`solmate-safe-transfer-lib`](/forge/linting/solmate-safe-transfer-lib) — Reports uses of `safeTransfer`, `safeTransferFrom`, and `safeApprove` from solmate's `SafeTransferLib`. ETH transfers and similarly named libraries from other packages are excluded. #### Informational * [`boolean-equal`](/forge/linting/boolean-equal) — Reports any equality comparison between a boolean expression and a literal `true` or `false`. * [`cyclomatic-complexity`](/forge/linting/cyclomatic-complexity) — Reports functions with a complexity score above 11. The score starts at one and increases for each decision point: `if`, a loop with a condition, a ternary, a `catch` clause, or an additional assembly `switch` case. Boolean `&&` and `||` operators do not add to the score. * [`event-fields`](/forge/linting/event-fields) — Reports unindexed `address` and `address payable` event parameters when the event has no indexed parameters. Contract, interface, and user-defined value types are excluded. * [`function-init-state`](/forge/linting/function-init-state) — Reports inline state-variable initializers that reference a non-constant state variable or a non-pure function. Constants, pure functions, and assignments in the constructor body are excluded. * [`incorrect-using-for`](/forge/linting/incorrect-using-for) — Reports `using L for T` when library `L` has no non-private function whose first parameter accepts `T`, including through an implicit conversion. * [`inline-assembly`](/forge/linting/inline-assembly) — Reports every inline assembly statement, including blocks marked `memory-safe`. * [`interface-file-naming`](/forge/linting/interface-file-naming) — Reports interface-only files whose path basename does not start with `I` (e.g. `IERC20.sol`). * [`interface-naming`](/forge/linting/interface-naming) — Reports `interface Foo` where `Foo` does not start with `I` (e.g. `IFoo`). * [`internal-function-used-once`](/forge/linting/internal-function-used-once) — Reports internal and free functions referenced exactly once across the compiled sources. * [`literal-instead-of-constant`](/forge/linting/literal-instead-of-constant) — Reports repeated number, address, or hex-string values within a contract's executable code. Equivalent spellings, such as `100` and `0x64`, count as the same value. * [`low-level-calls`](/forge/linting/low-level-calls) — Warns whenever a contract uses a low-level call expression, even if the success return value is captured and checked. * [`missing-inheritance`](/forge/linting/missing-inheritance) — Reports contracts that implement an interface's external functions without inheriting it. An already-inherited base that provides the interface's functions satisfies the lint. Abstract contracts containing only interface declarations are also considered. * [`mixed-case-function`](/forge/linting/mixed-case-function) — Reports functions whose names contain embedded underscores, start with an uppercase letter, or otherwise deviate from `mixedCase`. Leading and trailing underscores are preserved, and single-character names are not checked. Test functions starting with `test`, `invariant_`, or `statefulFuzz`, configured uppercase patterns (for example, `ERC20`), and external constant-style getters are exempted. * [`mixed-case-variable`](/forge/linting/mixed-case-variable) — Reports mutable variable identifiers that contain embedded underscores, start with an uppercase letter, or otherwise deviate from `mixedCase`. Leading and trailing underscores are preserved, and single-character names are not checked. * [`modifier-used-only-once`](/forge/linting/modifier-used-only-once) — Reports modifiers used by exactly one function or constructor across the compiled sources. Virtual modifiers, overrides, and unused modifiers are excluded. * [`multi-contract-file`](/forge/linting/multi-contract-file) — Reports each top-level `contract`, `interface`, or `library` definition (after the first) in a file that contains more than one such declaration. * [`named-struct-fields`](/forge/linting/named-struct-fields) — Reports `Struct(a, b, c)` style struct construction; suggests `Struct({ field1: a, field2: b, field3: c })` instead. * [`pascal-case-struct`](/forge/linting/pascal-case-struct) — Reports `struct` identifiers longer than one character that do not match the `PascalCase` convention. Single-character names are not checked. * [`pragma-inconsistent`](/forge/linting/pragma-inconsistent) — Reports inconsistent `pragma solidity ...;` requirements across source files, such as different exact versions or mixed caret, tilde, and range constraints. * [`redundant-base-constructor-call`](/forge/linting/redundant-base-constructor-call) — For every base contract listed in a contract's inheritance specifier or invoked from a derived constructor's header, the lint reports the empty `()` when the base does not require any arguments. * [`screaming-snake-case-const`](/forge/linting/screaming-snake-case-const) — Reports state variables declared `constant` whose identifier is longer than one character and deviates from `SCREAMING_SNAKE_CASE`. Leading and trailing underscores are preserved. * [`screaming-snake-case-immutable`](/forge/linting/screaming-snake-case-immutable) — Reports state variables declared `immutable` whose identifier deviates from `SCREAMING_SNAKE_CASE`. Single-character names are not checked, and leading and trailing underscores are preserved. * [`todo-comment`](/forge/linting/todo-comment) — Reports `TODO` and `FIXME` markers in line, block, and NatSpec comments, regardless of case. This includes common forms such as `TODO:`, `FIXME(...)`, and a bare marker at the start of a comment line. Ordinary filenames such as `todo.md` are not markers. * [`too-many-digits`](/forge/linting/too-many-digits) — Reports Solidity and Yul numeric literals that contain a run of 5 or more `0` characters. Decimal literals with scientific notation, literals with a sub-denomination, and 40-digit hexadecimal address literals are skipped. Other hexadecimal literals remain in scope because long padded masks and bit patterns are also difficult to review. * [`unaliased-plain-import`](/forge/linting/unaliased-plain-import) — Reports plain imports of the form `import "path";`. Suggests using either named imports (`import { A, B } from "path"`) or an aliased import (`import "path" as X`). * [`unsafe-cheatcode`](/forge/linting/unsafe-cheatcode) — Reports calls to `ffi`, `readFile`, `readLine`, `writeFile`, `writeLine`, `removeFile`, `closeFile`, `setEnv`, or `deriveKey`. Unrelated methods with these names may also be flagged. * [`unused-error`](/forge/linting/unused-error) — Reports custom error declarations that are never used by a revert, `require`, or selector reference anywhere in the compiled sources. * [`unused-import`](/forge/linting/unused-import) — Reports `import "..."`, `import "..." as X`, and `import { A, B } from "..."` statements where one or more imported names are never used. This includes unused namespace imports (`import * as X`). #### Gas optimization * [`asm-keccak256`](/forge/linting/asm-keccak256) — Reports direct `keccak256(...)` calls in statements and initializers for gas review. * [`cache-array-length`](/forge/linting/cache-array-length) — Reports comparison expressions in `for` loop conditions when either side reads `.length` from a state dynamic array, such as `i < values.length` or `values.length > i`, including comparisons nested inside `&&` / `||` conditions. * [`costly-loop`](/forge/linting/costly-loop) — Reports assignments, compound assignments, increments/decrements, and `delete` expressions that directly write to a storage variable inside any `for`, `while`, or `do-while` loop body, including writes through storage array indices and mapping keys. * [`could-be-constant`](/forge/linting/could-be-constant) — Reports non-`constant`, non-`immutable` state variables with a compile-time-constant initializer and no later assignments, when their type permits `constant`. * [`could-be-immutable`](/forge/linting/could-be-immutable) — Reports each non-`constant`, non-`immutable` state variable whose only writes occur in the constructor (or in initialization at declaration time). * [`custom-errors`](/forge/linting/custom-errors) — Reports `require` calls with no reason or whose second argument is a string literal, and `revert(...)` calls that are either bare or have a string-literal argument. * [`external-function`](/forge/linting/external-function) — Flags implemented `public` functions with reference-type `memory` parameters that are never called internally and do not modify their parameters. Overrides are excluded. * [`unused-state-variables`](/forge/linting/unused-state-variables) — Reports each state variable that has no read or write site across the project. * [`var-read-using-this`](/forge/linting/var-read-using-this) — Reports calls through `this` to the contract's own public variable getters and `view` or `pure` functions, including inherited functions. * [`write-after-write`](/forge/linting/write-after-write) — Reports assignments to state variables whose values are overwritten before being read. Compound assignments and writes to individual mapping entries, array elements, or struct fields are excluded. #### Code size * [`unwrapped-modifier-logic`](/forge/linting/unwrapped-modifier-logic) — Reports modifiers containing logic beyond a placeholder, simple `require` or `assert` checks, or a single library call. Assembly blocks are excluded from suggested extraction. ## Cast Cast performs Ethereum RPC calls, sends transactions, and decodes data from the command line. It's the Swiss Army knife for blockchain interaction. Cast works both inside and outside a Foundry project. ### Key capabilities | Feature | Description | |---------|-------------| | **Chain data** | Read blocks, transactions, logs, and account state | | **Transactions** | Send transactions and interact with contracts | | **Wallets** | Manage keys, sign messages, and verify signatures | | **ABI encoding** | Encode/decode calldata, events, and function signatures | | **Utilities** | Convert units, compute hashes, and manipulate data | ### Common workflows ```bash [Get the latest block number] $ cast block-number ``` ```bash [Read a contract's storage] $ cast call $CONTRACT "balanceOf(address)" $ADDRESS ``` ```bash [Send a transaction] $ cast send $CONTRACT "transfer(address,uint256)" $TO $AMOUNT --private-key $KEY ``` ```bash [Decode calldata] $ cast 4byte-calldata 0xa9059cbb000000000000000000000000... ``` ```bash [Get an account's ETH balance] $ cast balance $ADDRESS ``` ### Learn more * [Reading chain data](/cast/reading-chain-data) — Query blocks, transactions, and state * [Sending transactions](/cast/sending-transactions) — Execute on-chain operations * [Tokenized vaults](/cast/tokenized-vaults) — Inspect and transact with synchronous ERC-4626 vaults * [Wallet operations](/cast/wallet-operations) — Key management and signing * [EIP-7702 delegation](/cast/eip-7702-delegation) — Delegate EOA execution with signed authorizations * [ABI encoding](/cast/abi-encoding) — Encode and decode contract data * [Reference](/reference/cast/cast) — Full CLI reference ## Reading chain data Cast queries any Ethereum-compatible chain via JSON-RPC. Set your RPC endpoint with `--rpc-url` or the `ETH_RPC_URL` environment variable. ```bash $ export ETH_RPC_URL=https://ethereum.reth.rs/rpc ``` ### Blocks :::code-group ```bash [Block number] $ cast block-number ``` ```bash [Block details] $ cast block latest ``` ```bash [Specific block] $ cast block 18000000 ``` ```bash [Block field] $ cast block latest --field timestamp ``` ```bash [Base fee] $ cast base-fee ``` ::: ### Transactions :::code-group ```bash [Transaction details] $ cast tx $TX_HASH ``` ```bash [Receipt] $ cast receipt $TX_HASH ``` ```bash [Status] $ cast receipt $TX_HASH status ``` ```bash [Gas used] $ cast receipt $TX_HASH gasUsed ``` ::: ### Account state :::code-group ```bash [ETH balance] $ cast balance $ADDRESS ``` ```bash [Balance in ether] $ cast balance $ADDRESS --ether ``` ```bash [Nonce] $ cast nonce $ADDRESS ``` ```bash [Bytecode] $ cast code $ADDRESS ``` ```bash [Storage slot] $ cast storage $ADDRESS 0 ``` ::: ### Contract calls Call view functions without sending a transaction: :::code-group ```bash [Call function] $ cast call $CONTRACT "balanceOf(address)(uint256)" $ADDRESS ``` ```bash [Named output] $ cast call $CONTRACT "name()(string)" ``` ```bash [At block] $ cast call $CONTRACT "totalSupply()(uint256)" --block 18000000 ``` ::: ### Simulating state with call overrides `cast call` can temporarily replace account data for an `eth_call`. Overrides only affect the simulation; they do not change on-chain state. | Flag | Effect | | --- | --- | | `--override-balance ` | Replaces an account's native balance. | | `--override-nonce ` | Replaces an account's nonce. | | `--override-code ` | Replaces an account's bytecode. | | `--override-state ` | Replaces all storage for an account with the supplied slots. | | `--override-state-diff ` | Replaces the supplied storage slots while preserving the account's other storage. | Each flag accepts a comma-separated list of entries. For example, you can simulate a contract call after changing one storage slot: ```bash $ cast call $CONTRACT "balanceOf(address)(uint256)" $ADDRESS \ --override-state-diff $CONTRACT:$BALANCE_SLOT:$VALUE ``` Use `--override-state-diff` when you only want to change specific slots. `--override-state` replaces the account's entire storage, so omitted slots read as zero during the call. ### Simulating block context Use `--block` to select the chain state on which to base the call, then use `--block.*` options to change the block values visible to the simulated EVM: | Flag | EVM block value | | --- | --- | | `--block.number ` | Block number. | | `--block.time