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 个字节找到合约中对应的代码段并执行。