Foundry Smart Contract Development
Skill for professional Solidity smart contract development using Foundry framework. Provides code standards, security-first practices, and comprehensive testing patterns.
When to Use
Activate when:
- Working with
.sol(Solidity) files - Foundry project detected (
foundry.tomlpresent) - User mentions smart contracts, Solidity, Foundry, Web3
- Contract development, testing, deployment, or auditing
Quick Reference
Key Standards Summary
| Category | Key Rules |
|---|---|
| Version | pragma solidity ^0.8.23; with MIT license |
| Imports | Named imports {Contract}, grouped by source |
| Private vars | Underscore prefix: _variableName |
| Constants | SCREAMING_SNAKE_CASE: MAX_SUPPLY |
| Interfaces | Define ALL errors, events, structs in interface |
| Functions | Internal/private use _functionName prefix |
| NatSpec | @notice + @param + @return on all external functions |
| Coverage | Minimum 95% line coverage required |
Section Headers
Use decorative ASCII headers for contract organization:
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SECTION NAME */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Section order: LIBRARIES → CUSTOM ERRORS → EVENTS → STORAGE → CONSTANTS → MODIFIERS → CONSTRUCTOR → PUBLIC UPDATE FUNCTIONS → ADMIN FUNCTIONS → PUBLIC READ FUNCTIONS → INTERNAL FUNCTIONS → PRIVATE FUNCTIONS
Project Initialization
Create New Project
# Initialize new Foundry project
forge init my-project
cd my-project
# Initialize in existing directory
forge init --force
# Initialize with specific template
forge init --template transmissions11/solmate my-project
Install Dependencies
# Install OpenZeppelin contracts
forge install OpenZeppelin/openzeppelin-contracts
# Install Solady (gas-optimized utilities)
forge install Vectorized/solady
# Install specific version
forge install OpenZeppelin/openzeppelin-contracts@v5.0.0
# Update dependencies
forge update
Recommended Contract Libraries
| Library | Install Command | Use For |
|---|---|---|
| OpenZeppelin | forge install OpenZeppelin/openzeppelin-contracts |
ERC20/721/1155, Access Control, Proxies, Security |
| Solady | forge install Vectorized/solady |
Gas-optimized ERC20/721, Utilities, Math |
| Solmate | forge install transmissions11/solmate |
Minimal, gas-efficient implementations |
| PRBMath | forge install PaulRBerg/prb-math |
Fixed-point math (UD60x18, SD59x18) |
| forge-std | (included by default) | Testing utilities, console.log, vm cheats |
OpenZeppelin
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
Solady
import {ERC721} from "solady/tokens/ERC721.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {LibString} from "solady/utils/LibString.sol";
import {LibPRNG} from "solady/utils/LibPRNG.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
Configure foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.23"
optimizer = true
optimizer_runs = 200
# Remappings
remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/",
"solady/=lib/solady/src/",
]
# Formatter
[fmt]
line_length = 120
tab_width = 4
bracket_spacing = true
# Testing
[fuzz]
runs = 256
[invariant]
runs = 256
depth = 15
Configure remappings.txt
Foundry supports two ways to configure import remappings:
Option 1: In foundry.toml (shown above)
- Remappings are part of project config
- Single source of truth
Option 2: remappings.txt file (recommended for IDE compatibility)
- Better compatibility with VS Code Solidity extension, Hardhat, etc.
- Can be auto-generated from dependencies
# Auto-generate remappings.txt from installed dependencies
forge remappings > remappings.txt
remappings.txt format:
@openzeppelin/=lib/openzeppelin-contracts/
solady/=lib/solady/src/
forge-std/=lib/forge-std/src/
Best practice:
- Use
remappings.txtfor IDE compatibility - Run
forge remappings > remappings.txtafter installing new dependencies - Add
remappings.txtto version control
Project Structure
my-project/
├── foundry.toml # Configuration
├── remappings.txt # Import remappings (for IDE)
├── src/ # Contract source files
│ ├── interfaces/ # Interface definitions
│ └── libraries/ # Shared libraries
├── test/ # Test files (.t.sol)
│ ├── unit/
│ ├── integration/
│ └── security/
├── script/ # Deployment scripts (.s.sol)
└── lib/ # Dependencies (git submodules)
Development Workflow
1. Write Contract
Follow security-first patterns:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IMyContract} from "./interfaces/IMyContract.sol";
/// @title MyContract
/// @notice Brief description
/// @author Development Team
contract MyContract is IMyContract, Ownable, ReentrancyGuard {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
mapping(address => uint256) private _balances;
uint256 private _nextTokenId;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
constructor() Ownable(msg.sender) {
_nextTokenId = 1;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function deposit() external payable nonReentrant {
// CEI Pattern
if (msg.value == 0) revert InvalidAmount();
_balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
}
}
2. Write Tests
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {Test} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";
contract MyContractTest is Test {
MyContract public myContract;
address constant ALICE = address(0x1);
function setUp() public {
myContract = new MyContract();
}
function test_Deposit_Success() public {
vm.deal(ALICE, 1 ether);
vm.prank(ALICE);
myContract.deposit{value: 0.1 ether}();
// assertions
}
function test_Deposit_RevertWhen_ZeroAmount() public {
vm.prank(ALICE);
vm.expectRevert(IMyContract.InvalidAmount.selector);
myContract.deposit{value: 0}();
}
function testFuzz_Deposit(uint256 amount) public {
amount = bound(amount, 1, 100 ether);
vm.deal(ALICE, amount);
vm.prank(ALICE);
myContract.deposit{value: amount}();
}
}
3. Run Tests & Coverage
forge test -vvv # Run all tests verbose
forge test --gas-report # Include gas costs
forge coverage # Coverage report (target ≥95%)
forge snapshot # Gas snapshot
4. Security Analysis
# Static analysis
slither . --exclude-optimization --exclude-informational
# Linting
solhint 'src/**/*.sol' 'test/**/*.sol'
5. Build & Deploy
# Build with optimization
forge build --optimize --optimizer-runs 200
# Simulate deployment
forge script script/Deploy.s.sol --rpc-url $RPC_URL
# Deploy to testnet
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify
# Verify contract
forge verify-contract $ADDRESS src/MyContract.sol:MyContract --chain-id 1
Reference Files
Consult these for detailed standards:
| File | Use When |
|---|---|
| code-style.md | SPDX, pragma, imports, formatting |
| naming-conventions.md | Naming contracts, variables, functions, events |
| contract-structure.md | Organizing sections, interface architecture |
| documentation-standards.md | NatSpec comments for contracts, functions, events, errors |
| security-guidelines.md | Access control, CEI, reentrancy, hooks |
| testing-standards.md | Unit, integration, fuzz, gas tests |
| gas-optimization.md | Storage packing, loops, unchecked |
Interface Architecture (Critical)
MUST define errors, events, and structs in interface:
// Interface - defines everything
interface IMyContract {
error InvalidAmount();
error Unauthorized();
event Deposited(address indexed user, uint256 amount);
struct UserInfo { uint256 balance; uint64 lastUpdate; }
function deposit() external payable;
}
// Contract - inherits and uses
contract MyContract is IMyContract {
// DO NOT re-define errors, events, or structs here
// Use inherited ones directly
function deposit() external payable {
if (msg.value == 0) revert InvalidAmount(); // ✓ Uses interface error
emit Deposited(msg.sender, msg.value); // ✓ Uses interface event
}
}
Common Commands
# Project Setup
forge init my-project # New project
forge init --force # Init in existing dir
forge install org/repo # Install dependency
forge update # Update dependencies
# Development
forge build # Compile
forge test # Test
forge fmt # Format
forge clean # Clean
# Testing
forge test -vvv # Verbose
forge test --match-test NAME # Specific test
forge test --fork-url $RPC # Fork testing
forge coverage # Coverage
# Deployment
forge script script/Deploy.s.sol --broadcast
forge verify-contract $ADDR src/Contract.sol:Contract
# Local node
anvil # Start local node
anvil --fork-url $RPC # Fork mainnet
Pre-deployment Checklist
- All errors, events, structs defined in interfaces
- Reentrancy protection on external call functions
- Access controls on privileged functions
- Input validation on all external functions
- CEI pattern followed for state changes
- Events emitted for all state changes
- Test coverage ≥95%
- Slither analysis passed
- solhint checks passed
- Gas usage within acceptable limits
- Professional audit for mainnet