Web3专题(五) Hardhat 和 Foundry 该如何选择?

hardhat vs foundry

几个月前,我发表了一篇使用truffle进行空投报错的解决方案,经过频繁的使用,这个解决方案有时候管用,有时候不管用,实在不想被truffle的众多 bug 折腾的死去活来了,于是乎,我花功夫研究了一下目前最好用的合约开发工具,就有了今天这篇文章。建议大家不要用 truffle

hardhatfoundry是目前最受欢迎的两个 solidity 合约开发工具。二者各有各的优势,我刚开始也纠结到底该选择哪一个开发,通过这段时间详细研究他们的使用特点,我决定将他们整合在一起使用。

本文先分别介绍hardhatfoundry的使用技巧和特点,做一个简单对比,最后给出整合的步骤。

参考链接:hardhat 官方文档快速开始foundry 官方文档

Hardhat

Hardhat 是一个安装了hardhat包和增加了hardhat.config.js文件的 nodejs 项目,需要具备一定的 JavaScript 或 TypeScript 知识。

Hardhat 是围绕task为核心的开发工具,任何操作,如 编译、部署、测试 都是一个task,另外,社区也提供了许多plugins让其特别好用,其中就包括本文将要使用的插件hardhat-foundry(用于整合foundry)。

基本命令

npm install --save-dev hardhat # 安装hardhat

# 安装成功后的hardhat命令
npx hardhat init    # 初始化一个hardhat项目
npx hardhat compile # 编译
npx hardhat test    # 测试
npx hardhat run     # 执行脚本

使用技巧

1. 将命令npx hardhat变为短命令hh

# 安装hardhat-shorthand,只能使用 npm,不支持 yarn
npm install --global hardhat-shorthand

# 安装好以后,就不用每次写长长的 npx hardhat 了,直接用 hh 代替,如:
hh compile   # 等价于 npx hardhat compile
hh test      # 等价于 npx hardhat test 
hh run       # 等价于 npx hardhat run 

2. 迁移 dotenv,使用hardhat原生支持的 vars,好处是再也不用担心.env文件不小心提交到了公共仓库,导致安全性问题。

  • 在命令行设置配置属性,这些属性都会存储在本地一个文件内,通过命令hh vars path可以查看存储位置。下面是常用命令:
hh vars setup  # 检查项目依赖哪些配置
hh vars set API_KEY # 设置命令,回车输入API_KEY的值
hh vars get API_KEY # 获取API_KEY的值
hh vars list # 列出本地所有的KEY
hh vars delete API_KEY # 删除API_KEY
  • 在 hardhat 配置文件使用
// hardhat.config.js
const { vars } = require("hardhat/config");

const API_KEY = vars.get("API_KEY"); // 获取API_KEY的值,没设置会报错
const API_KEY = vars.get("API_KEY", "12345"); // 第二个参数是默认值,没设置就使用默认值,不报错

// 判断是否设置
if (vars.has("API_KEY")) {
     // do something
}

vars 还有许多能力,建议阅读官方参考文档: vars 的详细用法

3. 可定制性 - 自定义 task

hardhat中,除了原生支持的 编译、部署、测试等等task,还可以自定义task,也可以覆盖已有的task

一个简单例子

// 在 hardhat.config.js 文件中添加下面代码,就是创建一个 accounts 任务(获取所有账户地址)
// 和运行`hh compile`一样,可以直接运行`hh accounts`,非常方便
// 如果想创建更复杂的任务,可以在单独的js文件编写代码,在这里引入即可
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

4. 配置 HD Wallet

在使用hardhat.config.js配置钱包的时候,你是否经常用这样的配置:

module.exports = {
  networks: {
    sepolia: {
      url: "...",
      accounts: [privateKey1, privateKey2, ...]
    },
  },
};

这样的配置,在小型项目中没啥问题,因为用到的私钥也不多。然而,一旦项目变大,需要使用很多地址的时候,配置和管理很多私钥将是一个巨大的麻烦。其实,根据 HD Wallet 的性质,还有另一种配置方法,只需要配置助记词,就可以使用大量的地址。如以下代码:

module.exports = {
  networks: {
    sepolia: {
      url: "...",
      accounts: {
        mnemonic: "test test test test test test test test test test test junk",
        path: "m/44'/60'/0'/0",
        initialIndex: 0,
        count: 20,
        passphrase: "",
      },
    },
  },
};

如果你对mnemonicpath: "m/44'/60'/0'/0"passphrase不太理解,不知道怎么配置,请阅读我前面写的文章Web3专题(三) 2种钱包之分层确定性钱包(HD Wallet),里面很详细的讲解了这 3 个字段,这里不再赘述。假定你已经看过了那篇文章,在那个基础上,我们来说说initialIndexcount的配置:

  • initialIndex表示从哪个索引开始派生子账户,默认是 0
  • count表示派生的子账户数量,默认是 20

例如:在 path 配置为 "m/44'/60'/0'/0" 时

当 initialIndex 配置为 0,count 配置为 5 时,将生成以下子账户(用path表示):

