Hardhat简洁教程

1. 概述

HardHat为开发者提供了许多合约开发的便捷功能,包括合约的编译、部署、验证、调试等方面。相对于使用其它工具,使用HardHat进行智能合约开发,可以:

  • 方便的启动内置模拟节点;

  • 使用内置打印接口在solidity代码内打印调试日志(console.log)

  • 在合约报错查看call-stack,和详细报错原因

  • fork现有网络和状态来启动模拟节点

2. 开发环境搭建

可以使用npm install将hardhat工具嵌入到任意现有的solidity工程代码目录内,或者创建全新的工程代码,使用如下代码在空目录中创建全新的solidity合约开发目录框架:

$ yarn add -D hardhat
$ yarn hardhat init

初始化完成后,目录结构如下:

.
├── README.md
├── contracts
│   └── Lock.sol
├── hardhat.config.js
├── package.json
├── scripts
│   └── deploy.js
├── test
│   └── Lock.js
└── yarn.lock

contracts目录放置solidity合约代码,scripts目录放置部署脚本test目录放置测试脚本。

3. Fork现有区块链网络

使用Fork功能可以基于现有区块链网络RPC创建一个模拟的开发节点。这里的RPC推荐使用Alchemy提供的节点服务。

例如使用下面代码fork一个ethereum的主网开发环境:

(暂时不支持Wanchain,因为不识别transaction type: 255)

$ yarn hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/<YouAlchemyApiKey>

也可在后方加入 --fork-block-number 14390000 参数来指定基于某一个块的状态来进行fork。

fork成功后,它会自动注入20个包含10000ETH的开发账号。同时包含以太坊的所有合约以及地址状态信息。

fork后的默认的RPC是: http://localhost:8545

chainId会发生变化,可以在添加到MetaMask的时候看到新chainId。(我本地当前是31337)

将fork节点添加到MetaMask网络,并将生成的测试私钥导入MetaMask钱包中,方便下一步开发使用。

4. 使用remix开发和调试

为方便代码部署和调试,这里使用remix绑定本地文件目录功能,绑定刚刚建好的hardhat目录。使用如下指令绑定当前目录到remix:(remixd需要使用npm全局安装)

$ remixd -s .

在浏览器中打开:

https://remix.ethereum.org/

在workspace位置选择localhost,即可连接到本地绑定目录,并看到我们的实例代码文件。

remix界面截图
remix界面截图

可以看到Lock.sol中的示例代码。我们在其中加入两行console.log日志打印,并在编译窗口选择合适的solidity版本进行编译。

编译合约
编译合约

在合约部署页面,选择使用MetaMask部署。

合约部署
合约部署

输入错误的构造函数参数时,可以在控制台的fork节点日志中看到错误提示信息:

错误信息提示
错误信息提示

输入正确的构造函数部署合约,可以在控制台中看到在合约代码中console.log输出的日志信息:

日志输出
日志输出

5. 使用VSCode + 脚本编译和部署合约

编译合约代码可以使用如下指令:

$ yarn hardhat compile

使用本地fork节点部署合约:

yarn hardhat run --network localhost scripts/deploy.js

这里的deploy.js是hardhat自带的实例代码,可根据自己需要任意修改。在fork节点的控制台窗口中可以看到部署成功的合约地址日志信息。

仿照示例代码编写一个withdraw.js,并使用如下指令执行:

const hre = require("hardhat");

async function main() {
  const Lock = await hre.ethers.getContractFactory("Lock");
  const lock = await Lock.attach("0xcbbe2a5c3a22be749d5ddf24e9534f98951983e2")
  const tx = await lock.withdraw();
  console.log(tx);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
yarn hardhat run --network localhost scripts/withdraw.js

如果执行成功,可以看到执行结果输出,如果执行失败,可以看到失败的具体原因显示。如果有多层级的函数调用,在失败时,可以看到call-stack调用堆栈,方便定位问题所在位置。(注意:调用堆栈功能在remix中不可用)

call-stack显示
call-stack显示

6. 详细参数配置

通过修改hardhat.config.js可以对编译参数进行详细配置,包括但不限于网络信息、solidity版本与优化信息、部署账户信息等。

module.exports = {
  defaultNetwork: "rinkeby",
  networks: {
    hardhat: {
    },
    rinkeby: {
      url: "https://eth-rinkeby.alchemyapi.io/v2/123abc123abc123abc123abc123abcde",
      accounts: [privateKey1, privateKey2, ...]
    }
  },
  solidity: {
    version: "0.5.15",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts"
  },
  mocha: {
    timeout: 40000
  }
}

参考链接

https://hardhat.org/hardhat-network/docs/overview

https://hardhat.org/hardhat-runner/docs/getting-started#overview