1. 标准还原

1.1 在 src/ 定义业务逻辑

不要直接在测试里写,先在 src/ 创建零件。

  • 1.1.1 创建合约文件:新建 src/HelloWeb3.sol

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    contract HelloWeb3 {
        // 这行代码执行后,字符串 "Hello Web3!" 会被写入区块链的存储插槽(Storage)
        string public _string = "Hello Web3!";
    }
    
  • 1.1.2 源码逐行解析

    • // SPDX-License-Identifier: MIT:软件包数据交换许可证标识。这行告诉编译器和区块链浏览器该代码采用 MIT 开源协议,不写会报编译警告。

    • pragma solidity ^0.8.20;:版本指令。

      • 作用:告诉编译器必须使用 0.8.20 或更高版本(但小于 0.9.0)来编译此代码。

      • 用途:防止代码在未来语法变更的编译器中产生解析错误,确保字节码的一致性。

      • 其他用途:还可用于开启实验性功能(如 pragma experimental SMTChecker)或指定 ABI 编码器版本(如 pragma abicoder v2)。

    • contract HelloWeb3 { ... }:声明一个名为 HelloWeb3 的合约,类似于面向对象语言中的 class,它是逻辑和数据的容器。

    • string public _string = "Hello Web3!";

      • string:定义数据类型为动态大小的 UTF-8 编码字符串。

      • public:状态变量修饰符。编译器会自动生成一个只读函数 _string()

      • _string:变量名。在 EVM 底层,这个值被永久存储在合约账户对应的 Storage 插槽中。

1.2 在 test/ 进行沙盒验证

在真实部署前,先在实验室测试这个零件是否合格。

  • 1.2.1 创建测试文件:新建 test/HelloWeb3.t.sol

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    import {Test, console} from "forge-std/Test.sol";
    import {HelloWeb3} from "../src/HelloWeb3.sol";
    
    contract HelloWeb3Test is Test {
        HelloWeb3 hw;
    
        function setUp() public {
            hw = new HelloWeb3(); // 模拟部署动作
        }
    
        function test_Value() public view {
            // 验证合约里的值是否真的是 "Hello Web3!"
            console.log("Contract Value:", hw._string());
            assertEq(hw._string(), "Hello Web3!");
        }
    }
    
  • 1.2.2 测试代码逐行解析

    • import {Test, console} ...:导入 Foundry 标准库的测试基类(提供断言)和控制台模块(用于打印日志)。

    • import {HelloWeb3} ...:通过相对路径引用刚才在 src 中编写的业务合约。

    • contract HelloWeb3Test is Test { ... }:定义测试合约并继承 Test。继承后可直接使用 assertEq 等测试工具。

    • HelloWeb3 hw;:声明一个 HelloWeb3 类型的状态变量 hw,用于存储部署后的合约实例地址。

    • function setUp() public { ... }

      • hw = new HelloWeb3();:使用 new 关键字在本地测试环境中部署合约,并将返回的地址赋值给 hw
    • function test_Value() public view { ... }

      • console.log(...):在终端输出 _string 的当前值,方便调试。

      • hw._string():调用 hw 实例自动生成的 public 查询函数。

      • assertEq(a, b):核心测试逻辑。如果 hw._string() 的值不等于 "Hello Web3!",测试将失败并报错。

1.3 在 script/ 录制上链动作

当你确定逻辑没错,就可以通过“物流车”发货了。

  • 1.3.1 编写脚本:新建 script/HelloWeb3.s.sol

    pragma solidity ^0.8.20;
    import {Script} from "forge-std/Script.sol";
    import {HelloWeb3} from "../src/HelloWeb3.sol";
    
    contract DeployHello is Script {
        function run() external {
            vm.startBroadcast();
            new HelloWeb3(); // 这里的 new 动作会消耗真实 Gas
            vm.stopBroadcast();
        }
    }
    
  • 1.3.2 脚本代码逐行解析

    • import {Script} from "forge-std/Script.sol";:导入 Foundry 脚本基类。

    • contract DeployHello is Script { ... }:定义部署脚本合约。

    • function run() external { ... }:Foundry 执行脚本时默认寻找的入口函数。

    • vm.startBroadcast();:调用 Foundry 虚拟机特有的作弊码(Cheatcode)。从这行开始,所有的合约创建和交互都会被记录,并准备转换成真实的链上交易。

    • new HelloWeb3();:在广播状态下执行,这会生成一条 Contract Creation 交易。

    • vm.stopBroadcast();:停止录制交易。

2. Cast 交互

通过 Cast 命令见证数据在链上的物理存在。

2.1 链上状态

  • 2.1.1 闭环验证指令解析

    cast call <你的合约地址> "_string()(string)" --rpc-url http://127.0.0.1:8545
    
    • cast call:发起一个静态调用(eth_call),不消耗 Gas,用于读取数据。

    • <你的合约地址>:部署脚本运行后输出的 Deployed to: 0x... 地址。

    • "_string()(string)"

      • _string():我们要调用的函数名。

      • (string):告诉 Cast 返回值是字符串类型,以便它进行十六进制解码。

    • --rpc-url ...:指定与哪条链交互,此处为本地私链 Anvil。

  • 2.1.2 结果反馈:终端返回 "Hello Web3!"。此时,这个字符串已写入本地私链 Anvil 数据库的存储槽中。

3. 核心机制

3.1 状态存储

  • 3.1.1 存储持久化:在 HelloWeb3 合约中,即使代码运行结束,_string 依然存在。这是因为 EVM 将变量存储在合约地址对应的 Storage Trie 中。

  • 3.1.2 函数选择器:当你在 Cast 中输入 _string() 时,系统会将其计算为 Keccak-256 哈希的前 4 个字节。区块链根据这 4 个字节找到合约中对应的代码段并执行。