Hardhat 框架

admin
admin 2023年08月14日
  • 在其它设备中阅读本文章

一、简介

Hardhat 是以太坊合约开发环境,可以编辑、编译、调试和部署智能合约
Hardhat 是围绕任务和插件而设计的。每次从命令行运行 Hardhat 时,都相当于在运行一个任务。例如,npx hardhat compile运行内置编译合约任务。任务可以调用其他任务,从而完成复杂的工作流程

二、入门

  1. 操作系统:Linux
  2. node:20.5.0
  3. npm:9.8.1
  4. 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

更多插件内容参考 文档