Hardhat 开发环境教程
Hardhat 开发环境教程
Hardhat 是以太坊最流行的智能合约开发框架之一,提供了完整的开发、测试、调试和部署工具链。
安装与初始化
环境要求
- Node.js >= 16.0
- npm 或 yarn
创建新项目
mkdir my-hardhat-projectcd my-hardhat-project
npm init -ynpm 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配置文件
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" }};环境变量配置
# .env 文件(不要提交到 git)SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_IDPRIVATE_KEY=0x你的私钥ETHERSCAN_API_KEY=你的etherscan密钥编写智能合约
// SPDX-License-Identifier: MITpragma 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); }}编译合约
npx hardhat compile编译成功后,会在 artifacts/ 目录生成 ABI 和字节码文件。
本地测试网络
Hardhat 内置了一个本地以太坊节点,支持快速开发迭代。
启动本地节点
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 进行合约交互。
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"); }); });});运行测试
# 运行所有测试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)部署脚本
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); });执行部署
# 部署到本地网络npx hardhat run scripts/deploy.js
# 部署到 Sepolia 测试网npx hardhat run scripts/deploy.js --network sepolia
# 部署到主网npx hardhat run scripts/deploy.js --network mainnetHardhat Console
使用交互式控制台与合约交互:
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 插件
常用插件
# 必装工具箱(包含常用插件)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-upgradesrequire("@nomicfoundation/hardhat-toolbox");require("hardhat-contract-sizer");require("@openzeppelin/hardhat-upgrades");
module.exports = { contractSizer: { alphaSort: true, runOnCompile: true, disambiguatePaths: false, }};模拟主网状态(Fork)
Hardhat 支持分叉主网状态进行本地测试:
networks: { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, blockNumber: 19000000 // 指定区块(可选) } }}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 调试
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 合约库,可以快速构建安全可靠的智能合约项目。