Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:01:14 +08:00
commit e17e955d35
19 changed files with 6218 additions and 0 deletions

View File

@@ -0,0 +1,305 @@
# Anvil Advanced Usage
Advanced Anvil features for local development and testing.
## Account Impersonation
### Auto-Impersonate All Accounts
```bash
# Start with auto-impersonation
anvil --auto-impersonate
```
Now any account can send transactions without private key:
```bash
cast send $CONTRACT "transfer(address,uint256)" $TO 1000 \
--from 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
--unlocked
```
### Impersonate Specific Account
```bash
# Via RPC
cast rpc anvil_impersonateAccount 0x1234...
# Stop impersonating
cast rpc anvil_stopImpersonatingAccount 0x1234...
```
### In Foundry Tests
```solidity
address whale = 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503;
vm.startPrank(whale);
usdc.transfer(address(this), 1_000_000e6);
vm.stopPrank();
```
## State Manipulation
### Set Balance
```bash
# Set ETH balance
cast rpc anvil_setBalance 0x1234... 0xDE0B6B3A7640000 # 1 ETH in hex
# In tests
vm.deal(address, 100 ether);
```
### Set Code
```bash
# Deploy code at address
cast rpc anvil_setCode 0x1234... 0x608060405234801...
# In tests
vm.etch(address, code);
```
### Set Storage
```bash
# Set storage slot
cast rpc anvil_setStorageAt 0x1234... 0x0 0x...
# In tests
vm.store(address, slot, value);
```
### Set Nonce
```bash
cast rpc anvil_setNonce 0x1234... 0x10 # 16 in hex
```
## Mining Modes
### Auto-Mining (Default)
```bash
anvil # Mines block on each transaction
```
### Interval Mining
```bash
# Mine every 12 seconds
anvil --block-time 12
```
### Manual Mining
```bash
# Disable auto-mining
anvil --no-mining
# Mine manually
cast rpc evm_mine
# Mine multiple blocks
cast rpc anvil_mine 10 # Mine 10 blocks
```
### Mining Control
```bash
# Enable auto-mine
cast rpc evm_setAutomine true
# Set interval
cast rpc evm_setIntervalMining 5000 # 5 seconds in ms
```
## State Snapshots
### Dump State
```bash
# Start anvil, make changes, then dump
anvil --dump-state state.json
# Load from previous state
anvil --load-state state.json
```
### Runtime Snapshots
```bash
# Create snapshot
SNAPSHOT_ID=$(cast rpc evm_snapshot)
# Make changes...
# Revert to snapshot
cast rpc evm_revert $SNAPSHOT_ID
```
### In Tests
```solidity
uint256 snapshot = vm.snapshot();
// Make changes...
vm.revertTo(snapshot);
```
## Fork Configuration
### Basic Fork
```bash
anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/KEY
```
### Pin Block
```bash
anvil --fork-url $RPC_URL --fork-block-number 18000000
```
### Fork with Caching
```bash
# Cache fork data locally
anvil --fork-url $RPC_URL --fork-retry-backoff 1000
# Disable caching
anvil --fork-url $RPC_URL --no-storage-caching
```
### Multiple Forks
```solidity
// In tests
uint256 mainnetFork = vm.createFork("mainnet");
uint256 arbitrumFork = vm.createFork("arbitrum");
vm.selectFork(mainnetFork);
// Test on mainnet...
vm.selectFork(arbitrumFork);
// Test on arbitrum...
```
## Time Manipulation
```bash
# Set timestamp
cast rpc evm_setNextBlockTimestamp 1700000000
# Increase time
cast rpc evm_increaseTime 86400 # 1 day
# In tests
vm.warp(block.timestamp + 1 days);
vm.roll(block.number + 100);
```
## RPC Methods
### Common Anvil RPC
| Method | Description |
|--------|-------------|
| `anvil_setBalance` | Set ETH balance |
| `anvil_setCode` | Set contract code |
| `anvil_setStorageAt` | Set storage slot |
| `anvil_setNonce` | Set account nonce |
| `anvil_impersonateAccount` | Impersonate address |
| `anvil_mine` | Mine blocks |
| `anvil_reset` | Reset fork |
| `anvil_dumpState` | Export state |
| `anvil_loadState` | Import state |
### EVM Methods
| Method | Description |
|--------|-------------|
| `evm_snapshot` | Create snapshot |
| `evm_revert` | Revert to snapshot |
| `evm_mine` | Mine single block |
| `evm_setAutomine` | Toggle auto-mining |
| `evm_increaseTime` | Advance time |
| `evm_setNextBlockTimestamp` | Set next timestamp |
## Configuration
### Startup Options
```bash
anvil \
--port 8545 \
--accounts 10 \
--balance 10000 \
--mnemonic "test test test..." \
--derivation-path "m/44'/60'/0'/0/" \
--block-time 12 \
--gas-limit 30000000 \
--gas-price 0 \
--chain-id 31337 \
--hardfork prague
```
### Hardfork Selection
```bash
anvil --hardfork shanghai
anvil --hardfork cancun
anvil --hardfork prague # Latest
```
## Testing Patterns
### Fork Test Setup
```solidity
function setUp() public {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 18000000);
// Impersonate whale
address whale = 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503;
vm.startPrank(whale);
usdc.transfer(address(this), 1_000_000e6);
vm.stopPrank();
}
```
### State Reset Between Tests
```solidity
uint256 snapshot;
function setUp() public {
if (snapshot == 0) {
// First run: setup and snapshot
_deployContracts();
snapshot = vm.snapshot();
} else {
// Subsequent runs: revert to clean state
vm.revertTo(snapshot);
}
}
```
### Testing Mainnet Interactions
```solidity
function test_SwapOnUniswap() public {
vm.createSelectFork("mainnet");
address router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
deal(address(this), 10 ether);
IRouter(router).swapExactETHForTokens{value: 1 ether}(
0, path, address(this), block.timestamp
);
}
```

View File

@@ -0,0 +1,259 @@
# Cast Advanced Usage
Advanced cast commands for blockchain interaction, decoding, and analysis.
## Transaction Decoding
### Decode Transaction
```bash
# Decode transaction by hash
cast decode-tx 0x1234... --rpc-url mainnet
# Output includes:
# - from, to, value
# - function selector
# - decoded calldata
# - gas used
```
### 4byte Signature Lookup
```bash
# Get function name from selector
cast 4byte 0xa9059cbb
# transfer(address,uint256)
# Get selector from signature
cast sig "transfer(address,uint256)"
# 0xa9059cbb
# Decode calldata with known selector
cast 4byte-decode 0xa9059cbb000000000000000000000000...
```
### Decode Calldata
```bash
# Decode with ABI
cast calldata-decode "transfer(address,uint256)" 0xa9059cbb...
# Output:
# 0x1234... [address]
# 1000000 [uint256]
```
## ABI Encoding/Decoding
### Encode Function Call
```bash
# Encode calldata
cast calldata "transfer(address,uint256)" 0x1234... 1000000
# 0xa9059cbb000000000000000000000000...
# Encode with complex types
cast calldata "swap((address,uint256,bytes))" "(0x1234...,100,0x)"
```
### Encode Arguments
```bash
# ABI encode
cast abi-encode "constructor(string,uint256)" "Token" 1000000
# ABI encode packed
cast abi-encode --packed "test(string)" "hello"
```
### Decode ABI Data
```bash
# Decode return data
cast abi-decode "balanceOf(address)(uint256)" 0x00000000...
# 1000000
# Decode with multiple returns
cast abi-decode "getReserves()(uint112,uint112,uint32)" 0x...
```
## Wallet Management
### Create Wallet
```bash
# Generate new wallet
cast wallet new
# Generate with mnemonic
cast wallet new-mnemonic
# Derive from mnemonic
cast wallet derive-private-key "word1 word2 ... word12"
```
### Wallet Info
```bash
# Get address from private key
cast wallet address --private-key 0x...
# Get address from mnemonic
cast wallet address --mnemonic "word1 word2..."
# Sign message
cast wallet sign "message" --private-key 0x...
```
### Keystore
```bash
# Import to keystore
cast wallet import my-wallet --private-key 0x...
# List keystores
cast wallet list
# Use keystore
cast send ... --account my-wallet
```
## Contract Interaction
### Read Functions
```bash
# Call view function
cast call $CONTRACT "balanceOf(address)" $USER --rpc-url mainnet
# With block number
cast call $CONTRACT "balanceOf(address)" $USER --block 18000000
# Decode result
cast call $CONTRACT "decimals()" | cast to-dec
```
### Write Functions
```bash
# Send transaction
cast send $CONTRACT "transfer(address,uint256)" $TO $AMOUNT \
--private-key $KEY \
--rpc-url mainnet
# With value
cast send $CONTRACT "deposit()" --value 1ether --private-key $KEY
# Estimate gas
cast estimate $CONTRACT "transfer(address,uint256)" $TO $AMOUNT
```
## Storage Inspection
```bash
# Read storage slot
cast storage $CONTRACT 0 --rpc-url mainnet
# Read multiple slots
for i in {0..10}; do
echo "Slot $i: $(cast storage $CONTRACT $i)"
done
# Find storage slot for mapping
cast index address $KEY 0 # mapping at slot 0
```
## Block & Transaction Info
```bash
# Get block
cast block latest --rpc-url mainnet
cast block 18000000 --field timestamp
# Get transaction
cast tx 0x1234... --rpc-url mainnet
# Get receipt
cast receipt 0x1234... --rpc-url mainnet
# Get logs
cast logs --from-block 18000000 --to-block 18000100 \
--address $CONTRACT \
--topic0 0xddf252ad... # Transfer event
```
## Type Conversions
```bash
# Hex to decimal
cast to-dec 0x64
# 100
# Decimal to hex
cast to-hex 100
# 0x64
# Wei conversions
cast to-wei 1 ether
# 1000000000000000000
cast from-wei 1000000000000000000
# 1.000000000000000000
# ASCII/bytes conversions
cast to-ascii 0x68656c6c6f
# hello
cast from-utf8 "hello"
# 0x68656c6c6f
```
## Address Utilities
```bash
# Checksum address
cast to-checksum-address 0x1234...
# Compute CREATE address
cast compute-address $DEPLOYER --nonce 5
# Compute CREATE2 address
cast create2 --starts-with 0x1234 --init-code 0x...
```
## ENS
```bash
# Resolve ENS name
cast resolve-name vitalik.eth --rpc-url mainnet
# Lookup address
cast lookup-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
```
## Gas
```bash
# Get gas price
cast gas-price --rpc-url mainnet
# Get base fee
cast base-fee --rpc-url mainnet
# Estimate gas
cast estimate $CONTRACT "transfer(address,uint256)" $TO $AMOUNT
```
## Batch Operations
```bash
# Multiple calls with bash
for addr in $ADDR1 $ADDR2 $ADDR3; do
echo "$addr: $(cast call $TOKEN 'balanceOf(address)' $addr | cast to-dec)"
done
# Using multicall3
cast call 0xcA11bde05977b3631167028862bE2a173976CA11 \
"aggregate((address,bytes)[])" \
"[($TOKEN,$(cast calldata 'balanceOf(address)' $ADDR1)),...]"
```

View File

@@ -0,0 +1,127 @@
# Chisel REPL
Interactive Solidity REPL for quick experimentation.
## Starting Chisel
```bash
# Basic REPL
chisel
# With fork
chisel --fork-url https://eth-mainnet.g.alchemy.com/v2/KEY
# Specific block
chisel --fork-url $RPC_URL --fork-block-number 18000000
```
## Basic Usage
```
➜ uint256 x = 42
➜ x * 2
Type: uint256
├ Hex: 0x54
├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000000054
└ Decimal: 84
➜ address alice = address(0x1234)
➜ alice.balance
Type: uint256
└ Decimal: 0
```
## Commands
| Command | Description |
|---------|-------------|
| `!help` | Show all commands |
| `!clear` | Clear session state |
| `!source` | Show generated source |
| `!rawstack` | Show raw stack output |
| `!edit` | Open in editor |
| `!export` | Export session to script |
## Session Management
```bash
# List saved sessions
chisel list
# Load session
chisel load my-session
# Save current session (in REPL)
!save my-session
# Clear cache
chisel clear-cache
```
## Working with Contracts
```
➜ interface IERC20 {
function balanceOf(address) external view returns (uint256);
}
➜ IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
➜ usdc.balanceOf(0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503)
Type: uint256
└ Decimal: 1234567890
```
## Math Testing
Quick calculations without deploying:
```
➜ uint256 a = 1000000
➜ uint256 b = 3
➜ a / b
Type: uint256
└ Decimal: 333333
➜ (a * 1e18) / b
Type: uint256
└ Decimal: 333333333333333333333333
```
## Hash Functions
```
➜ keccak256("hello")
Type: bytes32
└ 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8
➜ keccak256(abi.encode(uint256(1), address(0x1234)))
```
## ABI Encoding
```
➜ abi.encode(uint256(42), address(0x1234))
➜ abi.encodePacked("hello", "world")
➜ abi.encodeWithSelector(bytes4(0x12345678), 100)
```
## Use Cases
1. **Quick math**: Test calculations before implementing
2. **ABI encoding**: Debug encoding issues
3. **Hash verification**: Check keccak256 outputs
4. **Contract interaction**: Test calls on fork
5. **Solidity syntax**: Experiment with new features
## Configuration
Chisel inherits project settings from `foundry.toml`:
```toml
[profile.default]
solc = "0.8.30"
evm_version = "prague"
```
Run chisel from project root to use these settings.

View File

