Skip to content

expectRevert

function expectRevert() external;
function expectRevert(bytes4 revertData) external;
function expectRevert(bytes4 revertData, address reverter) external;
function expectRevert(bytes4 revertData, uint64 count) external;
function expectRevert(
bytes4 revertData,
address reverter,
uint64 count
) external;
function expectRevert(bytes calldata revertData) external;
function expectRevert(bytes calldata revertData, address reverter) external;
function expectRevert(bytes calldata revertData, uint64 count) external;
function expectRevert(
bytes calldata revertData,
address reverter,
uint64 count
) external;
function expectRevert(address reverter) external;
function expectRevert(uint64 count) external;
function expectRevert(address reverter, uint64 count) external;
function expectPartialRevert(bytes4 revertData) external;
function expectPartialRevert(bytes4 revertData, address reverter) external;

To understand why you are getting this error you first have to understand what call depth means.

You can think of call depth in a similar way to function scoping. When you are entering an external call the call depth is increased by 1. When you exit the external call the call depth is decreased by 1. If you have nested calls it looks as follows:

0 → Contract A (calls B) → 1 → Contract B (calls C) → 2 → Contract C (returns) → 1 → Contract B (returns) → 0

Internal functions on the other hand do NOT increase the call depth. It is not actually making calls but rather jumping to the target location.

When testing internal functions with vm.expectRevert at the same call depth ONLY the FIRST vm.expectRevert is executed.

The following example shows where the footgun occurs. There are two vm.expectRevert’s that exist at the same call depth hence only the FIRST one is executed and the test returns a SUCCESS. This is likely different behavior from what you may assume.

// DO NOT IMPLEMENT AS FOLLOWS! THIS IS AN INCORRECT USE.
function testMultipleReverts() public {
vm.expectRevert();
revert();
vm.expectRevert();
console2.log("Does not revert");
}

If the next call does not revert with the expected data message, then expectRevert will.

As long as you are not using vm.expectRevert on multiple internal functions in a single test function body it is generally considered safe.

You are recommended to apply this rule in a similar manner one would when tagging assembly blocks as memory-safe.

After calling expectRevert, calls to other cheatcodes before the reverting call are ignored.

This means, for example, we can call prank immediately before the reverting call.

There are several signatures for expectRevert:

  • Without parameters: Asserts that the next call reverts, regardless of the message.
  • With bytes4 message: Asserts that the next call reverts with the specified 4 bytes and exact match of revert data.
  • With bytes message: Asserts that the next call reverts with the specified bytes.
  • With address reverter: Asserts that the next call is reverted by the specified address.
  • With uint64 count: Expects an exact number of reverts from the upcoming calls. If set to 0, it can be used to assert that a revert is not made.

and two signatures for expectPartialRevert:

  • bytes4 message: Asserts that the next call reverts and the specified 4 bytes match the first 4 bytes of revert data.
  • bytes4 message and reverter address: Asserts that the next call is reverted by specified address and the specified 4 bytes match the first 4 bytes of revert data.

To use expectRevert with a string, pass it as a string literal.

vm.expectRevert("error message");

To use expectRevert with a custom error type without parameters, use its selector.

vm.expectRevert(CustomError.selector);

To use expectRevert with a custom error type with parameters, ABI encode the error type.

vm.expectRevert(
abi.encodeWithSelector(CustomError.selector, 1, 2)
);

If you need to assert that a function reverts without a message, you can do so with expectRevert(bytes("")).

function testExpectRevertNoReason() public {
Reverter reverter = new Reverter();
vm.expectRevert(bytes(""));
reverter.revertWithoutReason();
}

Message-less reverts happen when there is an EVM error, such as when the transaction consumes more than the block’s gas limit.

If you need to assert that a function reverts a four character message, e.g. AAAA, you can do so with:

function testFourLetterMessage() public {
vm.expectRevert(bytes("AAAA"));
}

If used expectRevert("AAAA"), the compiler would throw an error because it wouldn’t know which overload to use.

Finally, you can also have multiple expectRevert() checks in a single test.

function testMultipleExpectReverts() public {
vm.expectRevert("INVALID_AMOUNT");
vault.send(user, 0);
vm.expectRevert("INVALID_ADDRESS");
vault.send(address(0), 200);
}

To use expectPartialRevert with a custom error type, use its selector.

vm.expectPartialRevert(CustomError.selector);

Forge Standard Library

Std Errors