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?
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?
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.
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.
delegatecall
? Errors only "bubble up" to be catchable for non sub-calls.