Foundry 是一个基于 Rust 的以太坊应用开发工具包,可编译 Solidity、运行用 Solidity 编写的单元测试和模糊测试、部署合约,并从命令行与链交互。其四个核心工具是:用于构建/测试/部署的 Forge、用于带分叉的本地节点的 Anvil、用于链交互的 Cast,以及用于 Solidity REPL 的 Chisel。本文提供一套可复现的工作流:搭建项目、编写 Forge 测试、针对 RPC 端点运行分叉、使用脚本部署并验证结果。文章还涵盖常见故障模式,如重映射、分叉 RPC 错误以及 nonce 或 gas 问题,并说明可靠的 RPC 提供商在哪些环节至关重要。
Foundry 为 Solidity 工作流带来的价值
Foundry 是一个用 Rust 编写的快速、便携、模块化的以太坊应用开发工具包。它管理依赖、编译 Solidity、运行单元测试和模糊测试、部署合约,并从终端或 Solidity 脚本驱动脚本化的链交互。核心工具包括 Forge(构建、测试、调试、部署、验证)、Anvil(带分叉的本地以太坊节点)、Cast(用于链交互的 CLI)和 Chisel(Solidity REPL)。
与 JavaScript 优先的框架相比,实际区别在于测试和部署脚本可以用 Solidity 编写。这消除了合约层面工作中在 Solidity 和 JavaScript 之间切换上下文的需要,并使模糊测试和基于追踪的调试成为默认循环的一部分。当你分叉实时网络或广播交易时,Foundry 并不能替代对 RPC 端点的需求;它消费 RPC 端点。该端点的质量和一致性直接影响分叉测试和部署。
- Forge:编译、测试、模糊测试、调试、部署、验证。
- Anvil:本地节点、分叉、确定性账户。
- Cast:调用、发送、解码、检查链状态。
- Chisel:快速 Solidity 原型设计和调试。
前提条件与假设
本工作流假设使用类 Unix shell、Git,并通过 foundryup 安装了可用的 Foundry。假设你拥有要分叉或部署到的网络的 RPC_URL,以及用于任何广播步骤的已充值 PRIVATE_KEY。切勿提交私钥;请使用环境变量或加密密钥库。
示例使用 RPC_URL 和 PRIVATE_KEY 等占位符,而非公共端点。要选择具体端点,RPC Assistant 提供了提供商和网络建议;本文侧重于开发方法以及你应该自行运行的检查。
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version
anvil --version
cast --version项目布局与依赖心智模型
Foundry 项目将合约、测试、脚本和依赖分开。src/ 目录存放 Solidity 源文件,test/ 存放 Forge 测试,script/ 存放部署脚本,lib/ 存放通过 forge install 安装的依赖。重映射将导入路径映射到这些依赖,这是大多数新用户容易出错的地方。
依赖通常是 Git 仓库或子模块。这很轻量,但意味着导入路径必须与安装的仓库结构匹配。如果导入失败,请检查 remappings.txt 并在更改代码前运行 forge remappings。对于偏好包管理器语义的团队,依赖也可以通过 package.json 和重映射来管理,但底层规则相同:编译器必须将每个导入解析到真实文件。
forge init my-foundry-app
cd my-foundry-app
forge install foundry-rs/forge-std
forge remappings
forge build用 Solidity 编写 Forge 测试
Forge 测试是继承自 forge-std 的 Test 合约的 Solidity 合约。测试函数以 test 为前缀,模糊测试接受参数。下面的示例测试一个最小的计数器合约,包括一个在递增后检查不变量的模糊测试用例。
使用 forge test 运行测试套件。添加 -vvvv 可在测试失败时查看执行追踪。预期的验证结果是显示通过测试和零失败的摘要行;如果模糊测试失败,Forge 会打印反例输入,以便你复现。
// src/Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 public number;
function increment() external { number += 1; }
}
// test/Counter.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
Counter counter;
function setUp() public { counter = new Counter(); }
function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testFuzz_Increment(uint8 times) public {
for (uint256 i = 0; i < times; i++) counter.increment();
assertEq(counter.number(), times);
}
}使用 Anvil 和 RPC 端点进行分叉测试
分叉测试针对实时链状态的快照运行你的合约。Anvil 启动一个本地节点,按需从 RPC 端点获取状态。这就是在不花费真实资金的情况下测试与已部署协议集成的方式。
使用分叉 URL 启动 Anvil,然后将 Forge 指向本地节点。预期的验证结果是 Anvil 报告分叉区块号,并且 Forge 测试针对分叉状态通过。如果分叉失败,错误通常会指出失败的 RPC 请求,这是你的第一个诊断信号。
anvil --fork-url $RPC_URL --fork-block-number 19000000
# In another terminal
forge test --fork-url http://127.0.0.1:8545 -vvv部署脚本与广播验证
Foundry 部署脚本是继承自 Script 的 Solidity 合约。run 函数部署并返回合约。广播是一个单独的步骤:带 --broadcast 的 forge script 将交易发送到 RPC 端点。
广播后,使用 Cast 独立验证结果。预期的验证结果是返回的地址有代码,且交易收据状态为 1。不要将脚本的控制台输出视为部署证明;查询链。
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Script} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
contract Deploy is Script {
function run() external returns (Counter) {
vm.startBroadcast();
Counter counter = new Counter();
vm.stopBroadcast();
return counter;
}
}
# Broadcast
forge script script/Deploy.s.sol:Deploy \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast
# Verify independently
cast code $CONTRACT_ADDRESS --rpc-url $RPC_URL
cast receipt $TX_HASH --rpc-url $RPC_URL分叉和 RPC 故障的诊断流程
当分叉测试或部署失败时,隔离问题出在你的代码、配置还是端点。按顺序运行以下检查并记录结果。这会生成一个结果表,你可以跨端点或运行进行比较。
流程:(1) 确认 RPC 端点响应基本的链 ID 请求;(2) 确认分叉区块可用;(3) 使用 -vvvv 运行失败的测试;(4) 如果可用,针对第二个端点重复。记录链 ID、分叉区块、测试结果和错误消息。不要编造数字;用你的实际观察填写表格。
- 链 ID 不匹配:端点对应的网络与你的配置不同。
- 区块缺失:该端点上分叉区块不可用。
- 速率限制或超时:端点正在限流或不可达。
- Nonce 错误:前一笔交易待处理或密钥被重复使用。
cast chain-id --rpc-url $RPC_URL
cast block-number --rpc-url $RPC_URL
cast block 19000000 --rpc-url $RPC_URL
forge test --fork-url $RPC_URL --fork-block-number 19000000 -vvvv结果表模板
使用如下 Markdown 表格记录你自己的测量结果。列是建议性的;重点是将文档描述的行为与你观察到的行为分开。用你的实际运行替换占位行。
| 检查项 | 命令 | 预期 | 观察结果 |
|---|---|---|---|
| 链 ID | cast chain-id | 匹配目标网络 | (填写) |
| 分叉区块 | cast block 19000000 | 区块存在 | (填写) |
| 单元测试 | forge test | 全部通过 | (填写) |
| 分叉测试 | forge test --fork-url | 全部通过 | (填写) |
| 部署收据 | cast receipt | 状态为 1 | (填写) |
排查常见的 Foundry 错误
大多数早期的 Foundry 错误属于几类。导入解析失败通常意味着缺少依赖或重映射错误。分叉失败通常意味着 RPC 端点无法提供请求的区块或被限流。广播失败通常意味着 nonce、gas 或密钥配置问题。
对于导入错误,运行 forge remappings 并将输出与你的导入语句比较。对于分叉错误,使用 Cast 测试同一区块。对于广播错误,使用 cast balance 和 cast nonce 检查发送者余额和待处理 nonce。如果端点是变量,比较不同提供商的行为;RPC Assistant 可以帮助你选择一个,而关于 RPC 端点的 Learn 指南解释了底层的请求模式。
- 导入未找到:验证 lib/ 内容和 remappings.txt。
- 分叉 RPC 错误:确认区块可用性和端点速率限制。
- Nonce 太低:检查同一密钥的待处理交易。
- Gas 估算失败:在发送前使用 cast call 模拟。
局限性与权衡
Foundry 的 Solidity 优先测试非常适合合约逻辑、模糊测试和追踪,但它并不能完全替代所有集成需求。测试 JavaScript 前端交互或复杂多链部署插件的团队有时会在 Foundry 之外保留基于 JavaScript 的工具。通过 Git 子模块管理依赖可能比包管理器感觉更轻量,而重映射需要纪律。
分叉测试依赖于 RPC 端点提供历史状态的能力。缓慢或限流的端点会使分叉测试不稳定,这可能看起来像代码错误。将端点可靠性视为测试基础设施的一部分,而不是事后考虑。对于生产部署,使用专用端点并独立验证交易。
后续步骤
一旦基本循环正常工作,就扩展它:添加不变量测试、集成覆盖率报告,并在 CI 中使用固定的分叉区块运行测试套件。关于端点选择,请参阅 RPC Assistant 关于以太坊 RPC API 的指南,并在确定提供商之前比较定价和网络覆盖范围。
有用的相关阅读:RPC 端点的 Learn 指南、多链 RPC 端点指南,以及用于生产工作负载的专用节点概述。如果你要部署到特定网络,请查看支持的网络列表和 RPC 计划的定价页面。
- 为关键不变量添加不变量测试和模糊测试。
- 在 CI 中固定分叉区块以确保可复现性。
- 使用 Cast 验证每次部署,而不仅仅是脚本输出。
- 选择符合你分叉和部署需求的端点。