foundry

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Foundry Testing & Script Skill

Foundry测试与脚本技能

Rules and patterns for Foundry tests. Find examples in the actual codebase.
Foundry测试的规则与模式。可在实际代码库中查找示例。

Bundled References

内置参考文档

ReferenceContentWhen to Read
./references/test-infrastructure.md
Constants, defaults, mocksWhen setting up tests
./references/cheat-codes.md
Common cheatcode patternsWhen using vm cheatcodes
./references/invariant-patterns.md
Handlers, stores, invariantsWhen writing invariant tests
./references/formal-verification.md
Halmos, Certora, symbolic execWhen proving correctness
./references/deployment-scripts.md
Script patterns, verificationWhen writing deploy scripts
./references/deployment-checklist.md
Pre-mainnet deployment stepsBefore deploying to production
./references/gas-benchmarking.md
Snapshot, profiling, CIWhen measuring gas performance
./references/sablier-conventions.md
Sablier-specific patternsWhen working in Sablier repos

参考文档内容阅读时机
./references/test-infrastructure.md
常量、默认值、模拟合约搭建测试环境时
./references/cheat-codes.md
常用cheatcode模式使用vm cheatcodes时
./references/invariant-patterns.md
处理器、存储、不变量编写不变量测试时
./references/formal-verification.md
Halmos、Certora、符号执行验证正确性时
./references/deployment-scripts.md
脚本模式、验证编写部署脚本时
./references/deployment-checklist.md
主网部署前步骤部署到生产环境前
./references/gas-benchmarking.md
快照、性能分析、CI测量gas性能时
./references/sablier-conventions.md
Sablier专属模式在Sablier仓库中工作时

Test Types

测试类型

TypeDirectoryNamingPurpose
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

类型目录命名规则用途
集成测试
tests/integration/concrete/
*.t.sol
基于BTT的具体测试
模糊测试
tests/integration/fuzz/
*.t.sol
基于属性的测试
分叉测试
tests/fork/
*.t.sol
主网状态测试
不变量测试
tests/invariant/
Invariant*.t.sol
有状态协议属性测试
脚本
scripts/solidity/
*.s.sol
部署/初始化

1. Integration Tests (Concrete)

1. 集成测试(具体)

Naming Convention

命名规范

PatternUsage
test_RevertWhen_{Condition}
Revert on input
test_RevertGiven_{State}
Revert on state
test_When_{Condition}
Success path
模式用途
test_RevertWhen_{Condition}
输入触发回滚
test_RevertGiven_{State}
状态触发回滚
test_When_{Condition}
成功路径测试

Rules

