4

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

2
  • Did you try catching the error InsufficientAmountOut?
    – Ismael
    Apr 1, 2022 at 23:57
  • Hello, I have tried that but it seems solidity 0.8.10 syntax simply doesn't allow that. This is the error message: TypeError: Invalid catch clause name. Expected either catch (...), catch Error(...), or catch Panic(...)..
    – Jan Beneš
    Apr 2, 2022 at 18:28

1 Answer 1

6

SOLUTION 1:

Best solution is using expectRevert cheat code from forge-std:

vm.expectRevert(ExampleBridgeContract.InvalidCaller.selector);
exampleBridge.convert(empty, empty, empty, empty, 0, 0, 0, address(0));

If you are using an error with parameters pass calldata instead of selector to the vm.expectRevert(...) method:

vm.expectRevert(abi.encodeWithSignature("IncorrectStatus(uint8,uint8)", 1, 0));

SOLUTION 2:

    try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
        assertTrue(false, "swap(..) should revert when amountOut <  amountIn.");
    } catch (bytes memory reason) {
        bytes4 desiredSelector = bytes4(keccak256(bytes("InsufficientAmountOut(uint256,uint256)")));
        bytes4 receivedSelector = bytes4(reason);
        assertEq(desiredSelector, receivedSelector);
    }

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 convert bytes memory reason to bytes4 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:

    try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
        assertTrue(false, "swap(..) should revert when amountOut <  amountIn.");
    } catch (bytes memory reason) {
        bytes4 expectedSelector = PathRegistry.InsufficientAmountOut.selector;
        bytes4 receivedSelector = bytes4(reason);
        assertEq(expectedSelector, receivedSelector);
    }
2
  • 1
    Nice work! The documentations about try/catch has some details. If you have the error defined as error InsufficientAmountOut(uint256,uint256) then you could use InsufficientAmountOut.selector. To decode the parameters you could use abi.decode after skipping the first 4 bytes.
    – Ismael
    Apr 3, 2022 at 5:11
  • Getting the selector from the error definition looks a bit less "hacky". Thanks Ismael
    – Jan Beneš
    Apr 4, 2022 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.