Custom errors are a feature introduced in Solidity v0.8.4 that allow developers to define their own error types with descriptive names and parameters, which can then be used with the revert statement. Unlike the older require statement with a string message, custom errors are defined using the error keyword (e.g., error InsufficientBalance(uint256 available, uint256 required)). When the contract reverts using a custom error (revert InsufficientBalance(balance, amount)), only the error's selector (a 4-byte identifier) and the encoded parameter data are passed to the caller, resulting in significant gas savings compared to storing and passing dynamic string messages.
Custom Errors
What are Custom Errors?
A gas-efficient and expressive mechanism for defining and throwing application-specific error conditions in Solidity smart contracts.
The primary advantage of custom errors is their efficiency. Deploying a contract with string error messages stores those strings permanently on-chain, incurring a one-time deployment cost and a runtime cost every time they are used. Custom errors, however, are defined only by their signature in the contract's ABI; the descriptive name and parameter structure are known off-chain by tools like Etherscan, which can decode and display them. This makes them a best practice for gas optimization without sacrificing debuggability for end-users and developers interacting with the contract.
From a development perspective, custom errors enhance code clarity and type safety. They function similarly to custom exception types in other languages, allowing for more structured error handling. Developers can define multiple specific errors (e.g., Unauthorized, InvalidDeadline, TransferFailed) and catch them off-chain using the contract's ABI. This is a marked improvement over parsing generic revert strings. Furthermore, because they are a compile-time feature, using an undefined error will cause a compilation error, preventing runtime typos.
A key technical detail is how custom errors interact with the Ethereum Virtual Machine (EVM). When a revert with a custom error occurs, the EVM uses the REVERT opcode, passing the ABI-encoded data of the error. This data is returned to the caller and can be decoded if the error's signature is known. This mechanism is also foundational for more advanced patterns, such as facilitating ERC-4337 Account Abstraction validation or creating complex, nested error conditions that can be cleanly propagated and inspected by off-chain infrastructure and user interfaces.
How Custom Errors Work
Custom errors are a gas-efficient Solidity feature introduced in v0.8.4 for defining and throwing revert conditions with descriptive data.
A custom error is a user-defined type in Solidity that provides a structured, low-cost alternative to the require statement with a string message. Defined with the error keyword, it allows developers to revert transactions while passing arbitrary data, such as an error identifier and relevant parameters, which is then ABI-encoded and returned to the caller. This mechanism is significantly more gas-efficient than legacy string messages because it avoids the cost of storing and hashing long strings on-chain.
The primary advantage of custom errors is their gas optimization. When a transaction reverts, only the 4-byte selector of the error and the encoded arguments are passed in the revert data, unlike string messages which require expensive storage. For example, error InsufficientBalance(uint256 available, uint256 required); defines an error that can be called with revert InsufficientBalance(balance, amount). This provides clear, machine-readable failure information to off-chain clients without incurring the high gas overhead of a descriptive string.
To use a custom error, you must first declare it, typically at the contract level or in a file-level error statement. It is then invoked using the revert statement. Off-chain applications can decode the revert data by matching the error's signature, allowing for precise error handling in front-ends and indexers. This creates a clean contract interface where failure modes are explicitly defined, improving code auditability and integration. Common use cases include input validation, access control checks, and state condition enforcement where specific failure data is valuable.
Key Features & Benefits
Custom errors are a Solidity feature that allows developers to define and use their own error types, providing a gas-efficient and descriptive way to revert transactions.
Gas Efficiency
Using revert with a custom error consumes significantly less gas than the older require statement with a string message. This is because the error type is encoded as a 4-byte selector, while strings are ABI-encoded and stored on-chain. For example, revert InsufficientBalance() is cheaper than require(balance >= amount, "Insufficient balance").
Named Parameters & Data
Custom errors can accept parameters, allowing developers to pass dynamic data about the failure. This provides more context for off-chain debugging and frontend applications. For example:
error InsufficientBalance(address user, uint256 available, uint256 required);revert InsufficientBalance(msg.sender, balance, amount);The error data is ABI-encoded and can be decoded by clients.
Improved Debugging
By using distinct, descriptive error names, developers and users can quickly identify the exact cause of a transaction failure. This is a major improvement over generic revert strings. Tools like block explorers and development frameworks can parse and display these named errors, making contract interaction and auditing more transparent.
Contract Interface Clarity
Declaring custom errors at the contract level serves as formal documentation for the specific failure conditions the contract can encounter. This makes the contract's ABI more informative, as the possible error signatures are part of the interface. It helps other developers understand the expected behavior and failure modes without scanning the entire codebase.
Comparison: Error vs. Require
Custom Error (Solidity >=0.8.4):
error Unauthorized();...revert Unauthorized();- Lower gas cost, can include parameters, defined in contract.
Require with String:
require(msg.sender == owner, "Unauthorized");- Higher gas cost, static string, no structured data.
Custom errors are the modern, recommended pattern for all new development.
Integration with Try/Catch
Custom errors are fully compatible with Solidity's try/catch statement. This allows a calling contract to gracefully handle specific failures from an external call. You can catch a custom error by its type, enabling more robust and granular error handling logic within smart contract systems.
Example: try otherContract.action() catch (InsufficientBalance memory err) { ... }
Custom Errors vs. Revert Strings: A Comparison
A technical comparison of the two primary methods for reverting transactions and providing failure information in Solidity smart contracts.
| Feature | Custom Error (Solidity >=0.8.4) | Revert String (require/revert) | Legacy Assert |
|---|---|---|---|
Gas Cost on Revert | ~100-200 gas (deployment) | ~20k+ gas (deployment & execution) | All gas consumed |
Contract Bytecode Size | Minimal increase | Significant increase per string | Minimal increase |
Error Information | Named error type + encoded parameters | Human-readable string message | Panic code (0x01, 0x11, etc.) |
Parameterization | |||
Off-chain Decoding | |||
Typed Error Handling | |||
Recommended Use Case | All production reverts | Simple prototypes, debugging | Internal invariants only |
Ecosystem Usage & Adoption
Custom errors are a Solidity feature (introduced in v0.8.4) that allows developers to define and revert with gas-efficient, descriptive error types, replacing generic require statements with string errors.
Core Definition & Syntax
A custom error is a user-defined type declared with the error keyword, used with the revert statement. It provides a structured, low-cost alternative to require(condition, "description").
- Declaration:
error InsufficientBalance(uint256 available, uint256 required); - Usage:
if (balance < amount) revert InsufficientBalance(balance, amount); - Gas Efficiency: Consumes significantly less gas than equivalent string error messages, as the error signature (4-byte selector) is stored on-chain, not the full string.
Primary Use Cases & Benefits
Custom errors are deployed for input validation, state checks, and access control where clarity and gas optimization are critical.
- Gas Savings: The primary driver for adoption; reverting with a custom error costs ~90% less gas than an equivalent error string.
- Improved Debugging: Provides structured data (parameters) in transaction revert traces, making failures easier to diagnose off-chain.
- Contract Upgrades: Error types can be added in later versions without breaking existing integrations that only check for revert conditions.
Adoption in Major Protocols
Leading DeFi and infrastructure projects have widely adopted custom errors for core contract logic.
- Uniswap V4: Uses custom errors like
InvalidPool,LockFailure, andCurrencyNotSettledthroughout its hook and pool management system. - OpenZeppelin Contracts: Libraries like
@openzeppelin/contractsuse errors such asERC20InsufficientBalanceandOwnableUnauthorizedAccount. - Compound Finance & Aave: Implement errors for interest rate model calculations and liquidity checks to minimize gas costs for users.
Developer Tooling & Standards
Ecosystem tooling has evolved to parse and display custom errors for developers and end-users.
- Ethers.js & Viem: These libraries decode custom error selectors and parameters from failed transactions, presenting them in human-readable form.
- Hardhat & Foundry: Testing frameworks have built-in support for expecting specific custom error reverts in unit tests (e.g.,
vm.expectRevert). - Block Explorers: Etherscan and others decode and display custom error names and arguments in transaction receipts.
Comparison to Require/Assert
Custom errors differ fundamentally from older Solidity error-handling methods.
- vs.
require(condition, "string"): Custom errors are cheaper on deployment and runtime because the string data is not stored in bytecode. They also allow parameterized data. - vs.
assert(condition):assertis for internal invariants and consumes all remaining gas on failure, while custom errors (used withrevert) refund remaining gas. - Error Signature: A custom error is identified by its 4-byte selector (first 4 bytes of
keccak256("ErrorName(type)")), which is logged in the transaction receipt.
Best Practices & Security Notes
Effective use of custom errors involves careful naming and parameter design.
- Descriptive Naming: Use names like
Unauthorized,InvalidInput, orOverflowthat clearly indicate the failure mode. - Parameter Selection: Include relevant state variables or arguments (e.g.,
uint256 actualValue,address caller) to aid off-chain debugging. - Security Consideration: The error type and parameters are not stored on-chain after the transaction; they are only emitted in the revert reason. Do not rely on them for on-chain logic.
- Backwards Compatibility: Interfaces or abstract contracts should declare expected custom errors to guide integrators.
Security Considerations & Best Practices
Custom errors in Solidity are a gas-efficient way to provide detailed revert reasons, but their implementation requires careful planning to avoid security pitfalls and ensure effective debugging.
Gas Efficiency vs. Debugging Clarity
While custom errors save significant gas compared to require statements with string messages, they can obscure debugging. Off-chain tools must map the 4-byte selector (e.g., 0x12345678) back to the error name. Ensure your development and monitoring stack supports this mapping for effective incident response.
- Gas Savings: Reverts with custom errors cost ~~50-100 gas, versus ~20,000+ gas for revert strings.
- Tooling Dependency: Debugging requires the contract's ABI or source code to decode the error.
Avoid Information Leakage
Custom errors can inadvertently expose sensitive contract state or business logic. The error parameters are visible on-chain in the revert data.
- Example Risk: Reverting with
InsufficientBalance(userBalance, requiredAmount)publicly reveals a user's balance. - Best Practice: Use generic error types for access control failures (e.g.,
Unauthorized()) and avoid passing private data as parameters.
Consistent Error Hierarchy
Define a clear and consistent error hierarchy to prevent ambiguity and improve maintainability. Group related errors within libraries or inherited contracts.
- Library Pattern: Define common errors in a base library (e.g.,
Errors.sol) usingerror InsufficientAllowance();. - Inheritance: Child contracts can use parent contract errors, ensuring uniform error signatures across the system.
- Naming Convention: Use descriptive, verb-noun names like
TransferFailed,InvalidSignature.
Testing and Validation
Thoroughly test custom error reverts. Since they do not return strings, standard assertion methods in testing frameworks need to be adapted to check for specific error selectors.
- Foundry Example: Use
vm.expectRevert(MyContract.CustomError.selector);. - Hardhat Example: Catch the revert and decode the data using the contract interface.
- Coverage: Ensure test suites validate all possible error conditions and that the correct error type is thrown.
Interface and Upgrade Compatibility
Adding, removing, or changing custom errors is a breaking change for interfaces and upgrades. The 4-byte selector is derived from the error name and parameter types.
- Breaking Change: Modifying an error's signature will change its selector, breaking off-chain integrations that listen for it.
- Upgrade Strategy: For upgradeable contracts, consider errors as part of the public API. New errors should be additive where possible.
Frontend and Monitoring Integration
Ensure your application frontend and monitoring services (like Chainscore) can properly interpret custom errors. This requires the ABI to be available for decoding transaction revert reasons.
- User Feedback: Map error selectors to user-friendly messages in your UI.
- Analytics: Monitoring dashboards should decode and categorize failures by error type to track the health of specific contract functions.
- Documentation: Publicly document all custom errors and their meanings for integrators.
Common Misconceptions About Custom Errors
Custom errors are a powerful Solidity feature for efficient and expressive error handling, but several persistent myths can lead to suboptimal or insecure implementations. This section debunks the most common misunderstandings.
While custom errors are significantly more gas-efficient than require with a string message, they are not a simple drop-in replacement. A custom error is a distinct revert type that allows you to pass structured data, enabling off-chain tools to decode the precise failure reason programmatically. This is a qualitative improvement in developer experience and debugging, not just a quantitative gas saving. For example, revert InsufficientBalance(balance, amountRequired); provides actionable data, whereas require(balance >= amountRequired, "Insufficient balance"); provides only a static string.
Technical Deep Dive
Custom errors are a low-level, gas-efficient way for smart contracts to revert with descriptive failure information, replacing the older `require` and `revert` statements with string messages.
Custom errors are a Solidity feature (introduced in v0.8.4) that allow developers to define and revert with custom, named error types instead of using string descriptions. They work by defining an error with the error keyword, which can include parameters, and then using the revert statement to trigger it. For example, error InsufficientBalance(uint256 available, uint256 required); can be reverted with revert InsufficientBalance(balance, amount);. This is more gas-efficient than string messages because the error signature and its arguments are encoded using ABI encoding (abi.encodeWithSignature) and only the 4-byte selector and encoded arguments are stored on-chain, avoiding the cost of storing lengthy strings.
Frequently Asked Questions (FAQ)
Custom errors are a Solidity feature that allows developers to define and use their own, more efficient and informative error types. This section answers the most common questions about their implementation, benefits, and best practices.
Custom errors are a Solidity feature, introduced in version 0.8.4, that allow developers to define their own error types with descriptive names and optional parameters to provide more informative and gas-efficient failure conditions than the older require statement with a string message. They are declared using the error keyword and are used with the revert statement. For example, error InsufficientBalance(uint256 available, uint256 required); defines a custom error that can be reverted with revert InsufficientBalance(balance, amount);, providing clear, structured data about the failure.
Get In Touch
today.
Our experts will offer a free quote and a 30min call to discuss your project.