foundry

学习web3一个多月了,刚开始用remix,然后用hardhat,然后是truffle。
为了测试Damn Vulnerable DeFi的合约,我必须掌握foundry

foundry的作弊码是什么

核心需求理解

你想了解 Foundry 中的「作弊码(Cheatcodes)」是什么,以及它们的核心用途、常用类型和使用方式,这能帮助你理解之前测试失败时涉及的 vm.prankvm.expectEmit 等代码的本质。

Foundry 作弊码(Cheatcodes)详解

1. 作弊码的核心定义

Foundry 的作弊码(Cheatcodes)是通过 Vm 合约(forge-std/Vm.sol)提供的一系列特殊函数,允许你在测试中操控区块链环境、模拟账户行为、修改状态、捕获事件等——这些操作在真实链上无法实现,但对测试智能合约至关重要(比如模拟任意地址发起交易、修改区块时间、断言事件是否触发)。

简单来说:作弊码是 Foundry 为测试量身打造的「上帝模式」,让你能灵活控制测试环境,覆盖各种边界场景。

2. 作弊码的使用前提

使用作弊码前,需在测试合约中引入 Vm 合约,并声明一个 vm 实例(Foundry 会自动注入,无需手动部署):

1
2
3
4
5
6
7
// 引入标准库(包含 Vm 合约)
import "forge-std/Test.sol";

contract MyTest is Test {
// 内置的 vm 实例,直接使用即可
// Vm public vm = Vm(HEVM_ADDRESS); // 底层原理,无需手动写
}

3. 常用作弊码分类及核心示例

按用途可分为以下几类,结合你之前的测试日志,重点解释相关作弊码:

