第一个智能合约
现在把只读变成可写。这一章你会在浏览器 IDE 里写一个最小合约、部署到测试网、调用它,并搞懂那个把"人类要调的函数"翻译成"链上一串十六进制 calldata"的东西——ABI 与函数选择器。你还能在下面亲手编一段 calldata。
写一个最小合约
不用装任何东西:打开 Remix(remix.ethereum.org,浏览器里的 Solidity IDE)。新建文件,写下:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract Counter { uint256 public count; // 状态变量,存进合约存储 event Changed(uint256 newCount); // 事件,写进日志 function increment() public { count += 1; emit Changed(count); } function set(uint256 n) public { count = n; emit Changed(n); } }
count 是持久化状态,像存进 Room 的一个字段——但这里"数据库"是合约自己的存储,全网可读。public 的状态变量会自动生成一个同名 getter(读它走 eth_call,免费)。event 像是结构化日志(≈ Log.i 但可被程序检索),它写进交易回执的 logs 里,前端和索引器靠它感知"发生了什么"(第 12、20 章细讲)。
编译、部署、调用
- 编译:Remix 左侧 Solidity 编译器,选 0.8.24 编译。产物是 EVM 字节码 + ABI(下面就讲)。
- 部署:切到 "Deploy" 面板,Environment 选 "Injected Provider - MetaMask"(这样用你第 6 章的测试网账户),点 Deploy。MetaMask 弹窗——这是一笔特殊交易:
to为空,data里装的是合约字节码。确认后,合约获得一个自己的地址。 - 调用:部署成功后 Remix 列出合约的按钮。点
count(蓝色,只读,免费);点increment(橙色,写操作,弹 MetaMask 花 gas)。increment 确认后,再点 count,变成了 1。
恭喜——你部署并改变了一个跑在全球状态机上的程序。它现在永久存在于测试网上,任何人都能调用。
ABI:人话和机器话之间的翻译表
EVM 只认字节。你点的 increment() 按钮,最终发出去的 data 是一串十六进制。把 increment() 这种人类可读的函数签名,翻译成 EVM 认识的 calldata 的规则,就是 ABI(Application Binary Interface)。编译时 Remix 会生成一份 ABI(一段 JSON),描述合约有哪些函数、参数类型、返回值。SDK 拿着这份 ABI 才知道怎么编码调用、怎么解码返回。
ABI 就是合约的接口契约文件,作用等同于你用过的 .aidl(AIDL 描述跨进程接口)或 protobuf 的 .proto:双方拿着同一份契约,一边负责把方法调用序列化成字节,另一边反序列化后执行。没有 ABI,你面对的就是一坨无法解读的字节码。
函数选择器:calldata 的前 4 个字节
一段 calldata 的结构极其规整:
0x a9059cbb ← 4 字节函数选择器
000000000000000000000000 Alice地址(20字节补齐到32) ← 参数1
00000000000000000000000000000000000000000000000000000000000f4240 ← 参数2 (1000000)
函数选择器 = 对函数签名字符串做 keccak256、取前 4 字节。比如 transfer(address,uint256) 的选择器就是 0xa9059cbb。之后每个参数按 ABI 规则各补齐成 32 字节拼在后面。节点收到 calldata,先看前 4 字节匹配到哪个函数,再按 ABI 解出参数。就这么简单——你现在就能自己算。
set(uint256) 参数 42,看它编出的 calldata——这正是 Remix 点 set 按钮时发出去的东西。选择器只有 4 字节,理论上会碰撞:不同签名可能算出同一个选择器。这被用于一类"函数选择器钓鱼"——恶意合约里藏一个和常见函数选择器相同、但语义完全不同的函数。所以第 19 章讲签名时会强调:永远要让钱包/前端把 calldata 解码成人类可读的意图给用户看,别让用户盲签一串十六进制。
合约交互的全貌:你写 Solidity → 编译出字节码 + ABI → 部署得到地址 → 调用时按 ABI 把"函数名+参数"编码成 calldata(4 字节选择器 + 32 字节对齐的参数)→ 发交易(写)或 call(读)。卷二到此,你已经跑通了从读到写的完整闭环。卷三开始,我们钻进 Solidity 语言本身和它脚下的 EVM,把"会用"变成"精通"。
本章小结
- 用 Remix 可零安装写、编、部署、调合约;部署是一笔
to为空、data装字节码的交易。 - 读
public状态走 call 免费,写操作(increment)发交易花 gas。 - ABI 是合约的接口契约(≈ AIDL/proto),SDK 靠它编解码调用。
- calldata = 4 字节函数选择器(keccak 签名前 4 字节)+ 32 字节对齐的参数,你已能亲手编出。