Using Solidity Custom Errors for Gas‑Efficient Reverts
Learn how Solidity custom errors (since 0.8.4) replace costly revert strings with cheap, typed data, saving gas while keeping error messages readable off‑chain.
18 Feb 2026, 04:51 UTC

Problem: Expensive revert strings
In Solidity versions before 0.8.4, the usual way to return information with a failing transaction is to pass a string to require or revert. Each character of that string ends up in the contract’s bytecode and costs gas every time the revert path is taken. For contracts that revert frequently (e.g., token transfers with insufficient balance), the overhead can add up to thousands of gas per transaction.
Thesis: Custom errors give cheap, typed revert data
Starting with Solidity 0.8.4, you can define custom errors—named, parameterized types that encode only a four‑byte selector and the ABI‑encoded arguments. The selector is derived from the error signature, so the contract stores far less bytecode, and off‑chain tools can decode the data back into a readable message.
How custom errors work
Define an error like a function, but without a body:
error InsufficientBalance(uint256 balance, uint256 needed);
When the condition fails, revert with the error and its arguments:
if (balance < amount) {
revert InsufficientBalance(balance, amount);
}
The EVM returns the selector (first four bytes of keccak256("InsufficientBalance(uint256,uint256)")) followed by the ABI‑encoded balance and amount. A front‑end that knows the contract’s ABI can turn those bytes back into InsufficientBalance(balance, needed).
Worked example: ERC20‑like token
Below is a minimal token that uses a custom error for the common insufficient‑balance case.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken {
mapping(address => uint256) public balanceOf;
uint256 public totalSupply;
error InsufficientBalance(uint256 balance, uint256 needed);
constructor(uint256 initial) {
balanceOf[msg.sender] = initial;
totalSupply = initial;
}
function transfer(address to, uint256 amount) public returns (bool) {
uint256 senderBalance = balanceOf[msg.sender];
if (senderBalance < amount) {
revert InsufficientBalance(senderBalance, amount);
}
balanceOf[msg.sender] = senderBalance - amount;
balanceOf[to] += amount;
return true;
}
}
When transfer reverts, the transaction receipt’s data field contains:
- 4‑byte selector for
InsufficientBalance(uint256,uint256) - 32‑byte encoding of
senderBalance - 32‑byte encoding of
amount
Verification and gas comparison
- Compile with a recent solver: ensure
solc --versionreports 0.8.4 or newer (e.g., 0.8.20). - Unit test: write a test that calls
transferwith an amount greater than the sender’s balance and expects a revert. - Inspect receipt: after the transaction, check
receipt.data. The first four bytes should match the error selector; the rest should decode to the twouint256values using the contract’s ABI. - Gas measurement: run the same test twice—once with
require(senderBalance >= amount, "Insufficient funds");and once with the custom error. The custom‑error version typically saves ~50‑100 gas because the revert string is omitted from bytecode and the selector is smaller than the full UTF‑8 string. - Tooling assistance: Hardhat’s
networkplugin or Foundry’sforge test -vvvautomatically decodes custom errors when the contract’s ABI is present, displaying something likeInsufficientBalance(balance: 5, needed: 10).
Trade‑offs and limitations
- Compiler version: Projects locked to Solidity 0.8.3 or earlier must upgrade the compiler and verify that all dependencies compile with the new version before adopting custom errors.
- Off‑chain decoding: Block explorers that are not ABI‑aware show only the four‑byte selector, which can look like opaque hex data. Developers need to use ABI‑aware tools (e.g., Etherscan’s “Contract” tab with ABI upload, or a local debugger) to see the meaningful message.
- Immutability: Existing contracts cannot be retrofitted with custom errors without deploying a new contract version; storage‑layout‑compatible upgrades (e.g., via proxies) are required if you want to keep the same address.
Actionable closing
If you are starting a new Solidity project or planning a major upgrade, adopt custom errors for any revert that needs to convey data. Define the error near the function that uses it, replace require strings with the typed revert, and verify gas savings in your test suite. The result is lower transaction costs, cleaner bytecode, and still‑readable error information for developers and users who have access to the contract’s ABI.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.