4、Solidity智能合约开发实战:Remix IDE使用、Hardhat框架搭建、合约编译与部署、单元测试编写

好,咱们进入第四章节。说实话,很多刚入行的朋友一上来就问我:「老师,写合约到底用什么工具?」我的回答永远是——先玩熟 Remix,再上 Hardhat。这两样东西,一个像新手村的木剑,一个像出山后的神兵,缺一不可。

4.1 Remix IDE:快速原型验证

Remix 这东西,说白了就是个浏览器里的 Solidity 编辑器。我最早接触它的时候还觉得「这玩意儿能写正经合约?」后来真香了——快速验证想法,它比任何本地环境都快。

核心功能点:

  • 在线编译器:支持 Solidity 0.4.x 到 0.8.x 所有版本
  • 内置调试器:可以单步执行,查看变量变化
  • JavaScript VM:本地模拟链,不用花钱
  • 插件系统:比如 Solidity 静态分析、ABI 编码器等
我的小技巧: 在 Remix 里写合约时,我习惯先把「Auto compile」关掉。为什么?因为每次改一行代码就自动编译,有时候语法错误还没写完就被报错打断,很烦。手动编译,心里有数。

举个例子,你写一个最简单的 ERC-20 合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyToken {
    string public name = "MyToken";
    string public symbol = "MTK";
    uint8 public decimals = 18;
    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * 10 ** decimals;
        balanceOf[msg.sender] = totalSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        return true;
    }
}

在 Remix 里,你直接粘贴这段代码,选好编译器版本,点「Compile」,然后切到「Deploy & Run Transactions」面板,选 JavaScript VM,填个初始供应量,点「Deploy」——整个过程不到 30 秒。嗯,这就是 Remix 的魅力。

4.2 Hardhat 框架搭建:从零到一

但 Remix 毕竟只是玩具。真正要上生产环境,你得用 Hardhat。我个人觉得 Hardhat 比 Truffle 好用太多,尤其是它的任务系统和插件生态。

搭建步骤:

  1. 初始化项目:mkdir my-nft-project && cd my-nft-project && npm init -y
  2. 安装 Hardhat:npm install --save-dev hardhat
  3. 初始化 Hardhat 项目:npx hardhat(选「Create an empty hardhat.config.js」)
  4. 安装常用插件:npm install --save-dev @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethers
注意: 我曾经遇到过一个问题——安装完插件后跑 npx hardhat compile 一直报错,后来发现是 Node.js 版本太旧。Hardhat 要求 Node.js 16.x 以上,低于这个版本会出各种奇怪问题。所以第一步先检查你的 Node 版本:node -v

配置好后的 hardhat.config.js 大概长这样:

require("@nomiclabs/hardhat-waffle");

module.exports = {
  solidity: "0.8.9",
  networks: {
    goerli: {
      url: "https://eth-goerli.g.alchemy.com/v2/YOUR_API_KEY",
      accounts: ["你的私钥"]
    }
  }
};

4.3 合约编译与部署

写合约和部署合约,其实是两回事。写的时候你只管逻辑对不对,部署的时候要考虑 gas 费、网络状态、甚至合约大小。

编译命令:

npx hardhat compile

编译成功后,你会在 artifacts/contracts/ 目录下看到编译产物。这里面最重要的是 ABI(Application Binary Interface)和 Bytecode。ABI 是给前端调用的接口描述,Bytecode 是真正上链的机器码。

部署脚本示例:

// scripts/deploy.js
async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying contracts with account:", deployer.address);

  const MyNFT = await ethers.getContractFactory("MyNFT");
  const myNFT = await MyNFT.deploy();

  console.log("MyNFT deployed to:", myNFT.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

运行部署:npx hardhat run scripts/deploy.js --network goerli

避坑指南: 我曾经在部署一个 NFT 合约时,因为合约里有个循环遍历了 1000 个地址,结果 gas 费直接爆了。后来我改成用 Merkle Tree 的方式批量 mint,gas 费降了 90%。所以部署前一定要估算 gas,用 hardhat-gas-reporter 插件可以帮你做这个。

4.4 单元测试编写:别偷懒

说实话,我见过太多人写完合约直接部署,结果上线后漏洞百出。单元测试不是可选项,是必选项。Hardhat 内置了 Waffle 测试框架,写起来很顺手。

测试文件结构:

// test/MyNFT.test.js
const { expect } = require("chai");

describe("MyNFT", function () {
  it("Should mint a token and assign it to the owner", async function () {
    const [owner] = await ethers.getSigners();
    const MyNFT = await ethers.getContractFactory("MyNFT");
    const myNFT = await MyNFT.deploy();

    await myNFT.mint(owner.address, 1);
    expect(await myNFT.ownerOf(1)).to.equal(owner.address);
  });

  it("Should not allow minting to zero address", async function () {
    const MyNFT = await ethers.getContractFactory("MyNFT");
    const myNFT = await MyNFT.deploy();

    await expect(
      myNFT.mint("0x0000000000000000000000000000000000000000", 1)
    ).to.be.revertedWith("ERC721: mint to the zero address");
  });
});

运行测试:npx hardhat test

测试覆盖哪些场景?

测试类型 覆盖内容 示例
正向测试 正常流程是否通过 mint 一个 token,检查 owner 是否正确
边界测试 极端值是否处理 mint 到零地址、mint 重复 tokenId
权限测试 只有授权者能操作 非 owner 调用 mint 应该 revert
Gas 测试 操作是否在合理 gas 范围内 批量 mint 100 个 token 的 gas 消耗
我的习惯: 每次写完一个函数,先写它的测试用例,再写实现代码。这叫测试驱动开发(TDD)。一开始可能觉得慢,但相信我,后期 debug 的时间会省下 80%。我曾经有个项目,因为没写测试,上线后一个简单的整数溢出漏洞导致用户损失了 2 个 ETH,那教训太深刻了。

好了,这一章的内容就这些。总结一下:Remix 用来快速验证想法,Hardhat 用来搭建生产级项目,编译部署要关注 gas 和网络状态,单元测试是保命符。下一章我们会深入 NFT 合约标准,讲讲 ERC-721 和 ERC-1155 的区别,以及怎么在合约里实现版税机制。