xyyme.eth

Posted on Jun 06, 2022Read on Mirror.xyz

Hardhat fork 简明使用教程

Hardhat fork 可以使我们将主网的区块 fork 到本地,这样我们就可以在本地与真实的链上数据进行交互,同时也可以模拟任意账户,方便我们进行一些测试,速度快,并且不用花费 Gas。

基本命令

首先,我们先创建一个 Hardhat 项目,不熟悉的朋友可以看看这里。创建完毕之后,就可以在本地 node 中 fork 区块了。使用命令(替换自己的 key):

npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/

这个命令会将当前最新的一个区块数据 fork 到本地,也就是说现在本地 node 链中存在的就是这个区块的数据。当然也可以指定区块号进行 fork:

npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/ --fork-block-number 14913700

每次 fork 都使用命令行指定这一堆参数很麻烦,因此可以将其放在 Hardhat 的配置文件 hardhat.config.js 中:

module.exports = {
  solidity: "0.8.4",

  networks: {
    hardhat: {
      // 添加 forking 内容
      forking: {
        url: "https://eth-mainnet.alchemyapi.io/v2/<key>",
        // 如果不指定区块,则默认 fork 当前最新区块
        blockNumber: 14913700
      }
    }
  }
};

这样配置之后,就可以直接使用

npx hardhat node

此时本地 node 的数据就是 fork 的区块数据。

需要注意的一点是,在执行 npx hardhat test 运行单元测试时,测试内容运行的链不是本地 node,而是沙盒模式。因此如果想要在单元测试的沙盒模式中使用 fork 的数据,需要在配置文件中显式配置 forking 内容。如果希望单元测试在本地 node 中执行,需要指定网络:

npx hardhat test --network localhost

测试 fork 数据

接下来,我们利用单元测试来实际验证一下我们的 fork 是否成功。在 test 文件夹下创建 fork.test.js 文件,编写代码:

const { ethers } = require("hardhat");

describe("Fork", function () {
  it("Testing fork data", async function () {
    console.log((await ethers.provider.getBlockNumber()).toString());
  });
});

执行测试,输出 14913700,说明 fork 成功。

现在我们来试试,实际调用主网的数据。我们以 USDT 合约为例,调用它的数据:

// 需要将 usdt 的 abi 保存在本地
const USDT_ABI = require("./usdt_abi.json");
// usdt 合约的主网地址
const USDT_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const { ethers } = require("hardhat");

describe("Fork", function () {
  it("Testing fork data", async function () {
    const provider = ethers.provider;
    // 构造 usdt 合约对象
    const USDT = new ethers.Contract(USDT_ADDRESS, USDT_ABI, provider);
    // 调用 usdt 的 totalSupply
    let totalSupply = await USDT.totalSupply();
    console.log(totalSupply.toString());
  });
});

结果为:

再去查看 USDT 的实际发行量:

验证数据正确。

fork 功能还有一个很有用的功能是,可以本地模拟任意账户,那么就意味着可以在本地拥有该地址的任意资产,这对我们做一些测试很有帮助。在测试文件中编写如下代码:

const mockAddress = "0x4c8CFE078a5B989CeA4B330197246ceD82764c63";

await network.provider.request({
  method: "hardhat_impersonateAccount",
  params: [mockAddress],
});

const signer = await ethers.provider.getSigner(mockAddress);

这样就可以模拟地址 0x4c8CFE078a5B989CeA4B330197246ceD82764c63,此时 signer 就是该地址的信息。先来查看该地址的账户信息:

let ETHBalance = await signer.getBalance();
console.log(`ETH balance is ${ETHBalance.toString() / 1e18}`);

let USDTBalance = await USDT.balanceOf(signer.getAddress()) / 1e6;
console.log(`USDT balance is ${USDTBalance.toString()}`);

执行结果为:

主网查询其资产为:

验证数据正确。我们再来看看如何模拟该账户发送交易,假设需要向其他地址发送一万 USDT,代码为:

// 打印转账前的账户余额
let USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6;
console.log(`USDT balance before transfer is ${USDTBalanceA.toString()}`);

const recipient = "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52";

let USDTBalanceB = await USDT.balanceOf(recipient) / 1e6;
console.log(`USDT balance of recipient before transfer is ${USDTBalanceB.toString()}`);

console.log("========Transfering========");

// 转账操作
await USDT.connect(signer).transfer(
  "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52",
  ethers.utils.parseUnits("10000", 6)
);

// 打印转账后的账户余额
USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6;
console.log(`USDT balance after transfer is ${USDTBalanceA.toString()}`);

USDTBalanceB = await USDT.balanceOf(recipient) / 1e6;
console.log(`USDT balance of recipient after transfer is ${USDTBalanceB.toString()}`);

结果为:

验证转账成功。

拓展

fork 下还有一些功能我们这里没有介绍,例如

// 设置账户余额
await network.provider.send("hardhat_setBalance", [
  "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B",
  "0x1000",
]);

// 设置账户 nonce
await network.provider.send("hardhat_setNonce", [
  "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B",
  "0x21",
]);

感兴趣的朋友可以查看文档自行了解。

总结

Hardhat fork 在平时的测试中非常方便,例如我们想要测试闪电贷,套利合约等,如果在主网测试,一是很慢,二是会花费 Gas。不过这个功能也不是 Hardhat 的专属,Ganache 和 Foundry 也有 fork 功能,感兴趣的朋友可以了解一下。

关于我

欢迎和我交流

参考

https://hardhat.org/hardhat-network/guides/mainnet-forking

https://hardhat.org/hardhat-network/reference

https://stackoverflow.com/questions/71106843/check-balance-of-erc20-token-in-hardhat-using-ethers-js