规则

  1. Stack modifiers to document BTT path (modifiers are often empty - just document the path)
  2. Expect events BEFORE action -
    vm.expectEmit()
    then call function
  3. Assert state AFTER action - Check state changes after function executes
  4. Use revert helpers for common patterns (
    expectRevert_DelegateCall
    ,
    expectRevert_Null
    )
  5. Named parameters in assertions -
    assertEq(actual, expected, "description")
  1. 使用栈修饰器记录BTT路径(修饰器通常为空,仅用于记录路径)
  2. 先声明事件预期,再执行操作 - 先调用
    vm.expectEmit()
    ,再执行函数
  3. 操作后断言状态 - 函数执行后检查状态变化
  4. 使用回滚辅助函数处理常见模式(
    expectRevert_DelegateCall
    expectRevert_Null
  5. 断言中使用命名参数 -
    assertEq(actual, expected, "描述")

Mock Rules

模拟合约规则

  1. Place all mocks in
    tests/mocks/
  2. One mock per scenario (not one mega-mock)
  3. Naming:
    *Good
    ,
    *Reverting
    ,
    *InvalidSelector
    ,
    *Reentrant

  1. 所有模拟合约放在
    tests/mocks/
    目录下
  2. 每个场景对应一个模拟合约(不要用一个巨型模拟合约)
  3. 命名规则:
    *Good
    *Reverting
    *InvalidSelector
    *Reentrant

2. Fuzz Tests

2. 模糊测试

Naming Convention

命名规范

testFuzz_{FunctionName}_{Scenario}
testFuzz_{FunctionName}_{Scenario}

Rules

规则

  1. Bound before assume -
    _bound()
    is more efficient than
    vm.assume()
  2. Bound in dependency order - Independent params first, then dependent
  3. Never hardcode params with validation constraints
  4. Document fuzzed scenarios in NatSpec
  1. 先绑定范围再假设 -
    _bound()
    vm.assume()
    更高效
  2. 按依赖顺序绑定 - 先绑定独立参数,再绑定依赖参数
  3. 不要硬编码带有验证约束的参数
  4. 在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

规则

  1. Create fork with
    vm.createSelectFork("ethereum")
  2. Use
    deal()
    to give tokens to test users
  3. Use
    assumeNoBlacklisted()
    for USDC/USDT
  4. Use
    forceApprove()
    for non-standard tokens (USDT)
  1. 使用
    vm.createSelectFork("ethereum")
    创建分叉
  2. 使用
    deal()
    给测试用户分配代币
  3. 对USDC/USDT使用
    assumeNoBlacklisted()
  4. 对非标准代币(如USDT)使用
    forceApprove()

Token Quirks

代币特殊处理

TokenIssueSolution
USDC/USDTBlacklist
assumeNoBlacklisted()
USDTNon-standard
forceApprove()
Fee-on-transferBalance diffCheck actual received amount

代币问题解决方案
USDC/USDT黑名单
assumeNoBlacklisted()
USDT非标准接口
forceApprove()
转账手续费代币余额差异检查实际到账金额

4. Invariant Tests

4. 不变量测试

Architecture

架构

tests/invariant/
├── handlers/     # State manipulation (call functions with bounded params)
├── stores/       # State tracking (record totals, IDs)
└── Invariant.t.sol
tests/invariant/
├── handlers/     # 状态操作(调用带范围绑定参数的函数)
├── stores/       # 状态跟踪(记录总量、ID)
└── Invariant.t.sol

Rules

规则

  1. Target handlers only -
    targetContract(address(handler))
  2. Exclude protocol contracts -
    excludeSender(address(vault))
  3. Use stores to track totals for invariant assertions
  4. Early return in handlers if preconditions not met

  1. 仅以处理器为目标 -
    targetContract(address(handler))
  2. 排除协议合约 -
    excludeSender(address(vault))
  3. 使用存储合约跟踪总量用于不变量断言
  4. 处理器中前置条件不满足时提前返回

5. Solidity Scripts

5. Solidity脚本

Rules

规则

  1. Inherit from
    BaseScript
    with
    broadcast
    modifier
  2. Use env vars:
    ETH_FROM
    ,
    MNEMONIC
  3. Simulation first, then broadcast
  1. 继承自
    BaseScript
    并使用
    broadcast
    修饰器
  2. 使用环境变量:
    ETH_FROM
    MNEMONIC
  3. 先模拟执行,再广播部署

Commands

命令

bash
undefined
bash
undefined

Simulation

模拟执行

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
undefined
bash
undefined

By 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

日志详细级别

FlagShows
-v
Logs for failing tests
-vv
Logs for all tests
-vvv
Stack traces for failures
-vvvv
Stack traces + setup traces
-vvvvv
Full execution traces
参数显示内容
-v
失败测试的日志
-vv
所有测试的日志
-vvv
失败测试的堆栈跟踪
-vvvv
堆栈跟踪 + 初始化跟踪
-vvvvv
完整执行日志

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
undefined
bash
undefined

Trace 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
undefined
forge inspect MyContract storage-layout
undefined

Debugging Tips

调试技巧

  1. Label addresses -
    vm.label(addr, "Recipient")
    for readable traces
  2. Check state with logs - Add
    console2.log
    before reverts
  3. Isolate failures - Run single test with
    --match-test
  4. Compare gas - Use
    --gas-report
    to spot unexpected costs
  5. Snapshot comparisons - Use
    vm.snapshot()
    /
    vm.revertTo()
    to isolate state changes

  1. 标记地址 - 使用
    vm.label(addr, "Recipient")
    让跟踪日志更易读
  2. 用日志检查状态 - 在回滚前添加
    console2.log
  3. 隔离失败案例 - 使用
    --match-test
    运行单个测试
  4. 对比gas消耗 - 使用
    --gas-report
    发现异常成本
  5. 快照对比 - 使用
    vm.snapshot()
    /
    vm.revertTo()
    隔离状态变化

Best Practices Summary

最佳实践总结

  1. Use constants from
    Defaults
    /
    Constants
    - never hardcode
  2. Specialized mocks - one per scenario, all in
    tests/mocks/
  3. Modifiers in
    Modifiers.sol
    - centralize BTT path modifiers
  4. Label addresses with
    vm.label()
    for traces
  5. Events before actions -
    vm.expectEmit()
    then call
  6. Bound before assume - more efficient
  1. 使用
    Defaults
    /
    Constants
    中的常量 - 绝不硬编码
  2. 专用模拟合约 - 每个场景一个,全部放在
    tests/mocks/
  3. 修饰器集中在
    Modifiers.sol
    - 统一管理BTT路径修饰器
  4. 使用
    vm.label()
    标记地址,优化跟踪日志
  5. 先声明事件预期再执行操作 - 先调用
    vm.expectEmit()
    再执行函数
  6. 先绑定范围再假设 - 效率更高

External References

外部参考

Example Invocations

调用示例

Test this skill with these prompts:
  1. Integration test: "Write a concrete test for
    withdraw
    that expects
    Errors.Flow_Overdraw
    when amount exceeds balance"
  2. Fuzz test: "Create a fuzz test for
    deposit
    that bounds amount between 1 and type(uint128).max"
  3. Fork test: "Write a fork test for USDC deposits on mainnet with blacklist handling"
  4. Invariant test: "Create an invariant handler for the
    deposit
    and
    withdraw
    functions"
  5. Deploy script: "Write a deployment script for SablierFlow with verification"
使用以下提示测试本技能:
  1. 集成测试:"为
    withdraw
    编写具体测试,当金额超过余额时预期触发
    Errors.Flow_Overdraw
    回滚"
  2. 模糊测试:"为
    deposit
    创建模糊测试,将金额范围绑定在1到type(uint128).max之间"
  3. 分叉测试:"编写主网USDC存款的分叉测试,包含黑名单处理"
  4. 不变量测试:"为
    deposit
    withdraw
    函数创建不变量处理器"
  5. 部署脚本:"编写带验证的SablierFlow部署脚本"