Solidity Custom Errors: Cheaper Reverts Without Losing Readability
Solidity custom errors replace costly revert strings with 4-byte selectors, cutting bytecode size and making reverts easier to decode off-chain.
06 May 2026, 12:05 UTC

The problem: expensive revert strings
When a Solidity transaction reverts, the EVM returns data explaining why. Many contracts use require(condition, "Insufficient balance");. That string is stored in deployment bytecode and copied into return data on every revert. A protocol with dozens of revert paths pays for those strings in deployment cost and user gas.
The takeaway: custom errors are selectors, not sentences
Solidity 0.8.4 introduced custom errors. You declare an error type once and revert with it:
error InsufficientBalance(uint256 requested, uint256 available);
The compiler hashes the error signature and keeps the first four bytes as a selector. On revert, the contract returns that selector plus ABI-encoded arguments. The full string never appears in bytecode or revert data unless a client decodes it.
Worked example: a withdraw function
This minimal contract shows the custom-error pattern. Treat it as a template, not a measured result.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Vault {
mapping(address => uint256) public balances;
error InsufficientBalance(uint256 requested, uint256 available);
function withdraw(uint256 amount) external {
uint256 available = balances[msg.sender];
if (amount > available) {
revert InsufficientBalance(amount, available);
}
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Compile locally with:
solc --abi --bin Vault.sol
Run this in your terminal where Solidity is installed. No special permissions are required. Replace Vault.sol with your file path. Inspect the ABI output for an errors entry named InsufficientBalance. Its selector should match the first four bytes of keccak256("InsufficientBalance(uint256,uint256)"). If the ABI has no errors array, your compiler is older than 0.8.4.
Diagnostic decision: decode or stay opaque
Raw traces and explorers show only the 4-byte selector unless tooling maps it to a name. Before deploying custom errors broadly, confirm that your monitoring and alerting system can decode ABI-encoded error arguments. Verified contract ABIs often let explorers display names automatically, but off-chain systems may need the selector-to-name mapping added manually.
Trade-offs and limits
- Compiler version: custom errors require Solidity >=0.8.4.
- Argument size: error arguments are ABI-encoded. Large structs or many arguments can remove the gas advantage over a short string.
- Off-chain readability: without ABI context, a revert looks like an opaque hex code in raw traces.
To check the savings yourself, compile two otherwise identical contracts—one using require(..., "short reason") and one using a custom error—and compare deployment bytecode size and revert gas in a local EVM harness. Do not adopt published gas figures without measuring your own contract shapes.
Closing: adopt per revert path, not all at once
You do not need to rewrite every string in a deployed contract. Start with high-frequency revert paths in new code or during a planned upgrade. Define errors near the functions that use them, keep arguments small, and update off-chain decoding tooling before users encounter opaque selectors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.