I want to catch a custom defined error in my Foundry tests. Before I was using require and the following code worked:
try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
assertTrue(false, "swap(..) should revert when amountOut < amountIn.");
} catch Error(string memory reason) {
assertEq(reason, "INSUFFICIENT_AMOUNT_OUT");
}
After replacing this require statement
require(amountOutMin <= amountOut, "INSUFFICIENT_AMOUNT_OUT");
with
if (amountOut < amountOutMin) revert InsufficientAmountOut();
the catch execution path no longer gets executed.
I managed to get the catch part executed using the following code:
try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
assertTrue(false, "swap(..) should revert when amountOut < amountIn.");
} catch (bytes memory reason) {
emit log_string(string(reason));
}
However an issue with this code is that string(reason) is equal to �)p�
and I would like to test that specifically the InsufficientAmountOut
error got thrown.
How would I solve this?
Thank you
EDIT:
According to this post errors are ABI encode. For this reason I assume that I should catch the error with catch (bytes memory reason)
. The issue I am now facing is that I don't know how to verify that bytes memory reason
can actually be decoded as error InsufficientAmountOut()
. I found a blog post where the author explains how to decode custom errors in javascript. However I don't know how to do it in Solidity. Thanks
Best Answer
SOLUTION 1:
Best solution is using
expectRevert
cheat code from forge-std:If you are using an error with parameters pass calldata instead of selector to the
vm.expectRevert(...)
method:SOLUTION 2:
Since custom errors are encoded the same way functions are,
bytes memory reason
contains function/error selector and the encoded values. I don't want to work with the values so I simply verify the selector. To do that I convertbytes memory reason
tobytes4
type which removes all the bytes related to the encoded values and leaves only the selector in place. Then I simply compare with assertEq whether the selector really corresponds to the custom error selector which is supposed to be thrown in this test.EDIT: As @Ismael pointed out it's possible to receive the selector directly from the error definition. The code then would look like this: