🧠 Foundry 学习笔记:完整知识体系整理
一、Foundry 核心组件简介
工具名 | 说明 |
---|---|
forge | 合约开发、测试、编译的主工具 |
cast | 与链交互工具:查询数据、发送交易、部署合约 |
anvil | 本地模拟链,用于测试、开发和 fork 主网数据 |
二、基础开发结构
forge init token-presale-foundry
生成项目目录结构:
token-presale-foundry/
├── src/ # 合约代码
├── test/ # 测试代码
├── script/ # 部署脚本
├── foundry.toml # 配置文件
├── .env # 环境变量
三、合约开发与部署流程
1. 编写合约(src/TokenPresale.sol)
使用 PriceConverter 库,实现预售逻辑,包含:
-
fund():捐赠 ETH -> USD 换算
-
withdrawETH():项目方提现
-
claimTokens():线性解锁后用户领取 token
-
modifiers 修饰器:权限、时间、状态判断
-
fallback/receive 函数自动处理 ETH 转账
2. 编译合约
forge build
四、合约测试模块详解
1. 单元测试(unit)
contract TokenPresaleTest_Unit is Test {
address owner = makeAddr("owner");
...
}
-
使用 makeAddr("user1") 创建虚拟地址
-
vm.prank(user):模拟下一个 tx 由 user 发出
-
vm.startPrank(user) 与 vm.stopPrank() 配套使用模拟多条 tx
-
vm.expectRevert(Error.selector) 断言 revert 错误
2. 测试
fund
函数最小捐赠限制
function testFund_RevertWhenTooSmall() public {
uint256 tooSmallAmount = 0.009 ether;
vm.prank(user1);
vm.expectRevert(TooSmall.selector);
tokenPresale.fund{value: tooSmallAmount}();
}
知识点:
-
Solidity 中 .selector 是函数/错误签名的前 4 字节
-
不能用 TooSmall() 因为 .expectRevert 期望的是 bytes4
3. 使用 warp 模拟时间推移
vm.warp(tokenPresale.unlockStartTime() + tokenPresale.unlockDuration() + 1);
-
vm.warp() 模拟区块时间
-
解锁线性领取功能需要模拟时间推进
五、Fork 测试(Forked)
使用主网预言机数据测试 ETH/USD 换算逻辑:
forge test --match-path test/forked/TokenPresaleTest_Forked.t.sol --fork-url $SEPOLIA_RPC_URL -vvvv
知识点:
-
Fork 测试不会消耗 Metamask 测试币
-
使用 getLatestETHPriceInUSD() 查询主网预言机价格
-
容忍误差断言写法:
assertApproxEqAbs(actual, expected, tolerance, "msg");
六、Staging 测试(模拟完整流程)
文件:test/staging/TokenPresaleTest_Staging.t.sol
流程覆盖:
-
多用户捐赠 → 达成目标
-
owner 提现 → 启用领取
-
模拟时间推进:领取 30%、60%、100%
-
紧急暂停:验证暂停状态下逻辑和领取是否被影响
七、部署脚本与 Makefile 自动化
1. 脚本文件
contract DeployTokenPresale is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
TokenPresale tokenPresale = new TokenPresale();
console.log("TokenPresale deployed at:", address(tokenPresale));
vm.stopBroadcast();
}
}
2. 本地
.env
示例
SEPOLIA_PRIVATE_KEY=你的私钥
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/xxx
ETHERSCAN_API_KEY=xxx
MYWALLET_ADDRESS=0xf3...266
SEPOLIA_MYWALLET_ADDRESS=0x44...6c5
RPC_URL=http://127.0.0.1:8545
3. Makefile 自动化部署命令
deploy-anvil:
forge script script/DeployTokenPresale.s.sol:DeployTokenPresale \
--rpc-url $(RPC_URL) \
--broadcast \
--wallet mywallet \
--sig "run()" \
-vvvv
注意:
- .env 不应上传至 GitHub,需加 .gitignore
八、cast send 直接部署(CLI 方式)
cast send \
--from 0xf39f...92266 \
--rpc-url http://127.0.0.1:8545 \
--create out/TokenPresale.sol/TokenPresale.json \
-- \
-vvvv
错误排查:
-
--unlocked 错误使用
-
--legacy 需要写在 -- 后
-
mywallet 不能直接作为 --from,需先用 cast wallet 解锁并查地址
九、Foundry 调试日志解释
调用结构如下:
TokenPresale::fund{value: 8091...}()
├─ EACAggregatorProxy::latestRoundData()
│ └─ AccessControlledOffchainAggregator::latestRoundData()
│ └─ 返回价格数据:24535,表示 $2453.5
关键术语解释:
-
staticcall\]:只读调用(不改变链状态)
-
1 ether = 1e18 wei,ETH 捐赠在换算 USD 时自动转换精度
十、总结
类别 | 工具 | 用法 |
---|---|---|
合约编写 | Solidity | 编写逻辑:fund、claim、withdraw |
本地模拟 | Anvil | anvil 启动本地链 |
测试工具 | forge test | 支持 unit / forked / staging |
脚本部署 | forge script | 搭配 .env 和 Makefile 实现自动化部署 |
链上交互 | cast send / call / wallet | 查询状态、部署合约、签名交易 |
环境变量管理 | .env + Makefile | 配置私钥、钱包地址、RPC 节点等信息 |