OpenZeppelin / OpenZeppelin/openzeppelin-contracts

Split out custom errors into separate interface

Open
#4,807 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Solidity
Stars
27.2k
Forks
12.4k
Avg merge
2d 19h
Merged PRs (30d)
33

Description

🧐 Motivation
We have been using custom errors for over a year in our contracts and have found some beneficial usage patterns. The most important is splitting out just the custom errors into their own interface so they can be re-used by final derived contracts.

With revert statements with strings, the errors were self-describing and did not need to be added to a contract's interface. Since custom errors have to be decoded by a user, all possible errors have to be known in advance. Some approaches that have not worked:

At the end of the day it's up to the writers of the final contracts to ensure they accurately reflect what errors can be raised so users can effectively decode them. By splitting out just errors into their own interface, it is now possible for the final contract to inherit all the error interfaces for all direct and transitive dependencies. It is manual, but better than not specifying anything and leaving users of the final contract in the dark.

📝 Details

As an abstract example:

// Before
interface IFoo {
  error FooError(uint256 foo);

  function foo() external;
}

contract Foo is IFoo { };

contract FooCaller {
  Foo private foo;

  function useFoo() external {
    foo.foo(); // can revert with FooError, but FooCaller does not include it in its interface
  }
};
// After
interface IFooErrors {
  error FooError(uint256 foo);
}

interface IFoo is IFooErrors {
  function foo() external;
}

contract Foo is IFoo { };

contract FooCaller is IFooErrors {
  Foo private foo;

  function useFoo() external {
    foo.foo(); // FooError is part of the interface
  }
};

As you can see FooCaller's ABI accurately reflects the fact that FooError can be raised by calling the contract's functions. For OpenZeppelin, it should be a backward compatible change. The errors can be extracted out into their own interfaces and the main interfaces inherit from the error interface. The interfaceId for ERC165 detection should also not be affected, since errors do not affect the interfaceId.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating OpenZeppelin interfaces that declare custom errors and review how their inheritance and ABI are tested. Extract errors into dedicated interfaces, have the existing interfaces inherit them, and verify that ABI error exposure remains accurate without changing ERC165 interface IDs.

Written by the indexing model from the issue text.

Assessment

Tech stack
solidity
Domain
blockchain
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.