Loading...
Loading...
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.
npx skill4agent add sablier-labs/plugin-marketplace foundry| Reference | Content | When to Read |
|---|---|---|
| Constants, defaults, mocks | When setting up tests |
| Common cheatcode patterns | When using vm cheatcodes |
| Handlers, stores, invariants | When writing invariant tests |
| Halmos, Certora, symbolic exec | When proving correctness |
| Script patterns, verification | When writing deploy scripts |
| Pre-mainnet deployment steps | Before deploying to production |
| Snapshot, profiling, CI | When measuring gas performance |
| Sablier-specific patterns | When working in Sablier repos |
| Type | Directory | Naming | Purpose |
|---|---|---|---|
| Integration | | | BTT-based concrete tests |
| Fuzz | | | Property-based testing |
| Fork | | | Mainnet state testing |
| Invariant | | | Stateful protocol properties |
| Scripts | | | Deployment/initialization |
| Pattern | Usage |
|---|---|
| Revert on input |
| Revert on state |
| Success path |
vm.expectEmit()expectRevert_DelegateCallexpectRevert_NullassertEq(actual, expected, "description")tests/mocks/*Good*Reverting*InvalidSelector*ReentranttestFuzz_{FunctionName}_{Scenario}_bound()vm.assume()// 1. Bound independent params first
cliffDuration = boundUint40(cliffDuration, 0, MAX - 1);
// 2. Bound dependent params based on constraints
totalDuration = boundUint40(totalDuration, cliffDuration + 1, MAX);vm.createSelectFork("ethereum")deal()assumeNoBlacklisted()forceApprove()| Token | Issue | Solution |
|---|---|---|
| USDC/USDT | Blacklist | |
| USDT | Non-standard | |
| Fee-on-transfer | Balance diff | Check actual received amount |
tests/invariant/
├── handlers/ # State manipulation (call functions with bounded params)
├── stores/ # State tracking (record totals, IDs)
└── Invariant.t.soltargetContract(address(handler))excludeSender(address(vault))BaseScriptbroadcastETH_FROMMNEMONIC# 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# 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| Flag | Shows |
|---|---|
| Logs for failing tests |
| Logs for all tests |
| Stack traces for failures |
| Stack traces + setup traces |
| Full execution traces |
import { console2 } from "forge-std/console2.sol";
console2.log("value:", someValue);
console2.log("address:", someAddress);
console2.logBytes32(someBytes32);# 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-layoutvm.label(addr, "Recipient")console2.log--match-test--gas-reportvm.snapshot()vm.revertTo()DefaultsConstantstests/mocks/Modifiers.solvm.label()vm.expectEmit()withdrawErrors.Flow_Overdrawdepositdepositwithdraw