Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
集成与开发阅读约 11 分钟

Foundry Solidity 开发:可复现的构建、测试与部署工作流

一份实用且可复现的指南,介绍如何使用 Foundry 工具包进行 Solidity 智能合约开发,涵盖项目布局、Forge 测试、Anvil 分叉以及基于 RPC 的部署。

TL;DR

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 表格记录你自己的测量结果。列是建议性的;重点是将文档描述的行为与你观察到的行为分开。用你的实际运行替换占位行。

检查项命令预期观察结果
链 IDcast 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 验证每次部署,而不仅仅是脚本输出。
  • 选择符合你分叉和部署需求的端点。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始