Post

Hardhat 以太坊开发必备工具

Hardhat 以太坊开发必备工具

Hardhat 简介

Hardhat 是一个以太坊开发环境,它能帮助开发者编译智能合约并在开发网络上运行,提供 Solidity 堆栈跟踪、console.log 等强大功能。

核心工作原理

1. 任务系统(Task System)

Hardhat 基于任务驱动的架构设计。每个功能都以任务的形式存在,比如编译、测试、部署等。这种设计让整个框架具有高度的可扩展性:

1
2
3
4
5
6
7
// hardhat.config.js
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();
  for (const account of accounts) {
    console.log(account.address);
  }
});

2. 插件生态系统

Hardhat 采用插件架构,核心功能通过插件提供。这种设计让开发者可以根据项目需求选择合适的插件组合,避免不必要的依赖:

1
2
3
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require("hardhat-deploy");

3. Hardhat Network

Hardhat 会为我们模拟一个 EVM 网络/区块链环境来进行开发测试。这个内置的以太坊网络专门为开发优化,提供:

  • 即时挖矿
  • 丰富的调试信息
  • 快照和时间控制
  • 主网分叉功能

核心功能与工具作用

1. 智能合约编译

Hardhat 提供了强大的 Solidity 编译功能,支持多版本编译器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// hardhat.config.js
module.exports = {
  solidity: {
    compilers: [
      {
        version: "0.8.19",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
      {
        version: "0.8.24",
      },
    ],
  },
};

作用

  • 自动管理 Solidity 编译器版本
  • 智能缓存,只编译修改过的文件
  • 详细的编译错误信息和警告
  • 支持自定义编译配置

2. 智能合约测试

Hardhat 集成了强大的测试框架,支持 JavaScript 和 TypeScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Token", function () {
  let token;
  let owner;
  let addr1;

  beforeEach(async function () {
    [owner, addr1] = await ethers.getSigners();
    const Token = await ethers.getContractFactory("Token");
    token = await Token.deploy(1000);
  });

  it("Should transfer tokens between accounts", async function () {
    await token.transfer(addr1.address, 50);
    expect(await token.balanceOf(addr1.address)).to.equal(50);
  });
});

它提供了完整的测试环境,支持异步测试和复杂场景模拟,集成 Chai 断言库,还能提供 gas 使用报告。

3. 合约部署与管理

Hardhat Ignition 是一个声明式的智能合约部署系统,它能让你定义想要部署的智能合约实例以及想要在它们上运行的操作:

1
2
3
4
5
6
7
8
9
10
// ignition/modules/TokenModule.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("TokenModule", (m) => {
  const initialSupply = m.getParameter("initialSupply", 1000000);

  const token = m.contract("Token", [initialSupply]);

  return { token };
});

4. 网络管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// hardhat.config.js
module.exports = {
  networks: {
    hardhat: {
      chainId: 31337,
    },
    sepolia: {
      url: process.env.SEPOLIA_URL,
      accounts: [process.env.PRIVATE_KEY],
    },
    mainnet: {
      url: process.env.MAINNET_URL,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

提供了统一的网络配置管理,支持本地、测试网、主网切换。环境变量集成,还能够自定义网络配置。

实际使用指南

1. 项目初始化

1
2
3
4
5
6
7
8
9
10
# 创建新项目
mkdir my-hardhat-project
cd my-hardhat-project
npm init -y

# 安装Hardhat
npm install --save-dev hardhat

# 初始化项目
npx hardhat init

运行 npx hardhat init 会在项目文件夹中创建示例项目,包含示例合约、测试和 Hardhat Ignition 模块。

2. 开发工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译合约
npx hardhat compile

# 运行测试
npx hardhat test

# 启动本地网络
npx hardhat node

# 部署到本地网络
npx hardhat ignition deploy ignition/modules/TokenModule.js --network localhost

# 部署到测试网
npx hardhat ignition deploy ignition/modules/TokenModule.js --network sepolia

3. 调试与开发

Hardhat 提供了强大的调试功能:

// contracts/Token.sol
pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Token {
    function transfer(address to, uint256 amount) public {
        console.log("Transferring %s tokens to %s", amount, to);
        // 转账逻辑
    }
}

4. 合约验证

1
2
3
4
5
6
7
8
// hardhat.config.js
require("@nomiclabs/hardhat-etherscan");

module.exports = {
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};
1
2
# 验证合约
npx hardhat verify --network sepolia CONTRACT_ADDRESS "Constructor argument 1"

高级使用技巧

1. 自定义任务

1
2
3
4
5
6
7
8
// hardhat.config.js
task("deploy-token", "Deploy the Token contract")
  .addParam("supply", "The initial supply")
  .setAction(async (taskArgs, hre) => {
    const TokenFactory = await hre.ethers.getContractFactory("Token");
    const token = await TokenFactory.deploy(taskArgs.supply);
    console.log("Token deployed to:", token.address);
  });

自定义任务类似批量部署多个合约(代币/NFT/Defi),合约升级任务等。

2. 环境配置管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// hardhat.config.js
require("dotenv").config();

const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
const INFURA_KEY = process.env.INFURA_KEY || "";

module.exports = {
  networks: {
    sepolia: {
      url: `https://sepolia.infura.io/v3/${INFURA_KEY}`,
      accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
    },
  },
};

3. 测试覆盖率

1
npm install --save-dev solidity-coverage
1
2
3
4
5
6
// hardhat.config.js
require("solidity-coverage");

module.exports = {
  // ... 其他配置
};
1
npx hardhat coverage

4. Gas 报告

1
npm install --save-dev hardhat-gas-reporter
1
2
3
4
5
6
7
8
9
10
// hardhat.config.js
require("hardhat-gas-reporter");

module.exports = {
  gasReporter: {
    enabled: true,
    currency: "USD",
    gasPrice: 20,
  },
};

与其他工具的比较

Hardhat vs Truffle

  • Hardhat: 更现代的架构,更好的 TypeScript 支持,更灵活的插件系统
  • Truffle: 更成熟的生态系统,更多的集成工具

Hardhat vs Foundry

  • Hardhat: JavaScript/TypeScript 生态,更适合前端开发者
  • Foundry: Rust 编写,执行速度更快,更适合合约安全审计

实际项目案例

DeFi 协议开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// test/DeFiProtocol.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("DeFi Protocol", function () {
  let protocol, token, owner, user1;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();

    // 部署代币
    const Token = await ethers.getContractFactory("ERC20Token");
    token = await Token.deploy(
      "Test Token",
      "TEST",
      ethers.parseEther("1000000")
    );

    // 部署协议
    const Protocol = await ethers.getContractFactory("DeFiProtocol");
    protocol = await Protocol.deploy(token.address);
  });

  it("Should allow users to stake tokens", async function () {
    const stakeAmount = ethers.parseEther("100");

    // 授权协议使用代币
    await token.connect(user1).approve(protocol.address, stakeAmount);

    // 质押代币
    await protocol.connect(user1).stake(stakeAmount);

    // 验证质押余额
    expect(await protocol.stakedBalance(user1.address)).to.equal(stakeAmount);
  });
});
This post is licensed under CC BY 4.0 by the author.