Foundry Testing & Script Skill
SkillCloud & infraWrite 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.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Foundry Testing & Script Skill skill
What this skill tells your AI
The instructions your AI receives, as published by sablier-labs/evm-monorepo in .agents/skills/foundry-testing/SKILL.md and read by ahel’s review.
Rules and patterns for Foundry tests. Find examples in the actual codebase.
Bundled References
| Reference | Content | When to Read |
|---|---|---|
.claude/skills/foundry-testing/references/test-infrastructure.md | Constants, defaults, mocks | When setting up tests |
.claude/skills/foundry-testing/references/cheat-codes.md | Common cheatcode patterns | When using vm cheatcodes |
.claude/skills/foundry-testing/references/invariant-patterns.md | Handlers, stores, invariants | When writing invariant tests |
.claude/skills/foundry-testing/references/formal-verification.md | Halmos, Certora, symbolic exec | When proving correctness |
.claude/skills/foundry-testing/references/deployment-scripts.md | Script patterns, verification | When writing deploy scripts |
.claude/skills/foundry-testing/references/deployment-checklist.md | Pre-mainnet deployment steps | Before deploying to production |
.claude/skills/foundry-testing/references/gas-benchmarking.md | Snapshot, profiling, CI | When measuring gas performance |
.claude/skills/foundry-testing/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 thanvm.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
BaseScriptwithbroadcastmodifier - 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.logbefore reverts - Isolate failures - Run single test with
--match-test - Compare gas - Use
--gas-reportto 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
withdrawthat expectsErrors.Flow_Overdrawwhen amount exceeds balance" - Fuzz test: "Create a fuzz test for
depositthat 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
depositandwithdrawfunctions" - Deploy script: "Write a deployment script for SablierFlow with verification"
Signals
- GitHub stars
- 352
- Forks
- 82
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
foundry-testing- Source
- github.com/sablier-labs/evm-monorepo