学习Foundry的理由和资源
Foundry介绍
Foundry由以下几个部分组成:
- Forge:以太坊测试框架(类似于 Truffle、Hardhat 和 DappTools)。
- Cast:用于与 EVM 智能合约交互、发送交易以及获取链上数据的多功能工具。
- Anvil:本地以太坊节点,类似于 Ganache 和 Hardhat Network。
- Chisel:快速、实用且详细的 Solidity REPL(交互式解释器)。
其他介绍见https://github.com/foundry-rs/foundry
总的来说是Rust实现的智能合约开发测试框架,能直接用Solidity写测试代码来测试Solidity合约,编译合约以及运行测试用例的速度比hardhat快。
学习资源
https://learnblockchain.cn/docs/foundry/i18n/zh/ 中文文档
solidity by example的DeFi章节,里边的例子大多都是用Foundry写的Solidity测试代码,而不是用hardhat+ethers.js写JavaScript代码。主要还是能给特定地址指定代币余额这类操作太方便了。
来自于跟ChatGPT老师的讨论。
学习 Foundry 对于你进行 DeFi 开发来说是一个不错的选择。Foundry 有以下几个优势:
-
速度和效率:Foundry 在编译和运行测试时比 Hardhat 更快。它使用基于 Rust 的工具来提高速度,这在处理大型项目和频繁测试时非常有用。
-
内置模糊测试:Foundry 自带模糊测试功能,能够通过提供随机输入有效地测试极端情况。这在 DeFi 系统中尤其重要,因为可以更好地检测潜在的边缘案例。
-
主网分叉:和 Hardhat 一样,Foundry 也支持主网分叉测试,但通常速度更快。这样你可以更容易地模拟真实世界中的 DeFi 交互,例如测试现有流动性池或在分叉主网上进行代币转账。
-
自定义 Solidity 脚本:Foundry 允许你使用 Solidity 编写脚本,使得直接与合约进行交互更加方便,因为你使用的语言和开发时是一样的。
鉴于你当前的背景以及对 DeFi Dex 合约的专注,Foundry 的工具和功能将帮助你优化测试、模拟和通过实践进行学习。
我看在Foundry里可以用deal(address(USDT), address(this), 1e6 * 1e6)这样的方式给指定的地址设置某个代币的余额,如果用Hardhat能做到吗?
在 Hardhat 中,没有像 Foundry 那样的原生 deal
方法直接修改代币余额,但你可以通过模拟代币合约的方式来实现类似的功能。通常的方法是通过与代币合约的 mint
或 transfer
函数交互来为某个地址设置余额。
如果你想在 Hardhat 中模拟像 deal
这样的方法,可以通过以下几种方式实现:
1. 使用 Forked Mainnet(主网分叉)
在 Hardhat 中,你可以使用主网分叉模式来测试真实代币的交互。你可以将真实的 ERC20 代币转账到某个地址。
// 使用 USDT 代币合约的 ABI 和地址
const USDT_ABI = [ /* USDT ABI */ ];
const USDT_ADDRESS = "0x..."; // USDT 合约地址
const usdt = new ethers.Contract(USDT_ADDRESS, USDT_ABI, signer);
// 给某个地址转账 USDT
await usdt.transfer(recipientAddress, ethers.utils.parseUnits("1000", 6)); // 转账 1000 USDT
2. 使用 Mock Token(模拟代币合约)
如果你只是在测试环境中,可以部署一个模拟的 ERC20 代币合约,然后在需要时调用 mint
函数直接给某个地址设置余额。
// ERC20 Mock Token 合约
contract MockERC20 is ERC20 {
constructor() ERC20("Mock Token", "MKT") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
在你的测试中调用 mint
方法:
const mockToken = await MockERC20.deploy();
await mockToken.mint(address, ethers.utils.parseUnits("1000", 18)); // 设置余额
3. Hardhat Impersonation(账户模拟)
如果你是在主网分叉上操作现有的代币合约,可以使用 Hardhat 的 impersonateAccount
方法来模拟任何账户(例如,使用代币合约的管理员地址来 mint 或转账代币)。
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: ["0x..."], // 代币合约的管理员地址
});
const signer = await ethers.getSigner("0x...");
await token.connect(signer).mint(recipient, amount);
总结
虽然 Hardhat 本身没有像 Foundry 中的 deal
函数直接设置代币余额的功能,但你可以通过以上几种方法,在测试中实现类似的功能。
方法3需要我去查询实际主网上一些有足够余额的地址来进行冒充。方法1也是一样,signer地址需要在主网上有相应的代币余额。我不想去找这些账户地址,而hardhat内置生成的模拟账户似乎每个地址只有100ETH,没有其他代币。我想有个能给模拟账户增加指定代币指定余额的方法,这个能实现吗?
在 Hardhat 中,确实没有像 Foundry 那样的原生方法可以直接给账户增加指定代币的余额。然而,有一个变通方法可以模拟这种效果——通过部署一个 ERC20 代币合约(或 Mock 代币),并在需要时手动调用 mint
函数为指定的模拟账户增加余额。这个方法不需要使用主网地址或实际存在的代币余额,也不需要 impersonate 账户。
以下是如何实现的详细步骤:
1. 部署 Mock Token(模拟代币合约)
你可以部署一个自定义的 ERC20 代币合约,并在测试过程中调用 mint
函数向模拟账户增加余额。
Step 1: 创建一个 Mock ERC20 代币合约
首先,定义一个简单的 ERC20 合约,它包含一个 mint
函数:
// contracts/MockERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
Step 2: 在测试脚本中部署 Mock Token 合约
在你的 Hardhat 测试脚本中,部署这个合约并调用 mint
方法给指定的账户增加余额。
const { ethers } = require("hardhat");
async function main() {
// 部署 MockERC20 合约
const MockERC20 = await ethers.getContractFactory("MockERC20");
const mockToken = await MockERC20.deploy("Mock Token", "MKT");
await mockToken.deployed();
console.log(`Mock Token 部署在: ${mockToken.address}`);
// 获取模拟账户
const [owner, addr1] = await ethers.getSigners();
// 给 addr1 增加 1000 个代币
const mintAmount = ethers.utils.parseUnits("1000", 18); // 假设 18 位小数
await mockToken.mint(addr1.address, mintAmount);
// 验证余额
const balance = await mockToken.balanceOf(addr1.address);
console.log(`Addr1 的代币余额为: ${ethers.utils.formatUnits(balance, 18)}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
2. 测试现有 ERC20 代币
如果你需要使用主网上存在的真实代币,并希望在本地测试环境下直接控制余额,可以选择主网分叉模式并使用 mint
函数,但如果无法直接控制代币的 mint
权限,则这种方式不适用。
总结
通过在测试环境中使用 Mock 代币合约,你可以模拟任何 ERC20 代币的行为,并为指定的模拟账户自由设置余额。这样就避免了去找真实地址或实际代币余额的问题,适用于大多数本地测试场景。如果你确实需要在测试环境中模拟真实的代币,则需要通过分叉主网并找到可以转账的地址来进行测试。
我的需求类似上面的2,是要使用主网上存在的代币,然而hardhat生成的模拟地址大概率是没有mint权限的,这就需要找到真实的有实际余额的地址。看来这个方面还是Foundry更好用了。
确实,针对你希望在测试中使用主网上存在的真实代币并为模拟账户设置余额的需求,Foundry 提供的 deal
方法确实更加方便。它允许直接为指定地址设置代币余额,而无需关心代币是否有 mint
权限或去寻找主网上真实有余额的地址。这大大简化了测试过程,特别是当你需要测试多个不同代币时。
相比之下,Hardhat 在这方面需要更多手动步骤,比如通过分叉主网找到有足够余额的地址并进行 impersonate,或者自己部署和管理 mock 代币来模拟真实代币的行为。而这些在 Foundry 中都可以直接通过 deal
来轻松完成。
所以如果你的测试需求频繁涉及主网上存在的真实代币,特别是大规模测试不同的账户和代币余额的情况,Foundry 的确是一个更合适的选择。