跳转到内容

Hardhat 开发环境教程

Hardhat 开发环境教程

Hardhat 是以太坊最流行的智能合约开发框架之一,提供了完整的开发、测试、调试和部署工具链。

安装与初始化

环境要求

  • Node.js >= 16.0
  • npm 或 yarn

创建新项目

Terminal window
mkdir my-hardhat-project
cd my-hardhat-project
npm init -y
npm install --save-dev hardhat
# 初始化 Hardhat 项目
npx hardhat init

选择项目类型:

? What do you want to do?
❯ Create a JavaScript project
Create a TypeScript project
Create an empty hardhat.config.js
Quit

项目结构

my-hardhat-project/
├── contracts/ # Solidity 合约
│ └── Lock.sol
├── scripts/ # 部署和交互脚本
│ └── deploy.js
├── test/ # 测试文件
│ └── Lock.js
├── hardhat.config.js # Hardhat 配置
└── package.json

配置文件

hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
// 本地开发网络(默认)
hardhat: {
chainId: 31337
},
// Sepolia 测试网
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 11155111
},
// 以太坊主网
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 1
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
gasReporter: {
enabled: process.env.REPORT_GAS !== undefined,
currency: "USD"
}
};

环境变量配置

Terminal window
# .env 文件(不要提交到 git)
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
PRIVATE_KEY=0x你的私钥
ETHERSCAN_API_KEY=你的etherscan密钥

编写智能合约

contracts/Token.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Token is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 * 10**18;
event TokensMinted(address indexed to, uint256 amount);
constructor(address initialOwner)
ERC20("My Token", "MTK")
Ownable(initialOwner)
{
_mint(initialOwner, 100_000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
emit TokensMinted(to, amount);
}
}

编译合约

Terminal window
npx hardhat compile

编译成功后,会在 artifacts/ 目录生成 ABI 和字节码文件。

本地测试网络

Hardhat 内置了一个本地以太坊节点,支持快速开发迭代。

启动本地节点

Terminal window
npx hardhat node

启动后,会生成 20 个测试账户,每个账户有 10000 ETH:

Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Account #1: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
...

编写测试

Hardhat 使用 Mocha + Chai 测试框架,配合 ethers.js 进行合约交互。

test/Token.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
describe("Token", function () {
// 使用 fixture 模式复用部署状态
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy(owner.address);
await token.waitForDeployment();
return { token, owner, addr1, addr2 };
}
describe("部署", function () {
it("应该设置正确的名称和符号", async function () {
const { token } = await loadFixture(deployTokenFixture);
expect(await token.name()).to.equal("My Token");
expect(await token.symbol()).to.equal("MTK");
});
it("应该将初始供应量分配给 owner", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("转账", function () {
it("应该成功转账代币", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
const transferAmount = ethers.parseEther("100");
await token.transfer(addr1.address, transferAmount);
expect(await token.balanceOf(addr1.address)).to.equal(transferAmount);
});
it("余额不足时应该失败", async function () {
const { token, addr1, addr2 } = await loadFixture(deployTokenFixture);
await expect(
token.connect(addr1).transfer(addr2.address, 1)
).to.be.revertedWithCustomError(token, "ERC20InsufficientBalance");
});
});
describe("铸造", function () {
it("只有 owner 可以铸造", async function () {
const { token, addr1 } = await loadFixture(deployTokenFixture);
await expect(
token.connect(addr1).mint(addr1.address, 100)
).to.be.revertedWithCustomError(token, "OwnableUnauthorizedAccount");
});
it("应该发出 TokensMinted 事件", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
const amount = ethers.parseEther("1000");
await expect(token.mint(addr1.address, amount))
.to.emit(token, "TokensMinted")
.withArgs(addr1.address, amount);
});
it("不能超过最大供应量", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
const maxSupply = await token.MAX_SUPPLY();
const totalSupply = await token.totalSupply();
const overAmount = maxSupply - totalSupply + 1n;
await expect(
token.mint(addr1.address, overAmount)
).to.be.revertedWith("Exceeds max supply");
});
});
});

运行测试

Terminal window
# 运行所有测试
npx hardhat test
# 运行特定测试文件
npx hardhat test test/Token.js
# 显示 Gas 报告
REPORT_GAS=true npx hardhat test
# 测试覆盖率
npx hardhat coverage