@@ -0,0 +1,289 @@
# CI/CD Integration
GitHub Actions workflows for Foundry projects.
## Basic Workflow
```yaml
# .github/workflows/test.yml
name: Test
on:
push:
branches: [main]
pull_request:
env:
FOUNDRY_PROFILE: ci
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
run: forge test -vvv
```
## With Caching
```yaml
name: Test
on: [push, pull_request]
env:
FOUNDRY_PROFILE: ci
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
lib
cache
out
key: ${{ runner.os }}-foundry-${{ hashFiles('**/foundry.toml') }}
restore-keys: |
${{ runner.os }}-foundry-
- name: Build
run: forge build
- name: Run tests
run: forge test -vvv
```
## Full Pipeline
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
env:
FOUNDRY_PROFILE: ci
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Build
run: forge build --sizes
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Run tests
run: forge test -vvv
coverage:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Generate coverage
run: forge coverage --report lcov
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: lcov.info
gas-report:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Gas snapshot
run: forge snapshot --check --tolerance 5
```
## Fork Testing in CI
```yaml
jobs:
fork-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
cache: true
- name: Run fork tests
run: forge test --match-test "testFork" -vvv
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
```
## Gas Snapshot Tracking
### Check for regressions
```yaml
- name: Gas snapshot check
run: |
forge snapshot
forge snapshot --diff .gas-snapshot
```
### Comment on PR
```yaml
- name: Compare gas
run: forge snapshot --diff .gas-snapshot > gas-diff.txt
- name: Post gas diff
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const diff = fs.readFileSync('gas-diff.txt', 'utf8');
if (diff.trim()) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Gas Changes\n```\n' + diff + '\n```'
});
}
```
## Deployment
```yaml
jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Deploy
run: |
forge script script/Deploy.s.sol \
--rpc-url ${{ secrets.RPC_URL }} \
--broadcast \
--verify
env:
PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
```
## CI Profile
Configure higher fuzz runs for CI in `foundry.toml`:
```toml
[profile.default]
fuzz.runs = 256
invariant.runs = 256
[profile.ci]
fuzz.runs = 10000
invariant.runs = 1000
verbosity = 3
```
Use with `FOUNDRY_PROFILE=ci forge test`.
## Secrets Management
Required secrets for CI:
- `MAINNET_RPC_URL`: For fork testing
- `DEPLOYER_PRIVATE_KEY`: For deployment
- `ETHERSCAN_API_KEY`: For verification
**Never commit secrets to the repository.**
## Matrix Testing
Test across Solidity versions:
```yaml
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
solc: ["0.8.20", "0.8.25", "0.8.30"]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Test with Solc ${{ matrix.solc }}
run: forge test
env:
FOUNDRY_SOLC_VERSION: ${{ matrix.solc }}
```

View File

@@ -0,0 +1,450 @@
# Foundry Configuration Reference
Complete reference for `foundry.toml` configuration options.
## Basic Structure
```toml
# Default profile
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# Additional profiles
[profile.ci]
# CI-specific overrides
[profile.production]
# Production build settings
```
## Project Structure
```toml
[profile.default]
# Source directories
src = "src" # Contract sources
test = "test" # Test files
script = "script" # Deployment scripts
out = "out" # Compiled output
libs = ["lib"] # Dependency directories
cache_path = "cache" # Compilation cache
# Remappings (alternative to remappings.txt)
remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/",
"@solmate/=lib/solmate/src/",
"forge-std/=lib/forge-std/src/"
]
```
## Compiler Settings
```toml
[profile.default]
# Solidity version
solc = "0.8.30" # Exact version
# solc = "^0.8.0" # Version range
# auto_detect_solc = true # Auto-detect from pragmas
# EVM version
evm_version = "prague" # Target EVM version
# Options: homestead, tangerineWhistle, spuriousDragon, byzantium,
# constantinople, petersburg, istanbul, berlin, london,
# paris, shanghai, cancun, prague
# Optimizer
optimizer = true
optimizer_runs = 200 # Optimize for ~200 runs
via_ir = false # Use IR-based compilation
# Output
extra_output = ["abi", "evm.bytecode", "storageLayout"]
extra_output_files = ["abi", "storageLayout"]
# Bytecode hash
bytecode_hash = "ipfs" # ipfs, bzzr1, or none
cbor_metadata = true # Include CBOR metadata
```
## Testing Configuration
```toml
[profile.default]
# Verbosity (0-5)
verbosity = 2
# Gas settings
gas_limit = 9223372036854775807
gas_price = 0
block_base_fee_per_gas = 0
tx_origin = "0x0000000000000000000000000000000000000001"
# Block settings
block_coinbase = "0x0000000000000000000000000000000000000000"
block_timestamp = 1
block_number = 1
block_difficulty = 0
block_gas_limit = 30000000
chain_id = 31337
# Sender
sender = "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38"
# Memory limit (bytes)
memory_limit = 33554432 # 32 MB
# Show gas reports
gas_reports = ["*"] # All contracts
# gas_reports = ["MyContract", "OtherContract"]
gas_reports_ignore = []
# Fail test if gas exceeds this limit
# gas_report_fail_on_increase = true
```
## Fuzz Testing
```toml
[profile.default]
# Number of fuzz runs
fuzz.runs = 256
# Seed for deterministic fuzzing
fuzz.seed = "0x1234"
# Maximum test rejects before failing
fuzz.max_test_rejects = 65536
# Dictionary weight (how much to use discovered values)
fuzz.dictionary_weight = 40
# Include push bytes
fuzz.include_push_bytes = true
# Include storage
fuzz.include_storage = true
# Show logs
fuzz.show_logs = false
```
## Invariant Testing
```toml
[profile.default]
# Number of runs (sequences)
invariant.runs = 256
# Depth (calls per run)
invariant.depth = 15
# Fail on revert
invariant.fail_on_revert = false
# Call override
invariant.call_override = false
# Dictionary weight
invariant.dictionary_weight = 80
# Include storage
invariant.include_storage = true
# Include push bytes
invariant.include_push_bytes = true
# Shrink run limit
invariant.shrink_run_limit = 5000
# Max fuzz dictionary addresses
invariant.max_fuzz_dictionary_addresses = 15
# Max fuzz dictionary values
invariant.max_fuzz_dictionary_values = 10
# Gas limit
invariant.gas_limit = 9223372036854775807
```
## Fork Testing
```toml
[profile.default]
# Default fork URL
# eth_rpc_url = "https://eth-mainnet.alchemyapi.io/v2/..."
# Fork block number
# fork_block_number = 18000000
# Fork retry backoff
fork_retry_backoff = "0"
# RPC storage caching
rpc_storage_caching = {
chains = "all",
endpoints = "all"
}
# No storage caching
no_storage_caching = false
```
## RPC Endpoints
```toml
[rpc_endpoints]
mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
sepolia = "https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_KEY}"
arbitrum = "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
optimism = "https://opt-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
polygon = "https://polygon-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
base = "https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
# Local
localhost = "http://localhost:8545"
anvil = "http://127.0.0.1:8545"
# Environment variable interpolation
custom = "${CUSTOM_RPC_URL}"
```
## Etherscan Configuration
```toml
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }
arbitrum = { key = "${ARBISCAN_API_KEY}" }
optimism = { key = "${OPTIMISTIC_ETHERSCAN_API_KEY}" }
polygon = { key = "${POLYGONSCAN_API_KEY}" }
base = { key = "${BASESCAN_API_KEY}" }
# Custom chain
custom = { key = "${CUSTOM_API_KEY}", url = "https://api.custom-explorer.com/api" }
```
## Formatting
```toml
[fmt]
# Line length
line_length = 120
# Tab width
tab_width = 4
# Bracket spacing
bracket_spacing = false
# Int types (preserve, short, long)
int_types = "long"
# Multiline function header
multiline_func_header = "attributes_first"
# Quote style
quote_style = "double"
# Number underscore (preserve, thousands, none)
number_underscore = "preserve"
# Hex underscore
hex_underscore = "remove"
# Single line statement blocks
single_line_statement_blocks = "preserve"
# Override spacing
override_spacing = false
# Wrap comments
wrap_comments = false
# Ignore files
ignore = ["src/external/**"]
# Contract new lines
contract_new_lines = false
# Sort imports
sort_imports = false
```
## Documentation
```toml
[doc]
# Output directory
out = "docs"
# Repository link
repository = "https://github.com/user/repo"
# Ignore patterns
ignore = ["src/test/**"]
```
## Profiles
### Default Profile
```toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 200
```
### CI Profile
```toml
[profile.ci]
fuzz.runs = 10000
invariant.runs = 1000
invariant.depth = 100
verbosity = 3
```
### Production Profile
```toml
[profile.production]
optimizer = true
optimizer_runs = 1000000
via_ir = true
bytecode_hash = "none"
cbor_metadata = false
```
### Gas Optimization Profile
```toml
[profile.gas]
optimizer = true
optimizer_runs = 1000000
gas_reports = ["*"]
```
### Fast Development Profile
```toml
[profile.fast]
optimizer = false
fuzz.runs = 100
invariant.runs = 50
no_match_test = "testFork_"
```
## Using Profiles
```bash
# Use default profile
forge build
forge test
# Use CI profile
FOUNDRY_PROFILE=ci forge test
# Use production profile
FOUNDRY_PROFILE=production forge build
```
## Environment Variables
```bash
# Override any config option
FOUNDRY_SRC=contracts forge build
FOUNDRY_OPTIMIZER=false forge build
FOUNDRY_OPTIMIZER_RUNS=1000000 forge build
FOUNDRY_EVM_VERSION=shanghai forge build
# Common overrides
FOUNDRY_PROFILE=ci # Select profile
FOUNDRY_FUZZ_RUNS=10000 # Fuzz runs
FOUNDRY_INVARIANT_RUNS=1000 # Invariant runs
FOUNDRY_VERBOSITY=3 # Test verbosity
```
## Complete Example
```toml
# foundry.toml
[profile.default]
# Project
src = "src"
out = "out"
libs = ["lib"]
test = "test"
script = "script"
# Compiler
solc = "0.8.30"
evm_version = "prague"
optimizer = true
optimizer_runs = 200
# Testing
verbosity = 2
fuzz.runs = 256
fuzz.seed = "0x1234"
invariant.runs = 256
invariant.depth = 50
# Gas
gas_reports = ["*"]
# Output
extra_output = ["storageLayout"]
# Remappings
remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/"
]
[profile.ci]
fuzz.runs = 10000
fuzz.seed = "0xdeadbeef"
invariant.runs = 1000
invariant.depth = 100
verbosity = 3
[profile.production]
optimizer = true
optimizer_runs = 1000000
via_ir = true
[profile.local]
optimizer = false
fuzz.runs = 100
[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
arbitrum = "${ARBITRUM_RPC_URL}"
optimism = "${OPTIMISM_RPC_URL}"
base = "${BASE_RPC_URL}"
localhost = "http://127.0.0.1:8545"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }
arbitrum = { key = "${ARBISCAN_API_KEY}" }
optimism = { key = "${OPTIMISTIC_ETHERSCAN_API_KEY}" }
base = { key = "${BASESCAN_API_KEY}" }
[fmt]
line_length = 120
tab_width = 4
bracket_spacing = false
int_types = "long"
multiline_func_header = "attributes_first"
quote_style = "double"
number_underscore = "thousands"
sort_imports = true
```

View File

@@ -0,0 +1,251 @@
# Debugging Workflows
Foundry debugging tools for finding and fixing smart contract issues.
## Verbosity Levels
```bash
forge test # Summary only
forge test -v # Show passing test names
forge test -vv # Print logs for all tests
forge test -vvv # Traces for failing tests
forge test -vvvv # Traces for all tests + setup
forge test -vvvvv # All traces + storage changes
```
**Use:**
- `-vv`: Quick check of console.log output
- `-vvv`: First step when test fails
- `-vvvv`: Full debugging with all traces
## Understanding Traces
### Trace Format
```
[24661] TestContract::testFunction()
├─ [2262] Target::read()
│ └─ ← 0
├─ [20398] Target::write(42)
│ └─ ← ()
└─ ← ()
```
- `[gas]`: Gas consumed by call and nested calls
- **Green**: Successful calls
- **Red**: Reverting calls
- **Blue**: Cheatcode calls
- **Cyan**: Emitted logs
- **Yellow**: Contract deployments
### Common Trace Errors
| Error | Meaning |
|-------|---------|
| `OOG` | Out of gas |
| `Revert` | Transaction reverted |
| `InvalidFEOpcode` | Unknown opcode (0xFE) |
| `NotActivated` | EVM feature not available |
## Console Logging
### Basic Usage
```solidity
import {console} from "forge-std/console.sol";
function test_Debug() public {
console.log("Value:", value);
console.log("Address:", msg.sender);
console.log("Balance:", address(this).balance);
}
```
### Format Specifiers
```solidity
console.log("String: %s", "hello");
console.log("Decimal: %d", 123);
console.log("Hex: %x", 255);
// Multiple arguments (up to 4)
console.log("From %s to %s: %d", from, to, amount);
// Type-specific
console.logBytes32(hash);
console.logAddress(token);
console.logBool(success);
```
### Debugging Pattern
```solidity
function test_Transfer() public {
console.log("=== Transfer ===");
console.log("From:", from);
console.log("To:", to);
console.log("Amount:", amount);
token.transfer(to, amount);
console.log("Balance after:", token.balanceOf(to));
}
```
## Breakpoints
Set breakpoints in code, jump to them in debugger:
```solidity
function test_Complex() public {
vm.breakpoint("start");
uint256 x = calculate();
vm.breakpoint("middle");
process(x);
vm.breakpoint("end");
}
```
In debugger, press `'` + letter to jump (e.g., `'a` for first breakpoint).
## Interactive Debugger
### Starting
```bash
# Debug specific test
forge test --debug --match-test "test_Function"
# Debug script
forge script script/Deploy.s.sol --debug
# Debug transaction from chain
cast run --debug 0x123...
```
### Debugger Layout
Four quadrants:
1. **Top-left**: EVM opcodes (current instruction highlighted)
2. **Top-right**: Stack state
3. **Bottom-left**: Solidity source code
4. **Bottom-right**: Memory contents
### Navigation Keys
**Movement:**
- `j/k`: Step forward/backward
- `g/G`: Go to beginning/end
- `c/C`: Next/previous CALL instruction
- `'a-z`: Jump to breakpoint
**Display:**
- `t`: Toggle stack labels
- `m`: Toggle memory as UTF8
- `h`: Help
- `q`: Quit
### Debugging Workflow
```bash
# 1. Test fails
forge test --match-test "test_Deposit"
# 2. See what failed
forge test -vvv --match-test "test_Deposit"
# 3. Add console.log for quick debugging
# OR use interactive debugger
forge test --debug --match-test "test_Deposit"
# 4. Step through with j/k, watch stack with t
```
## Gas Profiling
### Gas Reports
```bash
forge test --gas-report
```
Output:
```
│ Function │ min │ avg │ median │ max │ calls │
├─────────────┼───────┼───────┼────────┼───────┼───────┤
│ transfer │ 2900 │ 5234 │ 5200 │ 8901 │ 145 │
│ balanceOf │ 596 │ 596 │ 596 │ 596 │ 234 │
```
### Gas Snapshots
```bash
forge snapshot # Create snapshot
forge snapshot --diff # Compare to previous
forge snapshot --check # Fail if changed
```
### Inline Measurement
```solidity
function test_GasUsage() public {
uint256 gasBefore = gasleft();
contract.operation();
uint256 gasUsed = gasBefore - gasleft();
console.log("Gas used:", gasUsed);
}
```
## Common Error Patterns
### Assertion Failure
```
AssertionError: a == b
Expected: 1000000
Actual: 500000
```
**Debug:** Run with `-vvv` to trace calculation.
### Revert Without Message
```
Error: reverted
```
**Debug:**
1. Run with `-vvvv` for full trace
2. Find red (reverting) call in trace
3. Add console.log before suspect operations
### Out of Gas
```
Error: OutOfGas
```
**Fix:** Reduce loop iterations or split into batches.
### Custom Error
```solidity
vm.expectRevert(abi.encodeWithSelector(
InsufficientBalance.selector,
1000, // have
2000 // need
));
token.transfer(recipient, 2000);
```
## Best Practices
1. **Start simple**: Use console.log before --debug
2. **Isolate tests**: Test one thing per test function
3. **Use descriptive logs**: Log state at each step
4. **Check assumptions**: Verify preconditions
5. **Save traces**: `forge test -vvvv > trace.txt`

