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 转换为正式合约的 requirerevert 约束机制。

  • 0.2.2 引用链原则:始终保持“单向引用”。testscript 共同指向 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 广播部署

  1. 终端 A:执行 anvil 启动本地节点。

  2. 终端 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(目标合约类名)。