Skip to content

Commit

Permalink
Add ERC: Wrapping of bubbled up reverts
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
gretzke authored Sep 29, 2024
1 parent 0fc375d commit eeeeb5f
Showing 1 changed file with 143 additions and 0 deletions.
143 changes: 143 additions & 0 deletions ERCS/erc-7751.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
eip: 7751
title: Wrapping of bubbled up reverts
description: Handling bubbled up reverts using custom errors with additional context
author: Daniel Gretzke (@gretzke), Sara Reynolds (@snreynolds), Alice Henshaw (@hensha256), Marko Veniger <[email protected]>, Hadrien Croubois (@Amxx)
discussions-to: https://ethereum-magicians.org/t/erc-7751-wrapping-of-bubbled-up-reverts/20740
status: Draft
type: Standards Track
category: ERC
created: 2024-08-06
---

## Abstract

This ERC proposes a standard for handling bubbled up reverts in Ethereum smart contracts using custom errors. This standard aims to improve the clarity and usability of revert reasons by allowing additional context to be passed alongside the raw bytes of the bubbled up revert. The custom errors should follow the naming structure `Wrap__` followed by a descriptive name and allow an arbitrary number of parameters, with the first being the address of the called contract and the second being the raw bytes of the bubbled up revert.

## Motivation

Currently, when a smart contract calls another and the called contract reverts, the revert reason is usually bubbled up and thrown as is. This can make it more difficult to tell which context the error came from. By standardizing the use of custom errors with additional context, more meaningful and informative revert reasons can be provided. This will improve the debugging experience and make it easier for developers and infrastructure providers like Etherscan to display accurate stack traces.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

1. **Custom Error Naming Convention:**
- Custom errors that bubble up reverts MUST be prefixed with `Wrap__`.
- Example: `Wrap__ERC20TransferFailed`.
2. **Parameters:**
- The first parameter MUST be the address of the called contract that reverted.
- The second parameter MUST be the raw bytes of the bubbled up revert.
- Additional parameters MAY be included to provide context.
- Example: `Wrap__ERC20TransferFailed(address token, bytes reason, address recipient)`.

If no additional parameters are defined and a generic call reverts, the custom error `Wrap__SubcontextReverted(address, bytes)` SHOULD be thrown to ensure a consistent signature.

## Rationale

By including the called contract, raw revert bytes and additional context, developers can provide more detailed information about the failure. Additionally, by standardizing the way reverts are bubbled up, it also enables nested bubbled up reverts where multiple reverts thrown by different contracts can be followed recursively. The reverts can also be parsed and handled by tools like Etherscan and Foundry to further enhance the readability and debuggability of smart contract interactions, as well as facilitating better error handling practices in general.

## Backwards Compatibility

This ERC does not introduce any backwards incompatibilities. Existing contracts can adopt this standard incrementally.

## Test Cases

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract Token {
mapping(address => uint256) public balanceOf;
event Transfer(address indexed sender, address indexed recipient, uint amount);
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
contract Vault {
Token token;
error Wrap__ERC20TransferFailed(address token, bytes reason, address recipient);
constructor(Token token_) {
token = token_;
}
function withdraw(address to, uint256 amount) external {
// logic
try token.transfer(to, amount) {} catch (bytes memory error) {
revert Wrap__ERC20TransferFailed(address(token), error, to);
}
}
}
contract Router {
Vault vault;
error Wrap__SubcontextReverted(address target, bytes reason);
constructor(Vault vault_) {
vault = vault_;
}
function withdraw(uint256 amount) external {
// logic
try vault.withdraw(msg.sender, amount) {} catch (bytes memory error) {
revert Wrap__SubcontextReverted(address(vault), error);
}
}
}
contract Test {
function test_BubbledNestedReverts(uint256 amount) external {
Token token = new Token();
Vault vault = new Vault(token);
Router router = new Router(vault);
try router.withdraw(amount) {} catch (bytes memory thrownError) {
bytes memory expectedError = abi.encodeWithSelector(
Router.Wrap__SubcontextReverted.selector, address(vault), abi.encodeWithSelector(
Vault.Wrap__ERC20TransferFailed.selector,
address(token),
abi.encodeWithSignature("Error(string)", "insufficient balance"),
address(this)
)
);
assert(keccak256(thrownError) == keccak256(expectedError));
}
}
}
```

## Reference Implementation

When catching a revert from a called contract, the calling contract should revert with a custom error following the above conventions.

```solidity
contract Foo {
error Wrap__SubcontextReverted(address target, bytes reason);
function foo(address to, bytes memory data) external {
// logic
(bool success, bytes memory returnData) = to.call(data);
if (!success) {
revert Wrap__SubcontextReverted(to, returnData);
}
}
}
```

## Security Considerations

This EIP does not introduce new security risks.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

0 comments on commit eeeeb5f

Please sign in to comment.