测试输出示例:

Token
部署
✓ 应该设置正确的名称和符号
✓ 应该将初始供应量分配给 owner
转账
✓ 应该成功转账代币
✓ 余额不足时应该失败
铸造
✓ 只有 owner 可以铸造
✓ 应该发出 TokensMinted 事件
✓ 不能超过最大供应量
7 passing (1s)

部署脚本

scripts/deploy.js
const { ethers } = require("hardhat");
async function main() {
console.log("开始部署...");
// 获取部署账户
const [deployer] = await ethers.getSigners();
console.log("部署账户:", deployer.address);
console.log("账户余额:", ethers.formatEther(await deployer.provider.getBalance(deployer.address)), "ETH");
// 部署合约
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy(deployer.address);
await token.waitForDeployment();
const address = await token.getAddress();
console.log("Token 合约地址:", address);
// 等待几个区块确认(在测试网/主网部署时)
if (network.name !== "hardhat" && network.name !== "localhost") {
console.log("等待区块确认...");
await token.deploymentTransaction().wait(5);
// 验证合约(需要 Etherscan API Key)
console.log("验证合约...");
await hre.run("verify:verify", {
address: address,
constructorArguments: [deployer.address],
});
}
console.log("部署完成!");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

执行部署

Terminal window
# 部署到本地网络
npx hardhat run scripts/deploy.js
# 部署到 Sepolia 测试网
npx hardhat run scripts/deploy.js --network sepolia
# 部署到主网
npx hardhat run scripts/deploy.js --network mainnet

Hardhat Console

使用交互式控制台与合约交互:

Terminal window
npx hardhat console --network sepolia
// 在控制台中
const Token = await ethers.getContractFactory("Token");
const token = await Token.attach("0xDeployedAddress");
// 查询余额
const balance = await token.balanceOf("0xAddress");
console.log(ethers.formatEther(balance));
// 发送交易
const tx = await token.transfer("0xTo", ethers.parseEther("10"));
await tx.wait();

Hardhat 插件

常用插件

Terminal window
# 必装工具箱(包含常用插件)
npm install --save-dev @nomicfoundation/hardhat-toolbox
# Gas 报告
npm install --save-dev hardhat-gas-reporter
# 合约大小检查
npm install --save-dev hardhat-contract-sizer
# 合约部署和升级
npm install --save-dev @openzeppelin/hardhat-upgrades
hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("hardhat-contract-sizer");
require("@openzeppelin/hardhat-upgrades");
module.exports = {
contractSizer: {
alphaSort: true,
runOnCompile: true,
disambiguatePaths: false,
}
};

模拟主网状态(Fork)

Hardhat 支持分叉主网状态进行本地测试:

hardhat.config.js
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL,
blockNumber: 19000000 // 指定区块(可选)
}
}
}
test/fork-test.js
const { ethers } = require("hardhat");
it("测试与主网 USDC 交互", async function () {
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const USDC_WHALE = "0x55FE002aefF02F77364de339a1292923A15844B8";
// 模拟 USDC 巨鲸账户
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [USDC_WHALE],
});
const whale = await ethers.getSigner(USDC_WHALE);
const usdc = await ethers.getContractAt("IERC20", USDC_ADDRESS, whale);
const balance = await usdc.balanceOf(USDC_WHALE);
console.log("USDC 余额:", balance.toString());
});

调试技巧

console.log 调试

contracts/Debug.sol
import "hardhat/console.sol";
contract Debug {
function calculate(uint256 a, uint256 b) public view returns (uint256) {
console.log("a =", a);
console.log("b =", b);
uint256 result = a + b;
console.log("result =", result);
return result;
}
}

查看调用栈

当交易失败时,Hardhat 会显示详细的错误信息和调用栈。

总结

Hardhat 是功能强大的以太坊开发框架:

  • 编译npx hardhat compile
  • 测试npx hardhat test(支持 Mocha + Chai)
  • 部署npx hardhat run scripts/deploy.js --network <网络名>
  • 调试:内置 console.log、交互式控制台、主网 Fork
  • 插件生态:丰富的插件支持 Gas 报告、合约验证、升级等

配合 OpenZeppelin 合约库,可以快速构建安全可靠的智能合约项目。