参考资料
foundry doc ---> book.getfoundry.sh/introductio…
使用foundry实现你的第一个智能合约
目录
什么是 Foundry?
Foundry 是一个用于以太坊智能合约开发的高性能工具链。它由 Rust 编写,包含了合约编译、测试、部署、交互等一整套开发流程的命令行工具,主要包括:
核心组件:
forge:编译、测试、部署智能合约cast:链上交互、数据查询、发送交易anvil:本地以太坊节点模拟器chisel:Solidity REPL
与 Remix、Hardhat 的区别
| 工具 | 主要特点 | 适用场景 |
|---|---|---|
| Remix | Web IDE,零配置,适合新手和演示 | 快速原型、教学 |
| Hardhat | JS/TS 生态,插件丰富,脚本灵活 | 前端集成、复杂脚本 |
| Foundry | CLI 工具链,极致性能,原生支持 Solidity 脚本 | 高效测试、CI、Solidity |
- 开发体验:
- Remix 是网页 IDE,零配置,适合入门和小项目。
- Hardhat 以 JS/TS 为主,适合与前端集成,生态丰富。
- Foundry 完全基于命令行,配置简单,极致性能,适合大规模测试和 CI。
- 测试方式:
- Remix 支持简单的合约交互和测试。
- Hardhat 测试用 JS/TS 编写。
- Foundry 测试用 Solidity 编写,支持 fuzzing、property-based testing。
- 性能:
- Foundry 的测试和编译速度极快,适合大项目和持续集成。
- 生态集成:
- Hardhat 插件生态丰富,适合与 JS/TS 项目集成。
- Foundry 支持与 Hardhat 项目互操作(可共用合约、测试等)。
为什么要用foundry?Foundry 的优势?
- 极致性能:
- 编译和测试速度极快,适合大规模项目和 CI/CD。
- Solidity 原生测试:
- 测试用 Solidity 编写,支持 fuzzing、property-based testing。
- 易于集成:
- 命令行工具,易于脚本化和自动化。
- 本地节点(Anvil):
- 内置本地节点,方便调试和测试。
- 与 Hardhat 兼容:
- 可与 Hardhat 项目共用合约和依赖。 :
如何使用foundry
1. 安装 Foundry
在终端执行以下命令:
curl -L https://foundry.paradigm.xyz | bash
foundryup
2. 初始化项目
forge init my-foundry-project
cd my-foundry-project
tree -L 1
tree src
tree test
tree script
.
├── cache
├── foundry.toml
├── lib
├── out
├── README.md
├── script
├── src
└── test
7 directories, 2 files
src
└── Counter.sol
1 directory, 1 file
test
└── Counter.t.sol
1 directory, 1 file
script
└── Counter.s.sol
3.Foundry 的核心工具与常用命令
Foundry 工具链包含 4 个主要工具,每个工具都专注于以太坊开发流程中的不同环节:
3.1 forge
- 用途:合约的编译、测试、部署、脚本执行。
- 常用命令:
forge init <project-name>:初始化新项目forge build:编译合约forge test:运行 Solidity 测试forge script <script>:运行 Solidity 脚本forge create <contract>:部署合约
3.2 cast
- 用途:与链交互、数据查询、发送交易、工具函数等。
- 常用命令:
cast call <address> <sig>:调用合约的 view/pure 方法cast send <address> <sig>:发送交易cast block <block>:查询区块信息cast tx <hash>:查询交易信息cast keccak <data>:计算 keccak256 哈希
3.3 anvil
- 用途:本地以太坊节点模拟器,适合本地开发和测试。
- 常用命令:
anvil:启动本地节点- 支持自定义端口、账户、初始余额等参数
3.4 chisel
- 用途:Solidity REPL(交互式命令行),用于快速实验和调试 Solidity 代码片段。
- 常用命令:
chisel:进入 REPL 交互环境- 在环境中直接输入 Solidity 代码片段进行测试
4.测试
forge test
[⠊] Compiling...
No files changed, compilation skipped
Ran 4 tests for test/Counter.t.sol:CounterTest
[PASS] testAdd() (gas: 32441)
[PASS] testFuzz_Add(uint256) (runs: 256, μ: 32416, ~: 32572)
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 32398, ~: 32398)
[PASS] test_Increment() (gas: 31895)
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.43ms (19.59ms CPU time)
Ran 1 test suite in 160.68ms (16.43ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)
注意 测试的方法名必须是驼峰或下划线 以test开头
常用参数
-vv、-vvv、-vvvv等:增加输出详细程度(如显示测试日志、trace 等)--match-test <pattern>:只运行名称匹配的测试函数--gas-report:输出 gas 消耗报告--fork-url <RPC_URL>:在 fork 主网/测试网的环境下运行测试
示例
forge test -vvvvv //-v -vv -vvv -vvvv 测试严格程度依次增加
[⠊] Compiling...
No files changed, compilation skipped
Ran 4 tests for test/Counter.t.sol:CounterTest
[PASS] testAdd() (gas: 32441)
Traces:
[32441] CounterTest::testAdd()
├─ [22689] Counter::add(10)
│ └─ ← [Stop]
├─ [446] Counter::number() [staticcall]
│ └─ ← [Return] 10
├─ [0] VM::assertEq(10, 10) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
[PASS] testFuzz_Add(uint256) (runs: 256, μ: 32416, ~: 32572)
Traces:
[32572] CounterTest::testFuzz_Add(668)
├─ [22689] Counter::add(668)
│ └─ ← [Stop]
├─ [446] Counter::number() [staticcall]
│ └─ ← [Return] 668
├─ [0] VM::assertEq(668, 668) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 32242, ~: 32398)
Traces:
[32398] CounterTest::testFuzz_SetNumber(25185550902188 [2.518e13])
├─ [22514] Counter::setNumber(25185550902188 [2.518e13])
│ └─ ← [Stop]
├─ [446] Counter::number() [staticcall]
│ └─ ← [Return] 25185550902188 [2.518e13]
├─ [0] VM::assertEq(25185550902188 [2.518e13], 25185550902188 [2.518e13]) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
[PASS] test_Increment() (gas: 31895)
Traces:
[31895] CounterTest::test_Increment()
├─ [22440] Counter::increment()
│ └─ ← [Stop]
├─ [446] Counter::number() [staticcall]
│ └─ ← [Return] 1
├─ [0] VM::assertEq(1, 1) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 15.68ms (21.00ms CPU time)
Ran 1 test suite in 156.03ms (15.68ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)
function testFuzz_Add_Boundary(uint256 x) public {
// 只允许 x 为 0 或最大 uint256
vm.assume(x == 0 || x == type(uint256).max);
counter.setNumber(0);
counter.add(x);
assertEq(counter.number(), x);
}
forge test -vvvv --gas-report --match-test testFuzz_Add_Boundary
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] testFuzz_Add_Boundary(uint256) (runs: 256, μ: 70263, ~: 63446)
Traces:
[63446] CounterTest::testFuzz_Add_Boundary(0)
├─ [0] VM::assume(true) [staticcall]
│ └─ ← [Return]
├─ [23806] Counter::setNumber(0)
│ └─ ← [Stop]
├─ [23981] Counter::add(0)
│ └─ ← [Stop]
├─ [2446] Counter::number() [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::assertEq(0, 0) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 129.50ms (122.97ms CPU time)
╭----------------------------------+-----------------+-------+--------+-------+---------╮
| src/Counter.sol:Counter Contract | | | | | |
+=======================================================================================+
| Deployment Cost | Deployment Size | | | | |
|----------------------------------+-----------------+-------+--------+-------+---------|
| 181883 | 625 | | | | |
|----------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|----------------------------------+-----------------+-------+--------+-------+---------|
| add | 23981 | 30795 | 23981 | 44265 | 256 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| number | 2446 | 2446 | 2446 | 2446 | 256 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| setNumber | 23806 | 23806 | 23806 | 23806 | 257 |
╰----------------------------------+-----------------+-------+--------+-------+---------╯
Ran 1 test suite in 145.92ms (129.50ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
注意 vm.assume 可控制边界
- 运行所有测试,并显示详细日志和断言信息。
输出解读
- 每个测试函数会显示
PASS或FAIL,并统计通过/失败数量、总耗时等。 - 失败时会显示断言失败的具体行号和原因。
5.本地RPC节点 anvil
anvil
6.部署
Foundry 的 forge create 命令可以直接将合约部署到以太坊主网或测试网或本地anvil。
基本命令格式
forge create <合约路径>:<合约名> --rpc-url <RPC_URL> --private-key <PRIVATE_KEY>
<合约路径>:合约文件的相对路径,如src/Counter.sol<合约名>:要部署的合约名,如Counter<RPC_URL>:以太坊节点的 RPC 地址(如 Infura、Alchemy、公共 RPC 等)<PRIVATE_KEY>:用于部署的以太坊账户私钥
示例:部署到 Sepolia测试网或本地anvil节点
假设你要部署 src/Counter.sol 中的 Counter 合约:
forge create src/Counter.sol:Counter \
--rpc-url https://sepolia.infura.io/v3/<YOUR_INFURA_PROJECT_ID> \
--private-key <YOUR_PRIVATE_KEY>
常用参数
--constructor-args <args>:传递构造函数参数--verify:部署后自动在 Etherscan 验证合约--etherscan-api-key <API_KEY>:用于合约验证的 Etherscan API Key
keystore方式
cast wallet import -i test_account1 //输入私钥 输入密码加密
cast wallet address --account test_account1 //查看
cat ~/.foundry/keystores/test_account1
chain id
cast chain-id --rpc-url https://sepolia.infura.io/v3/你的APIKEY
11155111
注意 sepolia.infura.io/v3/你的APIKEY 在developer.metamask.io 获取Sepolia的infura RPC 或者chainlist.org/ 寻找免费节点
.env
# sepolia
# ETH_RPC_URL=https://sepolia.infura.io/v3/你的APIKEY
# ETH_FROM=你的ADDRESS
# CHAIN=11155111
# ETH_KEYSTORE_ACCOUNT=test_account1
# local
ETH_KEYSTORE_ACCOUNT=local_account1
ETH_FROM=你的anvil_address
forge create -h|grep env
部署anvil
forge create src/Counter.sol:Counter --broadcast
[⠊] Compiling...
No files changed, compilation skipped
Enter keystore password:
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9xxxx
Deployed to: 0x5FbDB2315678afecb367f032d93F642f641xxxx
Transaction hash: 0xad70bb57c7cad1fc9c13aa8619c7613d976a89c0964e8c4555710ef3xxxxxxxx
注意
1.部署前使用forge create src/Counter.sol:Counter不加广播(--broadcast)进行模拟部署不会上链
2.anvil本地节点部署在.env中只需要本地节点的ETH_KEYSTORE_ACCOUNT和ETH_FROM,
部署sepolia
# sepolia
ETH_RPC_URL=https://sepolia.infura.io/v3/你的APIKEY
ETH_FROM=你的address
CHAIN=11155111
ETH_KEYSTORE_ACCOUNT=test_account1
VERIFIER_API_KEY=你的etherscanAPIKey
# local
# ETH_KEYSTORE_ACCOUNT=local_account1
# ETH_FROM=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
forge create --account test_account1 Counter --broadcast
[⠊] Compiling...
No files changed, compilation skipped
Enter keystore password:
Deployer: 0xeeb0F1Dc21d47B81545c923523eE345002A2E5C0
Deployed to: 0x2F29BEc88245e3a334e94d6fdE27000f9b8407cE
Transaction hash: 0x3d71ee0c76eeadaba0a9b5b8b6a8aa98b814c981dcf7adfebec35628e3a747e9
验证开源
forge v 合约地址 Counter
Submitted contract for verification:
Response: `OK`
GUID: `ntyadhwyse3m6c82hwmt3zagza4hdfwcleg3f1mj1lusnahkma`
URL: https://sepolia.etherscan.io/address/0x2f29bec88245e3a334e94d6fde27000f9b8407ce
注意 需要申请区块链浏览器的apikey 并写入环境变量VERIFIER_API_KEY
注意 等待verified绿色 出现源码
脚本部署
注意 使用脚本部署依然依赖于你的环境变量.env,使用forge script -h|grep -Ei "ETH_RPC_URL|ETH_FROM|CHAIN|ETH_KEYSTORE_ACCOUNT|VERIFIER_API_KEY" 获取检查你的环境变量,脚本部署与命令行部署区别不大,脚本部署可以一次部署多个合约,并且可以编排检查
contract CounterScript is Script {
Counter public counter;
function setUp() public {}
function run() public {
vm.startBroadcast();
counter = new Counter();
vm.stopBroadcast();
}
}
forge script script/Counter.s.sol:CounterScript --rpc-url https://sepolia.infura.io/v3/你的APIKEY
注意 先不广播 先模拟 可检查gas消耗
forge script script/Counter.s.sol:CounterScript --rpc-url https://sepolia.infura.io/v3/你的APIKEY
[⠊] Compiling...
No files changed, compilation skipped
Enter keystore password:
Script ran successfully.
## Setting up 1 EVM.
==========================
Chain 11155111
Estimated gas price: 0.033585386 gwei
Estimated total gas used for script: 236447
Estimated amount required: 0.000007941163763542 ETH
==========================
真实部署加验证开源
forge script script/Counter.s.sol:CounterScript --rpc-url https://sepolia.infura.io/v3/你的apikey --broadcast --verify -vvvv
[⠊] Compiling...
No files changed, compilation skipped
Enter keystore password:
Traces:
[160270] CounterScript::run()
├─ [0] VM::startBroadcast()
│ └─ ← [Return]
├─ [119563] → new Counter@0xbDf202A319868310FEaBd0b18A4306491832AF7c
│ └─ ← [Return] 597 bytes of code
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]
Script ran successfully.
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[119563] → new Counter@0xbDf202A319868310FEaBd0b18A4306491832AF7c
└─ ← [Return] 597 bytes of code
==========================
Chain 11155111
Estimated gas price: 0.065441444 gwei
Estimated total gas used for script: 236447
Estimated amount required: 0.000015473433109468 ETH
==========================
##### sepolia
✅ [Success] Hash: 0x3df28f7c798a51e39ba667987a224cdc66782a6273c0ace8fa6fc7675d887394
Contract Address: 0xbDf202A319868310FEaBd0b18A4306491832AF7c
Block: 8475622
Paid: 0.000006333707343808 ETH (181883 gas * 0.034822976 gwei)
✅ Sequence #1 on sepolia | Total Paid: 0.000006333707343808 ETH (181883 gas * avg 0.034822976 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (1) contracts
Start verifying contract `0xbDf202A319868310FEaBd0b18A4306491832AF7c` deployed on sepolia
EVM version: cancun
Compiler version: 0.8.30
Submitting verification for [src/Counter.sol:Counter] 0xbDf202A319868310FEaBd0b18A4306491832AF7c.
Warning: Could not detect the deployment.; waiting 5 seconds before trying again (4 tries remaining)
Submitting verification for [src/Counter.sol:Counter] 0xbDf202A319868310FEaBd0b18A4306491832AF7c.
Warning: Could not detect the deployment.; waiting 5 seconds before trying again (3 tries remaining)
Submitting verification for [src/Counter.sol:Counter] 0xbDf202A319868310FEaBd0b18A4306491832AF7c.
Warning: Could not detect the deployment.; waiting 5 seconds before trying again (2 tries remaining)
Submitting verification for [src/Counter.sol:Counter] 0xbDf202A319868310FEaBd0b18A4306491832AF7c.
Warning: Could not detect the deployment.; waiting 5 seconds before trying again (1 tries remaining)
Submitting verification for [src/Counter.sol:Counter] 0xbDf202A319868310FEaBd0b18A4306491832AF7c.
Submitted contract for verification:
Response: `OK`
GUID: `snbeuz71u1bsp3n8ms8rxe4tipwbcqbjcu5nn3qmkhjhravndm`
URL: https://sepolia.etherscan.io/address/0xbdf202a319868310feabd0b18a4306491832af7c
Contract verification status:
Response: `NOTOK`
Details: `Already Verified`
Contract source code already verified
All (1) contracts were verified!
7. Cast 操作合约
7.1 基础查询命令
网络状态查询:
# 查询当前网络 Chain ID
cast chain-id --rpc-url $RPC_URL
# 查询最新区块号
cast block-number --rpc-url $RPC_URL
# 查询 Gas 价格
cast gas-price --rpc-url $RPC_URL
7.2 合约交互
查询合约状态:
# 查询合约变量
cast call $CONTRACT_ADDRESS "number()" --rpc-url $RPC_URL
# 查询合约字节码
cast code $CONTRACT_ADDRESS --rpc-url $RPC_URL
# 查询合约余额
cast balance $CONTRACT_ADDRESS --rpc-url $RPC_URL
发送交易:
# 调用合约方法(需要私钥)
cast send $CONTRACT_ADDRESS "increment()" \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL
# 带参数的合约调用
cast send $CONTRACT_ADDRESS "setNumber(uint256)" 100 \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL
7.3 交易信息查询
交易详情:
# 查询交易信息
cast tx $TX_HASH --rpc-url $RPC_URL
# 查询交易收据
cast receipt $TX_HASH --rpc-url $RPC_URL
# 追踪交易执行
cast trace $TX_HASH --rpc-url $RPC_URL
7.4 实用工具
数据转换:
# ABI 编码
cast abi-encode "transfer(address,uint256)" $ADDRESS 100
# 计算函数选择器
cast sig "transfer(address,uint256)"
# 解码交易输入数据
cast 4byte $TX_INPUT_DATA
8. Chisel - Solidity REPL 工具
8.1 什么是 Chisel?
Chisel 是 Foundry 工具链中的 Solidity REPL(Read-Eval-Print Loop)工具,它允许你:
- 快速测试 Solidity 代码片段
- 实时验证合约逻辑
- 学习和实验 Solidity 语法
- 调试合约功能
8.2 基本使用
启动 Chisel:
# 进入 Chisel REPL 环境
chisel
# 你会看到类似这样的提示符
➜
基本操作示例:
// 声明变量
uint256 x = 1;
// 定义函数
function add(uint256 a, uint256 b) pure returns (uint256) {
return a + b;
}
// 调用函数
add(1, 2)
8.3 高级功能
导入外部合约
// 导入 OpenZeppelin 合约
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 创建合约实例
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {}
}
9. 总结与最佳实践
9.1 完整开发流程回顾
从零到部署的完整流程:
-
环境准备
- 安装 Foundry:
curl -L https://foundry.paradigm.xyz | bash - 更新工具链:
foundryup
- 安装 Foundry:
-
项目初始化
- 创建项目:
forge init my-foundry-project - 目录结构:
src/(合约)、test/(测试)、script/(部署脚本)
- 创建项目:
-
合约开发
- 编写智能合约(如 Counter.sol)
- 使用
forge build编译 - 使用
forge test运行测试
-
本地测试
- 启动本地节点:
anvil - 运行测试:
forge test -vvvv - 模拟部署:
forge script script/Counter.s.sol:CounterScript
- 启动本地节点:
-
正式部署
- 配置环境变量(.env)
- 准备部署脚本
- 执行部署:
forge script script/Counter.s.sol:CounterScript --broadcast --verify
-
合约验证
- 确认部署成功
- 验证合约源码
- 在区块浏览器查看
-
合约交互
- 使用
cast查询状态 - 发送交易调用函数
- 监控交易状态
- 使用