Why is call reverting when decoding custom error

159 Views Asked by At

I have a contract that calls another contract using a try-catch, which reverts with a custom error. I am decoding the error reason bytes using abi.decode, but it is reverting when I try to decode.

This is using solidity 0.8.4 in remix.

The code is:

pragma solidity = 0.8.4;

error MyCustomError(uint256 value1, uint256 value2);

contract Reverter {
    function revertMe(uint256 valueA, uint256 valueB) public {
        revert MyCustomError(valueA, valueB);
    }
}

contract RevertCaller {
    Reverter reverter;
    event Revert(uint256 indexed value1, uint256 indexed value2);
    event LogBytes(bytes);
    
    constructor() {
        reverter = new Reverter();
    }

    function callRevert(uint256 valueA, uint256 valueB) public {
        try reverter.revertMe(valueA, valueB) {

        } catch (bytes memory reason) {
            emit Revert(valueA, valueB);
            emit LogBytes(reason);        

            (bytes4 errorSelector, uint256 value1, uint256 value2) = abi.decode(reason, (bytes4, uint256, uint256));

            if (errorSelector == MyCustomError.selector) {
                // Handle invalid amount error
                emit Revert(value1, value2);
            } else {
                emit Revert(0, 0);
            }
        }
    }
}

If I comment out the abi.decode line and pass some other values into the event, it does not revert. Also it works to only decoide the selector like this:

(bytes4 errorSelector) = abi.decode(reason, (bytes4));

but then I dont get the error args that I want to use.

The logged reason (using LogBytes) is 0x64f2b82800000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005

Maybe I am using abi.decode incorrectly?

1

There are 1 best solutions below

0
On

You are indeed using abi.decode incorrectly.

First 4 bytes of the error are treated the same way as the function selectors, and their encoding does not follow ABI specification. According to the ABI spec bytes4 values should be padded with zeroes up to full 32 bytes. This is why abi.decode(reason, (bytes4)) works, because of the following zeroes. However when you try to decode full bytes, there is not enough padding in the data for the decoder and it just reverts the transaction.

Unfortunately there is still no good way to do custom error catching in solidity.

The following assembly code will achieve desired error matching behaviour:

callRevert(uint256 valueA, uint256 valueB) public {
    try reverter.revertMe(valueA, valueB) {
    } catch (bytes memory reason) {
        emit Revert(valueA, valueB);
        emit LogBytes(reason);        

        bytes4 errorSelector = bytes4(reason);
        uint256 value1;
        uint256 value2;

        assembly ("memory-safe") {
            value1 := mload(add(reason, 0x24))
            value2 := mload(add(reason, 0x44))
        }

        if (errorSelector == MyCustomError.selector) {
            // Handle invalid amount error
            emit Revert(value1, value2);
        } else {
            emit Revert(0, 0);
        }
    }
}

For bytes4 errorSelector = bytes4(reason) casting to work you'll need to update to solidity 0.8.5.