写一份智能合约:Counter 计数器

创建一个最简单的链上程序:记录一个数字,允许用户把它加一。

这份代码会让你接触 Solidity 的基本语法、链上存储、函数调用和交易费用。

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

contract Counter {
    uint256 public count;

    constructor() {
        count = 0;
    }

    function increment() public {
        count += 1;
    }

    function decrement() public {
        require(count > 0, "Count is zero");
        count -= 1;
    }

    function reset() public {
        count = 0;
    }
}

逐行理解

① pragma solidity ^0.8.24;

声明合约使用的 Solidity 编译器版本范围。这里要求兼容 0.8.24 及以上、低于 0.9.0 的版本。

② uint256 public count;

声明一个无符号整数变量 count,用来保存计数值。哈哈哈哈这不是c语言吗

public 会让编译器自动生成一个外部可调用的读取函数 count()。它不是让任何人都能随意修改这个变量。

③ constructor()

构造函数,在合约部署时执行一次。由于 Solidity 会将整数状态变量默认初始化为 0,这里的显式赋值其实可以省略。

④ increment()

将计数值增加 1。它会修改链上状态,因此通过钱包在真实网络调用时,通常需要发送交易并支付 Gas。

⑤ require(count > 0, "Count is zero")

检查计数值是否大于 0。如果条件不满足,执行会回退,后续状态修改不会生效。

⑥ reset()

把计数值重置为 0。注意,这里没有设置管理员权限,因此任何人都可以调用它——这是我们之后要改进的设计。

部署ETH开发环境(docker)

笔者使用mac所以希望使用docker部署会方便一些,以后可以在各平台迁移使用

您需要下载docker和homebrew在mac上这我就不介绍了!

接下来我们建立一个可迁移的 Hardhat 3 + Solidity 开发环境。代码保存在 Mac 本地,容器提供运行环境;将来迁移时,可以用同一份项目配置重建。

第 1 步:创建项目目录和 Docker 配置

mkdir -p ~/eth-learning
cd ~/eth-learning

mkdir -p contracts test scripts

然后创建 compose.yaml:

cat > compose.yaml <<'EOF'
services:
  dev:
    image: node:22-bookworm-slim
    working_dir: /workspace
    command: ["sleep", "infinity"]
    ports:
      - "127.0.0.1:8545:8545"
    volumes:
      - .:/workspace   #@项目源代码保存在 Mac 本地
      - node_modules:/workspace/node_modules
      - npm_cache:/root/.npm
    init: true

volumes:
  node_modules:
  npm_cache:
EOF

这个配置有三个重要设计:

  • .:/workspace:项目源代码保存在 Mac 本地。

  • node_modules:依赖保存在 Docker 管理的独立卷中,避免把 Linux 依赖直接混入 Mac 项目目录。

  • 127.0.0.1:8545:8545:为后续本地测试链预留端口,只允许 Mac 本机访问,不直接暴露到局域网。

第 2 步:启动容器并验证 Node.js

执行:

cd ~/eth-learning

docker compose run --rm dev node --version
docker compose run --rm dev npm --version

第一条命令应显示 Node.js v22.13.0 或更高的 22.x 版本,满足 Hardhat 3 的最低版本要求。

再检查容器架构:

docker compose run --rm dev node -p \
  "process.platform + '/' + process.arch"

预期类似:

linux/arm64

这一步只是检查容器环境,不会启动区块链,也不会花费真实 ETH。

第 3 步:初始化 Hardhat 3

执行:

cd ~/eth-learning
docker compose run --rm dev npx hardhat --init

如果出现交互式初始化菜单:

要保存在哪个路径直接回车

  1. 选择官方推荐的默认项目模板。A TypeScript Hardhat project using Node Test Runner and Viem

  2. 如果询问是否安装依赖,选择确认。

  3. 如果询问是否允许安装所需的软件包,确认继续。

  4. 最后问是否run选择y

Hardhat 3 官方提供 TypeScript、Solidity 测试和合约部署相关的项目模板。

报错

