foundry
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFoundry Testing & Script Skill
Foundry测试与脚本技能
Rules and patterns for Foundry tests. Find examples in the actual codebase.
Foundry测试的规则与模式。可在实际代码库中查找示例。
Bundled References
内置参考文档
| 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 |
| 参考文档 | 内容 | 阅读时机 |
|---|---|---|
| 常量、默认值、模拟合约 | 搭建测试环境时 |
| 常用cheatcode模式 | 使用vm cheatcodes时 |
| 处理器、存储、不变量 | 编写不变量测试时 |
| Halmos、Certora、符号执行 | 验证正确性时 |
| 脚本模式、验证 | 编写部署脚本时 |
| 主网部署前步骤 | 部署到生产环境前 |
| 快照、性能分析、CI | 测量gas性能时 |
| Sablier专属模式 | 在Sablier仓库中工作时 |
Test Types
测试类型
| Type | Directory | Naming | Purpose |
|---|---|---|---|
| Integration | | | BTT-based concrete tests |
| Fuzz | | | Property-based testing |
| Fork | | | Mainnet state testing |
| Invariant | | | Stateful protocol properties |
| Scripts | | | Deployment/initialization |
| 类型 | 目录 | 命名规则 | 用途 |
|---|---|---|---|
| 集成测试 | | | 基于BTT的具体测试 |
| 模糊测试 | | | 基于属性的测试 |
| 分叉测试 | | | 主网状态测试 |
| 不变量测试 | | | 有状态协议属性测试 |
| 脚本 | | | 部署/初始化 |
1. Integration Tests (Concrete)
1. 集成测试(具体)
Naming Convention
命名规范
| Pattern | Usage |
|---|---|
| Revert on input |
| Revert on state |
| Success path |
| 模式 | 用途 |
|---|---|
| 输入触发回滚 |
| 状态触发回滚 |
| 成功路径测试 |
Rules
规则
- Stack modifiers to document BTT path (modifiers are often empty - just document the path)
- Expect events BEFORE action - then call function
vm.expectEmit() - 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")
- 使用栈修饰器记录BTT路径(修饰器通常为空,仅用于记录路径)
- 先声明事件预期,再执行操作 - 先调用,再执行函数
vm.expectEmit() - 操作后断言状态 - 函数执行后检查状态变化
- 使用回滚辅助函数处理常见模式(、
expectRevert_DelegateCall)expectRevert_Null - 断言中使用命名参数 -
assertEq(actual, expected, "描述")
Mock Rules
模拟合约规则
- Place all mocks in
tests/mocks/ - One mock per scenario (not one mega-mock)
- Naming: ,
*Good,*Reverting,*InvalidSelector*Reentrant
- 所有模拟合约放在目录下
tests/mocks/ - 每个场景对应一个模拟合约(不要用一个巨型模拟合约)
- 命名规则:、
*Good、*Reverting、*InvalidSelector*Reentrant
2. Fuzz Tests
2. 模糊测试
Naming Convention
命名规范
testFuzz_{FunctionName}_{Scenario}testFuzz_{FunctionName}_{Scenario}Rules
规则
- Bound before assume - is more efficient than
_bound()vm.assume() - Bound in dependency order - Independent params first, then dependent
- Never hardcode params with validation constraints
- Document fuzzed scenarios in NatSpec
- 先绑定范围再假设 - 比
_bound()更高效vm.assume() - 按依赖顺序绑定 - 先绑定独立参数,再绑定依赖参数
- 不要硬编码带有验证约束的参数
- 在NatSpec中记录模糊测试场景
Bounding Pattern
范围绑定示例
solidity
// 1. Bound independent params first
cliffDuration = boundUint40(cliffDuration, 0, MAX - 1);
// 2. Bound dependent params based on constraints
totalDuration = boundUint40(totalDuration, cliffDuration + 1, MAX);solidity
// 1. 先绑定独立参数
cliffDuration = boundUint40(cliffDuration, 0, MAX - 1);
// 2. 根据约束绑定依赖参数
totalDuration = boundUint40(totalDuration, cliffDuration + 1, MAX);3. Fork Tests
3. 分叉测试
Rules
规则
- Create fork with
vm.createSelectFork("ethereum") - Use to give tokens to test users
deal() - Use for USDC/USDT
assumeNoBlacklisted() - Use for non-standard tokens (USDT)
forceApprove()
- 使用创建分叉
vm.createSelectFork("ethereum") - 使用给测试用户分配代币
deal() - 对USDC/USDT使用
assumeNoBlacklisted() - 对非标准代币(如USDT)使用
forceApprove()
Token Quirks
代币特殊处理
| Token | Issue | Solution |
|---|---|---|
| USDC/USDT | Blacklist | |
| USDT | Non-standard | |
| Fee-on-transfer | Balance diff | Check actual received amount |
| 代币 | 问题 | 解决方案 |
|---|---|---|
| USDC/USDT | 黑名单 | |
| USDT | 非标准接口 | |
| 转账手续费代币 | 余额差异 | 检查实际到账金额 |
4. Invariant Tests
4. 不变量测试
Architecture
架构
tests/invariant/
├── handlers/ # State manipulation (call functions with bounded params)
├── stores/ # State tracking (record totals, IDs)
└── Invariant.t.soltests/invariant/
├── handlers/ # 状态操作(调用带范围绑定参数的函数)
├── stores/ # 状态跟踪(记录总量、ID)
└── Invariant.t.solRules
规则
- 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
- 仅以处理器为目标 -
targetContract(address(handler)) - 排除协议合约 -
excludeSender(address(vault)) - 使用存储合约跟踪总量用于不变量断言
- 处理器中前置条件不满足时提前返回
5. Solidity Scripts
5. Solidity脚本
Rules
规则
- Inherit from with
BaseScriptmodifierbroadcast - Use env vars: ,
ETH_FROMMNEMONIC - Simulation first, then broadcast
- 继承自并使用
BaseScript修饰器broadcast - 使用环境变量:、
ETH_FROMMNEMONIC - 先模拟执行,再广播部署
Commands
命令
bash
undefinedbash
undefinedSimulation
模拟执行
forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC
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
______________________________________________________________________forge script scripts/Deploy.s.sol --sig "run(...)" ARGS --rpc-url $RPC --broadcast --verify
______________________________________________________________________Running Tests
运行测试
bash
undefinedbash
undefinedBy type
按类型运行
forge test --match-path "tests/integration/concrete/"
forge test --match-path "tests/fork/"
forge test --match-contract Invariant_Test
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
forge test --match-test test_WhenCallerRecipient -vvvv
Fuzz with more runs
增加模糊测试运行次数
forge test --match-test testFuzz_ --fuzz-runs 1000
forge test --match-test testFuzz_ --fuzz-runs 1000
Coverage
生成覆盖率报告
forge coverage --report lcov
______________________________________________________________________forge coverage --report lcov
______________________________________________________________________Debugging
调试
Verbosity Levels
日志详细级别
| Flag | Shows |
|---|---|
| Logs for failing tests |
| Logs for all tests |
| Stack traces for failures |
| Stack traces + setup traces |
| Full execution traces |
| 参数 | 显示内容 |
|---|---|
| 失败测试的日志 |
| 所有测试的日志 |
| 失败测试的堆栈跟踪 |
| 堆栈跟踪 + 初始化跟踪 |
| 完整执行日志 |
Console Logging
控制台日志
solidity
import { console2 } from "forge-std/console2.sol";
console2.log("value:", someValue);
console2.log("address:", someAddress);
console2.logBytes32(someBytes32);solidity
import { console2 } from "forge-std/console2.sol";
console2.log("value:", someValue);
console2.log("address:", someAddress);
console2.logBytes32(someBytes32);Debugging Commands
调试命令
bash
undefinedbash
undefinedTrace specific failing test
跟踪指定失败测试
forge test --match-test test_MyTest -vvvv
forge test --match-test test_MyTest -vvvv
Gas report for a test
生成测试的gas报告
forge test --match-test test_MyTest --gas-report
forge test --match-test test_MyTest --gas-report
Debug in interactive debugger
在交互式调试器中调试
forge debug --debug tests/MyTest.t.sol --sig "test_MyTest()"
forge debug --debug tests/MyTest.t.sol --sig "test_MyTest()"
Inspect storage layout
查看存储布局
forge inspect MyContract storage-layout
undefinedforge inspect MyContract storage-layout
undefinedDebugging Tips
调试技巧
- Label addresses - for readable traces
vm.label(addr, "Recipient") - Check state with logs - Add before reverts
console2.log - Isolate failures - Run single test with
--match-test - Compare gas - Use to spot unexpected costs
--gas-report - Snapshot comparisons - Use /
vm.snapshot()to isolate state changesvm.revertTo()
- 标记地址 - 使用让跟踪日志更易读
vm.label(addr, "Recipient") - 用日志检查状态 - 在回滚前添加
console2.log - 隔离失败案例 - 使用运行单个测试
--match-test - 对比gas消耗 - 使用发现异常成本
--gas-report - 快照对比 - 使用/
vm.snapshot()隔离状态变化vm.revertTo()
Best Practices Summary
最佳实践总结
- Use constants from /
Defaults- never hardcodeConstants - Specialized mocks - one per scenario, all in
tests/mocks/ - Modifiers in - centralize BTT path modifiers
Modifiers.sol - Label addresses with for traces
vm.label() - Events before actions - then call
vm.expectEmit() - Bound before assume - more efficient
- 使用/
Defaults中的常量 - 绝不硬编码Constants - 专用模拟合约 - 每个场景一个,全部放在中
tests/mocks/ - 修饰器集中在- 统一管理BTT路径修饰器
Modifiers.sol - 使用标记地址,优化跟踪日志
vm.label() - 先声明事件预期再执行操作 - 先调用再执行函数
vm.expectEmit() - 先绑定范围再假设 - 效率更高
External References
外部参考
Example Invocations
调用示例
Test this skill with these prompts:
- Integration test: "Write a concrete test for that expects
withdrawwhen amount exceeds balance"Errors.Flow_Overdraw - Fuzz test: "Create a fuzz test for that bounds amount between 1 and type(uint128).max"
deposit - Fork test: "Write a fork test for USDC deposits on mainnet with blacklist handling"
- Invariant test: "Create an invariant handler for the and
depositfunctions"withdraw - Deploy script: "Write a deployment script for SablierFlow with verification"
使用以下提示测试本技能:
- 集成测试:"为编写具体测试,当金额超过余额时预期触发
withdraw回滚"Errors.Flow_Overdraw - 模糊测试:"为创建模糊测试,将金额范围绑定在1到type(uint128).max之间"
deposit - 分叉测试:"编写主网USDC存款的分叉测试,包含黑名单处理"
- 不变量测试:"为和
deposit函数创建不变量处理器"withdraw - 部署脚本:"编写带验证的SablierFlow部署脚本"