ERC-6093 Common custom error standard for tokens (Custom errors for commonly-used tokens)
Final · Tokens · created 2022-12-06
Defines a common format for the errors that ERC-20, ERC-721, and ERC-1155 tokens use when they fail. Wallets and apps can interpret the cause of a failure as structured data and show it to the user.
Why it is needed
Ethereum apps and wallets have long relied on revert reason strings to show users why a transaction failed. Since Solidity 0.8.4, custom errors have made it possible to interpret the cause by type, but the leading token standards ERC-20, ERC-721, and ERC-1155 were finalized earlier and contain no errors in their specifications. As a result, error names and contents differ from token to token, and it is hard for wallets and test environments to interpret failure causes consistently. Long strings in deployed code also raise deployment costs. This standard organizes the failures that commonly occur in the three token standards into a structured, common list of errors, so that they provide at least the same information as the existing strings in a more consistent and predictable form.
What to implement
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);: The error for when the sender's balance is less than the needed amount. It is used in transfers.error ERC20InvalidSender(address sender);: The error for when the sending address itself is invalid in a transfer. It is recommended for prohibiting transfers out of the zero address.error ERC20InvalidReceiver(address receiver);: The error for when the receiving address is invalid in a transfer. It is recommended for prohibiting transfers to the zero address or to an incompatible address.error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);: The error for when the delegated spending limit (allowance) is less than the needed amount. It is used in transfers.error ERC20InvalidApprover(address approver);: The error for when the approving address is invalid. It is recommended for prohibiting approvals from the zero address.error ERC20InvalidSpender(address spender);: The error for when the address being approved is invalid. It is recommended for prohibiting approvals to the zero address or to the owner itself.error ERC721InvalidOwner(address owner);: The error for an address that cannot be an owner. It is used in balance queries.error ERC721NonexistentToken(uint256 tokenId);: The error for a token whose owner is the zero address, that is, a tokenId that has not been minted or has been burned.error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);: The error about ownership of a specific token. It is used in transfers when the sender is not the actual owner.error ERC721InvalidSender(address sender);: The error for when the sending address is invalid in a transfer. It is recommended for prohibiting transfers out of the zero address.error ERC721InvalidReceiver(address receiver);: The error for when the receiving address is invalid in a transfer. It is recommended for prohibiting transfers to the zero address or to a contract that lacks the receiver interface.error ERC721InsufficientApproval(address operator, uint256 tokenId);: The error for when the operator has no approval for the token in a transfer.error ERC721InvalidApprover(address approver);: The error for when the approving address is invalid. It is recommended for prohibiting approvals from the zero address.error ERC721InvalidOperator(address operator);: The error for when the approved operator is invalid. The operator must not be the owner of the token being approved.error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);: The error for when the sender's balance for a specific tokenId is less than the needed amount. It is used in transfers.error ERC1155InvalidSender(address sender);: The error for when the sending address is invalid in a transfer. It is recommended for prohibiting transfers out of the zero address.error ERC1155InvalidReceiver(address receiver);: The error for when the receiving address is invalid in a transfer. It is recommended for prohibiting transfers to the zero address or to a contract that lacks the receiver interface.error ERC1155MissingApprovalForAll(address operator, address owner): The error for when the operator has not received full approval (isApprovedForAll) from the owner in a transfer.error ERC1155InvalidApprover(address approver);: The error for when the approving address is invalid. It is recommended for prohibiting approvals from the zero address.error ERC1155InvalidOperator(address operator);: The error for when the approved operator is invalid. This error MUST be used to prohibit approvals to the owner itself.error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);: The error for when the ids array and the values array in safeBatchTransferFrom have different lengths.
In depth
Error name grammar: domain, prefix, subject
Every error name follows the grammar <Domain><ErrorPrefix><Subject>(<Arguments>). Domain is a standard name such as ERC20, ERC721, or ERC1155, and ErrorPrefix indicates the reason for the failure, such as Invalid (a general error) or Insufficient (a shortage of quantity). Subject is the party that failed, such as Sender, Receiver, Balance, Approver, Operator, or Approval. Actions are of two kinds, Transfer and Approval. An action itself is not the subject of an error. An error is built by combining which subject failed during which action.
Why a domain prefix is needed
Error arguments differ by token type. If errors with the same name and different arguments exist, the Solidity compiler fails with a DeclarationError. For example, InsufficientApproval(address spender, uint256 allowance, uint256 needed) and InsufficientApproval(address operator, uint256 tokenId) cannot coexist. So a domain prefix made of the ERC name and number (ERC20InsufficientApproval, ERC721InsufficientApproval) avoids declaration conflicts.
Argument order: who, what, why
Arguments follow a fixed order. First comes the subject of the error (for example, address sender), next what failed (for example, uint256 allowance), and last why it failed as an additional argument (for example, uint256 needed). One argument can overlap several categories, so not all three are always present, but the order must be kept. For tokens that need a tokenId, it is recommended to put it at the end as additional information rather than as the subject. When adding implementation-specific errors such as extensions, this guideline SHOULD also be followed for consistency.
Nature of the usage guidance: whether to revert is up to the implementer
This standard only defines standard errors that can be used in specific situations. It does not decide whether to revert in those situations. Unless the relevant token EIP enforces it, whether to revert is up to the implementer. However, each error has rules that must be followed. For example, ERC20InsufficientBalance requires that balance be less than needed (MUST), and ERC20InvalidSender MUST NOT be used for approval operations or for balance and allowance requirements, in which case ERC20InsufficientBalance or ERC20InsufficientAllowance is used. Argument names MUST be used as defined in the Parameter Glossary (MUST).
Error composition by standard
ERC-20 defines 6 errors in total: 4 for transfers (InsufficientBalance, InvalidSender, InvalidReceiver, InsufficientAllowance) and 2 for approvals (InvalidApprover, InvalidSpender). ERC-721 defines 8 errors centered on ownership. They include InvalidOwner for balance queries, NonexistentToken for unminted or burned tokens, IncorrectOwner for when the sender is not the owner, and InsufficientApproval for missing approval. ERC-1155 defines 7 errors. Balance errors carry a tokenId, and it adds MissingApprovalForAll for the absence of a blanket approval and InvalidArrayLength for mismatched array lengths in batch transfers.
Compatibility with existing deployed tokens
Most already deployed tokens use require with revert strings, and many tokens deployed after Solidity 0.8.4 also inherit string-based implementations. This standard cannot be forced on existing tokens that cannot be upgraded. They use similar conventions with small variations, such as having or lacking a domain, a different prefix, a similar subject, or a different word order. Upgradeable contracts MAY be upgraded to implement this standard. Implementers and dapp developers who support this standard SHOULD also accept the other errors that non-conforming contracts emit and the classic revert strings.
Things to watch out for
- Signature hash collisions: There are no known signature hash collisions among the specified errors.
- Conflicts with string-dependent systems: A token upgraded to implement this standard may break the assumptions of other systems that depend on revert strings.
- Abuse of errors from untrusted contracts: Off-chain apps must be careful with untrusted contracts that can revert with these custom errors. If a user interface prompts actions based on the decoded error, a malicious contract could exploit this to push users toward untrusted and potentially harmful operations.