npm install --save-dev "hardhat@^3.19.0" "@nomicfoundation/hardhat-toolbox-viem@^5.0.7" "@nomicfoundation/hardhat-ignition@^3.1.8" "@types/node@^22.8.5" "forge-std@foundry-rs/forge-std#v1.16.2" "typescript@~6.0.3" "viem@^2.47.6"
npm error code ENOENT
npm error syscall spawn git
npm error path git
npm error errno -2
npm error enoent An unknown git error occurred
npm error enoent This is related to npm not being able to find a file.
npm error enoent
npm error A complete log of this run can be found in: /root/.npm/_logs/2026-10-12T13_03_44_317Z-debug-0.log
Error HHE1400: Failed to install the project dependencies.

Please try again. If it persists, please open an issue in the Hardhat repository.

For more info go to https://hardhat.org/HHE1400 or run Hardhat with --show-stack-traces
npm notice
npm notice New major version of npm available! 10.9.9 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g [email protected]
npm notice


What's next:
    Debug this Compose error with Gordon → docker ai "help me fix this compose error"

我们之前使用的是 node:22-bookworm-slim 镜像,它是精简镜像,不保证预装 Git。

推荐的修复方式:在 Docker 镜像中安装 Git,然后重新运行依赖安装。这样仍然保留 Docker 的可迁移性,不需要在 Mac 上额外安装 Git 来满足容器内部的依赖要求。

第 1 步:给开发容器安装 Git

在 Mac 终端中,确认你当前位于 ~/eth-learning,然后执行:

cd ~/eth-learning