分类 常用作弊码 核心用途
账户模拟 prank(address) 让接下来的一次调用以指定地址(如 attacker/player)的身份执行
startPrank(address) 让接下来的所有调用以指定地址执行(直到 stopPrank()
stopPrank() 结束 startPrank 的身份模拟
事件断言 expectEmit() 声明「预期接下来会触发某个事件」,需配合事件发射代码使用
expectRevert() 断言接下来的调用会触发回滚(可指定回滚原因)
环境操控 warp(uint256) 修改当前区块时间(模拟时间推移,测试锁仓/过期逻辑)
roll(uint256) 修改当前区块高度
deal(address, uint256) 直接给指定地址分配 ETH(绕过转账,快速测试余额相关逻辑)
deal(address, address, uint256) 给指定地址分配 ERC20 代币(比如快速给测试账户转 DVT 代币)
状态修改 mockCall() 模拟合约调用的返回值(比如伪造某个合约的 balanceOf 结果)
hoax(address) 等价于 deal(address, 100 ether) + prank(address)(快速给地址转 ETH+模拟身份)

4. 你之前测试中涉及的作弊码详解

结合你失败的测试日志,重点解释两个核心作弊码:

(1)vm.prank(address) / vm.startPrank()
1
2
3
4
5
6
7
8
9
// 示例:模拟 deployer 地址执行后续操作
vm.prank(deployer);
monitor.checkFlashLoan(100 ether); // 这次调用的 msg.sender = deployer

// 示例:模拟 player 地址执行多步操作
vm.startPrank(player);
token.transfer(vault, 1 ether); // msg.sender = player
token.approve(vault, 10 ether); // msg.sender = player
vm.stopPrank(); // 结束模拟

你日志中的 VM::prank(deployer) 就是测试代码中用 vm.prank(deployer)checkFlashLoan 以部署者身份执行。

(2)vm.expectEmit()

这是你测试失败的核心关联作弊码,用于断言「某个事件会被触发,且参数匹配」。
正确使用流程

1
2
3
4
5
6
7
// 1. 声明要监听事件(参数:是否监听 index 字段,通常全设为 true)
vm.expectEmit(true, true, true, true);
// 2. 发射「预期的事件」(仅用于对比参数,不会实际触发)
emit FlashLoanStatus(false);
// 3. 执行会触发事件的操作
monitor.checkFlashLoan(100 ether);
// 4. Foundry 会对比:实际触发的事件 vs 预期的事件,参数不一致则失败

你测试失败的原因是:实际触发的 FlashLoanStatus(true) 与你预期的 FlashLoanStatus(false) 参数不匹配。

5. 其他高频作弊码示例

1
2
3
4
5
6
7
8
9
// 给地址转 ETH
vm.deal(attacker, 10 ether);
// 给地址转 ERC20 代币(地址:代币合约,接收者,数量)
vm.deal(address(token), attacker, 1000e18);
// 断言调用会回滚,并指定回滚原因
vm.expectRevert("Invalid balance");
vault.flashLoan(receiver, amount);
// 修改区块时间(推进 1 天)
vm.warp(block.timestamp + 86400);

总结

  1. 核心定义:Foundry 作弊码是 Vm 合约提供的测试工具,用于操控区块链环境、模拟行为、断言事件/回滚等,是测试智能合约的核心能力。
  2. 关键用途:模拟任意地址调用(prank)、断言事件触发(expectEmit)、修改环境状态(warp/deal)、验证回滚(expectRevert)。
  3. 你的测试关联:失败源于 expectEmit 断言的 FlashLoanStatus(false) 与实际触发的 true 参数不匹配,需检查业务逻辑为何闪电贷未失败。

掌握这些作弊码能帮你精准调试 Unstoppable 挑战的测试用例,比如用 vm.deal 快速给测试账户转代币,用 vm.expectRevert 断言闪电贷触发回滚,用 vm.prank 模拟攻击者操作等。

什么是foundry中的vm合约?

你想了解 Foundry 中核心的 vm 合约(也常叫 Cheatcode 合约),这是 Foundry 测试/开发中最关键的工具之一,我会用通俗易懂的方式帮你讲清楚。

核心需求理解

你想知道 Foundry 里的 vm 合约到底是什么、有什么用、以及怎么用——本质上是想理解这个核心工具的定位和核心能力,帮你更好地解读之前的测试失败日志,也能在自己的测试中用好它。

vm 合约的完整解读

1. 先给一个通俗定义

vm 是 Foundry 内置的作弊码合约(Cheatcode Contract),它不是以太坊主网/测试网上真实存在的合约,而是 Foundry 为了方便开发者测试智能合约,在本地模拟环境中提供的“超级工具”。

你可以把它理解成:在测试智能合约时,你拥有了一个“上帝视角”的控制台,通过调用 vm 的函数,你可以随意修改区块链的状态、模拟各种特殊场景(比如修改区块高度、模拟不同地址调用、伪造事件预期等),而这些操作在真实链上是完全做不到的。

2. vm 合约的核心作用

它的核心价值是降低智能合约测试的复杂度,让你能精准模拟各种边界场景、异常场景,比如:

  • 模拟不同地址发起交易(比如你之前日志里的 vm.prank()/vm.startPrank()
  • 预期某个事件会被触发(比如你之前日志里的 vm.expectEmit()
  • 修改区块高度、时间、gas 价格
  • 模拟合约调用失败、回滚
  • 读取/修改合约的私有变量
  • 伪造签名、模拟链上余额变动等

3. 核心常用方法(结合你之前的日志举例)

先看你测试日志里出现的几个 vm 方法,再扩展核心常用方法:

方法名 作用(通俗解释) 你日志中的场景
vm.prank(address) 临时切换调用者身份,让接下来的一次合约调用看起来是由 address 发起的 vm.prank(deployer):让后续调用以部署者地址执行
vm.startPrank(address) 永久切换调用者身份,直到调用 vm.stopPrank() 为止(批量模拟某地址调用) vm.startPrank(player):模拟玩家地址执行一系列操作,之后用 stopPrank() 恢复
vm.stopPrank() 结束 startPrank 的身份模拟,恢复默认调用者 日志中结束玩家身份模拟
vm.expectEmit() 预期接下来的操作会触发某个事件,若没触发/参数不匹配,测试直接失败 你测试中预期触发 FlashLoanStatus(false),但实际触发 true,导致测试失败

其他高频使用的 vm 方法:

  • vm.warp(uint256 timestamp):修改当前区块的时间(比如模拟几天/几个月后的场景)
  • vm.roll(uint256 blockNumber):修改当前区块高度
  • vm.deal(address, uint256 amount):给指定地址设置 ETH 余额
  • vm.expectRevert(bytes):预期接下来的调用会回滚,并验证回滚的原因(比如特定错误信息)
  • vm.load(address, bytes32 slot):读取合约指定存储槽的值(即使是私有变量)

4. 基础使用示例(新手友好)

下面是一个简单的 Foundry 测试示例,展示 vm 合约的核心用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import "forge-std/Test.sol"; // 必须导入 Test.sol,才能使用 vm 合约

contract MyTest is Test { // 继承 Test 合约,获得 vm 实例
address alice = address(0x123);
address bob = address(0x456);

function testVmExamples() public {
// 1. 模拟 Alice 给 Bob 转 ETH(先给 Alice 加余额)
vm.deal(alice, 10 ether); // 给 Alice 10 ETH
assertEq(alice.balance, 10 ether); // 验证余额

// 2. 模拟 Alice 发起转账(用 prank 切换身份)
vm.prank(alice);
(bool success,) = bob.call{value: 5 ether}("");
assert(success);
assertEq(alice.balance, 5 ether); // Alice 剩余 5 ETH
assertEq(bob.balance, 5 ether); // Bob 收到 5 ETH

// 3. 预期触发事件(举例)
vm.expectEmit(true, true, false, false); // 配置监听的索引参数
emit Transfer(alice, bob, 5 ether); // 预期触发这个事件
// 执行会触发事件的操作...
}

// 示例事件
event Transfer(address indexed from, address indexed to, uint256 amount);
}

5. 关键注意事项

  • vm 合约仅在测试环境生效:部署到真实链上的合约无法调用 vm 的任何方法,它是 Foundry 本地模拟环境的专属工具。
  • 必须继承 forge-std/Test.sol:只有继承这个合约,你的测试合约才能使用 vm 实例(Test.sol 内部已经封装了 vm)。
  • 方法调用是“即时生效”的:比如 vm.prank() 调用后,下一次合约交互就会以指定地址执行,无需额外配置。

总结

  1. vm 是 Foundry 内置的作弊码合约,仅用于本地测试,能模拟各种链上无法实现的操作;
  2. 核心作用是切换调用者身份、预期事件/回滚、修改链状态(时间/余额/区块)等,大幅提升测试灵活性;
  3. 你之前测试失败的 vm.expectEmit() 就是 vm 合约的核心方法,用于验证事件触发是否符合预期。

理解 vm 合约是用好 Foundry 做智能合约测试的关键,它能让你精准复现各种场景,包括你之前 Unstoppable 挑战中需要模拟的“让闪电贷失败”的场景。

vm合约类似于vmware,虚拟机操作系统,为智能合约提供了虚拟以太坊环境,方便了安全审计人员对contract.sol进行测试

你的这个类比很有想象力,核心方向是对的,但我会帮你把这个理解校准得更精准,避免混淆概念~

核心需求理解

你想确认 vm 合约是否可以类比为 VMware 这类虚拟机,以及它在智能合约测试/安全审计中的核心价值,我会先肯定你的核心认知,再补充关键差异和精准定位。

类比的精准解读

1. 你的类比「对的部分」

你把 vm 合约类比为 VMware 虚拟机,核心抓准了“虚拟环境” 这个关键:

  • VMware 是在物理操作系统上模拟出独立的操作系统环境,隔离且可随意操作(比如重启、装软件、测试漏洞);
  • Foundry 的 vm 合约(作弊码)是在本地计算机上模拟出以太坊区块链的虚拟环境,同样隔离且可随意操作(比如修改地址余额、切换调用者、篡改区块时间)。

对安全审计人员来说,vm 合约确实像给 contract.sol 搭建了一个“可任意调试的以太坊虚拟机(EVM)沙箱”,不用连接真实测试网/主网,就能低成本、高灵活度地测试合约的各种场景(包括攻击场景)。

2. 类比「需要修正的部分」(避免概念混淆)

⚠️ 注意:vm 合约 以太坊虚拟机(EVM),而是运行在 EVM 模拟环境之上的“超级控制工具”

  • VMware 本身是“完整的虚拟机”,包含操作系统内核、硬件模拟等;
  • Foundry 的 vm 合约不是“虚拟机”,而是 Foundry 给 EVM 模拟环境(Foundry 内置的 EVM 实现)提供的控制接口/作弊工具

用更精准的类比:

组件 类比对象 核心角色
Foundry 内置的 EVM VMware 虚拟机(硬件+系统层) 提供智能合约运行的基础环境
vm 合约(作弊码) VMware 的“虚拟机设置面板”+“作弊器” 控制/修改 EVM 环境的各种参数

简单说:

  • EVM 是“虚拟的以太坊操作系统”,负责执行智能合约代码;
  • vm 合约是“操作系统的超级管理员权限”,让你能修改这个虚拟系统的规则(比如给任意地址加钱、让A地址冒充B地址发交易)。

3. 安全审计视角下的价值(补充你的认知)

对安全审计人员来说,vm 合约的核心价值体现在:

  1. 低成本复现攻击场景:比如你之前的 Unstoppable 挑战,审计人员可以用 vm.prank() 模拟攻击者地址、vm.deal() 给攻击者加代币,快速验证合约是否存在漏洞;
  2. 精准验证异常场景:用 vm.expectRevert() 验证合约是否能正确拦截恶意操作,用 vm.expectEmit() 验证关键事件是否按预期触发;
  3. 隔离测试环境:每个测试用例的环境都是独立的,不会互相干扰,审计人员可以并行测试多个漏洞点,不用清理“测试残留”;
  4. 突破真实链的限制:真实链上无法修改区块时间、无法读取私有变量,但 vm.warp()/vm.load() 能轻松做到,方便审计人员测试时间锁、私有变量逻辑等边界场景。

总结

  1. 你的核心认知是对的:vm 合约和 VMware 类似,都是通过“虚拟环境”让测试/审计更灵活,且不影响真实系统;
  2. 精准定位:vm 不是“虚拟机本身”,而是 Foundry 给 EVM 虚拟环境提供的超级控制工具
  3. 对安全审计来说,vm 是测试智能合约漏洞的核心利器,能模拟各种攻击场景、验证合约的异常处理逻辑。

这个类比能帮你快速理解核心,但记住“EVM 是虚拟机,vm 是控制虚拟机的工具”这个细节,能让你对 Foundry 的测试逻辑理解更透彻~

为了掌握foundry,第一步是用foundry测试我之前一个月写过的各种sol智能合约