hardhat vs foundry
几个月前,我发表了一篇使用truffle
进行空投报错的解决方案,经过频繁的使用,这个解决方案有时候管用,有时候不管用,实在不想被truffle
的众多 bug 折腾的死去活来了,于是乎,我花功夫研究了一下目前最好用的合约开发工具,就有了今天这篇文章。建议大家不要用 truffle。
hardhat
和foundry
是目前最受欢迎的两个 solidity 合约开发工具。二者各有各的优势,我刚开始也纠结到底该选择哪一个开发,通过这段时间详细研究他们的使用特点,我决定将他们整合在一起使用。
本文先分别介绍hardhat
和foundry
的使用技巧和特点,做一个简单对比,最后给出整合的步骤。
参考链接:hardhat 官方文档快速开始,foundry 官方文档
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.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: "",
},
},
},
};
如果你对mnemonic
,path: "m/44'/60'/0'/0"
,passphrase
不太理解,不知道怎么配置,请阅读我前面写的文章Web3专题(三) 2种钱包之分层确定性钱包(HD Wallet),里面很详细的讲解了这 3 个字段,这里不再赘述。假定你已经看过了那篇文章,在那个基础上,我们来说说initialIndex
和count
的配置:
initialIndex
表示从哪个索引开始派生子账户,默认是 0count
表示派生的子账户数量,默认是 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 项目使用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 # 测试
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. 强大的命令行工具,如cast
,chisel
总体上来说,Foundry 与链上交互的能力很方便、很强大。
Hardhat | Foundry | |
---|---|---|
入门语言门槛 | solidity , nodejs , javascript or typescript |
solidity |
个性化 | 可定制性 比较强大 | 不可定制 |
与链上交互 | 一般 | 强大 |
自定义任务 | 可以定义很个性化的task |
无 |
生态系统和社区 | 社区活跃,有众多扩展插件 | 一般 |
我们既想用hardhat
自定义的能力, 又想用foundry
与链上交互的能力,不如整合起来一起用!更何况 热心的 hardhat 社区 已经帮我们写好了插件@nomicfoundation/hardhat-foundry
,使我们非常容易就能做到这一点。
按照前面的基本命令,先创建一个 Hardhat 项目
执行命令npm i --save-dev @nomicfoundation/hardhat-foundry
安装插件
在hardhat.config.js
文件顶部引入插件,如下所示:
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-foundry");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
};
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。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!