View File

@@ -0,0 +1,203 @@
# Dependency Management
Managing dependencies in Foundry using git submodules and Soldeer.
## Git Submodules (Default)
### Installing Dependencies
```bash
# Install latest master
forge install vectorized/solady
# Install specific tag
forge install vectorized/solady@v0.0.265
# Install specific commit
forge install vectorized/solady@a5bb996e91aae5b0c068087af7594d92068b12f1
# No automatic commit (for CI)
forge install OpenZeppelin/openzeppelin-contracts --no-commit
```
Dependencies are cloned to `lib/[name]`.
### Remappings
Forge auto-generates remappings:
```bash
$ forge remappings
forge-std/=lib/forge-std/src/
solady/=lib/solady/src/
```
Use in imports:
```solidity
import {Test} from "forge-std/Test.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
```
### Custom Remappings
Create `remappings.txt`:
```
@openzeppelin/=lib/openzeppelin-contracts/contracts/
@solmate/=lib/solmate/src/
forge-std/=lib/forge-std/src/
```
Or in `foundry.toml`:
```toml
[profile.default]
remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/contracts/",
"@solmate/=lib/solmate/src/"
]
```
### Updating Dependencies
```bash
# Update specific dependency
forge update lib/solady
# Update all
forge update
```
### Removing Dependencies
```bash
forge remove solady
# or
forge remove lib/solady
```
## Soldeer (Modern Package Manager)
Soldeer provides npm-style dependency management with a central registry.
### Initialize
```bash
forge soldeer init
```
Creates `dependencies/` folder and configures `foundry.toml`.
### Installing Packages
```bash
# From registry (soldeer.xyz)
forge soldeer install @openzeppelin-contracts~5.0.2
forge soldeer install forge-std~1.8.1
# From URL
forge soldeer install @custom~1.0.0 --url https://example.com/lib.zip
# From git
forge soldeer install lib~1.0 --git https://github.com/org/lib.git --tag v1.0
```
Configuration in `foundry.toml`:
```toml
[profile.default]
libs = ["dependencies"]
[dependencies]
"@openzeppelin-contracts" = { version = "5.0.2" }
forge-std = { version = "1.8.1" }
```
### Updating
```bash
forge soldeer update
forge soldeer update --regenerate-remappings
```
### Removing
```bash
forge soldeer uninstall @openzeppelin-contracts
```
### Soldeer Config
```toml
[soldeer]
remappings_generate = true
remappings_version = true # @lib-5.0.2 suffix
remappings_prefix = "@" # @lib instead of lib
remappings_location = "txt" # or "config"
recursive_deps = true # Install sub-dependencies
```
## Comparison
| Feature | Git Submodules | Soldeer |
|---------|----------------|---------|
| Setup | Simple | Requires config |
| Version pinning | Commit hash | Semantic version |
| Central registry | No | Yes (soldeer.xyz) |
| Team sharing | .gitmodules | foundry.toml |
| Private repos | Full support | URL only |
## Best Practices
### Version Pinning
```bash
# Production: Use specific tag
forge install openzeppelin/openzeppelin-contracts@v5.0.0
# Development: Can use master
forge install vectorized/solady
```
### Handling Conflicts
When dependencies have conflicting versions, use remapping contexts:
```
# remappings.txt
lib/lib_1/:@openzeppelin/=lib/lib_1/node_modules/@openzeppelin/
lib/lib_2/:@openzeppelin/=lib/lib_2/node_modules/@openzeppelin/
```
### CI Configuration
```bash
# After clone, init submodules
git submodule update --init --recursive
# Or use --no-commit during install
forge install OpenZeppelin/openzeppelin-contracts --no-commit
```
### Hardhat Compatibility
```bash
# Enable node_modules support
forge install --hh
```
## Troubleshooting
**Submodule not found after clone:**
```bash
git submodule update --init --recursive
```
**Remapping not working:**
```bash
forge remappings > remappings.txt
```
**Soldeer package missing:**
Check [soldeer.xyz](https://soldeer.xyz) or publish your own.

View File

@@ -0,0 +1,554 @@
# Foundry Deployment Guide
Complete guide to deploying and verifying smart contracts with Foundry.
## forge create (Single Contract)
Quick deployment for single contracts.
### Basic Usage
```bash
# Deploy with constructor args
forge create src/Token.sol:Token \
--rpc-url sepolia \
--private-key $PRIVATE_KEY \
--constructor-args "MyToken" "MTK" 18
# Deploy and verify
forge create src/Token.sol:Token \
--rpc-url sepolia \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--constructor-args "MyToken" "MTK" 18
# Deploy with value (payable constructor)
forge create src/Vault.sol:Vault \
--rpc-url sepolia \
--private-key $PRIVATE_KEY \
--value 1ether
```
### Using Ledger
```bash
forge create src/Token.sol:Token \
--rpc-url mainnet \
--ledger \
--mnemonic-derivation-path "m/44'/60'/0'/0/0"
```
## Solidity Scripts (Recommended)
More powerful and flexible deployment method.
### Basic Deploy Script
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployToken is Script {
function run() external {
// Load private key from environment
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerKey);
console.log("Deploying from:", deployer);
console.log("Balance:", deployer.balance);
vm.startBroadcast(deployerKey);
Token token = new Token("MyToken", "MTK", 18);
console.log("Token deployed to:", address(token));
// Initial setup
token.mint(deployer, 1_000_000e18);
vm.stopBroadcast();
}
}
```
### Running Scripts
```bash
# Dry run (simulation only)
forge script script/Deploy.s.sol:DeployToken --rpc-url sepolia
# Broadcast transactions
forge script script/Deploy.s.sol:DeployToken \
--rpc-url sepolia \
--broadcast
# Broadcast and verify
forge script script/Deploy.s.sol:DeployToken \
--rpc-url sepolia \
--broadcast \
--verify
# Resume failed deployment
forge script script/Deploy.s.sol:DeployToken \
--rpc-url sepolia \
--broadcast \
--resume
```
### Script Execution Phases
1. **Local Simulation**: Run script, collect `vm.broadcast()` transactions
2. **On-chain Simulation**: (with `--rpc-url`) Simulate against chain state
3. **Broadcasting**: (with `--broadcast`) Send transactions to network
4. **Verification**: (with `--verify`) Verify contracts on Etherscan
### Complex Deployment
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
import {Staking} from "../src/Staking.sol";
import {Governance} from "../src/Governance.sol";
contract DeployProtocol is Script {
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address admin = vm.envAddress("ADMIN_ADDRESS");
vm.startBroadcast(deployerKey);
// Deploy token
Token token = new Token("Protocol Token", "PTK", 18);
console.log("Token:", address(token));
// Deploy staking with token reference
Staking staking = new Staking(address(token));
console.log("Staking:", address(staking));
// Deploy governance with token and staking
Governance gov = new Governance(
address(token),
address(staking),
admin
);
console.log("Governance:", address(gov));
// Setup permissions
token.grantRole(token.MINTER_ROLE(), address(staking));
staking.setGovernance(address(gov));
vm.stopBroadcast();
}
}
```
### Configuration-Based Deployment
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployConfigured is Script {
struct Config {
string name;
string symbol;
uint256 initialSupply;
address admin;
}
function run() external {
Config memory config = getConfig();
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
Token token = new Token(config.name, config.symbol, 18);
token.mint(config.admin, config.initialSupply);
if (config.admin != vm.addr(deployerKey)) {
token.transferOwnership(config.admin);
}
vm.stopBroadcast();
console.log("Deployed:", address(token));
}
function getConfig() internal view returns (Config memory) {
uint256 chainId = block.chainid;
if (chainId == 1) {
return Config({
name: "Production Token",
symbol: "PROD",
initialSupply: 100_000_000e18,
admin: 0x1234567890123456789012345678901234567890
});
} else if (chainId == 11155111) {
return Config({
name: "Test Token",
symbol: "TEST",
initialSupply: 1_000_000e18,
admin: vm.envAddress("TEST_ADMIN")
});
} else {
revert("Unsupported chain");
}
}
}
```
### Deterministic Deployment (CREATE2)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployDeterministic is Script {
// Deterministic deployment factory (present on most chains)
address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
bytes32 salt = keccak256("my-token-v1");
// Predict address
bytes memory bytecode = abi.encodePacked(
type(Token).creationCode,
abi.encode("MyToken", "MTK", 18)
);
address predicted = computeCreate2Address(salt, keccak256(bytecode));
console.log("Predicted address:", predicted);
// Check if already deployed
if (predicted.code.length > 0) {
console.log("Already deployed!");
return;
}
vm.startBroadcast(deployerKey);
Token token = new Token{salt: salt}("MyToken", "MTK", 18);
require(address(token) == predicted, "Address mismatch");
vm.stopBroadcast();
console.log("Deployed to:", address(token));
}
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)
internal
view
returns (address)
{
return address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
initCodeHash
)))));
}
}
```
## Multi-Chain Deployment
### Sequential Deployment
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployMultiChain is Script {
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
// Deploy to Ethereum
vm.createSelectFork("mainnet");
vm.startBroadcast(deployerKey);
Token mainnetToken = new Token("Token", "TKN", 18);
vm.stopBroadcast();
console.log("Mainnet:", address(mainnetToken));
// Deploy to Arbitrum
vm.createSelectFork("arbitrum");
vm.startBroadcast(deployerKey);
Token arbitrumToken = new Token("Token", "TKN", 18);
vm.stopBroadcast();
console.log("Arbitrum:", address(arbitrumToken));
// Deploy to Optimism
vm.createSelectFork("optimism");
vm.startBroadcast(deployerKey);
Token optimismToken = new Token("Token", "TKN", 18);
vm.stopBroadcast();
console.log("Optimism:", address(optimismToken));
}
}
```
Run with:
```bash
forge script script/DeployMultiChain.s.sol \
--broadcast \
--multi \
--slow \
--verify
```
## Contract Verification
### Auto-Verification
```bash
# During deployment
forge create src/Token.sol:Token \
--rpc-url sepolia \
--private-key $KEY \
--verify \
--etherscan-api-key $ETHERSCAN_KEY \
--constructor-args "Name" "SYM" 18
# With script
forge script script/Deploy.s.sol --broadcast --verify
```
### Manual Verification
```bash
# Verify existing contract
forge verify-contract \
--chain sepolia \
--compiler-version 0.8.30 \
--num-of-optimizations 200 \
--constructor-args $(cast abi-encode "constructor(string,string,uint8)" "Name" "SYM" 18) \
0xYourContractAddress \
src/Token.sol:Token
# Check verification status
forge verify-check \
--chain sepolia \
$GUID
```
### Verification with Libraries
```bash
forge verify-contract \
--chain mainnet \
--libraries src/lib/Math.sol:Math:0xLibraryAddress \
--libraries src/lib/Utils.sol:Utils:0xUtilsAddress \
0xContractAddress \
src/MyContract.sol:MyContract
```
### Configuration for Verification
```toml
# foundry.toml
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }
arbitrum = { key = "${ARBISCAN_API_KEY}" }
optimism = { key = "${OPTIMISTIC_ETHERSCAN_API_KEY}" }
base = { key = "${BASESCAN_API_KEY}" }
polygon = { key = "${POLYGONSCAN_API_KEY}" }
```
## Broadcast Artifacts
Scripts save transaction data to `broadcast/` directory:
```
broadcast/
└── Deploy.s.sol/
└── 11155111/ # Chain ID
├── run-latest.json # Latest run
├── run-1699999999.json # Timestamped runs
└── receipts/
└── ...
```
### Reading Artifacts in Scripts
```solidity
function readDeployment() internal view returns (address) {
string memory root = vm.projectRoot();
string memory path = string.concat(
root,
"/broadcast/Deploy.s.sol/11155111/run-latest.json"
);
string memory json = vm.readFile(path);
bytes memory contractAddr = json.parseRaw(".transactions[0].contractAddress");
return abi.decode(contractAddr, (address));
}
```
## Best Practices
### 1. Use Environment Variables
```solidity
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address admin = vm.envAddress("ADMIN_ADDRESS");
string memory rpcUrl = vm.envString("RPC_URL");
```
### 2. Validate Before Broadcasting
```solidity
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerKey);
// Pre-flight checks
require(deployer.balance > 0.1 ether, "Insufficient balance");
require(block.chainid == 11155111, "Wrong network");
vm.startBroadcast(deployerKey);
// ...
}
```
### 3. Log Everything
```solidity
vm.startBroadcast(deployerKey);
Token token = new Token("Name", "SYM", 18);
console.log("Token deployed to:", address(token));
console.log(" - Name:", token.name());
console.log(" - Symbol:", token.symbol());
console.log(" - Owner:", token.owner());
vm.stopBroadcast();
```
### 4. Use Numbered Scripts
```
script/
├── 01_DeployToken.s.sol
├── 02_DeployStaking.s.sol
├── 03_ConfigurePermissions.s.sol
└── 04_TransferOwnership.s.sol
```
### 5. Test Scripts
```solidity
contract DeployTokenTest is Test {
DeployToken deployer;
function setUp() public {
deployer = new DeployToken();
// Set required env vars
vm.setEnv("PRIVATE_KEY", vm.toString(uint256(1)));
}
function testDeploy() public {
deployer.run();
// Verify deployment
}
}
```
### 6. Handle Failures Gracefully
```solidity
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
try new Token("Name", "SYM", 18) returns (Token token) {
console.log("Success:", address(token));
} catch Error(string memory reason) {
console.log("Failed:", reason);
}
vm.stopBroadcast();
}
```
## Upgrade Patterns
### Transparent Proxy
```solidity
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
contract DeployUpgradeable is Script {
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
// Deploy implementation
TokenV1 impl = new TokenV1();
// Deploy proxy admin
ProxyAdmin admin = new ProxyAdmin(msg.sender);
// Deploy proxy
bytes memory initData = abi.encodeCall(TokenV1.initialize, ("Name", "SYM"));
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(impl),
address(admin),
initData
);
vm.stopBroadcast();
console.log("Implementation:", address(impl));
console.log("ProxyAdmin:", address(admin));
console.log("Proxy:", address(proxy));
}
}
```
### UUPS Proxy
```solidity
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract DeployUUPS is Script {
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
// Deploy implementation
TokenV1 impl = new TokenV1();
// Deploy proxy
bytes memory initData = abi.encodeCall(TokenV1.initialize, ("Name", "SYM"));
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData);
vm.stopBroadcast();
console.log("Implementation:", address(impl));
console.log("Proxy:", address(proxy));
}
}
```

View File

@@ -0,0 +1,567 @@
# forge-std API Reference
Complete reference for the Forge Standard Library.
## Overview
```solidity
import {Test, console} from "forge-std/Test.sol";
import {Script} from "forge-std/Script.sol";
```
`Test` inherits: `StdAssertions`, `StdChains`, `StdCheats`, `StdInvariant`, `StdUtils`
## StdAssertions
### Boolean
```solidity
assertTrue(bool condition);
assertTrue(bool condition, string memory err);
assertFalse(bool condition);
assertFalse(bool condition, string memory err);
```
### Equality
```solidity
// Works with: bool, uint256, int256, address, bytes32, string, bytes
assertEq(T left, T right);
assertEq(T left, T right, string memory err);
assertNotEq(T left, T right);
assertNotEq(T left, T right, string memory err);
// Arrays
assertEq(T[] memory left, T[] memory right);
```
### Comparison
```solidity
// Works with: uint256, int256
assertLt(T left, T right); // <
assertLt(T left, T right, string memory err);
assertGt(T left, T right); // >
assertGt(T left, T right, string memory err);
assertLe(T left, T right); // <=
assertLe(T left, T right, string memory err);
assertGe(T left, T right); // >=
assertGe(T left, T right, string memory err);
```
### Decimal Formatting
```solidity
// Shows values with decimal places in error messages
assertEqDecimal(uint256 left, uint256 right, uint256 decimals);
assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals);
assertLtDecimal(uint256 left, uint256 right, uint256 decimals);
assertGtDecimal(uint256 left, uint256 right, uint256 decimals);
assertLeDecimal(uint256 left, uint256 right, uint256 decimals);
assertGeDecimal(uint256 left, uint256 right, uint256 decimals);
// Example
assertEqDecimal(1e18, 1e18, 18); // Shows "1.0" not "1000000000000000000"
```
### Approximation
```solidity
// Absolute difference
assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta);
assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string memory err);
// Relative difference (maxPercentDelta: 1e18 = 100%)
assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta);
assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string memory err);
// Examples
assertApproxEqAbs(1000, 1005, 10); // Pass: |1000-1005| <= 10
assertApproxEqRel(100, 101, 0.02e18); // Pass: 1% diff <= 2%
```
### Call Comparison
```solidity
assertEqCall(address target, bytes memory callDataA, bytes memory callDataB);
assertEqCall(address targetA, bytes memory callDataA, address targetB, bytes memory callDataB);
```
### Failure
```solidity
fail();
fail(string memory err);
bool hasFailed = failed();
```
## StdCheats
### Address Creation
```solidity
// Create labeled address
address alice = makeAddr("alice");
// Create address with private key
(address bob, uint256 bobKey) = makeAddrAndKey("bob");
// Create account struct
Account memory account = makeAccount("charlie");
// account.addr, account.key
```
### Account Setup
```solidity
// ETH
deal(address to, uint256 amount);
// ERC20
deal(address token, address to, uint256 amount);
deal(address token, address to, uint256 amount, bool adjustTotalSupply);
// ERC721
dealERC721(address token, address to, uint256 tokenId);
// ERC1155
dealERC1155(address token, address to, uint256 id, uint256 amount);
dealERC1155(address token, address to, uint256 id, uint256 amount, bool adjustTotalSupply);
```
### Time Manipulation
```solidity
skip(uint256 seconds); // Move forward
rewind(uint256 seconds); // Move backward
// Examples
skip(1 days);
skip(1 hours);
rewind(30 minutes);
```
### Prank with ETH (hoax)
```solidity
// Single call as sender with ETH
hoax(address sender);
hoax(address sender, uint256 give);
hoax(address sender, address origin);
hoax(address sender, address origin, uint256 give);
// Multiple calls
startHoax(address sender);
startHoax(address sender, uint256 give);
// ... calls ...
vm.stopPrank();
// Example
hoax(alice, 10 ether);
vault.deposit{value: 1 ether}();
```
### Code Deployment
```solidity
// Deploy from artifacts
address deployed = deployCode("ContractName.sol");
address deployed = deployCode("ContractName.sol:ContractName");
address deployed = deployCode("ContractName.sol", constructorArgs);
address deployed = deployCode("ContractName.sol", constructorArgs, value);
// Deploy to specific address
deployCodeTo("ContractName.sol", targetAddress);
deployCodeTo("ContractName.sol", constructorArgs, targetAddress);
```
### Assumptions
```solidity
// Address type checks
assumeNotZeroAddress(address addr);
assumeNotPrecompile(address addr);
assumeNotPrecompile(address addr, uint256 chainId);
assumeNotForgeAddress(address addr);
assumePayable(address addr);
assumeNotPayable(address addr);
// Token blacklists
assumeNotBlacklisted(address token, address addr);
// Combined checks
assumeAddressIsNot(address addr, AddressType t);
assumeAddressIsNot(address addr, AddressType t1, AddressType t2);
// AddressType enum: ZeroAddress, Precompile, ForgeAddress
```
### Fork Detection
```solidity
bool forking = isFork();
// Modifiers
function testOnlyLocal() public skipWhenForking { }
function testOnlyForked() public skipWhenNotForking { }
```
### Gas Metering
```solidity
// Disable gas metering for expensive setup
modifier noGasMetering;
function testExpensiveSetup() public noGasMetering {
// Gas not counted
}
```
### Account Destruction
```solidity
destroyAccount(address target, address beneficiary);
```
## StdStorage
Dynamic storage slot finding and manipulation.
### Setup
```solidity
using stdStorage for StdStorage;
```
### Finding Slots
```solidity
// Simple variable
uint256 slot = stdstore
.target(address(contract))
.sig("variableName()")
.find();
// Mapping
uint256 slot = stdstore
.target(address(contract))
.sig("balances(address)")
.with_key(user)
.find();
// Nested mapping
uint256 slot = stdstore
.target(address(contract))
.sig("allowances(address,address)")
.with_key(owner)
.with_key(spender)
.find();
// Struct field
uint256 slot = stdstore
.target(address(contract))
.sig("structVar()")
.depth(0) // Field index
.find();
```
### Writing Values
```solidity
stdstore
.target(address(contract))
.sig("balances(address)")
.with_key(user)
.checked_write(1000e18);
// For int256
stdstore
.target(address(contract))
.sig("delta()")
.checked_write_int(-100);
```
### Example
```solidity
function testSetBalance() public {
// Set alice's balance to 1000 tokens
stdstore
.target(address(token))
.sig("balanceOf(address)")
.with_key(alice)
.checked_write(1000e18);
assertEq(token.balanceOf(alice), 1000e18);
}
```
## StdUtils
### Bounded Randomness
```solidity
// Constrain fuzz input to range
uint256 bounded = bound(uint256 x, uint256 min, uint256 max);
int256 bounded = bound(int256 x, int256 min, int256 max);
// Constrain to valid private key range
uint256 key = boundPrivateKey(uint256 pk);
// Example
function testFuzz(uint256 amount) public {
amount = bound(amount, 1, 1000);
// amount is now in [1, 1000]
}
```
### Address Computation
```solidity
// CREATE address
address addr = computeCreateAddress(address deployer, uint256 nonce);
// CREATE2 address
address addr = computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer);
address addr = computeCreate2Address(bytes32 salt, bytes32 initCodeHash); // Uses CREATE2_FACTORY
// Init code hash
bytes32 hash = hashInitCode(bytes memory creationCode);
bytes32 hash = hashInitCode(bytes memory creationCode, bytes memory args);
// Example
bytes32 initHash = hashInitCode(type(MyContract).creationCode);
address predicted = computeCreate2Address(salt, initHash, factory);
```
### Token Utilities
```solidity
// Batch balance query (uses Multicall3)
uint256[] memory balances = getTokenBalances(address token, address[] memory addresses);
```
### Byte Conversion
```solidity
uint256 value = bytesToUint(bytes memory b);
```
## StdJson
### Reading
```solidity
using stdJson for string;
string memory json = vm.readFile("data.json");
// Single values
uint256 amount = json.readUint(".amount");
int256 balance = json.readInt(".balance");
address addr = json.readAddress(".recipient");
bytes32 hash = json.readBytes32(".hash");
string memory name = json.readString(".name");
bytes memory data = json.readBytes(".data");
bool flag = json.readBool(".enabled");
// Arrays
uint256[] memory amounts = json.readUintArray(".amounts");
address[] memory addrs = json.readAddressArray(".addresses");
string[] memory names = json.readStringArray(".names");
// With defaults
uint256 amount = json.readUintOr(".amount", 100);
address addr = json.readAddressOr(".recipient", address(0));
// Check existence
bool exists = json.keyExists(".key");
// Raw bytes
bytes memory raw = json.parseRaw(".data");
```
### Writing
```solidity
using stdJson for string;
string memory json = "obj";
json = json.serialize("amount", uint256(100));
json = json.serialize("recipient", address(0x123));
json = json.serialize("enabled", true);
json = json.serialize("amounts", amounts);
json.write("output.json");
json.write("output.json", ".config");
```
## StdToml
Identical API to StdJson:
```solidity
using stdToml for string;
string memory toml = vm.readFile("config.toml");
uint256 runs = toml.readUint(".profile.default.fuzz_runs");
```
## StdChains
Access chain configuration:
```solidity
Chain memory chain = getChain("mainnet");
// chain.name, chain.chainId, chain.rpcUrl
Chain memory chain = getChain(1); // By chain ID
// Set custom RPC
setChain("custom", ChainData({
name: "Custom Chain",
chainId: 12345,
rpcUrl: "https://..."
}));
```
## StdInvariant
For invariant testing targets:
```solidity
// Target contracts for fuzzing
targetContract(address);
targetContracts(); // Returns address[]
// Exclude from fuzzing
excludeContract(address);
excludeContracts(); // Returns address[]
// Target senders
targetSender(address);
targetSenders(); // Returns address[]
// Exclude senders
excludeSender(address);
excludeSenders(); // Returns address[]
// Target specific selectors
targetSelector(FuzzSelector memory);
targetSelectors(); // Returns FuzzSelector[]
// Target artifacts (deploy and fuzz)
targetArtifact(string memory);
targetArtifacts(); // Returns string[]
// Target artifact selectors
targetArtifactSelector(FuzzArtifactSelector memory);
targetArtifactSelectors(); // Returns FuzzArtifactSelector[]
```
## StdError
Common error selectors:
```solidity
import {stdError} from "forge-std/StdError.sol";
vm.expectRevert(stdError.arithmeticError); // Overflow/underflow
vm.expectRevert(stdError.assertionError); // assert() failed
vm.expectRevert(stdError.divisionError); // Division by zero
vm.expectRevert(stdError.encodeStorageError); // Storage encoding
vm.expectRevert(stdError.enumConversionError); // Invalid enum
vm.expectRevert(stdError.indexOOBError); // Array index out of bounds
vm.expectRevert(stdError.memOverflowError); // Memory overflow
vm.expectRevert(stdError.popEmptyArrayError); // Pop empty array
vm.expectRevert(stdError.zeroVarError); // Zero-initialized function pointer
```
## StdMath
```solidity
import {stdMath} from "forge-std/StdMath.sol";
uint256 absolute = stdMath.abs(int256 x);
uint256 delta = stdMath.delta(uint256 a, uint256 b);
uint256 delta = stdMath.delta(int256 a, int256 b);
uint256 percent = stdMath.percentDelta(uint256 a, uint256 b);
```
## Console Logging
```solidity
import {console} from "forge-std/console.sol";
// or
import {console2} from "forge-std/console2.sol"; // Smaller bytecode
console.log("message");
console.log("value:", value);
console.log("a:", a, "b:", b);
// Type-specific
console.log(uint256 x);
console.log(int256 x);
console.log(address x);
console.log(bool x);
console.log(string memory x);
console.log(bytes memory x);
console.log(bytes32 x);
// Formatted
console.logBytes(bytes memory);
console.logBytes1(bytes1);
// ... up to logBytes32
```
## Script.sol
Base for deployment scripts:
```solidity
import {Script, console} from "forge-std/Script.sol";
contract DeployScript is Script {
function setUp() public {}
function run() public {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
MyContract c = new MyContract();
c.initialize();
vm.stopBroadcast();
console.log("Deployed:", address(c));
}
}
```
### Script vs Test
| Feature | Test | Script |
|---------|------|--------|
| Base | `Test` | `Script` |
| Cheats | Full `StdCheats` | `StdCheatsSafe` |
| Purpose | Testing | Deployment |
| Broadcast | No | Yes |
| State changes | Local | On-chain |
## Constants
```solidity
// From CommonBase
address constant VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
address constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67;
address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
address constant DEFAULT_SENDER = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38;
address constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
address constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11;
```

View File

@@ -0,0 +1,333 @@
# Solidity Gas Optimization Guide
Comprehensive gas optimization techniques for Solidity 0.8.30 with Foundry profiling.
## Storage Optimization
### Variable Packing
Pack multiple variables into single 32-byte storage slots:
```solidity
// BAD: 3 storage slots (3 SSTORE operations)
contract Unoptimized {
uint256 amount; // slot 0
uint8 status; // slot 1
address owner; // slot 2
}
// GOOD: 2 storage slots
contract Optimized {
uint128 amount; // slot 0 (16 bytes)
uint96 lockTime; // slot 0 (12 bytes)
uint8 status; // slot 0 (1 byte)
address owner; // slot 1 (20 bytes)
}
```
**Savings:** 15,000+ gas per packed write
### Struct Packing
Pack struct fields in declaration order:
```solidity
// BAD: 3 slots
struct BadPacking {
uint256 a; // slot 0
uint8 b; // slot 1
uint256 c; // slot 2
}
// GOOD: 2 slots
struct GoodPacking {
uint256 a; // slot 0
uint128 c; // slot 1 (16 bytes)
uint8 b; // slot 1 (1 byte)
}
```
### Storage vs Memory vs Calldata
**Gas Costs:**
| Operation | Gas |
|-----------|-----|
| SLOAD (cold) | 2,100 |
| SLOAD (warm) | 100 |
| SSTORE (0→non-0) | 20,000 |
| SSTORE (non-0→non-0) | 5,000 |
| MLOAD/MSTORE | 3 per word |
| Calldata read | 3 per byte |
**Cache storage reads:**
```solidity
// BAD: 3 SLOAD operations
function process() external returns (uint256) {
return value * 2 + value * 3 + value * 4;
}
// GOOD: 1 SLOAD + memory operations
function process() external returns (uint256) {
uint256 v = value;
return v * 2 + v * 3 + v * 4;
}
```
**Use calldata for external function arrays:**
```solidity
// BAD: Copies to memory
function batchProcess(uint256[] memory values) external { }
// GOOD: Reads directly from calldata
function batchProcess(uint256[] calldata values) external {
for (uint256 i; i < values.length; ) {
// Process values[i]
unchecked { i++; }
}
}
```
## Arithmetic Optimization
### Unchecked Blocks
Solidity 0.8+ adds overflow checks by default (~50-100 gas per operation):
```solidity
// BAD: Overflow check on every increment
for (uint256 i = 0; i < 100; i++) {
result += i;
}
// GOOD: Unchecked increment (safe when bounded)
for (uint256 i = 0; i < 100; ) {
result += i;
unchecked { i++; }
}
```
**Savings:** 50-100 gas per iteration
### Short-Circuit Logic
Order conditions by likelihood to fail:
```solidity
// Expensive check last (short-circuits if first fails)
if (amount > 0 && expensiveValidation(amount)) {
// ...
}
```
### Bitwise Flag Packing
Combine booleans into single uint256:
```solidity
// BAD: 4 storage slots
bool isActive;
bool isVerified;
bool isPaused;
bool isBlocked;
// GOOD: 1 storage slot, bit manipulation
uint256 flags;
function isActive() view returns (bool) {
return (flags & (1 << 0)) != 0;
}
function setActive(bool _active) {
if (_active) flags |= (1 << 0);
else flags &= ~(1 << 0);
}
```
## Variable Declaration
### Immutable vs Constant vs Storage
```solidity
// Storage variable: 2,100 gas (cold read)
uint256 public maxSupply = 1_000_000;
// Constant: ~3 gas (embedded in bytecode)
uint256 public constant MAX_SUPPLY = 1_000_000;
// Immutable: ~100 gas (set once in constructor)
uint256 public immutable maxSupply;
constructor(uint256 _max) { maxSupply = _max; }
```
**Use constant for compile-time values, immutable for constructor-set values.**
### Function Visibility
```solidity
// Public creates getter (extra dispatch)
uint256 public value;
// External is cheaper for functions not called internally
function getValue() external view returns (uint256) {
return _value;
}
```
## Error Handling
### Custom Errors vs Require Strings
```solidity
// BAD: String stored in bytecode (~50+ gas)
require(msg.sender == owner, "Unauthorized access");
// GOOD: Custom error (~24 gas)
error Unauthorized();
if (msg.sender != owner) revert Unauthorized();
```
**Savings:** 20-50 gas per revert
## Events vs Storage
Store transient data in events instead of contract storage:
```solidity
// BAD: 20,000+ gas per write
uint256[] public transfers;
function recordTransfer(uint256 amount) external {
transfers.push(amount);
}
// GOOD: ~375 gas base + 8 gas per byte
event Transfer(address indexed to, uint256 amount);
function recordTransfer(address to, uint256 amount) external {
emit Transfer(to, amount);
}
```
## Compiler Settings
```toml
# foundry.toml
[profile.default]
optimizer = true
optimizer_runs = 200 # Balance size/runtime
[profile.production]
optimizer = true
optimizer_runs = 1000000 # Optimize for runtime
via_ir = true # IR pipeline (slower compile, better optimization)
```
**Guidance:**
- `200` runs: Smaller deployment, higher runtime cost
- `10000+` runs: Larger deployment, lower runtime cost
- `via_ir = true`: Best optimization, slowest compilation
## EVM Opcode Reference
| Operation | Gas | Notes |
|-----------|-----|-------|
| ADD/SUB/MUL | 3 | Basic arithmetic |
| DIV/MOD | 5 | Division operations |
| SLOAD (cold) | 2,100 | First storage read |
| SLOAD (warm) | 100 | Subsequent reads |
| SSTORE (0→non-0) | 20,000 | New storage slot |
| SSTORE (non-0→non-0) | 5,000 | Update existing |
| SSTORE (→0) | 5,000 | +15,000 refund |
| CALL | 700+ | External call base |
| KECCAK256 | 30 | +6 per word |
| LOG0-LOG4 | 375-1,875 | Events |
## Foundry Gas Profiling
### Gas Reports
```bash
# Generate gas report
forge test --gas-report
# Filter by contract
forge test --match-contract MyContract --gas-report
```
### Gas Snapshots
```bash
# Create snapshot
forge snapshot
# Compare against previous
forge snapshot --diff
# Check threshold
forge snapshot --check --tolerance 5
```
### Inline Gas Measurement
```solidity
function testGasUsage() public {
uint256 gasBefore = gasleft();
// Code to measure
contract.doSomething();
uint256 gasUsed = gasBefore - gasleft();
console.log("Gas used:", gasUsed);
}
```
### Section Snapshots
```solidity
function testOptimization() public {
vm.startSnapshotGas("operation");
// Code to profile
value = 1;
uint256 gasUsed = vm.stopSnapshotGas();
}
```
## Optimization Checklist
**Storage:**
- [ ] Pack related variables together
- [ ] Use smaller integer types when possible
- [ ] Cache frequently accessed storage variables
- [ ] Use mappings instead of arrays for lookups
- [ ] Use immutable/constant for fixed values
**Functions:**
- [ ] Mark external (not public) when not called internally
- [ ] Use calldata for array parameters
- [ ] Use unchecked blocks for safe arithmetic
- [ ] Short-circuit expensive conditions
- [ ] Use custom errors instead of require strings
**Compilation:**
- [ ] Enable optimizer
- [ ] Set appropriate optimizer_runs
- [ ] Consider via_ir for production
**Testing:**
- [ ] Use `forge test --gas-report` regularly
- [ ] Create gas snapshots for regression detection
- [ ] Profile critical functions
## Quick Wins
| Pattern | Gas Saved | Effort |
|---------|-----------|--------|
| Custom errors | 20-50/revert | Low |
| Unchecked loops | 50-100/iter | Low |
| Calldata vs memory | 5,000+ | Low |
| Variable packing | 15,000/write | Low |
| Immutable vars | 2,000/read | Low |
| Bitwise flags | 15,000 | Medium |
| Events vs storage | 19,000+ | Medium |

View File

@@ -0,0 +1,581 @@
# Solidity Patterns and Idioms
Common patterns for modern Solidity 0.8.30 smart contract development.
## Access Control Patterns
### Ownable Pattern
Single owner - simplest but centralized:
```solidity
contract Ownable {
address private _owner;
error Unauthorized();
modifier onlyOwner() {
if (msg.sender != _owner) revert Unauthorized();
_;
}
constructor() {
_owner = msg.sender;
}
function owner() public view returns (address) {
return _owner;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid owner");
_owner = newOwner;
}
}
```
**Use:** Simple contracts, single admin
**Pitfall:** Single point of failure
### Role-Based Access Control
Fine-grained permissions:
```solidity
contract AccessControl {
mapping(bytes32 => mapping(address => bool)) private _roles;
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
error AccessDenied(address account, bytes32 role);
modifier onlyRole(bytes32 role) {
if (!_roles[role][msg.sender])
revert AccessDenied(msg.sender, role);
_;
}
function grantRole(bytes32 role, address account) public onlyRole(ADMIN_ROLE) {
_roles[role][account] = true;
}
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roles[role][account];
}
}
```
**Use:** DeFi protocols, complex permissions
**Recommendation:** Use OpenZeppelin's AccessControl
### Multi-Signature Pattern
Require multiple approvals for critical operations:
```solidity
contract MultiSig {
address[] public owners;
uint256 public requiredSignatures;
mapping(bytes32 => mapping(address => bool)) public confirmations;
mapping(bytes32 => bool) public executed;
function submitTransaction(
address target,
uint256 value,
bytes calldata data
) external returns (bytes32) {
bytes32 txHash = keccak256(abi.encode(target, value, data, block.timestamp));
confirmations[txHash][msg.sender] = true;
return txHash;
}
function executeTransaction(
address target,
uint256 value,
bytes calldata data,
bytes32 txHash
) external {
require(!executed[txHash], "Already executed");
uint256 count = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (confirmations[txHash][owners[i]]) count++;
}
require(count >= requiredSignatures, "Not enough signatures");
executed[txHash] = true;
(bool success,) = target.call{value: value}(data);
require(success, "Execution failed");
}
}
```
**Use:** Treasury, critical upgrades, governance
## Reentrancy Protection
### Checks-Effects-Interactions (CEI)
Most important pattern - prevent state inconsistencies:
```solidity
// BAD: State update AFTER external call
function withdrawBad() external {
uint256 amount = balances[msg.sender];
(bool success,) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0; // Vulnerable!
}
// GOOD: CEI pattern
function withdrawGood() external {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // Effects first
(bool success,) = msg.sender.call{value: amount}("");
require(success); // Interactions last
}
```
### Mutex Lock (ReentrancyGuard)
Traditional approach using storage:
```solidity
contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status = NOT_ENTERED;
modifier nonReentrant() {
require(_status != ENTERED, "ReentrancyGuard: reentrant call");
_status = ENTERED;
_;
_status = NOT_ENTERED;
}
}
```
**Cost:** ~5,000 gas (2 SSTOREs)
### Transient Storage Lock (0.8.28+)
Gas-efficient using EIP-1153:
```solidity
contract TransientReentrancyGuard {
modifier nonReentrant() {
assembly {
if tload(0) { revert(0, 0) }
tstore(0, 1)
}
_;
assembly {
tstore(0, 0)
}
}
}
```
**Cost:** ~200 gas (TSTORE/TLOAD) - 25x cheaper
## Factory Patterns
### Basic Factory (CREATE)
```solidity
contract TokenFactory {
address[] public deployedTokens;
event TokenCreated(address indexed token, address indexed creator);
function createToken(string memory name, string memory symbol)
external
returns (address)
{
Token token = new Token(name, symbol, msg.sender);
deployedTokens.push(address(token));
emit TokenCreated(address(token), msg.sender);
return address(token);
}
}
```
**Use:** Simple deployments where address doesn't matter
### Deterministic Factory (CREATE2)
Deploy to predictable addresses across chains:
```solidity
contract DeterministicFactory {
event Deployed(address indexed addr, bytes32 salt);
function deploy(bytes32 salt, bytes memory bytecode)
external
returns (address addr)
{
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
if iszero(extcodesize(addr)) { revert(0, 0) }
}
emit Deployed(addr, salt);
}
function computeAddress(bytes32 salt, bytes32 bytecodeHash)
external
view
returns (address)
{
return address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
bytecodeHash
)))));
}
}
```
**Use:** Counterfactual deployments, cross-chain consistency
### Minimal Proxy (ERC-1167 Clones)
Deploy cheap proxies (~45 bytes):
```solidity
contract CloneFactory {
function clone(address implementation) internal returns (address instance) {
assembly {
mstore(0x00, or(
shr(0xe8, shl(0x60, implementation)),
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000
))
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(0, 0x09, 0x37)
}
}
}
```
**Cost:** ~10K gas vs ~170K for full contract
**Use:** Token factories, vault templates
## Payment Patterns
### Pull Over Push
**Push (Anti-pattern):**
```solidity
// BAD: Can fail if recipient reverts
function sendRewards() external {
for (uint256 i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]);
}
}
```
**Pull (Recommended):**
```solidity
contract PullPayments {
mapping(address => uint256) public pendingWithdrawals;
function claimReward() external {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "Nothing to claim");
pendingWithdrawals[msg.sender] = 0;
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
```
**Why pull is safer:**
- User controls when to withdraw
- Failing transfers don't block protocol
- Prevents DoS via malicious fallbacks
## Emergency Controls
### Pausable Pattern
```solidity
contract Pausable {
bool public paused;
address public owner;
error ContractPaused();
modifier whenNotPaused() {
if (paused) revert ContractPaused();
_;
}
function pause() external onlyOwner {
paused = true;
}
function unpause() external onlyOwner {
paused = false;
}
function transfer(address to, uint256 amount) external whenNotPaused {
// Transfer logic
}
}
```
**Use:** Security response, gradual rollouts
## Proxy Patterns
### UUPS (Recommended)
Implementation controls upgrades:
```solidity
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyContractV1 is UUPSUpgradeable {
uint256 public value;
function initialize(uint256 _value) public initializer {
value = _value;
}
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner
{}
}
```
### Transparent Proxy
Admin calls go to proxy, user calls go to implementation:
```solidity
// Use OpenZeppelin's TransparentUpgradeableProxy
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
```
**Trade-offs:**
- UUPS: Smaller proxy, upgrade logic in implementation
- Transparent: Larger proxy, clearer admin separation
## State Machine Pattern
```solidity
contract Auction {
enum Phase { Bidding, Reveal, Finished }
Phase public currentPhase;
uint256 public phaseDeadline;
error WrongPhase(Phase expected, Phase actual);
modifier atPhase(Phase expected) {
_checkPhaseTransition();
if (currentPhase != expected)
revert WrongPhase(expected, currentPhase);
_;
}
function _checkPhaseTransition() internal {
if (block.timestamp >= phaseDeadline) {
currentPhase = Phase(uint256(currentPhase) + 1);
phaseDeadline = block.timestamp + 1 days;
}
}
function placeBid(bytes32 hashedBid) external atPhase(Phase.Bidding) {
// Bidding logic
}
function revealBid(uint256 value, bytes32 secret) external atPhase(Phase.Reveal) {
// Reveal logic
}
}
```
## Commit-Reveal Scheme
Prevent front-running:
```solidity
contract CommitReveal {
mapping(address => bytes32) public commits;
mapping(address => uint256) public commitTimes;
uint256 public constant REVEAL_DELAY = 1 hours;
function commit(bytes32 hash) external {
commits[msg.sender] = hash;
commitTimes[msg.sender] = block.timestamp;
}
function reveal(uint256 value, bytes32 salt) external {
require(block.timestamp >= commitTimes[msg.sender] + REVEAL_DELAY, "Too early");
require(commits[msg.sender] == keccak256(abi.encode(value, salt, msg.sender)), "Invalid reveal");
delete commits[msg.sender];
// Process revealed value
}
}
```
**Use:** Auctions, voting, sealed-bid protocols
## Timelock Pattern
```solidity
contract Timelock {
uint256 public constant DELAY = 2 days;
mapping(bytes32 => uint256) public queuedTransactions;
function queue(address target, bytes calldata data) external returns (bytes32) {
bytes32 txHash = keccak256(abi.encode(target, data, block.timestamp));
queuedTransactions[txHash] = block.timestamp + DELAY;
return txHash;
}
function execute(address target, bytes calldata data, bytes32 txHash) external {
uint256 executeTime = queuedTransactions[txHash];
require(executeTime != 0, "Not queued");
require(block.timestamp >= executeTime, "Too early");
delete queuedTransactions[txHash];
(bool success,) = target.call(data);
require(success, "Execution failed");
}
}
```
**Use:** Governance, critical upgrades, parameter changes
## Error Handling
### Custom Errors (Preferred)
```solidity
error InsufficientBalance(uint256 available, uint256 required);
error Unauthorized(address caller);
error ZeroAddress();
function withdraw(uint256 amount) external {
if (balances[msg.sender] < amount)
revert InsufficientBalance(balances[msg.sender], amount);
// ...
}
```
**Advantages:**
- ~90% gas savings vs require strings
- Structured error data for debugging
- Type-safe parameters
## Initialization Patterns
### Constructor (Non-upgradeable)
```solidity
contract Permanent {
address public immutable owner;
uint256 public immutable maxSupply;
constructor(address _owner, uint256 _maxSupply) {
owner = _owner;
maxSupply = _maxSupply;
}
}
```
### Initializer (Upgradeable)
```solidity
contract Upgradeable {
bool private _initialized;
address public owner;
modifier initializer() {
require(!_initialized, "Already initialized");
_initialized = true;
_;
}
function initialize(address _owner) external initializer {
owner = _owner;
}
}
```
**Critical:** Constructors don't run on proxies - use initializers
## Common Pitfalls
### tx.origin vs msg.sender
```solidity
// WRONG: Vulnerable to phishing
if (tx.origin == owner) { /* ... */ }
// CORRECT
if (msg.sender == owner) { /* ... */ }
```
### Storage Layout with Upgrades
```solidity
// V1
contract V1 {
uint256 public value; // slot 0
}
// V2 - CORRECT: Append only
contract V2 is V1 {
uint256 public newValue; // slot 1
}
// V2 - WRONG: Inserting breaks layout
contract V2Bad {
uint256 public newValue; // slot 0 - collision!
uint256 public value; // slot 1
}
```
### Unbounded Loops
```solidity
// DANGEROUS: Can run out of gas
function distributeAll() external {
for (uint256 i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]);
}
}
// SAFE: Batch processing
function distributeBatch(uint256 start, uint256 end) external {
require(end <= recipients.length && end - start <= 100);
for (uint256 i = start; i < end; i++) {
payable(recipients[i]).transfer(amounts[i]);
}
}
```
### Unchecked External Calls
```solidity
// WRONG: Return value ignored
token.transfer(user, amount);
// CORRECT: Check return
bool success = token.transfer(user, amount);
require(success, "Transfer failed");
// BEST: Use SafeERC20
IERC20(token).safeTransfer(user, amount);
```

View File

@@ -0,0 +1,126 @@
# Foundry & Solidity Resources
Authoritative resources for Foundry and Solidity development.
## Official Documentation
### Foundry
- **Foundry Book**: https://book.getfoundry.sh - Comprehensive guide (forge, anvil, cast, chisel)
- **Foundry GitHub**: https://github.com/foundry-rs/foundry - Source code and issues
- **forge-std**: https://github.com/foundry-rs/forge-std - Standard library reference
### Solidity
- **Solidity Docs**: https://docs.soliditylang.org - Language reference
- **Solidity GitHub**: https://github.com/ethereum/solidity - Compiler source
- **Solidity Blog**: https://soliditylang.org/blog - Release notes and features
### Ethereum
- **Ethereum.org Developers**: https://ethereum.org/developers - Developer portal
- **EIPs**: https://eips.ethereum.org - Ethereum Improvement Proposals
- **Ethereum Stack Exchange**: https://ethereum.stackexchange.com - Q&A community
## Smart Contract Libraries
### Production-Ready
- **OpenZeppelin Contracts**: https://docs.openzeppelin.com/contracts - Audited implementations
- ERC-20, ERC-721, ERC-1155 tokens
- Access control, pausable, upgradeable
- Security utilities
- **Solady**: https://github.com/Vectorized/solady - Gas-optimized utilities
- Used by Coinbase, Optimism, Uniswap
- Highly optimized ERC implementations
- **Solmate**: https://github.com/transmissions11/solmate - Minimalist implementations
- Gas-efficient ERC tokens
- Auth and utility contracts
## Security Resources
### Audit Firms
- **OpenZeppelin Security**: https://www.openzeppelin.com/security-audits
- **Trail of Bits**: https://blog.trailofbits.com
- **Consensys Diligence**: https://consensys.io/diligence
### Crowdsourced Audits
- **Code4rena**: https://code4rena.com - Competitive audits
- **Sherlock**: https://sherlock.xyz - Audit competitions
- **Immunefi**: https://immunefi.com - Bug bounties
### Vulnerability Resources
- **SWC Registry**: https://swcregistry.io - Smart Contract Weaknesses
- **Rekt News**: https://rekt.news - Exploit post-mortems
- **DeFiHackLabs**: https://github.com/SunWeb3Sec/DeFiHackLabs - Exploit reproductions
### Security Tools
- **Slither**: https://github.com/crytic/slither - Static analysis
- **Mythril**: https://mythril.ai - Symbolic execution
- **Echidna**: https://github.com/crytic/echidna - Fuzzing
- **Certora**: https://www.certora.com - Formal verification
## Learning Resources
### Interactive
- **Solidity by Example**: https://solidity-by-example.org - Runnable code examples
- **CryptoZombies**: https://cryptozombies.io - Gamified learning
- **SpeedRun Ethereum**: https://speedrunethereum.com - Hands-on challenges
### Courses
- **Cyfrin Updraft**: https://updraft.cyfrin.io - Patrick Collins courses
- **Alchemy University**: https://university.alchemy.com - Web3 development
### Patterns & Best Practices
- **Solidity Patterns**: https://docs.soliditylang.org/en/latest/common-patterns.html
- **OpenZeppelin Docs**: https://docs.openzeppelin.com/contracts - Implementation guides
## Development Tools
### IDEs & Editors
- **VS Code + Solidity Extension**: https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity
- **Remix IDE**: https://remix.ethereum.org - Browser-based
### Testing & Debugging
- **Tenderly**: https://tenderly.co - Transaction debugging
- **Phalcon**: https://explorer.phalcon.xyz - Transaction analysis
### Infrastructure
- **Alchemy**: https://www.alchemy.com - RPC provider
- **Infura**: https://www.infura.io - RPC provider
- **QuickNode**: https://www.quicknode.com - RPC provider
## Community
### Discussion
- **Foundry Telegram**: https://t.me/foundry_rs
- **Ethereum R&D Discord**: https://discord.gg/ethereum-r-d
- **OpenZeppelin Forum**: https://forum.openzeppelin.com
### Research
- **Paradigm Research**: https://www.paradigm.xyz/writing
- **a]16z Crypto Research**: https://a16zcrypto.com/research
## Quick Reference
### Getting Started Path
1. **Solidity by Example** - Learn syntax
2. **Foundry Book** - Learn tooling
3. **OpenZeppelin Docs** - Learn patterns
4. **Code4rena Reports** - Learn security
### Testing Best Practices
1. **forge-std** - Test utilities
2. **Foundry Book Testing** - Fuzz and invariant testing
3. **DeFiHackLabs** - Real exploit patterns
### Security Path
1. **OpenZeppelin Audit Readiness** - Preparation
2. **Trail of Bits Blog** - Deep dives
3. **Code4rena** - Real audit reports
## Version Context
Current versions (November 2025):
- **Foundry**: v1.5.0
- **Solidity**: 0.8.30
- **OpenZeppelin Contracts**: v5.x
- **forge-std**: v1.9.x

View File

@@ -0,0 +1,520 @@
# Solidity Security & Audit Patterns
Security vulnerabilities, defensive patterns, and Foundry testing strategies for smart contract audits.
## Critical Vulnerabilities
### Reentrancy
**Risk:** External calls allow recursive entry before state updates, draining contracts.
```solidity
// VULNERABLE: State update after external call
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
(bool sent,) = msg.sender.call{value: amount}("");
require(sent);
balances[msg.sender] -= amount; // Too late!
}
// SECURE: CEI pattern + reentrancy guard
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Effects first
(bool sent,) = msg.sender.call{value: amount}("");
require(sent); // Interactions last
}
```
**Foundry Test:**
```solidity
contract ReentrancyAttacker {
Vault vault;
uint256 count;
receive() external payable {
if (count++ < 5 && address(vault).balance > 0) {
vault.withdraw(1 ether);
}
}
function attack() external {
vault.withdraw(1 ether);
}
}
function test_reentrancy_protected() public {
ReentrancyAttacker attacker = new ReentrancyAttacker(vault);
deal(address(attacker), 2 ether);
attacker.deposit{value: 1 ether}();
vm.expectRevert("ReentrancyGuard");
attacker.attack();
}
```
### Access Control
**Risk:** Missing permission checks allow unauthorized privileged operations.
```solidity
// VULNERABLE: No access control
function drain() public {
payable(owner).transfer(address(this).balance);
}
// VULNERABLE: Using tx.origin
function authorize(address user) public {
require(tx.origin == admin); // Phishing vulnerable!
}
// SECURE: Role-based access
function drain() external onlyRole(ADMIN_ROLE) {
payable(msg.sender).transfer(address(this).balance);
}
```
**Foundry Test:**
```solidity
function test_accessControl_unauthorized() public {
vm.prank(attacker);
vm.expectRevert(
abi.encodeWithSelector(
AccessControl.AccessControlUnauthorizedAccount.selector,
attacker,
vault.ADMIN_ROLE()
)
);
vault.drain();
}
```
### Oracle Manipulation
**Risk:** Manipulated price feeds cause unfair liquidations or loan exploits.
```solidity
// VULNERABLE: Spot price (flash-loanable)
function getPrice() public view returns (uint256) {
return dex.spotPrice(token);
}
// SECURE: Chainlink with staleness check
function getPrice() public view returns (int256) {
(
uint80 roundId,
int256 price,
,
uint256 updatedAt,
uint80 answeredInRound
) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
require(answeredInRound >= roundId, "Stale round");
require(block.timestamp - updatedAt < 3600, "Price too old");
return price;
}
```
**Foundry Test:**
```solidity
function test_oracle_staleness() public {
vm.warp(block.timestamp + 4000); // Past staleness threshold
vm.expectRevert("Price too old");
oracle.getPrice();
}
```
### Integer Overflow/Underflow
Solidity 0.8+ has built-in checks, but watch for:
```solidity
// VULNERABLE: Unchecked block disables protection
unchecked {
balance -= amount; // Can underflow!
}
// VULNERABLE: Type casting
uint8 small = uint8(largeNumber); // Truncates silently
// SECURE: Explicit checks
require(balance >= amount, "Insufficient balance");
unchecked { balance -= amount; }
```
### Front-Running / MEV
**Risk:** Attackers see pending transactions and extract value.
**Mitigations:**
- Commit-reveal schemes for sensitive operations
- Flashbots for transaction privacy
- Slippage protection in DEX trades
- Batch auctions instead of first-come-first-served
```solidity
// Commit-reveal pattern
function commit(bytes32 hash) external {
commits[msg.sender] = hash;
commitTime[msg.sender] = block.timestamp;
}
function reveal(uint256 value, bytes32 salt) external {
require(block.timestamp >= commitTime[msg.sender] + 1 hours);
require(commits[msg.sender] == keccak256(abi.encode(value, salt)));
// Process value
}
```
### Flash Loan Attacks
**Risk:** Attackers borrow large amounts to manipulate protocol state within single transaction.
**Mitigations:**
- Use TWAP instead of spot prices
- Require multi-block operations
- Over-collateralization requirements
- Block flash loans if not needed
### Signature Malleability
**Risk:** Attackers modify signatures to replay transactions.
```solidity
// VULNERABLE: No nonce or deadline
function permit(address owner, uint256 value, bytes calldata sig) external {
// Can be replayed!
}
// SECURE: EIP-712 with nonce and deadline
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
) external {
require(block.timestamp <= deadline, "Expired");
// Verify EIP-712 signature with nonce
nonces[owner]++;
}
```
### Storage Collision (Proxies)
**Risk:** Proxy and implementation have different storage layouts.
```solidity
// WRONG: Storage mismatch
contract Proxy {
address implementation; // slot 0
address owner; // slot 1
}
contract Implementation {
address owner; // slot 0 - COLLISION!
}
// CORRECT: Use EIP-1967 storage slots
bytes32 constant IMPL_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
```
### Denial of Service
**Patterns:**
- Unbounded loops exhausting gas
- External calls that always revert
- Block gas limit exceeded
```solidity
// VULNERABLE: Unbounded loop
function distributeAll() external {
for (uint256 i = 0; i < users.length; i++) {
payable(users[i]).transfer(rewards[i]);
}
}
// SECURE: Paginated distribution
function distributeBatch(uint256 start, uint256 count) external {
require(count <= 100, "Batch too large");
uint256 end = start + count;
if (end > users.length) end = users.length;
for (uint256 i = start; i < end; i++) {
payable(users[i]).transfer(rewards[i]);
}
}
```
## Foundry Security Testing
### Fuzz Testing
Generate random inputs to find edge cases:
```solidity
function testFuzz_transfer(address to, uint256 amount) public {
vm.assume(to != address(0));
vm.assume(to != address(token));
amount = bound(amount, 0, token.balanceOf(address(this)));
uint256 balanceBefore = token.balanceOf(to);
token.transfer(to, amount);
assertEq(token.balanceOf(to), balanceBefore + amount);
}
```
**Configuration:**
```toml
[fuzz]
runs = 10000
seed = "0x1234"
max_test_rejects = 65536
```
### Invariant Testing
Test properties that must always hold:
```solidity
contract VaultInvariant is Test {
Vault vault;
VaultHandler handler;
function setUp() public {
vault = new Vault();
handler = new VaultHandler(vault);
targetContract(address(handler));
}
// Total deposits must equal total shares value
function invariant_solvency() public view {
assertGe(
vault.totalAssets(),
vault.totalSupply(),
"Vault insolvent"
);
}
// Sum of balances equals total supply
function invariant_balanceSum() public view {
uint256 sum = 0;
address[] memory users = handler.getUsers();
for (uint256 i = 0; i < users.length; i++) {
sum += vault.balanceOf(users[i]);
}
assertEq(sum, vault.totalSupply());
}
}
```
### Handler Pattern
Control fuzz inputs and track state:
```solidity
contract VaultHandler is Test {
Vault vault;
address[] public users;
uint256 public ghost_totalDeposited;
constructor(Vault _vault) {
vault = _vault;
users.push(makeAddr("alice"));
users.push(makeAddr("bob"));
}
function deposit(uint256 userSeed, uint256 amount) external {
address user = users[bound(userSeed, 0, users.length - 1)];
amount = bound(amount, 1, 1e24);
deal(address(vault.asset()), user, amount);
vm.startPrank(user);
vault.asset().approve(address(vault), amount);
vault.deposit(amount, user);
vm.stopPrank();
ghost_totalDeposited += amount;
}
function withdraw(uint256 userSeed, uint256 shares) external {
address user = users[bound(userSeed, 0, users.length - 1)];
shares = bound(shares, 0, vault.balanceOf(user));
if (shares == 0) return;
vm.prank(user);
uint256 assets = vault.redeem(shares, user, user);
ghost_totalDeposited -= assets;
}
}
```
**Configuration:**
```toml
[invariant]
runs = 256
depth = 100
fail_on_revert = false
```
### Fork Testing
Test against real mainnet state:
```solidity
function setUp() public {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 18000000);
}
function testFork_usdcIntegration() public {
IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
address whale = 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503;
uint256 balanceBefore = usdc.balanceOf(address(this));
vm.prank(whale);
usdc.transfer(address(this), 1_000_000e6);
assertEq(usdc.balanceOf(address(this)), balanceBefore + 1_000_000e6);
}
```
### Symbolic Execution (Halmos)
Prove properties mathematically:
```solidity
function check_transferPreservesSupply(address from, address to, uint256 amount) public {
uint256 supplyBefore = token.totalSupply();
token.transfer(from, to, amount);
assert(token.totalSupply() == supplyBefore);
}
```
Run with: `halmos --contract MyTest`
## Pre-Audit Checklist
### Code Quality
- [ ] NatSpec documentation on all public functions
- [ ] No console.log or debug statements
- [ ] All TODO/FIXME resolved
- [ ] Consistent naming conventions
### Security Patterns
- [ ] Reentrancy guards on external-calling functions
- [ ] CEI pattern followed
- [ ] Access control on privileged functions
- [ ] No tx.origin for authentication
- [ ] SafeERC20 for token transfers
- [ ] Input validation on all parameters
### Testing
- [ ] >95% code coverage
- [ ] Fuzz tests with 10,000+ runs
- [ ] Invariant tests for core properties
- [ ] Edge case tests (zero, max values)
- [ ] Fork tests for integrations
### Static Analysis
- [ ] Slither: no HIGH/CRITICAL issues
- [ ] Manual review of MEDIUM issues
### Documentation
- [ ] README with security assumptions
- [ ] Threat model documented
- [ ] Known limitations listed
- [ ] Admin functions documented
## Common Audit Findings
### Missing Return Value Check
```solidity
// BAD
token.transfer(user, amount);
// GOOD
require(token.transfer(user, amount), "Transfer failed");
// BEST
IERC20(token).safeTransfer(user, amount);
```
### Uninitialized Proxy
```solidity
// BAD: Anyone can initialize
function initialize(address _owner) external {
owner = _owner;
}
// GOOD: Use initializer modifier
function initialize(address _owner) external initializer {
owner = _owner;
}
```
### Precision Loss
```solidity
// BAD: Division before multiplication
uint256 share = (amount / total) * balance; // Rounds to 0
// GOOD: Multiplication before division
uint256 share = (amount * balance) / total;
```
### Unchecked Array Access
```solidity
// BAD
return users[index]; // Can revert
// GOOD
require(index < users.length, "Out of bounds");
return users[index];
```
## Security Tools
**Static Analysis:**
- Slither: `slither . --json report.json`
- Mythril: `myth analyze contracts/Vault.sol`
**Fuzzing:**
- Forge fuzz: Built-in
- Echidna: Property-based fuzzing
**Formal Verification:**
- Certora Prover
- Halmos
## Foundry Security Commands
```bash
# Run all tests with coverage
forge coverage --report lcov
# Fuzz with high iterations
forge test --fuzz-runs 50000
# Invariant testing
forge test --match-contract Invariant -vv
# Fork testing
forge test --fork-url $RPC_URL
# Gas analysis
forge test --gas-report
# Debug failing test
forge test --match-test testName -vvvv
```

View File

@@ -0,0 +1,641 @@
# Modern Solidity (0.8.30)
Complete guide to Solidity 0.8.20-0.8.30 features, gas optimization, and security patterns.
## Version Feature Summary
| Version | Key Features |
|---------|-------------|
| 0.8.30 | Prague EVM default, NatSpec for enums |
| 0.8.29 | Custom storage layout (`layout at`) |
| 0.8.28 | Transient storage for value types |
| 0.8.27 | Transient storage parser support |
| 0.8.26 | `require(bool, Error)` custom errors |
| 0.8.25 | Cancun EVM default, MCOPY opcode |
| 0.8.24 | `blobbasefee`, `blobhash()`, tload/tstore in Yul |
| 0.8.22 | File-level events |
| 0.8.21 | Relaxed immutable initialization |
| 0.8.20 | Shanghai EVM default, PUSH0 |
| 0.8.19 | Custom operators for user-defined types |
| 0.8.18 | Named mapping parameters |
## Custom Errors (0.8.4+)
Gas-efficient error handling.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
error Unauthorized(address caller, address required);
error InsufficientBalance(uint256 available, uint256 required);
error InvalidAmount();
error Expired(uint256 deadline, uint256 current);
contract Token {
mapping(address => uint256) public balanceOf;
address public owner;
constructor() {
owner = msg.sender;
}
function transfer(address to, uint256 amount) external {
if (amount == 0) revert InvalidAmount();
if (amount > balanceOf[msg.sender]) {
revert InsufficientBalance(balanceOf[msg.sender], amount);
}
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}
function adminFunction() external {
if (msg.sender != owner) {
revert Unauthorized(msg.sender, owner);
}
// ...
}
}
```
### require with Custom Errors (0.8.26+)
```solidity
function transfer(address to, uint256 amount) external {
require(amount > 0, InvalidAmount());
require(
amount <= balanceOf[msg.sender],
InsufficientBalance(balanceOf[msg.sender], amount)
);
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}
```
### Gas Comparison
```solidity
// String error: ~4 bytes selector + string data
require(x > 0, "Amount must be greater than zero");
// Custom error: ~4 bytes selector only
if (x == 0) revert InvalidAmount();
// Custom error with params: ~4 bytes + encoded params
if (x > balance) revert InsufficientBalance(balance, x);
// Savings: 50-200+ gas per error
```
## Transient Storage (0.8.28+)
Cheap temporary storage cleared after transaction.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract ReentrancyGuard {
bool transient locked;
modifier nonReentrant() {
require(!locked, "Reentrancy");
locked = true;
_;
locked = false; // CRITICAL: Reset for composability
}
function withdraw() external nonReentrant {
// Safe from reentrancy
}
}
```
### Flash Loan Example
```solidity
contract FlashLender {
IERC20 public token;
bool transient flashLoanActive;
function flashLoan(uint256 amount, address receiver) external {
require(!flashLoanActive, "Flash loan active");
flashLoanActive = true;
uint256 balanceBefore = token.balanceOf(address(this));
token.transfer(receiver, amount);
IFlashBorrower(receiver).onFlashLoan(amount);
require(
token.balanceOf(address(this)) >= balanceBefore,
"Flash loan not repaid"
);
flashLoanActive = false;
}
}
```
### Gas Comparison
| Operation | Persistent Storage | Transient Storage |
|-----------|-------------------|-------------------|
| First write | 20,000+ gas | 100 gas |
| Subsequent write | 2,900 gas | 100 gas |
| Read | 100 gas | 100 gas |
**Important**: Always reset transient storage at function exit for composability.
## Immutable Variables (Relaxed in 0.8.21+)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
contract Config {
address public immutable owner;
uint256 public immutable deployTime;
bytes32 public immutable configHash;
uint256 public immutable maxSupply;
constructor(uint256 _maxSupply, bytes32 _configHash) {
owner = msg.sender;
deployTime = block.timestamp;
// Relaxed: can read/write immutables anywhere in constructor
if (_maxSupply == 0) {
maxSupply = 1_000_000e18;
} else {
maxSupply = _maxSupply;
}
// Can use other immutables
configHash = _configHash != bytes32(0)
? _configHash
: keccak256(abi.encode(owner, maxSupply));
}
}
```
### Gas Benefits
- Immutable: **3 gas** (value inlined)
- Storage: **100 gas** (SLOAD)
- Constant: **0 gas** (compile-time constant)
## User-Defined Types with Operators (0.8.19+)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
type Amount is uint256;
type Price is uint256;
using {add as +, sub as -, mul, eq as ==, lt as <} for Amount global;
function add(Amount a, Amount b) pure returns (Amount) {
return Amount.wrap(Amount.unwrap(a) + Amount.unwrap(b));
}
function sub(Amount a, Amount b) pure returns (Amount) {
return Amount.wrap(Amount.unwrap(a) - Amount.unwrap(b));
}
function mul(Amount a, Price p) pure returns (uint256) {
return Amount.unwrap(a) * Price.unwrap(p);
}
function eq(Amount a, Amount b) pure returns (bool) {
return Amount.unwrap(a) == Amount.unwrap(b);
}
function lt(Amount a, Amount b) pure returns (bool) {
return Amount.unwrap(a) < Amount.unwrap(b);
}
contract TypeSafe {
function calculate(Amount a, Amount b, Price p) external pure returns (uint256) {
Amount total = a + b;
if (total < Amount.wrap(100)) {
return 0;
}
return total.mul(p);
}
}
```
## Named Mapping Parameters (0.8.18+)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Token {
// Highly readable
mapping(address account => uint256 balance) public balanceOf;
mapping(address owner => mapping(address spender => uint256 amount)) public allowance;
// Complex mappings
mapping(uint256 tokenId => mapping(address operator => bool approved)) public operatorApproval;
}
```
## File-Level Events (0.8.22+)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
// File-level event
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
}
contract Token is IERC20 {
mapping(address => uint256) public balanceOf;
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
```
## Custom Storage Layout (0.8.29+)
For EIP-7702 delegate implementations:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
// Delegate 1: Storage starts at 0x1000
contract DelegateA layout at 0x1000 {
uint256 public valueA; // Slot 0x1000
function setA(uint256 v) external {
valueA = v;
}
}
// Delegate 2: Storage starts at 0x2000
contract DelegateB layout at 0x2000 {
uint256 public valueB; // Slot 0x2000
function setB(uint256 v) external {
valueB = v;
}
}
// Both can be delegated to from same account without storage collision
```
## Checked vs Unchecked Arithmetic
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract Arithmetic {
// Default: checked (reverts on overflow)
function checkedAdd(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // Reverts if overflow
}
// Explicit: unchecked (wraps on overflow)
function uncheckedIncrement(uint256 i) public pure returns (uint256) {
unchecked {
return i + 1; // Does not revert
}
}
// Common pattern: loop counter
function sum(uint256[] calldata arr) public pure returns (uint256 total) {
for (uint256 i = 0; i < arr.length;) {
total += arr[i];
unchecked { ++i; } // Safe: i < arr.length guarantees no overflow
}
}
}
```
### When to Use Unchecked
Safe scenarios:
- Loop counters bounded by array length
- Incrementing from known safe values
- Subtracting after explicit bounds check
- Timestamp/block number arithmetic
Never use unchecked:
- User-provided inputs without validation
- Token amounts in DeFi
- Any value that could overflow
## Gas Optimization Techniques
### 1. Storage Packing
```solidity
// Bad: 3 slots
contract Unpacked {
uint256 a; // Slot 0
uint128 b; // Slot 1
uint128 c; // Slot 2
}
// Good: 2 slots
contract Packed {
uint256 a; // Slot 0
uint128 b; // Slot 1 (first half)
uint128 c; // Slot 1 (second half)
}
```
### 2. Caching Storage in Memory
```solidity
// Bad: Multiple SLOADs
function bad(uint256 amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
emit Transfer(msg.sender, amount, balances[msg.sender]);
}
// Good: Cache storage
function good(uint256 amount) external {
uint256 balance = balances[msg.sender];
require(balance >= amount);
uint256 newBalance = balance - amount;
balances[msg.sender] = newBalance;
emit Transfer(msg.sender, amount, newBalance);
}
```
### 3. Use calldata for Read-Only Arrays
```solidity
// Bad: Copies to memory
function bad(uint256[] memory data) external { }
// Good: Direct calldata access
function good(uint256[] calldata data) external { }
```
### 4. Short-Circuit Conditionals
```solidity
// Cheaper checks first
function check(uint256 amount, address user) external view {
// Cheap: comparison
// Expensive: storage read
if (amount > 0 && balances[user] >= amount) {
// ...
}
}
```
### 5. Use Constants and Immutables
```solidity
// Constant: 0 gas (inlined at compile time)
uint256 constant MAX_SUPPLY = 1_000_000e18;
// Immutable: 3 gas (inlined in bytecode)
address immutable owner;
// Storage: 100 gas (SLOAD)
address _owner;
```
### 6. Increment Patterns
```solidity
// Most efficient
unchecked { ++i; }
// Less efficient
unchecked { i++; }
// Least efficient (checked)
i++;
```
### Gas Costs Reference
| Operation | Gas Cost |
|-----------|----------|
| SLOAD (warm) | 100 |
| SLOAD (cold) | 2,100 |
| SSTORE (zero to non-zero) | 20,000 |
| SSTORE (non-zero to non-zero) | 2,900 |
| SSTORE (non-zero to zero) | Refund 4,800 |
| TLOAD | 100 |
| TSTORE | 100 |
| Memory read/write | 3 |
| Calldata read | 3 |
## Security Best Practices
### 1. Checks-Effects-Interactions
```solidity
function withdraw(uint256 amount) external {
// CHECKS
require(amount <= balances[msg.sender], "Insufficient");
// EFFECTS
balances[msg.sender] -= amount;
// INTERACTIONS
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
```
### 2. Reentrancy Protection
```solidity
// Using transient storage (0.8.28+)
bool transient locked;
modifier nonReentrant() {
require(!locked);
locked = true;
_;
locked = false;
}
// Or OpenZeppelin ReentrancyGuard
```
### 3. Access Control
```solidity
error Unauthorized();
modifier onlyOwner() {
if (msg.sender != owner) revert Unauthorized();
_;
}
// Use OpenZeppelin AccessControl for complex permissions
```
### 4. Safe External Calls
```solidity
// Check return value
(bool success, bytes memory data) = target.call(payload);
require(success, "Call failed");
// Or use SafeCall pattern
function safeTransferETH(address to, uint256 amount) internal {
(bool success,) = to.call{value: amount}("");
if (!success) revert TransferFailed();
}
```
### 5. Input Validation
```solidity
function deposit(uint256 amount) external {
if (amount == 0) revert InvalidAmount();
if (amount > MAX_DEPOSIT) revert ExceedsMax(amount, MAX_DEPOSIT);
// Proceed
}
```
### 6. Integer Overflow Protection
```solidity
// Built-in since 0.8.0, but be careful with unchecked
function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
// This reverts on overflow (default behavior)
return a + b;
}
```
### 7. Timestamp Dependence
```solidity
// Don't use for randomness
// Small manipulations possible by miners
// OK for time-based logic with tolerance
require(block.timestamp >= deadline, "Not yet");
// Bad: exact timing
require(block.timestamp == exactTime, "Wrong time");
```
## EVM Version Features
### Prague (0.8.30 default)
- All Cancun features
- EIP-7702 preparation
### Cancun (0.8.25 default)
- Transient storage (TLOAD, TSTORE)
- MCOPY for memory copying
- BLOBHASH, BLOBBASEFEE
### Shanghai (0.8.20 default)
- PUSH0 opcode
### Block Properties by Version
```solidity
// Always available
block.number
block.timestamp
block.chainid
block.coinbase
block.gaslimit
// Paris+
block.prevrandao // Replaces block.difficulty
// Cancun+
block.blobbasefee
blobhash(index)
```
## Common Patterns
### Factory Pattern
```solidity
contract Factory {
event Created(address indexed instance);
function create(bytes32 salt, bytes calldata initData) external returns (address) {
address instance = address(new Contract{salt: salt}(initData));
emit Created(instance);
return instance;
}
function predictAddress(bytes32 salt, bytes calldata initData) external view returns (address) {
bytes32 hash = keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(Contract).creationCode,
abi.encode(initData)
))
));
return address(uint160(uint256(hash)));
}
}
```
### Minimal Proxy (EIP-1167)
```solidity
function clone(address implementation) internal returns (address instance) {
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
instance := create(0, ptr, 0x37)
}
require(instance != address(0), "Clone failed");
}
```
### Merkle Proof Verification
```solidity
function verify(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) public pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length;) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
unchecked { ++i; }
}
return computedHash == root;
}
```

View File

@@ -0,0 +1,635 @@
# Foundry Testing Guide
Complete guide to testing smart contracts with Forge.
## Test Structure
### File Conventions
- Test files: `ContractName.t.sol`
- Script files: `ScriptName.s.sol`
- Test contracts inherit from `Test`
### Function Prefixes
| Prefix | Purpose |
|--------|---------|
| `test_` | Unit test |
| `testFuzz_` | Fuzz test |
| `testFork_` | Fork test |
| `invariant_` | Invariant test |
| `test_RevertIf_` | Expected revert |
| `test_RevertWhen_` | Expected revert |
### Basic Test Contract
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console} from "forge-std/Test.sol";
import {Vault} from "../src/Vault.sol";
contract VaultTest is Test {
Vault public vault;
address public alice;
address public bob;
function setUp() public {
vault = new Vault();
alice = makeAddr("alice");
bob = makeAddr("bob");
deal(alice, 100 ether);
deal(bob, 100 ether);
}
function test_Deposit() public {
vm.prank(alice);
vault.deposit{value: 1 ether}();
assertEq(vault.balanceOf(alice), 1 ether);
}
}
```
## Unit Testing
### Assertions
```solidity
// Equality
assertEq(actual, expected);
assertEq(actual, expected, "custom message");
assertNotEq(actual, expected);
// Comparisons
assertGt(a, b); // a > b
assertGe(a, b); // a >= b
assertLt(a, b); // a < b
assertLe(a, b); // a <= b
// Boolean
assertTrue(condition);
assertFalse(condition);
// Approximation (for rounding)
assertApproxEqAbs(actual, expected, maxDelta);
assertApproxEqRel(actual, expected, maxPercentDelta); // 1e18 = 100%
// Arrays
assertEq(arr1, arr2);
```
### Testing Reverts
```solidity
// Expect any revert
vm.expectRevert();
vault.withdraw(1000 ether);
// Expect specific message
vm.expectRevert("Insufficient balance");
vault.withdraw(1000 ether);
// Expect custom error (selector only)
vm.expectRevert(InsufficientBalance.selector);
vault.withdraw(1000 ether);
// Expect custom error with parameters
vm.expectRevert(
abi.encodeWithSelector(
InsufficientBalance.selector,
0, // available
1000 // required
)
);
vault.withdraw(1000 ether);
// Partial revert (match selector only)
vm.expectPartialRevert(InsufficientBalance.selector);
vault.withdraw(1000 ether);
```
### Testing Events
```solidity
// Expect event emission
vm.expectEmit(true, true, false, true);
// topic1, topic2, topic3, data
emit Transfer(alice, bob, 100);
// Call that should emit the event
token.transfer(bob, 100);
// Record and inspect logs
vm.recordLogs();
token.transfer(bob, 100);
Vm.Log[] memory logs = vm.getRecordedLogs();
assertEq(logs[0].topics[0], keccak256("Transfer(address,address,uint256)"));
```
### Testing Access Control
```solidity
function test_OnlyOwner() public {
// Should succeed as owner
vault.adminFunction();
// Should fail as non-owner
vm.expectRevert("Ownable: caller is not the owner");
vm.prank(alice);
vault.adminFunction();
}
```
## Fuzz Testing
Forge automatically generates random inputs for test parameters.
### Basic Fuzz Test
```solidity
function testFuzz_Deposit(uint256 amount) public {
// Bound input to valid range
amount = bound(amount, 0.01 ether, 100 ether);
deal(alice, amount);
vm.prank(alice);
vault.deposit{value: amount}();
assertEq(vault.balanceOf(alice), amount);
}
```
### Fuzz Configuration
```toml
# foundry.toml
[fuzz]
runs = 256 # Number of fuzz runs
seed = 42 # Deterministic seed (optional)
max_test_rejects = 65536
```
### Fixtures (Pre-defined Values)
```solidity
// Array fixture (must match parameter name)
uint256[] public fixtureAmount = [0, 1, 100, type(uint256).max];
// Function fixture
function fixtureUser() public returns (address[] memory) {
address[] memory users = new address[](3);
users[0] = makeAddr("alice");
users[1] = makeAddr("bob");
users[2] = address(0);
return users;
}
function testFuzz_Transfer(uint256 amount, address user) public {
// amount will be one of: 0, 1, 100, max
// user will be one of: alice, bob, address(0)
}
```
### Using `vm.assume()`
```solidity
function testFuzz_Transfer(address recipient) public {
// Skip invalid inputs
vm.assume(recipient != address(0));
vm.assume(recipient != address(token));
// Or use helper
assumeNotZeroAddress(recipient);
assumeNotPrecompile(recipient);
}
```
## Invariant Testing
Test that properties hold across random function call sequences.
### Basic Invariant Test
```solidity
contract VaultInvariantTest is Test {
Vault public vault;
VaultHandler public handler;
function setUp() public {
vault = new Vault();
handler = new VaultHandler(vault);
// Only fuzz the handler
targetContract(address(handler));
}
function invariant_SolvencyCheck() public view {
assertGe(
address(vault).balance,
vault.totalDeposits(),
"Vault is insolvent"
);
}
function invariant_TotalSupplyConsistent() public view {
assertEq(
vault.totalSupply(),
sumAllBalances(),
"Supply mismatch"
);
}
}
```
### Handler Pattern
Wrap target contract to bound inputs and track state:
```solidity
contract VaultHandler is Test {
Vault public vault;
uint256 public ghost_totalDeposited;
address[] public actors;
constructor(Vault _vault) {
vault = _vault;
actors.push(makeAddr("alice"));
actors.push(makeAddr("bob"));
actors.push(makeAddr("charlie"));
}
function deposit(uint256 actorIndex, uint256 amount) external {
// Bound inputs
actorIndex = bound(actorIndex, 0, actors.length - 1);
amount = bound(amount, 0.01 ether, 10 ether);
address actor = actors[actorIndex];
deal(actor, amount);
vm.prank(actor);
vault.deposit{value: amount}();
// Track ghost variable
ghost_totalDeposited += amount;
}
function withdraw(uint256 actorIndex, uint256 amount) external {
actorIndex = bound(actorIndex, 0, actors.length - 1);
address actor = actors[actorIndex];
uint256 balance = vault.balanceOf(actor);
amount = bound(amount, 0, balance);
vm.prank(actor);
vault.withdraw(amount);
ghost_totalDeposited -= amount;
}
}
```
### Target Configuration
```solidity
function setUp() public {
// Only fuzz specific contracts
targetContract(address(handler));
// Exclude contracts from fuzzing
excludeContract(address(vault));
excludeContract(address(token));
// Only use specific senders
targetSender(alice);
targetSender(bob);
// Exclude specific senders
excludeSender(address(0));
// Target specific functions
bytes4[] memory selectors = new bytes4[](2);
selectors[0] = handler.deposit.selector;
selectors[1] = handler.withdraw.selector;
targetSelector(FuzzSelector({
addr: address(handler),
selectors: selectors
}));
}
```
### Invariant Configuration
```toml
# foundry.toml
[invariant]
runs = 256 # Number of sequences
depth = 50 # Calls per sequence
fail_on_revert = false # Continue on reverts
shrink_run_limit = 5000 # Shrinking iterations
```
## Fork Testing
Test against live blockchain state.
### Basic Fork Test
```solidity
contract ForkTest is Test {
uint256 mainnetFork;
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address constant WHALE = 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503;
function setUp() public {
mainnetFork = vm.createFork(vm.envString("MAINNET_RPC_URL"));
vm.selectFork(mainnetFork);
}
function testFork_USDCTransfer() public {
vm.prank(WHALE);
IERC20(USDC).transfer(address(this), 1000e6);
assertEq(IERC20(USDC).balanceOf(address(this)), 1000e6);
}
}
```
### Fork Cheatcodes
```solidity
// Create fork
uint256 forkId = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/...");
uint256 forkId = vm.createFork("mainnet"); // Uses foundry.toml rpc_endpoints
uint256 forkId = vm.createFork("mainnet", 18000000); // Pin to block
// Create and select in one call
vm.createSelectFork("mainnet");
// Switch between forks
vm.selectFork(mainnetFork);
vm.selectFork(arbitrumFork);
// Get active fork
uint256 active = vm.activeFork();
// Roll fork to different block
vm.rollFork(18500000);
vm.rollFork(arbitrumFork, 150000000);
// Make account persist across forks
vm.makePersistent(address(myContract));
vm.makePersistent(alice, bob, charlie);
// Check persistence
bool isPersistent = vm.isPersistent(address(myContract));
// Revoke persistence
vm.revokePersistent(address(myContract));
```
### Multi-Fork Testing
```solidity
function testFork_CrossChain() public {
// Deploy on mainnet
vm.selectFork(mainnetFork);
address mainnetToken = address(new Token());
// Deploy on Arbitrum
vm.selectFork(arbitrumFork);
address arbitrumToken = address(new Token());
// Verify different deployments
vm.selectFork(mainnetFork);
assertEq(Token(mainnetToken).name(), "Token");
vm.selectFork(arbitrumFork);
assertEq(Token(arbitrumToken).name(), "Token");
}
```
### Fork Configuration
```toml
# foundry.toml
[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
arbitrum = "${ARBITRUM_RPC_URL}"
optimism = "${OPTIMISM_RPC_URL}"
```
## Differential Testing
Compare implementations against reference.
```solidity
function testDifferential_MerkleRoot(bytes32[] memory leaves) public {
vm.assume(leaves.length > 0 && leaves.length < 100);
// Solidity implementation
bytes32 solidityRoot = merkle.getRoot(leaves);
// Reference implementation (via FFI)
string[] memory cmd = new string[](3);
cmd[0] = "node";
cmd[1] = "scripts/merkle.js";
cmd[2] = vm.toString(abi.encode(leaves));
bytes memory result = vm.ffi(cmd);
bytes32 jsRoot = abi.decode(result, (bytes32));
assertEq(solidityRoot, jsRoot, "Merkle root mismatch");
}
```
## Complete Cheatcode Reference
### State Manipulation
```solidity
// ETH balance
vm.deal(address, uint256);
// Code at address
vm.etch(address, bytes memory code);
// Storage
vm.store(address, bytes32 slot, bytes32 value);
bytes32 value = vm.load(address, bytes32 slot);
// Nonce
vm.setNonce(address, uint64 nonce);
vm.resetNonce(address);
uint64 nonce = vm.getNonce(address);
// Copy storage
vm.copyStorage(address from, address to);
```
### Caller Context
```solidity
// Single call
vm.prank(address sender);
vm.prank(address sender, address origin);
// Multiple calls
vm.startPrank(address sender);
vm.startPrank(address sender, address origin);
vm.stopPrank();
```
### Time & Block
```solidity
vm.warp(uint256 timestamp); // block.timestamp
vm.roll(uint256 blockNumber); // block.number
vm.fee(uint256 gasPrice); // tx.gasprice
vm.difficulty(uint256 difficulty); // block.difficulty
vm.coinbase(address miner); // block.coinbase
vm.chainId(uint256 chainId); // block.chainid
vm.prevrandao(bytes32 prevrandao); // block.prevrandao
```
### Expectations
```solidity
// Reverts
vm.expectRevert();
vm.expectRevert(bytes memory message);
vm.expectRevert(bytes4 selector);
vm.expectPartialRevert(bytes4 selector);
// Events
vm.expectEmit(bool topic1, bool topic2, bool topic3, bool data);
vm.expectEmit(bool topic1, bool topic2, bool topic3, bool data, address emitter);
// Calls
vm.expectCall(address target, bytes memory data);
vm.expectCall(address target, uint256 value, bytes memory data);
vm.expectCall(address target, bytes memory data, uint64 count);
```
### Snapshots
```solidity
uint256 snapshot = vm.snapshot();
vm.revertTo(snapshot);
vm.revertToAndDelete(snapshot);
```
### Environment
```solidity
// Read .env
string memory value = vm.envString("KEY");
uint256 value = vm.envUint("KEY");
address value = vm.envAddress("KEY");
bool value = vm.envBool("KEY");
bytes32 value = vm.envBytes32("KEY");
// With default
uint256 value = vm.envOr("KEY", uint256(100));
// Check existence
bool exists = vm.envExists("KEY");
```
### Cryptography
```solidity
// Sign message
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
(bytes32 r, bytes32 vs) = vm.signCompact(privateKey, digest);
// Get address from private key
address addr = vm.addr(privateKey);
// Derive key from mnemonic
uint256 key = vm.deriveKey(mnemonic, index);
uint256 key = vm.deriveKey(mnemonic, path, index);
// Create wallet
Vm.Wallet memory wallet = vm.createWallet("label");
Vm.Wallet memory wallet = vm.createWallet(privateKey);
```
### Labels & Debugging
```solidity
vm.label(address, "name");
string memory name = vm.getLabel(address);
vm.breakpoint(); // Debugger breakpoint
vm.breakpoint("name"); // Named breakpoint
```
### Gas Metering
```solidity
vm.pauseGasMetering();
// ... expensive operations ...
vm.resumeGasMetering();
Vm.Gas memory gas = vm.lastCallGas();
```
### File I/O
```solidity
string memory content = vm.readFile("path/to/file");
vm.writeFile("path/to/file", "content");
bool exists = vm.exists("path/to/file");
vm.removeFile("path/to/file");
```
## Test Verbosity Levels
```bash
forge test # Summary only
forge test -v # + Logs
forge test -vv # + Assertion errors
forge test -vvv # + Stack traces (failures)
forge test -vvvv # + Stack traces (all) + setup
forge test -vvvvv # + All traces always
```
## Best Practices
### Test Organization
1. One test file per contract
2. Group related tests in same contract
3. Use descriptive function names
4. Include failure reason in test name
### Fuzz Testing
1. Always `bound()` numeric inputs
2. Use `vm.assume()` sparingly (reduces coverage)
3. Use fixtures for edge cases
4. Set deterministic seed in CI
### Invariant Testing
1. Use handler pattern for complex protocols
2. Track ghost variables for assertions
3. Start with simple invariants
4. Increase depth/runs gradually
### Fork Testing
1. Pin to specific block for reproducibility
2. Configure RPC endpoints in foundry.toml
3. Use `vm.makePersistent()` for deployed contracts
4. Cache responses with deterministic seed
### General
1. Test public interface, not internal state
2. Use `makeAddr()` for labeled addresses
3. Use `deal()` instead of minting
4. Label important addresses for traces
5. Test both success and failure paths