name: foundry-framework description: Foundry(Forge、Cast、Anvil、Chisel)在智能合约开发、测试和部署方面的专家级使用。包括模糊测试、Gas报告、本地开发和部署脚本能力。 allowed-tools: Read, Grep, Write, Bash, Edit, Glob, WebFetch
Foundry 框架技能
Foundry 的专家级使用,这是一个用于以太坊应用开发的极速、可移植和模块化工具包。
能力
- Forge 测试:使用模糊测试编写和运行 Solidity 测试
- Gas 优化:生成详细的 Gas 报告和快照
- 本地开发:使用 Anvil 作为本地区块链
- 链上交互:执行 Cast 命令进行链上操作
- 项目配置:为项目设置 foundry.toml
- 部署脚本:编写和运行 forge 脚本
- REPL 调试:使用 Chisel 进行 Solidity 探索
安装
# 安装 Foundry
curl -L https://foundry.paradigm.xyz | bash
# 更新到最新版本
foundryup
# 验证安装
forge --version
cast --version
anvil --version
chisel --version
项目设置
初始化项目
# 新项目
forge init my_project
cd my_project
# 添加依赖
forge install OpenZeppelin/openzeppelin-contracts
forge install foundry-rs/forge-std
foundry.toml 配置
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.20"
optimizer = true
optimizer_runs = 200
via_ir = false
[profile.default.fuzz]
runs = 256
max_test_rejects = 65536
seed = "0x1234"
[profile.default.invariant]
runs = 256
depth = 15
fail_on_revert = false
[profile.ci]
fuzz = { runs = 10000 }
invariant = { runs = 1000, depth = 50 }
[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
arbitrum = "${ARBITRUM_RPC_URL}"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }
Forge 测试
基础测试
// test/Counter.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}
function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testFail_Underflow() public {
counter.decrement();
}
}
模糊测试
contract FuzzTest is Test {
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
function testFuzz_BoundedInput(uint256 x) public {
x = bound(x, 1, 100);
// x 现在在 1 到 100 之间
}
}
不变性测试
contract InvariantTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
targetContract(address(counter));
}
function invariant_NumberNeverNegative() public {
assertTrue(counter.number() >= 0);
}
function invariant_NumberUnderMax() public {
assertTrue(counter.number() < type(uint256).max);
}
}
分叉测试
contract ForkTest is Test {
function setUp() public {
// 在特定区块分叉主网
vm.createSelectFork("mainnet", 18000000);
}
function test_MainnetState() public {
// 与主网合约交互
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EescdeCB5c811d7);
uint256 balance = dai.balanceOf(address(this));
}
}
Forge 命令
# 构建项目
forge build
# 运行测试
forge test
# 详细模式运行测试
forge test -vvvv
# 运行特定测试
forge test --match-test testFuzz_SetNumber
# 运行测试并生成 Gas 报告
forge test --gas-report
# 生成 Gas 快照
forge snapshot
# 比较 Gas 快照
forge snapshot --diff
# 覆盖率
forge coverage
# 格式化代码
forge fmt
Cast 命令
读取链上数据
# 获取 ETH 余额
cast balance 0x... --rpc-url $RPC
# 读取合约存储
cast storage 0x... 0 --rpc-url $RPC
# 调用视图函数
cast call 0x... "balanceOf(address)" 0x... --rpc-url $RPC
# 解码 calldata
cast calldata-decode "transfer(address,uint256)" 0x...
发送交易
# 发送 ETH
cast send 0x... --value 1ether --rpc-url $RPC --private-key $KEY
# 调用合约函数
cast send 0x... "transfer(address,uint256)" 0x... 1000 --rpc-url $RPC --private-key $KEY
实用命令
# 单位转换
cast to-wei 1 ether
cast from-wei 1000000000000000000
# 计算函数选择器
cast sig "transfer(address,uint256)"
# 获取 ABI 编码
cast abi-encode "transfer(address,uint256)" 0x... 100
# 解码 ABI
cast abi-decode "balanceOf(address)(uint256)" 0x...
Anvil 本地节点
# 启动本地节点
anvil
# 使用特定链 ID 启动
anvil --chain-id 31337
# 分叉主网
anvil --fork-url $MAINNET_RPC
# 在特定区块分叉
anvil --fork-url $MAINNET_RPC --fork-block-number 18000000
# 预加载账户
anvil --accounts 20 --balance 10000
Anvil RPC
# 模拟账户
cast rpc anvil_impersonateAccount 0x... --rpc-url http://localhost:8545
# 设置余额
cast rpc anvil_setBalance 0x... 0x1000000000000000000 --rpc-url http://localhost:8545
# 挖矿
cast rpc anvil_mine 10 --rpc-url http://localhost:8545
# 设置区块时间戳
cast rpc evm_setNextBlockTimestamp 1700000000 --rpc-url http://localhost:8545
部署脚本
脚本示例
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/Counter.sol";
contract DeployScript is Script {
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Counter counter = new Counter();
counter.setNumber(42);
vm.stopBroadcast();
console.log("Counter 部署地址:", address(counter));
}
}
运行脚本
# 模拟部署
forge script script/Deploy.s.sol --rpc-url $RPC
# 部署到网络
forge script script/Deploy.s.sol --rpc-url $RPC --broadcast
# 在 Etherscan 上验证
forge script script/Deploy.s.sol --rpc-url $RPC --broadcast --verify
Chisel REPL
# 启动 Chisel
chisel
# 在 REPL 中
> uint256 x = 100
> x * 2
200
> address(this)
0x...
流程集成
| 流程 | 目的 |
|---|---|
smart-contract-development-lifecycle.js |
完整开发流程 |
smart-contract-fuzzing.js |
模糊测试和不变性测试 |
invariant-testing.js |
基于属性的测试 |
gas-optimization.js |
Gas 性能分析 |
| 所有 DeFi 流程 | 测试和部署 |
最佳实践
- 提交前使用
forge fmt - 维护 Gas 快照用于回归测试
- 使用分叉测试进行集成测试
- 为 CI 设置适当的模糊测试运行次数
- 使用基于配置文件的配置
- 将 foundry.toml 保存在版本控制中
另请参阅
skills/hardhat-framework/SKILL.md- 替代框架skills/echidna-fuzzer/SKILL.md- 高级模糊测试skills/gas-optimization/SKILL.md- Gas 优化- Foundry 手册