Skip to content
Back to skills

Foundry

ASecurity

Foundry is a command-line toolkit for Ethereum smart-contract development: forge compiles, tests, fuzzes and deploys Solidity contracts, cast calls contracts and sends transactions, anvil runs a local node, and chisel is a Solidity REPL. Use when a user asks to set up a Foundry project, write Solidity unit or fuzz tests, fork mainnet in a test, deploy with forge script, verify a contract on Etherscan, track gas with forge snapshot, or read and write chain state with cast.

  • 142 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 29, 2026
developmentrustgoshellbashnodedockertestingdebugginggitapi

Works with

  • terminal
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill foundry --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Foundry?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Foundry
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-foundry/badge)](https://www.skillsdirectory.com/skills/terminalskills-foundry)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: foundry
description: >-
  Foundry is a command-line toolkit for Ethereum smart-contract development:
  forge compiles, tests, fuzzes and deploys Solidity contracts, cast calls
  contracts and sends transactions, anvil runs a local node, and chisel is a
  Solidity REPL. Use when a user asks to set up a Foundry project, write
  Solidity unit or fuzz tests, fork mainnet in a test, deploy with forge
  script, verify a contract on Etherscan, track gas with forge snapshot, or
  read and write chain state with cast.
license: Apache-2.0
compatibility: 'Linux, macOS, or Windows via WSL/Git Bash; git for dependencies. Checked against Foundry v1.8.3.'
metadata:
  author: terminal-skills
  version: 1.1.0
  category: development
  repository: https://github.com/foundry-rs/foundry
  tags:
    - solidity
    - ethereum
    - smart-contracts
    - testing
    - forge
---

# Foundry — Blazing Fast Ethereum Development Toolkit

## Overview

Foundry is a Rust toolkit for Ethereum development made of four binaries: `forge` (build, test, fuzz, script, verify), `cast` (RPC calls, transactions, ABI utilities, keystores), `anvil` (local node, mainnet forking) and `chisel` (Solidity REPL). Tests and deployment scripts are written in Solidity, dependencies are git submodules under `lib/`, and configuration lives in `foundry.toml`.

## Instructions

### Installation

```bash
# macOS / Linux with Homebrew
brew install foundry

# Or a release archive, verified against the published SHA-256 before extracting
FOUNDRY_VERSION=v1.8.3
ARCHIVE="foundry_${FOUNDRY_VERSION}_linux_amd64.tar.gz"   # also linux_arm64, darwin_arm64, darwin_amd64, alpine_amd64
curl -fsSLO "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/${ARCHIVE}"
curl -fsSLO "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/${ARCHIVE%.tar.gz}.sha256"
sha256sum -c "${ARCHIVE%.tar.gz}.sha256" \
  && mkdir -p "$HOME/.local/bin" \
  && tar -xzf "$ARCHIVE" -C "$HOME/.local/bin"            # forge, cast, anvil, chisel, solar
export PATH="$HOME/.local/bin:$PATH"                      # if it is not on PATH already
forge --version                                           # forge Version: 1.8.3

# Or Docker / a source build
docker pull ghcr.io/foundry-rs/foundry:v1.8.3
cargo install --git https://github.com/foundry-rs/foundry --profile release --locked forge cast chisel anvil solar
```

On macOS use `shasum -a 256 -c` in place of `sha256sum -c`. The project's own installer and version manager is `foundryup` (see https://getfoundry.sh/introduction/installation); it compares each binary's hash with the release attestation. Foundry no longer publishes npm packages. In GitHub Actions use `foundry-rs/foundry-toolchain@v1`.

### Project setup

```bash
forge init vault-demo && cd vault-demo
# src/ contracts · test/ *.t.sol · script/ *.s.sol · lib/ dependencies · foundry.toml
forge install OpenZeppelin/openzeppelin-contracts@v5.7.0   # pinned tag, recorded in foundry.lock
forge remappings   # includes @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ and forge-std/=lib/forge-std/src/
```

`forge install` adds a git submodule and does not commit unless `--commit` is passed. Commit `foundry.lock`. `forge update` moves only branch-based dependencies; a tag is changed explicitly, e.g. `forge update openzeppelin/openzeppelin-contracts@tag=v5.7.0`.

```toml
# foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
[profile.ci]                 # FOUNDRY_PROFILE=ci forge test
fuzz = { runs = 10000 }
```

### Smart contract

```solidity
// src/Vault.sol — ERC-4626 vault with an owner-controlled rate
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract Vault is ERC4626, Ownable {
    error RateTooHigh(uint256 rate);

    uint256 public totalDeposited;
    uint256 public yieldRate; // basis points per year, 500 = 5%

    constructor(IERC20 asset_, uint256 yieldRate_, address owner_)
        ERC4626(asset_)
        ERC20("Vault Share", "vSHARE")
        Ownable(owner_)
    {
        yieldRate = yieldRate_;
    }

    function deposit(uint256 assets, address receiver) public override returns (uint256 shares) {
        shares = super.deposit(assets, receiver);
        totalDeposited += assets;
    }

    function setYieldRate(uint256 newRate) external onlyOwner {
        if (newRate > 2000) revert RateTooHigh(newRate); // max 20%
        yieldRate = newRate;
    }
}
```

### Testing in Solidity

Test contracts inherit `Test` from forge-std; `setUp()` runs before every test. A test function with parameters is a fuzz test (256 runs by default).

```solidity
// test/Vault.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Vault} from "../src/Vault.sol";

contract MockUSDC is ERC20("USD Coin", "USDC") {
    function decimals() public pure override returns (uint8) {
        return 6;
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

contract VaultTest is Test {
    Vault vault;
    MockUSDC token;
    address alice = makeAddr("alice");

    function setUp() public {
        token = new MockUSDC();
        vault = new Vault(token, 500, address(this));
        token.mint(alice, 10_000e6);
    }

    function test_Deposit() public {
        vm.startPrank(alice); // every call until stopPrank comes from alice
        token.approve(address(vault), 1_000e6);
        uint256 shares = vault.deposit(1_000e6, alice);
        vm.stopPrank();
        assertEq(vault.balanceOf(alice), shares);
        assertEq(vault.totalDeposited(), 1_000e6);
    }

    function test_RevertWhen_CallerIsNotOwner() public {
        vm.prank(alice); // next call only
        vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
        vault.setYieldRate(1000);
    }

    function testFuzz_Deposit(uint256 amount) public {
        amount = bound(amount, 1, 10_000e6); // prefer bound() over vm.assume()
        vm.startPrank(alice);
        token.approve(address(vault), amount);
        vault.deposit(amount, alice);
        vm.stopPrank();
        assertEq(vault.totalDeposited(), amount);
    }

    // Fork test: "mainnet" is the alias from [rpc_endpoints]
    function test_ForkMainnetUsdc() public {
        vm.createSelectFork("mainnet");
        IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
        assertGt(usdc.totalSupply(), 0);
    }
}
```

```bash
forge test                                   # all tests
forge test --match-test testFuzz_ -vvv       # filter by name; -vvv prints traces for failures
forge test --no-match-test Fork              # skip tests that need an RPC endpoint
forge test --gas-report                      # per-function gas table
forge snapshot && forge snapshot --check     # write .gas-snapshot, then fail on regressions
forge fmt --check && forge lint              # formatter and built-in linter
```

### Deployment script

```solidity
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Vault} from "../src/Vault.sol";

contract DeployScript is Script {
    function run() public {
        address asset = vm.envAddress("ASSET_ADDRESS");
        address owner = vm.envAddress("OWNER_ADDRESS");
        vm.startBroadcast(); // signer comes from --account, --ledger or --private-key
        Vault vault = new Vault(IERC20(asset), 500, owner);
        vm.stopBroadcast();
        console.log("Vault deployed at:", address(vault));
    }
}
```

```bash
cast wallet import deployer --interactive    # encrypted keystore in ~/.foundry/keystores
forge script script/Deploy.s.sol --rpc-url sepolia                       # simulate only
forge script script/Deploy.s.sol --rpc-url sepolia --account deployer \
  --broadcast --verify --etherscan-api-key "$ETHERSCAN_API_KEY"
# A broadcast interrupted halfway continues with the same command plus --resume
```

Without `--broadcast` nothing is sent. Receipts are written to `broadcast/Deploy.s.sol/11155111/run-latest.json` (the directory is the chain id).

## Examples

### Example 1: Run the test suite and read the result

**User request:** "Run the vault tests, including the mainnet fork test, and show me the result."

```bash
export MAINNET_RPC_URL="https://ethereum.reth.rs/rpc"
forge test --match-contract VaultTest        # plain `forge test` also runs the Counter suite that `forge init` generates
```

```text
Ran 4 tests for test/Vault.t.sol:VaultTest
[PASS] testFuzz_Deposit(uint256) (runs: 256, μ: 200059, ~: 199478)
[PASS] test_Deposit() (gas: 207745)
[PASS] test_ForkMainnetUsdc() (gas: 16165)
[PASS] test_RevertWhen_CallerIsNotOwner() (gas: 36107)
Suite result: ok. 4 passed; 0 failed; 0 skipped
```

With `MAINNET_RPC_URL` unset the fork test fails with "vm.createSelectFork: environment variable `MAINNET_RPC_URL` not found"; `forge test --rerun` then retries only the failed tests.

### Example 2: Deploy to a local Anvil node and interact with cast

**User request:** "Deploy the vault locally and change the yield rate to 8%."

```bash
anvil                                        # terminal 1: chain id 31337 on http://127.0.0.1:8545, 10 funded accounts

# terminal 2 — account (0) printed by anvil; this key is public, never fund it on a real network
export RPC_URL=http://127.0.0.1:8545
export ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

forge create test/Vault.t.sol:MockUSDC --rpc-url $RPC_URL --private-key $ANVIL_KEY --broadcast
# Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
export ASSET_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3

forge script script/Deploy.s.sol --rpc-url $RPC_URL --private-key $ANVIL_KEY --broadcast
#   Vault deployed at: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
# ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
export VAULT=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512

cast call $VAULT "yieldRate()(uint256)" --rpc-url $RPC_URL                 # 500
cast send $VAULT "setYieldRate(uint256)" 800 --rpc-url $RPC_URL --private-key $ANVIL_KEY
# status               1 (success)
cast call $VAULT "yieldRate()(uint256)" --rpc-url $RPC_URL                 # 800
cast send $VAULT "setYieldRate(uint256)" 2500 --rpc-url $RPC_URL --private-key $ANVIL_KEY
# Error: Failed to estimate gas: ... execution reverted ... RateTooHigh(2500)
cast balance $OWNER_ADDRESS --ether --rpc-url $RPC_URL                     # 9999.99...
```

`anvil --fork-url "$MAINNET_RPC_URL" --fork-block-number 21000000` starts the same node on top of mainnet state at a fixed block.

## Guidelines

1. **Keys stay out of the shell and the repo** — on real networks sign with `--account` (keystore from `cast wallet import`) or `--ledger`. `--private-key` and `vm.envUint("PRIVATE_KEY")` are for Anvil's public test keys only; keep `.env` out of version control.
2. **Simulate before broadcasting** — run `forge script` without `--broadcast` first. `--chain` only sets `block.chainid`; the network is chosen by `--rpc-url` (a URL or an `[rpc_endpoints]` alias).
3. **`testFail*` is gone** — such tests now fail with "`testFail*` has been removed". Name them `test_RevertWhen_...` and use `vm.expectRevert()`, matching custom errors by selector.
4. **Fuzzing** — constrain inputs with `bound()`; `vm.assume()` discards inputs and slows the run. Raise `fuzz.runs` in a CI profile (`FOUNDRY_PROFILE=ci`). Stateful properties go in `invariant_*` functions.
5. **Fork tests need an RPC endpoint** — pin a block (`vm.createSelectFork("mainnet", 21_000_000)`) so results are reproducible and cached, and keep them out of the default run with `--no-match-test` when no endpoint is configured.
6. **Gas tracking** — commit `.gas-snapshot` and run `forge snapshot --check` in CI; `forge snapshot --diff` shows the change per test.
7. **Dependencies** — pin tags with `forge install owner/repo@tag`, commit `foundry.lock` and `.gitmodules`, and clone with submodules (`actions/checkout` with `submodules: recursive`). Libraries from npm work with `libs = ["lib", "node_modules"]`, but submodules are the supported path.
8. **Verify what you install** — check release archives against the `.sha256` asset before extracting; for signed build provenance the docs use `gh attestation verify` with `--repo foundry-rs/foundry`.
9. **Debugging** — `cast decode-calldata`, `cast decode-error`, `cast 4byte`, `cast tx` and `forge test -vvvv` (full traces) explain most failures. Foundry targets the EVM and Solidity/Vyper; it is not a frontend or indexing framework.

Files in this skill

  • SKILL.md6.8 KB
  • _scores.json1.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…