# Hardhat fork 简明使用教程

By [xyyme.eth](https://paragraph.com/@xyyme) · 2022-06-06

---

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

### 基本命令

首先，我们先创建一个 Hardhat 项目，不熟悉的朋友可以看看[这里](https://hardhat.org/tutorial/creating-a-new-hardhat-project)。创建完毕之后，就可以在本地 node 中 fork 区块了。使用命令（替换自己的 key）：

> npx hardhat node --fork [https://eth-mainnet.alchemyapi.io/v2/](https://eth-mainnet.alchemyapi.io/v2/)
> 
> 这个命令会将当前最新的一个区块数据 fork 到本地，也就是说现在本地 node 链中存在的就是这个区块的数据。当然也可以指定区块号进行 fork：
> 
> > npx hardhat node --fork [https://eth-mainnet.alchemyapi.io/v2/](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 成功。
> > 
> > ![](https://storage.googleapis.com/papyrus_images/4fda163b65156c264630022b337646188be08746a88188c8f5c9d9c59f425b7f.png)
> > 
> > 现在我们来试试，实际调用主网的数据。我们以 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());
> >       });
> >     });
> >     
> > 
> > 结果为：
> > 
> > ![](https://storage.googleapis.com/papyrus_images/64c33a01816f3484d7f153d3cf911d494d549f7c8c042dbe6ca9d68427fe720f.png)
> > 
> > 再去查看 USDT 的实际发行量：
> > 
> > ![](https://storage.googleapis.com/papyrus_images/e399a81235a00ea6b7ccb091ea5ae68169dde24088375f09eb063382dcf7c65f.png)
> > 
> > 验证数据正确。
> > 
> > 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()}`);
> >     
> > 
> > 执行结果为：
> > 
> > ![](https://storage.googleapis.com/papyrus_images/00190f883654a9fe567815830d2801a61eb5053ef68fbfdbdf95fdd383bbe04b.png)
> > 
> > 主网查询其资产为：
> > 
> > ![](https://storage.googleapis.com/papyrus_images/f34b2edba86014dfd382f6d2d1286c7b01b13a89e5a14ad60249b820549ce38b.png)
> > 
> > 验证数据正确。我们再来看看如何模拟该账户发送交易，假设需要向其他地址发送一万 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()}`);
> >     
> > 
> > 结果为：
> > 
> > ![](https://storage.googleapis.com/papyrus_images/54f3075285499f83e6e1e778fd5a73c3d24ac4c8a8d3c1c050e02a542d6ceadf.png)
> > 
> > 验证转账成功。
> > 
> > ### 拓展
> > 
> > fork 下还有一些功能我们这里没有介绍，例如
> > 
> >     // 设置账户余额
> >     await network.provider.send("hardhat_setBalance", [
> >       "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B",
> >       "0x1000",
> >     ]);
> >     
> >     // 设置账户 nonce
> >     await network.provider.send("hardhat_setNonce", [
> >       "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B",
> >       "0x21",
> >     ]);
> >     
> > 
> > 感兴趣的朋友可以查看[文档](https://hardhat.org/hardhat-network/reference)自行了解。
> > 
> > ### 总结
> > 
> > Hardhat fork 在平时的测试中非常方便，例如我们想要测试闪电贷，套利合约等，如果在主网测试，一是很慢，二是会花费 Gas。不过这个功能也不是 Hardhat 的专属，Ganache 和 Foundry 也有 fork 功能，感兴趣的朋友可以了解一下。
> > 
> > ### 关于我
> > 
> > 欢迎[和我交流](https://linktr.ee/xyymeeth)
> > 
> > ### 参考
> > 
> > [https://hardhat.org/hardhat-network/guides/mainnet-forking](https://hardhat.org/hardhat-network/guides/mainnet-forking)
> > 
> > [https://hardhat.org/hardhat-network/reference](https://hardhat.org/hardhat-network/reference)
> > 
> > [https://stackoverflow.com/questions/71106843/check-balance-of-erc20-token-in-hardhat-using-ethers-js](https://stackoverflow.com/questions/71106843/check-balance-of-erc20-token-in-hardhat-using-ethers-js)

---

*Originally published on [xyyme.eth](https://paragraph.com/@xyyme/hardhat-fork)*
