Foundry Testing & Script Skill
Rules and patterns for Foundry tests. Find examples in the actual codebase.
Bundled References
| Reference |
Content |
When to Read |
./references/test-infrastructure.md |
Constants, defaults, mocks |
When setting up tests |
./references/cheat-codes.md |
Common cheatcode patterns |
When using vm cheatcodes |
./references/invariant-patterns.md |
Handlers, stores, invariants |
When writing invariant tests |
./references/formal-verification.md |
Halmos, Certora, symbolic exec |
When proving correctness |
./references/deployment-scripts.md |
Script patterns, verification |
When writing deploy scripts |
./references/deployment-checklist.md |
Pre-mainnet deployment steps |
Before deploying to production |
./references/gas-benchmarking.md |
Snapshot, profiling, CI |
When measuring gas performance |
./references/sablier-conventions.md |
Sablier-specific patterns |
When working in Sablier repos |
Test Types
| Type |
Directory |
Naming |
Purpose |
| Integration |
tests/integration/concrete/ |
*.t.sol |
BTT-based concrete tests |
| Fuzz |
tests/integration/fuzz/ |
*.t.sol |
Property-based testing |
| Fork |
tests/fork/ |
*.t.sol |
Mainnet state testing |
| Invariant |
tests/invariant/ |
Invariant*.t.sol |
Stateful protocol properties |
| Scripts |
scripts/solidity/ |
*.s.sol |
Deployment/initialization |
1. Integration Tests (Concrete)
Naming Convention
| Pattern |
Usage |
test_RevertWhen_{Condition} |
Revert on input |
test_RevertGiven_{State} |
Revert on state |
test_When_{Condition} |
Success path |
Rules
- Stack modifiers to document BTT path (modifiers are often empty - just document the path)
- Expect events BEFORE action -
vm.expectEmit() then call function
- Assert state AFTER action - Check state changes after function executes
- Use revert helpers for common patterns (
expectRevert_DelegateCall, expectRevert_Null)
- Named parameters in assertions -
assertEq(actual, expected, "description")
Mock Rules
- Place all mocks in
tests/mocks/
- One mock per scenario (not one mega-mock)
- Naming:
*Good, *Reverting, *InvalidSelector, *Reentrant
2. Fuzz Tests
Naming Convention
testFuzz_{FunctionName}_{Scenario}
Rules
- Bound before assume -
_bound() is more efficient than vm.assume()
- Bound in dependency order - Independent params first, then dependent
- Never hardcode params with validation constraints
- Document fuzzed scenarios in NatSpec
Bounding Pattern
// 1. Bound independent params first
cliffDuration = boundUint40(cliffDuration, 0, MAX - 1);
// 2. Bound dependent params based on constraints
totalDuration = boundUint40(totalDuration, cliffDuration + 1, MAX);
3. Fork Tests
Rules
- Create fork with
vm.createSelectFork("ethereum")
- Use
deal() to give tokens to test users
- Use
assumeNoBlacklisted() for USDC/USDT
- Use
forceApprove() for non-standard tokens (USDT)
Token Quirks
| Token |
Issue |
Solution |
| USDC/USDT |
Blacklist |
assumeNoBlacklisted() |
| USDT |
Non-standard |
forceApprove() |
| Fee-on-transfer |
Balance diff |
Check actual received amount |
4. Invariant Tests
Architecture
tests/invariant/
├── handlers/ # State manipulation (call functions with bounded params)
├── stores/ # State tracking (record totals, IDs)
└── Invariant.t.sol
Rules
- Target handlers only -
targetContract(address(handler))
- Exclude protocol contracts -
excludeSender(address(vault))
- Use stores to track totals for invariant assertions
- Early return in handlers if preconditions not met
5. Solidity Scripts
Rules
- Inherit from
BaseScript with broadcast modifier
- Use env vars:
ETH_FROM, MNEMONIC
- Simulation first, then broadcast
Commands
# Simulation
forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC
# Broadcast
forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC --broadcast --verify
Running Tests
# By type
forge test --match-path "tests/integration/concrete/**"
forge test --match-path "tests/fork/**"
forge test --match-contract Invariant_Test
# Specific test
forge test --match-test test_WhenCallerRecipient -vvvv
# Fuzz with more runs
forge test --match-test testFuzz_ --fuzz-runs 1000
# Coverage
forge coverage --report lcov
Debugging
Verbosity Levels
| Flag |
Shows |
-v |
Logs for failing tests |
-vv |
Logs for all tests |
-vvv |
Stack traces for failures |
-vvvv |
Stack traces + setup traces |
-vvvvv |
Full execution traces |
Console Logging
import { console2 } from "forge-std/console2.sol";
console2.log("value:", someValue);
console2.log("address:", someAddress);
console2.logBytes32(someBytes32);
Debugging Commands
# Trace specific failing test
forge test --match-test test_MyTest -vvvv
# Gas report for a test
forge test --match-test test_MyTest --gas-report
# Debug in interactive debugger
forge debug --debug tests/MyTest.t.sol --sig "test_MyTest()"
# Inspect storage layout
forge inspect MyContract storage-layout
Debugging Tips
- Label addresses -
vm.label(addr, "Recipient") for readable traces
- Check state with logs - Add
console2.log before reverts
- Isolate failures - Run single test with
--match-test
- Compare gas - Use
--gas-report to spot unexpected costs
- Snapshot comparisons - Use
vm.snapshot() / vm.revertTo() to isolate state changes
Best Practices Summary
- Use constants from
Defaults/Constants - never hardcode
- Specialized mocks - one per scenario, all in
tests/mocks/
- Modifiers in
Modifiers.sol - centralize BTT path modifiers
- Label addresses with
vm.label() for traces
- Events before actions -
vm.expectEmit() then call
- Bound before assume - more efficient
External References
Example Invocations
Test this skill with these prompts:
- Integration test: "Write a concrete test for
withdraw that expects Errors.Flow_Overdraw when amount exceeds
balance"
- Fuzz test: "Create a fuzz test for
deposit that bounds amount between 1 and type(uint128).max"
- Fork test: "Write a fork test for USDC deposits on mainnet with blacklist handling"
- Invariant test: "Create an invariant handler for the
deposit and withdraw functions"
- Deploy script: "Write a deployment script for SablierFlow with verification"
1---2name: cli-forge3description: Write Foundry-based tests and scripts. Trigger phrases - foundry testing, write test, fuzz test, fork test, invariant test, deploy script, gas benchmark, coverage, or when working in tests/ or scripts/ directories.4---56# Foundry Testing & Script Skill78Rules and patterns for Foundry tests. Find examples in the actual codebase.910## Bundled References1112| Reference | Content | When to Read |13| -------------------------------------- | ------------------------------ | ------------------------------ |14| `./references/test-infrastructure.md` | Constants, defaults, mocks | When setting up tests |15| `./references/cheat-codes.md` | Common cheatcode patterns | When using vm cheatcodes |16| `./references/invariant-patterns.md` | Handlers, stores, invariants | When writing invariant tests |17| `./references/formal-verification.md` | Halmos, Certora, symbolic exec | When proving correctness |18| `./references/deployment-scripts.md` | Script patterns, verification | When writing deploy scripts |19| `./references/deployment-checklist.md` | Pre-mainnet deployment steps | Before deploying to production |20| `./references/gas-benchmarking.md` | Snapshot, profiling, CI | When measuring gas performance |21| `./references/sablier-conventions.md` | Sablier-specific patterns | When working in Sablier repos |2223______________________________________________________________________2425## Test Types2627| Type | Directory | Naming | Purpose |28| ----------- | ----------------------------- | ------------------ | ---------------------------- |29| Integration | `tests/integration/concrete/` | `*.t.sol` | BTT-based concrete tests |30| Fuzz | `tests/integration/fuzz/` | `*.t.sol` | Property-based testing |31| Fork | `tests/fork/` | `*.t.sol` | Mainnet state testing |32| Invariant | `tests/invariant/` | `Invariant*.t.sol` | Stateful protocol properties |33| Scripts | `scripts/solidity/` | `*.s.sol` | Deployment/initialization |3435______________________________________________________________________3637## 1. Integration Tests (Concrete)3839### Naming Convention4041| Pattern | Usage |42| ----------------------------- | --------------- |43| `test_RevertWhen_{Condition}` | Revert on input |44| `test_RevertGiven_{State}` | Revert on state |45| `test_When_{Condition}` | Success path |4647### Rules48491. **Stack modifiers** to document BTT path (modifiers are often empty - just document the path)502. **Expect events BEFORE action** - `vm.expectEmit()` then call function513. **Assert state AFTER action** - Check state changes after function executes524. **Use revert helpers** for common patterns (`expectRevert_DelegateCall`, `expectRevert_Null`)535. **Named parameters in assertions** - `assertEq(actual, expected, "description")`5455### Mock Rules56571. Place all mocks in `tests/mocks/`582. One mock per scenario (not one mega-mock)593. Naming: `*Good`, `*Reverting`, `*InvalidSelector`, `*Reentrant`6061______________________________________________________________________6263## 2. Fuzz Tests6465### Naming Convention6667`testFuzz_{FunctionName}_{Scenario}`6869### Rules70711. **Bound before assume** - `_bound()` is more efficient than `vm.assume()`722. **Bound in dependency order** - Independent params first, then dependent733. **Never hardcode** params with validation constraints744. **Document fuzzed scenarios** in NatSpec7576### Bounding Pattern7778```solidity79// 1. Bound independent params first80cliffDuration = boundUint40(cliffDuration, 0, MAX - 1);8182// 2. Bound dependent params based on constraints83totalDuration = boundUint40(totalDuration, cliffDuration + 1, MAX);84```8586______________________________________________________________________8788## 3. Fork Tests8990### Rules91921. Create fork with `vm.createSelectFork("ethereum")`932. Use `deal()` to give tokens to test users943. Use `assumeNoBlacklisted()` for USDC/USDT954. Use `forceApprove()` for non-standard tokens (USDT)9697### Token Quirks9899| Token | Issue | Solution |100| --------------- | ------------ | ---------------------------- |101| USDC/USDT | Blacklist | `assumeNoBlacklisted()` |102| USDT | Non-standard | `forceApprove()` |103| Fee-on-transfer | Balance diff | Check actual received amount |104105______________________________________________________________________106107## 4. Invariant Tests108109### Architecture110111```112tests/invariant/113├── handlers/ # State manipulation (call functions with bounded params)114├── stores/ # State tracking (record totals, IDs)115└── Invariant.t.sol116```117118### Rules1191201. **Target handlers only** - `targetContract(address(handler))`1212. **Exclude protocol contracts** - `excludeSender(address(vault))`1223. **Use stores** to track totals for invariant assertions1234. **Early return** in handlers if preconditions not met124125______________________________________________________________________126127## 5. Solidity Scripts128129### Rules1301311. Inherit from `BaseScript` with `broadcast` modifier1322. Use env vars: `ETH_FROM`, `MNEMONIC`1333. Simulation first, then broadcast134135### Commands136137```bash138# Simulation139forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC140141# Broadcast142forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC --broadcast --verify143```144145______________________________________________________________________146147## Running Tests148149```bash150# By type151forge test --match-path "tests/integration/concrete/**"152forge test --match-path "tests/fork/**"153forge test --match-contract Invariant_Test154155# Specific test156forge test --match-test test_WhenCallerRecipient -vvvv157158# Fuzz with more runs159forge test --match-test testFuzz_ --fuzz-runs 1000160161# Coverage162forge coverage --report lcov163```164165______________________________________________________________________166167## Debugging168169### Verbosity Levels170171| Flag | Shows |172| -------- | --------------------------- |173| `-v` | Logs for failing tests |174| `-vv` | Logs for all tests |175| `-vvv` | Stack traces for failures |176| `-vvvv` | Stack traces + setup traces |177| `-vvvvv` | Full execution traces |178179### Console Logging180181```solidity182import { console2 } from "forge-std/console2.sol";183184console2.log("value:", someValue);185console2.log("address:", someAddress);186console2.logBytes32(someBytes32);187```188189### Debugging Commands190191```bash192# Trace specific failing test193forge test --match-test test_MyTest -vvvv194195# Gas report for a test196forge test --match-test test_MyTest --gas-report197198# Debug in interactive debugger199forge debug --debug tests/MyTest.t.sol --sig "test_MyTest()"200201# Inspect storage layout202forge inspect MyContract storage-layout203```204205### Debugging Tips2062071. **Label addresses** - `vm.label(addr, "Recipient")` for readable traces2082. **Check state with logs** - Add `console2.log` before reverts2093. **Isolate failures** - Run single test with `--match-test`2104. **Compare gas** - Use `--gas-report` to spot unexpected costs2115. **Snapshot comparisons** - Use `vm.snapshot()` / `vm.revertTo()` to isolate state changes212213______________________________________________________________________214215## Best Practices Summary2162171. Use constants from `Defaults`/`Constants` - never hardcode2182. Specialized mocks - one per scenario, all in `tests/mocks/`2193. Modifiers in `Modifiers.sol` - centralize BTT path modifiers2204. Label addresses with `vm.label()` for traces2215. Events before actions - `vm.expectEmit()` then call2226. Bound before assume - more efficient223224## External References225226- [Foundry Book](https://getfoundry.sh)227228______________________________________________________________________229230## Example Invocations231232Test this skill with these prompts:2332341. **Integration test**: "Write a concrete test for `withdraw` that expects `Errors.Flow_Overdraw` when amount exceeds235 balance"2362. **Fuzz test**: "Create a fuzz test for `deposit` that bounds amount between 1 and type(uint128).max"2373. **Fork test**: "Write a fork test for USDC deposits on mainnet with blacklist handling"2384. **Invariant test**: "Create an invariant handler for the `deposit` and `withdraw` functions"2395. **Deploy script**: "Write a deployment script for SablierFlow with verification"