vm.expectRevert 是 Foundry 中用于测试合约函数是否会抛出 revert 错误的一个非常有用的工具。它允许你验证在执行某个操作时,合约是否会按预期抛出特定的错误。这对编写安全和可靠的智能合约测试至关重要,尤其是在涉及访问控制、权限检查等场景时。

1. 基本功能

vm.expectRevert 用于设置期望捕获的错误。当你调用合约函数时,如果该函数触发了 revertexpectRevert 会检查其是否与预期的错误类型一致。如果一致,测试通过;如果不一致,或者没有触发 revert,测试失败。

2. 语法和使用

vm.expectRevert(bytes memory revertData);
  • revertData:一个字节数组,表示你期望捕获的 revert 数据。你可以传递错误选择器或者错误数据(错误选择器是 Solidity 错误的前四个字节,通常通过 abi.encodeWithSelectorabi.encodeWithSignature 编码)。

3. 常见用法

3.1. 捕获简单的 revert 错误

假设你有一个合约,其中的 onlyOwner 修饰符检查调用者是否为合约的拥有者:

function freezeAccount(address account) public onlyOwner {
    // 只有拥有者可以调用
    // 这里的错误是权限错误,非拥有者调用会触发 revert
    revert("Ownable: caller is not the owner");
}

在测试中,你期望捕获这个 revert 错误:

function testOnlyOwnerCanFreeze() public {
    address nonOwner = address(0x123);

    // 使用 vm.expectRevert 捕获 `revert` 错误
    vm.expectRevert("Ownable: caller is not the owner");
    
    // 试图非拥有者调用,应该触发 revert
    myToken.freezeAccount(nonOwner);
}

在这个例子中,vm.expectRevert("Ownable: caller is not the owner") 将捕获 revert 错误信息并验证它是否与预期的错误消息匹配。

3.2. 捕获自定义错误(Custom Errors)

自定义错误(在 Solidity 0.8+ 中引入)比传统的 revert 错误更加高效。它们通常用于减少 gas 消耗,并且不再依赖于字符串消息。

假设你在合约中定义了一个自定义错误:

// 自定义错误类型
error OwnableUnauthorizedAccount(address account);

function freezeAccount(address account) public onlyOwner {
    // 如果调用者不是合约的拥有者,抛出自定义错误
    revert OwnableUnauthorizedAccount(account);
}

在测试中,你可以通过 vm.expectRevert 捕获这个自定义错误:

function testOnlyOwnerCanFreeze() public {
    address nonOwner = address(0x123);

    // 使用 abi.encodeWithSelector 构造错误数据
    vm.expectRevert(
        abi.encodeWithSelector(OwnableUnauthorizedAccount.selector, nonOwner)
    );

    // 非拥有者调用时应该触发 revert
    myToken.freezeAccount(nonOwner);
}

3.3. 捕获其他类型的 revert 错误

你可以通过 abi.encodeWithSelectorabi.encodeWithSignature 编码捕获任何 Solidity 错误。通常,错误可以分为两类:

  • 标准错误:如 require 失败、revert 抛出带消息的错误。
  • 自定义错误:通过 error 关键字定义的错误类型。

例如,如果你想捕获一个带有参数的错误:

// 自定义错误
error InsufficientBalance(address account, uint256 amount);

你可以使用以下代码来捕获这个错误:

vm.expectRevert(
    abi.encodeWithSelector(InsufficientBalance.selector, address(0x123), 100)
);

3.4. 捕获没有任何错误消息的 revert

如果你期望捕获没有任何附加错误信息的 revert,可以直接调用 vm.expectRevert(),并且不传递任何参数:

function testShouldRevert() public {
    vm.expectRevert();  // 仅检查是否 revert,但不关心具体错误消息
    
    // 任何会导致 revert 的操作
    myToken.transfer(address(0x123), 100);
}

4. 总结 vm.expectRevert 的关键点

  • vm.expectRevert() 是一个 Foundry 提供的测试工具,用于预期合约中的 revert 错误。
  • 它可以捕获 普通的错误消息,或者 自定义错误
  • 它通过接受 bytes memory revertData 来检查 revert 错误的数据。
    • 如果捕获的是 普通的 revert 错误,你可以直接传递错误消息字符串。
    • 如果捕获的是 自定义错误,你需要使用 abi.encodeWithSelectorabi.encodeWithSignature 来构建错误的数据。

5. 如何构建 revert 错误数据

  • 普通 revert 错误:如果 revert 只带有消息字符串,可以直接传递字符串。
    vm.expectRevert("Custom error message");
    
  • 自定义错误:如果是 Solidity 0.8.x+ 引入的自定义错误,则需要使用 abi.encodeWithSelectorabi.encodeWithSignature 来编码错误数据。
    vm.expectRevert(abi.encodeWithSelector(CustomError.selector, param1, param2));
    

6. 常见错误

  • 没有发生 revert 错误时的错误:如果你期望某个函数会触发 revert 错误,但实际上没有触发,则会导致测试失败。
  • 错误数据不匹配时的错误:如果你期望捕获某个特定的错误,但错误数据不匹配(例如,错误的错误类型或错误参数),测试会失败。

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特