0. 开发闭环
0.1 实战流转
-
0.1.1 阶段一:
src/资产固化 (生产线):所有经过验证的、最终要部署的业务逻辑必须存放在此。它是项目的“零件库”,要求代码极其纯净,禁止包含任何测试工具或作弊码。 -
0.1.2 阶段二:
test/压测验证 (实验室):-
初期练手:学习 Solidity 语法时,直接在
test/编写脚本,利用极速反馈观察逻辑。 -
逻辑引用:通过
import "../src/..."调用成品合约,利用“上帝模式”(Cheatcodes)进行边界测试。
-
-
0.1.3 阶段三:
script/自动化发布 (物流车):不生产逻辑,仅负责“调度”。它引用src/中的成品,通过vm.startBroadcast()将其安全地运输并发布到区块链上。
0.2 去测试化
-
0.2.1 提取动作要点:从
test/向src/迁移代码时,必须完成以下清理:-
0.2.1.1 剥离外壳:删除
is Test继承,删除所有以test_开头的函数前缀。 -
0.2.1.2 移除作弊码:彻底清理所有
vm.*指令,这些指令在真实链上无法执行。 -
0.2.1.3 转换逻辑:将测试用的
assertEq转换为正式合约的require或revert约束机制。
-
-
0.2.2 引用链原则:始终保持“单向引用”。
test和script共同指向src,而src保持独立,不反向引用任何测试或脚本文件。
0.3 运维概览
-
0.3.1 命令行即仪表盘:
-
逻辑验证:
forge test获取真理。 -
实战部署:
forge script改变世界。
-
-
0.3.2 核心运维金句:在
src里写产品,在test里找麻烦,在script里发货。
1. 本地验证
场景:不涉及任何合约部署,单纯利用 Foundry 的测试沙盒运行一段 Solidity 代码逻辑,在终端快速输出结果。
1.1 轻量演示
文件路径:test/PureHello.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
contract PureHello is Test {
function test_Run() public pure {
// 纯逻辑输出
console.log("-------------------------------");
console.log("Hello World! This is a Dry Run.");
console.log("-------------------------------");
}
}
-
1.1.1 细节说明:
-
无需业务合约:此模式不需要
src/下的代码,仅用于测试环境是否通畅或练习 Solidity 基础语法。 -
极速反馈:使用
test目录不需要启动 Anvil 节点,也不需要配置私钥,适合高频逻辑调试。
-
1.2 执行版本
-
1.2.1 参数说明:
-
—use solc:0.8.20:强制指定编译器版本。
-
-vv:必须开启,否则无法查看到
console.log的输出内容。 -
—match-path:明确指定只运行这一个演示文件,避免执行项目中其他的测试用例。
-
-
1.2.2 指定 Foundry 的 Solidity 版本运行:
forge test --match-path test/Test.t.sol --use solc:0.8.20 -vv
forge test --mp test/Test.t.sol --use solc:0.8.20 --mc ContractName -vv
- 1.2.3 Foundry 尝试调用系统默认或已缓存的编译器:
forge test --match-path test/Test.t.sol -vv
forge test --mp test/Test.t.sol --mc ContractName -vv
2. 私链部署
场景:将定义好的业务合约真实部署到本地 Anvil 环境,模拟完整上链生命周期。
2.1 合约定义
文件路径:src/HelloWorld.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract HelloWorld {
string private message;
constructor(string memory _initialMessage) { message = _initialMessage; }
function getMessage() public view returns (string memory) { return message; }
function setMessage(string memory _newMessage) public { message = _newMessage; }
}
-
2.1.1 细节说明:
-
状态变量:
message存储在区块链的存储插槽(Storage Slot)中,具有持久性。 -
构造函数:在部署那一刻运行,用于写入初始数据。
-
编码要求:文件必须保存为 UTF-8 编码,否则编译器会报源码流读取错误。
-
2.2 调度脚本
文件路径:script/HelloWorld.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {HelloWorld} from "../src/HelloWorld.sol";
contract HelloWorldScript is Script {
function run() external {
vm.startBroadcast(); // 开启真实上链录制
HelloWorld hello = new HelloWorld("Deploy to Anvil");
console.log("Deployed Address:", address(hello));
vm.stopBroadcast(); // 结束录制
}
}
-
2.2.1 细节说明:
-
vm.startBroadcast():告知 Foundry 之后的所有指令需要进行私钥签名并发送至 RPC 节点。
-
实例化:
new HelloWorld动作在区块链上会触发CREATE指令,生成新的合约地址。
-
2.3 广播部署
-
终端 A:执行
anvil启动本地节点。 -
终端 B:执行以下部署命令。
forge script script/HelloWorld.s.sol --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --use solc:0.8.20 --tc HelloWorldScript -vv
2.4 交互验证
部署完成后,必须脱离脚本环境,使用命令行工具直接与链上合约对话。
- 2.4.1 状态查询 (Cast Call):使用
cast call验证合约中的初始信息是否为 “Deploy to Anvil”,将 <合约地址> 替换为 2.3.2 步骤中记录的地址
cast call <合约地址> "getMessage()(string)" --rpc-url http://127.0.0.1:8545
-
2.4.2 细节说明:
cast call发起的是一个查询请求,它不消耗 Gas,也不改变链上状态。 -
2.4.3 状态修改 (Cast Send):模拟用户通过私钥修改合约里的信息,修改信息为 “New Message”
cast send <合约地址> "setMessage(string)" "New Message" --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-
2.4.4 细节说明:
cast send发起的是一个真实交易,它需要签名并消耗 Gas。执行成功后会返回transactionHash。 -
2.4.5 最终确认:再次运行 2.4.1 的查询命令,如果返回结果变为
"New Message",则标志着该合约在生产环境下功能完全正常。
3. Debug调试
在 Web3 运维体系中,Debug 的本质不是“设断点”,而是通过控制 EVM 执行环境的信息暴露等级来回溯交易细节。
3.1 核心指令
- 3.2.1 验证真理 (Test):逻辑验证第一步。用于断言结果、复现 Bug 及安全性审计。
* # 运行单元测试并查看日志输出
forge test -vv
# 查看失败测试的详细内部调用追踪 (Debug 必备)
forge test -vvvv
- 3.2.2 改变世界 (Script):逻辑通过后的实战。适用于部署、转账及线上环境模拟。
* # 运行脚本并查看控制台输出
forge script script/FileName.s.sol -vv --tc ContractName
# 生产级部署并查看详细上链过程 (Traces)
forge script script/HelloWorld.s.sol --rpc-url http://127.0.0.1:8545 --private-key $KEY --broadcast -vvvv
- 3.2.3 底层审计 (Inspect):收尾阶段。审计存储插槽分布、获取 ABI、分析字节码大小。
forge inspect ContractName storage
3.2 详细等级:
-
3.2.1 基础日志 (-vv):显示
console.log输出内容,适用于基础业务逻辑验证。 -
3.2.2 失败堆栈 (-vvv):执行失败时显示调用堆栈,帮助精准定位报错所在的源码行数。
-
3.2.3 完整追踪 (-vvvv):上帝视角,展示完整的执行轨迹 (Traces),包括所有内部调用、入参及回传值。
-
3.2.4 终极视角 (-vvvvv):额外打印存储插槽 (Storage Slots) 的读写变化及每一步的堆栈快照。
4. Solidity版本
4.1 脚本锁定
- 为了避免每次命令都输入
--use参数,需在 foundry.toml 中完成全局锁定。
[profile.default]
# 运维核心:锁定编译器版本,确保团队环境一致性
solc_version = "0.8.20"
4.2 优先级
- 配置文件设定的版本是默认值,命令行参数
--use具有最高优先级,可临时覆盖。
5. 避坑整理
5.1 Foundry
-
独立管理机制:Foundry 使用一个名为
svm(Solidity Version Manager) 的内置组件。它会尝试从官方服务器下载特定版本的静态二进制文件,并存储在~/.svm/目录下。 -
报错根源:
solc --version能显全局版本,但命令有--use solc:0.8.20时,Foundry 优先检查~/.svm/0.8.20是否存在。不存在它会立即尝试联网下载list.json来寻找下载地址。 -
系统solc限制:Foundry 默认不调用系统
solc是为了防止不同开发者因为系统补丁、g++ 版本不同而导致编译出的合约字节码(Bytecode)不一致。
5.2 路径检查
- 必须
cd进入项目根目录(含有foundry.toml的目录)执行forge命令。
5.3 编码冲突
- 若报错
stream did not contain valid UTF-8,请检查文件右下角是否误设为 GB2312。
5.4 版本调度
-
常规运维:在
foundry.toml中通过solc_version锁定版本。 -
紧急调试:在命令行使用
--use solc优先级最高,用于临时测试。
5.5 参数遗漏
- 运行脚本时若报格式错误,优先检查是否漏掉了
--tc(目标合约类名)。