Hardhat 框架
一、简介
Hardhat 是以太坊合约开发环境,可以编辑、编译、调试和部署智能合约
Hardhat 是围绕任务和插件而设计的。每次从命令行运行 Hardhat 时,都相当于在运行一个任务。例如,npx hardhat compile
运行内置编译合约任务。任务可以调用其他任务,从而完成复杂的工作流程
二、入门
- 操作系统:Linux
- node:20.5.0
- npm:9.8.1
- hardhat:2.17.1
1、初始化空白项目
集成到已有项目可跳过此步骤
创建示例目录后使用 npm 初始化
mkdir example && cd example
npm init -y
2、集成 hardhat
安装 hardhat 工具
npm install -D hardhat
创建 hardhat 项目
npx hardhat
选择创建 JavaScript 项目,按照向导提示操作创建相关文件和安装基本 hardhat 插件
初始化后的项目将包括以下文件:
- contracts:智能合约文件夹。
- test:合约测试文件夹。
- scripts:自动化脚本文件夹。
- hardhat.config.js:配置文件
初始化时自动配置了一个示例合约,可以运行以下命令查看效果
npx hardhat compile
:编译智能合约npx hardhat test
:执行合约测试npx hardhat node
:启动一个虚拟的本地测试节点npx hardhat run scripts/deploy.js
:运行部署合约脚本
3、配置
hardhat.config.js 是 hardhat 的配置文件,此文件需要导出一个对象,对象可以包括 defaultNetwork、networks、solidity、paths 等多个配置选项,如果需要使用插件,则需要在此文件中导入申明,基本的配置文件内容如下
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
};
网络配置
networks 字段配置可用的 hardhat 网络,是一个可选对象。defaultNetwork 字段配置 hardhat 运行时默认使用的网络,如果省略此配置,则其默认值为 "hardhat"
自定义网络可配置以下字段:
- url: 节点的连接地址。自定义网络需要此参数。
- chainId:可选的链 id,用于验证 Hardhat 连接到的网络。如果不存在,则省略此验证。
- from:用作默认发送交易的地址。如果不存在,则使用节点的第一个帐户。
- accounts:此字段控制 Hardhat 使用哪些帐户。它可以使用节点的帐户(设置为 "remote")、本地帐户列表(设置为 16 进制的私钥数组)和 HD 钱包,默认为 "remote"
Hardhat 内置了一个名为hardhat
的特殊网络。当使用这个网络时将会自动创建,比如运行任务、脚本或测试智能合约时
编译器配置
solidity 字段配置合约编译器,可选以下值之一:
- 要使用的 solc 版本,例如 "0.8.19"
- 描述编译器配置的对象。它包含 version 字段和 settings 字段(编译器设置对象)
目录配置
paths 字段配置 hardhat 的文件目录
- root:Harhat 的根目录,默认值为
hardhat.config.js
所在的目录。 - sources:存储合约的目录,默认值为
./contracts
- tests:测试所在的目录, 默认值为
./test
- cache:用于缓存其内部内容的目录, 默认值为
./cache
- artifacts:存储编译结果的目录,默认值为
./artifacts
示例配置文件
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
defaultNetwork: "goerli",
networks: {
goerli: {
url: "https://goerli.infura.io/v3/<key>",
accounts: {
mnemonic: "test test test test test test test test test test test test",
},
}
},
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
paths: {
sources: "./contracts",
tests: "./test"
},
}
更多内容参考 hardhat 官方文档
三、进阶
以一个暂存资产的合约为例,从头开始搭建项目
1、初始化一个 hardhat 项目
mkdir demo && cd demo
npm init -y
npm install -D hardhat
2、创建 hardhat 配置文件
执行命令npx hardhat
后选择Create an empty hardhat.config.js
,也可以手动创建这个文件,内容如下
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
};
3、安装 hardhat 核心插件
先使用 npm 安装@nomicfoundation/hardhat-toolbox
npm install -D @nomicfoundation/hardhat-toolbox
再编辑 hardhat 配置文件hardhat.config.js
导入插件,更新后内容如下
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
};
4、创建合约文件
创建合约目录contracts
并编写合约文件Demo.sol
,内容如下
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Demo {
event Deposit(address user, uint amount);
event Withdrawal(address user, uint amount);
receive() external payable {
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint amount) public payable {
require(amount <= address(this).balance, "there is not enough deposit");
emit Withdrawal(msg.sender, msg.value);
payable(msg.sender).transfer(amount);
}
}
这是一个名为 Demo 的合约,任意用户可以往这个合约存款和取款,更多合约开发参考 solidity 文档
执行以下命令编译合约,如果编译失败,可以按照提示修改合约
npx hardhat compile
5、创建测试文件
创建测试目录test
并编写测试文件Demo.js
,内容如下
const { ethers } = require("hardhat");
const { expect } = require("chai");
describe("Demo", function() {
async function deploy() {
const Demo = await ethers.getContractFactory("Demo");
const demo = await Demo.deploy({ value: ethers.parseEther("1") });
const signers = await ethers.getSigners();
return { demo, signers };
}
describe("Deployment", function() {
it("Demo contract should deposit 1 ETH", async function() {
const { demo } = await deploy();
const deposit = await ethers.provider.getBalance(demo.target);
expect(deposit).to.equal(ethers.parseEther("1"));
});
});
describe("Withdraw", function() {
it("should withdraw 1 ETH", async function() {
const { demo } = await deploy();
await expect(demo.withdraw(ethers.parseEther("1")))
.to.changeEtherBalance(demo.target, -ethers.parseEther("1"));
});
});
describe("Events", function() {
it("Should emit an event on withdrawals", async function() {
const { demo, signers: [owner] } = await deploy();
await expect(demo.withdraw(ethers.parseEther("1")))
.to.emit(demo, "Withdrawal")
.withArgs(owner.address, ethers.parseEther("1"));
});
});
});
执行以下命令测试合约,如果测试未通过,可以按照提示修改测试用例
npx hardhat test
6、创建部署文件
创建脚本目录scripts
并编写部署文件deploy.js
,内容如下
const { ethers } = require("hardhat");
async function main() {
const demo= await ethers.deployContract("Demo", { value: ethers.parseEther("1")});
await demo.waitForDeployment();
console.log(`Demo deployed to ${demo.target}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
执行以下命令部署合约,如果部署失败,可以按照提示修改部署文件
npx hardhat run scripts/deploy.js
7、部署合约到本地网络
使用下面命令启动一个虚拟本地节点
npx hardhat node
然后使用下面命令部署合约到网络localhost
,无需修改配置(hardhat 配置中预置了 hardhat 和 localhost 两个网络)
npx hardhat run --network localhost scripts/deploy.js
如果需要部署到其他网络,可使用--network
选项指定要部署的网络名称
8、编辑 npm 脚本
修改 package.json 文件,编辑 scripts 字段,内容如下
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"node": "hardhat node",
"deploy": "hardhat run --network localhost scripts/deploy.js"
},
保存更改之后,就可以使用 npm 命令来运行 hardhat 任务
npm run compile #编译合约,等效:npx hardhat compile
四、合约部署插件
除了使用部署脚本外,在复杂环境下可以使用插件部署合约,提供更灵活的部署方法,使用以下命令安装插件包
npm install -D hardhat-deploy
编辑 hardhat 配置文件hardhat.config.js
导入插件,同时命名 deployer 账户(第一个账户)用于部署合约,更新后内容如下
require("@nomicfoundation/hardhat-toolbox");
require("hardhat-deploy");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
namedAccounts: {
deployer: 0
}
};
创建目录deploy
并编写部署文件00_demo.js
,内容如下
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy("Demo", {from: deployer, args: [],log: true});
};
module.exports.tags = ["Demo"];
执行以下命令部署合约,如果部署失败,可以按照提示修改部署文件
npx hardhat deploy
更多插件内容参考 文档