第二个合约:Hello World
好,咱们正式开始写第一个智能合约。
说实话,我当年学智能合约时,第一个程序也是 Hello World。这就像学编程语言的 tradition,你懂的。不过区块链上的 Hello World 有点不一样——它跑在链上,谁都能查,谁也改不了。
创建文件
先建个文件夹,叫 hello-world。在里面新建一个文件,命名为 HelloWorld.sol。
注意后缀名是 .sol,这是 Solidity 的标准扩展名。我见过有人写成 .txt 然后编译报错,嗯,这种坑我踩过。
编写基础合约结构
打开文件,敲入以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
string public greeting;
constructor() {
greeting = "Hello, World!";
}
function setGreeting(string memory _newGreeting) public {
greeting = _newGreeting;
}
function getGreeting() public view returns (string memory) {
return greeting;
}
}
咱们一行行拆开看。
第一行:// SPDX-License-Identifier: MIT
这是开源许可证声明。说白了就是告诉别人:这代码你可以随便用,遵循 MIT 协议。我个人习惯每个文件都加上,省得后面审计时被人问。
第二行:pragma solidity ^0.8.0;
指定编译器版本。^0.8.0 表示兼容 0.8.x 系列。为什么这么写?因为 0.8 版本之后 Solidity 做了很多安全改进,比如默认检查整数溢出。我之前用 0.7 写过一个合约,结果上线后被人利用溢出漏洞……从那以后我再也不用低于 0.8 的版本了。
第三行:contract HelloWorld { ... }
合约定义。你可以把它理解成一个类(class),里面可以存数据、写函数。
状态变量:string public greeting;
这个 public 关键字很关键。它自动生成一个 getter 函数,外部可以直接读取。你想想看,省了你写一个 view 函数的功夫。
构造函数:constructor()
部署时自动执行一次。这里我们把 greeting 初始化为 "Hello, World!"。
两个函数:
setGreeting:修改 greeting 的值。注意参数用了memory关键字,这是 Solidity 的数据位置,后面会细讲。getGreeting:读取当前 greeting。加了view表示不消耗 gas,只读操作。
public 就够了。我写 getGreeting 函数纯粹是为了演示,实际项目中很少这么干。
编译与部署
写完了代码,下一步就是编译。我用的是 Hardhat 工具链,你也可以用 Remix(在线 IDE)。
用 Hardhat 编译:
npx hardhat compile
如果一切顺利,你会看到 Compiled 1 Solidity file successfully。如果报错,多半是版本问题,检查一下 hardhat.config.js 里的 Solidity 版本是否匹配。
部署脚本:
// scripts/deploy.js
async function main() {
const HelloWorld = await ethers.getContractFactory("HelloWorld");
const hello = await HelloWorld.deploy();
await hello.deployed();
console.log("合约部署地址:", hello.address);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
运行部署:
npx hardhat run scripts/deploy.js --network localhost
部署成功后,你会得到一个合约地址。这个地址就是你在链上的“门牌号”。
在链上交互
合约部署上链后,怎么跟它玩?两种方式:
方式一:用 Hardhat 控制台
npx hardhat console --network localhost
// 获取合约实例
const HelloWorld = await ethers.getContractFactory("HelloWorld");
const hello = await HelloWorld.attach("0x你的合约地址");
// 读取 greeting
await hello.getGreeting();
// 输出: "Hello, World!"
// 修改 greeting
await hello.setGreeting("你好,区块链!");
// 再次读取
await hello.getGreeting();
// 输出: "你好,区块链!"
方式二:用 ethers.js 写脚本
// scripts/interact.js
const { ethers } = require("hardhat");
async function main() {
const hello = await ethers.getContractAt("HelloWorld", "0x你的合约地址");
// 读取
let msg = await hello.getGreeting();
console.log("当前消息:", msg);
// 修改
let tx = await hello.setGreeting("从零构建智能合约");
await tx.wait(); // 等待交易确认
// 再读
msg = await hello.getGreeting();
console.log("修改后消息:", msg);
}
main();
运行交互脚本:
npx hardhat run scripts/interact.js --network localhost
setGreeting 都会产生一笔交易,需要 gas 费。而 getGreeting 是只读操作,免费。这就是区块链的“写要钱,读免费”原则。
避坑指南
- 字符串比较:Solidity 不支持
==直接比较字符串,需要用 keccak256 哈希后比较。我早期写过一个投票合约,因为字符串比较写错了,导致结果全乱套。 - Gas 估算:部署合约时,gas 上限设高一点。我习惯设 300000,不够再加。
- 版本兼容:0.8.x 和 0.7.x 的语法有差异,比如
now被移除了,改用block.timestamp。
总结
第一个合约跑通了,恭喜你!
说白了,智能合约就是一个“永不宕机的数据库+执行逻辑”。你写进去的数据,没人能删,没人能改(除非你留了后门)。
下一章咱们会深入 Solidity 的数据类型和变量,到时候你会看到更多有意思的东西。
uint256 public count。