install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/foundry" ~/.claude/skills/comeonoliver-skillshub-foundry && rm -rf "$T"
manifest:
skills/TerminalSkills/skills/foundry/SKILL.mdsource content
Foundry — Blazing Fast Ethereum Development Toolkit
You are an expert in Foundry, the blazing-fast Ethereum development toolkit written in Rust. You help developers write, test, deploy, and debug Solidity smart contracts using Forge (testing), Cast (CLI interactions), Anvil (local node), and Chisel (Solidity REPL) — with native Solidity testing (no JavaScript), fuzz testing, gas optimization, and fork testing against mainnet state.
Core Capabilities
Project Setup
# Create new project forge init my-project cd my-project # Structure: # src/ — Solidity contracts # test/ — Solidity tests # script/ — Deployment scripts # lib/ — Dependencies (git submodules) # foundry.toml — Configuration # Install dependencies forge install OpenZeppelin/openzeppelin-contracts forge install transmissions11/solmate
Smart Contract
// src/Vault.sol — ERC-4626 yield vault // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract Vault is ERC4626, Ownable { uint256 public totalDeposited; uint256 public yieldRate; // Basis points per year (e.g., 500 = 5%) mapping(address => uint256) public depositTimestamp; constructor(IERC20 asset_, uint256 yieldRate_) ERC4626(asset_) ERC20("Vault Share", "vSHARE") Ownable(msg.sender) { yieldRate = yieldRate_; } function deposit(uint256 assets, address receiver) public override returns (uint256 shares) { shares = super.deposit(assets, receiver); totalDeposited += assets; depositTimestamp[receiver] = block.timestamp; return shares; } function setYieldRate(uint256 newRate) external onlyOwner { require(newRate <= 2000, "Rate too high"); // Max 20% yieldRate = newRate; } }
Testing in Solidity
// test/Vault.t.sol — Native Solidity tests // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test, console} from "forge-std/Test.sol"; import {Vault} from "../src/Vault.sol"; import {MockERC20} from "./mocks/MockERC20.sol"; contract VaultTest is Test { Vault vault; MockERC20 token; address alice = makeAddr("alice"); address bob = makeAddr("bob"); function setUp() public { token = new MockERC20("USDC", "USDC", 6); vault = new Vault(token, 500); // 5% yield // Fund test accounts token.mint(alice, 10_000e6); token.mint(bob, 5_000e6); } function test_Deposit() public { vm.startPrank(alice); // Impersonate 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); assertEq(token.balanceOf(address(vault)), 1_000e6); } function test_OnlyOwnerCanSetRate() public { vm.prank(alice); // Not owner vm.expectRevert(); vault.setYieldRate(1000); } function test_RateCannotExceed20Percent() public { vm.expectRevert("Rate too high"); vault.setYieldRate(2001); } // Fuzz testing: Foundry generates random inputs function testFuzz_Deposit(uint256 amount) public { amount = bound(amount, 1, 10_000e6); // Constrain range vm.startPrank(alice); token.approve(address(vault), amount); vault.deposit(amount, alice); vm.stopPrank(); assertEq(vault.totalDeposited(), amount); } // Fork testing: test against mainnet state function test_ForkMainnet() public { vm.createSelectFork("mainnet"); // Requires RPC URL in foundry.toml // Now interacting with real mainnet contracts IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); assertGt(usdc.totalSupply(), 0); } }
Deployment Scripts
// script/Deploy.s.sol pragma solidity ^0.8.20; import {Script} from "forge-std/Script.sol"; import {Vault} from "../src/Vault.sol"; contract DeployScript is Script { function run() external { uint256 deployerKey = vm.envUint("PRIVATE_KEY"); address usdc = vm.envAddress("USDC_ADDRESS"); vm.startBroadcast(deployerKey); Vault vault = new Vault(IERC20(usdc), 500); vm.stopBroadcast(); console.log("Vault deployed at:", address(vault)); } }
# Deploy forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify # Cast: interact with contracts from CLI cast call $VAULT "totalDeposited()" --rpc-url $RPC_URL cast send $VAULT "setYieldRate(uint256)" 800 --private-key $KEY --rpc-url $RPC_URL cast balance $ADDRESS --rpc-url $RPC_URL # Anvil: local node anvil # Starts at localhost:8545 anvil --fork-url $MAINNET_RPC # Fork mainnet locally
Installation
curl -L https://foundry.paradigm.xyz | bash foundryup # Install/update forge, cast, anvil, chisel
Best Practices
- Test in Solidity — Write tests in Solidity, not JavaScript; faster execution, better type safety, same language as contracts
- Fuzz testing — Use
prefix; Foundry generates 256 random inputs by default, catches edge casestestFuzz_ - Fork testing — Test against real mainnet state with
; verify integrations with live contractsvm.createSelectFork - Gas snapshots — Run
to track gas usage; commitforge snapshot
to detect regressions.gas-snapshot - Cheatcodes —
,vm.prank()
,vm.warp()
,vm.roll()
for comprehensive testingvm.expectRevert() - Invariant testing — Define invariants that must always hold; Foundry tries to break them with random sequences
- Deployment scripts — Use Forge scripts instead of raw transactions; reproducible, verified deployments
- Cast for debugging —
,cast calldata-decode
,cast abi-encode
for on-chain debuggingcast tx