12

Solidity v0.8.4 introduced custom errors:

error Unauthorized();

Say I have contract A which delegate calls to contract B. The latter reverts with the custom error defined above. How can I bubble that error up in contract A?

7
  • What deos "bubble that error" mean ? Commented Sep 28, 2021 at 7:13
  • 1
    Maybe I'm misunderstanding something. This is from the link you posted: "The error data by default bubbles up through the chain of external calls, which means that a contract may forward an error not defined in any of the contracts it calls directly. Furthermore, any contract can fake any error by returning data that matches an error signature, even if the error is not defined anywhere." Commented Sep 28, 2021 at 8:53
  • Hey @LauriPeltonen, that's a good point. I updated the question title to be more specific. The question is particularly about the case when using DELEGATECALL. Commented Sep 28, 2021 at 14:23
  • @Itération122442 contract A would revert with the same custom error as contract B, not a different error, and not a generic reason-less revert. Commented Sep 28, 2021 at 14:24
  • 2
    @Ismael Did you notice that the docs also say that try/catch doesn't apply to delegatecall? Errors only "bubble up" to be catchable for non sub-calls.
    – Shiri
    Commented Dec 13, 2021 at 10:56

2 Answers 2

16
+100

Check out this function I modified from the openzepplin's Address.sol.

function _delegatecall(address target, bytes memory data) internal returns (bytes memory) {
    (bool success, bytes memory returndata) = target.delegatecall(data);
    if (!success) {
        if (returndata.length == 0) revert();
        assembly {
            revert(add(32, returndata), mload(returndata))
        }
    }
    return returndata;
}

returndata is a dynamic-sized byte array. If the delegate call fails, returndata would be the error object raised by the failed call. Say I have contract A which delegate calls to contract B. The latter reverts with the custom error GonnaMakeIt(uint256 data). Then, the returndata would be:

0xbf42f2660000000000000000000000000000000000000000000000000000000000000539
  +------+---------------------------------------------------------------+
  selector                       uint256 data

Then, we make use of Yul's revert(p, s) to revert with the custom error in contract A. p refers to the pointer of where the error byte array starts, s refers to the how long the byte array is.

Since returndata is a dynamic-sized byte array, the first 32 bit of the pointer stores the size of it, which we mload(returndata) to retrieve it. And similarly, add(32, returndata) would be the pointer to where the byte array starts. Altogther, it forms the line revert(add(32, returndata), mload(returndata)), which reverts the error you get from contract B.

3
  • 2
    From where can you discover such an exact solution? What reference do you use? Commented Feb 27, 2022 at 5:29
  • 3
    Check openzepplin Address.sol and solidity documentation on Yul.
    – diccccn
    Commented Mar 8, 2022 at 9:38
  • It works, but if you're using custom errors and trying to get the error on a hardhat test then you have a problem, at least until github.com/nomiclabs/hardhat/issues/1618 gets fixed.
    – n3n3
    Commented Jul 29, 2022 at 12:55
1

Normally a call or a delegatecall doesn't revert while calling a function that reverts.

(bool success, bytes memory result) = to.call/delegatecall{value,gas,etc}(data).

The success variable will determine if the call was successful true or unsuccessful false. So if you call:

error Unauthorized(string reason);


function withdraw() public {
   revert Unauthorized("No Funds"); // or revert("No Funds") 
}

The call won't revert but the success bool will be false. So to revert and return the error you need extra code.

function delegate(address to,bytes memory data) public  {
     (bool success, bytes memory result) = to.delegatecall(data);
   
       if (!success) {
       if (result.length < 68) revert();
       assembly {
           result := add(result, 0x04)
        }
       revert(abi.decode(result, (string)));
       }
}

So if you execute a delegatecall/call on the withdraw function you'll get (Remix):

revert
The transaction has been reverted to the initial state.
Reason provided by the contract: "No Funds".

This works on the new type of error and the old one too.

2
  • 2
    Thanks for your answer, though I don't think it covers my question. I am not interested in getting the "No Funds" string. Custom errors are not an updated version of revert reasons - they are a different animal altogether. Thus I am interested in bubbling up the name of the custom error itself: "Unauthorized". Commented Oct 6, 2021 at 16:25
  • 1
    Sorry i don't know about that. Hope my answer will help someone else. Commented Oct 6, 2021 at 17:19

Your Answer

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

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