m/44'/60'/0'/0/0
m/44'/60'/0'/0/1
m/44'/60'/0'/0/2
m/44'/60'/0'/0/3
m/44'/60'/0'/0/4

当 initialIndex 配置为 3,count 配置为 5 时,将生成以下子账户(用path表示):

m/44'/60'/0'/0/3
m/44'/60'/0'/0/4
m/44'/60'/0'/0/5
m/44'/60'/0'/0/6
m/44'/60'/0'/0/7

以此类推

Foundry

Foundry 项目使用solidity编写测试脚本,除solidity之外,不需要其他语言基础。

主要包含了 4 个工具:forge, cast, anvil, chisel,这些工具可以通过执行foundryup命令安装。

简要介绍几个工具的功能:

  • forge: 对项目进行 编译,部署,测试,验证等
  • cast: 在命令行轻松进行以太坊 RPC 调用,拉取链上信息、调用智能合约、发送交易等
  • anvil: 在本地创建测试节点,也用于 fork 其他 EVM 兼容的网络
  • chisel: 在本地开启 solidity 环境,使你能够在命令行编写 solidity 代码并运行。方便快速运行一段 solidity 代码

基本命令

# 1.安装foundryup命令
curl -L https://foundry.paradigm.xyz | bash
# 2.执行foundryup命令,安装forge, cast, anvil, and chisel
foundryup

# 安装完成后
forge init project_name # 初始化一个foundry项目,project_name必须是一个不存在的文件夹名称
forge init --force      # 初始化一个foundry项目,会强制把当前目录初始化为foundry项目
forge build             # 编译
forge test              # 测试

foundry 独有的特点(hardhat 没有)

1. 使用solidity编写测试脚本

pragma solidity 0.8.10;

import "forge-std/Test.sol";

contract OwnerUpOnlyTest is Test {
    OwnerUpOnly upOnly;

    // 做一些初始化的工作
    function setUp() public {
        upOnly = new OwnerUpOnly();
    }

    // 以 test 为前缀的方法会被识别为测试方法
    function test_IncrementAsOwner() public {
        assertEq(upOnly.count(), 0);
        // 调用一个只有owner才能调用的合约
        upOnly.increment();
        assertEq(upOnly.count(), 1);
    }
}

2. 使用vm对象操控链上状态

这个功能很强大,也是很多人使用 foundry 的原因之一,建议去官网文档学习更详细的内容

// 以 testFail 为前缀的方法都会被识别为执行失败的方法
function testFail_IncrementAsNotOwner() public {
    // 这里的 vm.prank 会设置下一个调用的msg.sender,改变链上状态
    // 类似的,vm 对象提供了许多可以操控链上状态的接口方法,给测试带来了极大的灵活性
    vm.prank(address(0));
    upOnly.increment();
}

3. Fork 主网环境进行本地测试,如果你的项目需要和已有的项目交互,比如uniswap,那这个功能会非常方便

4. 强大的命令行工具,如castchisel

总体上来说,Foundry 与链上交互的能力很方便、很强大。

Hardhat vs Foundry

Hardhat Foundry
入门语言门槛 solidity, nodejs, javascript or typescript solidity
个性化 可定制性 比较强大 不可定制
与链上交互 一般 强大
自定义任务 可以定义很个性化的task
生态系统和社区 社区活跃,有众多扩展插件 一般

在 Hardhat 项目里整合 Foundry

我们既想用hardhat自定义的能力, 又想用foundry与链上交互的能力,不如整合起来一起用!更何况 热心的 hardhat 社区 已经帮我们写好了插件@nomicfoundation/hardhat-foundry,使我们非常容易就能做到这一点。

  1. 按照前面的基本命令,先创建一个 Hardhat 项目

  2. 执行命令npm i --save-dev @nomicfoundation/hardhat-foundry安装插件

  3. hardhat.config.js文件顶部引入插件,如下所示:

require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-foundry");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.19",
};
  1. 执行命令npx hardhat init-foundry会生成一个foundry.toml文件和安装 forge-std 标准库

现在,目录结构应该如下所示:

contracts hardhat 风格存放合约的地方,foundry.toml 配置了这个文件夹,从这里查找编译合约 lib foundry 依赖的标准库,只供 foundry 使用 node_modules hardhat 风格的依赖模块, foundry.toml 配置了这个文件夹,从这里查找依赖 scripts 存放脚本的地方 test hardhat 和 foundry 都会从这里检索并执行测试用例,hardhat 执行.js 文件,foundry 执行.t.sol 文件 foundry.toml foundry 配置文件 hardhat.config.js hardhat 配置文件 package.json nodejs 依赖包管理文件

恭喜,到这里已经整合完成,这是将foundry整合进hardhat的步骤。如果你已经有了一个foundry项目,想反过来把hardhat整合进你的foundry项目,可以参考Adding Hardhat to a Foundry project

点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
认知那些事
认知那些事
0x2b62...95a0
人立于天地之间,必然有我们的出路。