ERC-7751 Nested revert error wrapping (Wrapping of bubbled up reverts)

Final · Other · created 2024-08-06

A standard that, when a contract call to another contract fails, wraps the failure in a standard error carrying context about where and why it failed, instead of rethrowing the failure reason as is.

Why it is needed

Today, when contract A calls contract B and B fails (reverts), the failure reason is usually passed up and thrown unchanged. But when several contracts are called in a chain within one transaction, it is hard to tell from the received error alone which contract and what context it came from. Developers struggle to trace where the problem occurred, and services such as Etherscan find it hard to show the exact call path and failure point. This standard unifies how errors are wrapped and passed along into a single custom error format, so that the failing contract address and function, the original error content, and additional explanation are carried upward together. As a result, error messages become more meaningful and the debugging experience improves.

What to implement

In depth

Unifying the wrapping method with a single WrappedError

A contract that wraps an error and bubbles it up MUST revert with a custom error of the form error WrappedError(address target, bytes4 selector, bytes reason, bytes details). The signature of this error is 0x90bfb865. Because the format is fixed, tools can parse an error thrown by any contract in the same way.

Meaning and rules of the four fields

target is the address of the callee contract that reverted. selector is the selector of the callee function that reverted, and it MUST be bytes4(0) if the call was an ETH transfer with no data. reason holds the raw bytes of the revert reason that was received, unchanged. details is optional extra context and may be left as empty bytes if it is not needed. When extra context is included, it must be an ABI encoding of a custom error declared in the contract that throws the WrappedError.

Tracing nested reverts recursively

Once the wrapping method is standardized, errors can be nested inside other errors. Reverts thrown by several contracts can be followed by unwrapping them recursively, starting from the outermost WrappedError. Because the called contract and function, the raw revert bytes, and the extra context are all included, more detailed information about the failure can be provided, and tools such as Etherscan and Foundry can parse it to improve readability and debugging convenience.

Three-level nesting shown by the test case

In the test case, a Router calls a Vault, and the Vault calls a Token. When the Token's transfer reverts with "insufficient balance", the Vault throws a WrappedError containing the Token address, the transfer selector, the original error, and the context encoded with abi.encodeWithSelector(ERC20TransferFailed.selector, to). The Router then wraps this again with the Vault address, the withdraw selector, the entire Vault error, and empty details. Unwrapping the final error restores all three levels of the failure path.

Backwards compatibility and standard status

This standard causes no backward compatibility problems, and existing contracts can adopt it gradually. As in the reference implementation, a contract only needs to catch the callee's revert and then revert again with a WrappedError that follows the rules above. The status of the original document is Final, meaning the content of the standard is settled.

Things to watch out for

Original ERC · ← ERCs