cat > Dockerfile <<'EOF'
FROM node:22-bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends git ca-certificates \
    && rm -rf /var/lib/apt/lists/*
EOF

第 2 步:让 Compose 使用自己的镜像

用下面的内容替换 compose.yaml:

cat > compose.yaml <<'EOF'
services:
  dev:
    build:
      context: .
      dockerfile: Dockerfile
    working_dir: /workspace
    command: ["sleep", "infinity"]
    ports:
      - "127.0.0.1:8545:8545"
    volumes:
      - .:/workspace
      - node_modules:/workspace/node_modules
      - npm_cache:/root/.npm
    init: true

volumes:
  node_modules:
  npm_cache:
EOF

现在的区别是:

  • Dockerfile 负责安装 Git。

  • compose.yaml 负责启动和管理开发容器。

  • 将来迁移时,只需要携带这两个文件和项目代码,就能重建相同的基础环境。

第 3 步:重新构建并检查 Git

执行:

docker compose build dev

构建完成后,再执行:

docker compose run --rm dev git --version
docker compose run --rm dev node --version

如果第一条显示类似 git version 2.x.x,说明问题已经解决。

重新安装初始化所需的依赖

现在 Git 已经可用,直接在 Mac 终端执行下面的命令。它会把 Hardhat 和初始化模板需要的依赖写入 package.json,并生成锁文件。

cd ~/eth-learning

docker compose run --rm dev npm install --save-dev \
  "hardhat@^3.19.0" \
  "@nomicfoundation/hardhat-toolbox-viem@^5.0.7" \
  "@nomicfoundation/hardhat-ignition@^3.1.8" \
  "@types/node@^22.8.5" \
  "forge-std@foundry-rs/forge-std#v1.16.2" \
  "typescript@~6.0.3" \
  "viem@^2.47.6"

这条命令沿用 Hardhat 初始化程序此前给出的依赖版本范围,不需要重新初始化项目。

检查安装结果

如果安装完成,再执行:

cd ~/eth-learning

docker compose run --rm dev npx hardhat --version

docker compose run --rm dev npm ls --depth=0

ls -l package.json package-lock.json

预期能看到 Hardhat 版本 3.x,并且 package.json、package-lock.json 均存在。

很好,Hardhat 3 本地开发环境的依赖已经安装成功。

目前确认的状态:

  • Hardhat:3.19.0

  • Node.js:v22.23.3

  • TypeScript:6.0.3

  • Viem:2.57.4

  • Hardhat Ignition:3.1.8

  • 依赖安装完成,npm 报告 0 vulnerabilities

你现在已经有了一套运行在 Docker 中的 Solidity 开发工具链,而且 package-lock.json 已经生成,后续迁移到 Ubuntu 时可以用它锁定依赖版本。

把现有项目接入 VS Code

检查 VS Code 命令是否可用

在 Mac 终端执行:

code --version

如果输出版本号,继续执行:

cd ~/eth-learning
code .

这会在 VS Code 中打开整个 ETH 项目。

如果提示 command not found: code,也没关系,可以先从应用程序中手动打开 VS Code,再选择 File → Open Folder,打开 ~/eth-learning。

安装 Solidity 扩展

在 VS Code 左侧点击扩展图标,搜索:

Solidity

选择一个支持 Solidity 语法高亮、代码诊断和编译器版本配置的扩展。安装后打开项目中的 .sol 文件,就能更方便地阅读和编写合约。

注意:扩展可以辅助编写代码,但实际编译和测试仍然由我们的 Hardhat 容器执行,避免本机和容器使用不同的依赖版本。

认识项目文件

打开项目后,你会看到类似这样的结构:

eth-learning/
├── contracts/          # Solidity 智能合约
├── test/               # 自动化测试
├── ignition/           # 合约部署模块
├── scripts/            # 辅助脚本
├── hardhat.config.ts   # Hardhat 配置
├── package.json        # 项目依赖
├── package-lock.json   # 锁定依赖版本
├── Dockerfile          # 开发镜像
└── compose.yaml        # Docker 服务配置

以后主要在 IDE 里编写 contracts/Counter.sol,然后通过 VS Code 终端或任务调用 Docker 编译、测试和部署。

在 VS Code 里运行第一次编译

在 VS Code 顶部菜单点击 Terminal → New Terminal(终端 → 新建终端),然后执行:

cd ~/eth-learning
docker compose run --rm dev npx hardhat compile

这条命令的意思是:

命令

作用

docker compose run

启动一个临时开发容器

--rm

命令执行结束后删除临时容器

npx hardhat compile

编译 Solidity 智能合约

编译器会检查合约语法,并生成编译产物。即使你在 Mac 上没有安装 Node.js 或 Solidity 编译器,这一步也可以通过 Docker 中的开发环境执行。

从输出看,Docker 和 Hardhat 已经可以正常运行,Solidity 编译器也已成功下载。 目前没有报错。

我这里已经安装好了一些拓展,你可以再安装这两个扩展

好,那我们继续把 VS Code 连接到 Docker 容器。接下来你就能在 IDE 里使用容器中的 Node.js、Hardhat 和 Solidity 开发工具。

第一步:创建 Dev Containers 配置

在 VS Code 的终端中执行下面的命令:

cd ~/eth-learning
mkdir -p .devcontainer

cat > .devcontainer/devcontainer.json <<'EOF'
{
  "name": "ETH Solidity Learning",
  "dockerComposeFile": "../compose.yaml",
  "service": "dev",
  "workspaceFolder": "/workspace",
  "shutdownAction": "stopCompose",
  "overrideCommand": false,
  "customizations": {
    "vscode": {
      "extensions": [
        "NomicFoundation.hardhat-solidity"
      ]
    }
  }
}
EOF

这个配置会让 VS Code 使用你现有的 compose.yaml,而不是再创建一套独立的开发环境。

第二步:在容器中重新打开项目

  1. 按 Cmd + Shift + P。

  2. 输入 Dev Containers: Reopen in Container。

  3. 点击对应命令。

  4. 等待 VS Code 连接容器完成。

如果弹出是否信任工作区的提示,确认这是你自己创建的 eth-learning 项目后,可以选择信任。

第三步:验证环境

重新连接完成后,在 VS Code 的终端执行:记得再打开一个bash

pwd
node --version
npx hardhat --version

预期结果:

  • 工作目录:/workspace

  • Node.js:v22.x

  • Hardhat:3.x

然后执行测试:

npx hardhat test

预期仍然是 5 passing。

好了总算配置好了

马上可以学习代码了

然后配置好的结果在bash里看是这样!

root@ad7657016973:/workspace# node --version
npx hardhat --version
npx hardhat test
v22.23.3
3.19.0
No contracts to compile

Running Solidity tests

  contracts/Counter.t.sol:CounterTest
    ✔ test_InitialValue()
    ✔ test_IncByZero()
    ✔ testFuzz_Inc(uint8) (runs: 256)

Running node:test tests

  Counter
    ✔ Should emit the Increment event when calling the inc() function
    ✔ The sum of the Increment events should match the current value


5 passing (3 solidity, 2 nodejs)

root@ad7657016973:/workspace#