Solidity Custom Errors: Gas‑Efficient Reverts with Typed Data
Solidity custom errors (v0.8.4+) replace revert strings with a 4‑byte selector and typed arguments, cutting bytecode size and gas on failure.
08 Jul 2025, 03:02 UTC

When a transaction fails, Solidity can return either a plain string (require("msg")) or a custom error. Custom errors, introduced in Solidity 0.8.4, encode the failure reason as a 4‑byte selector followed by typed arguments. This reduces both contract bytecode size and the gas consumed on a revert compared with a string‑based revert.
How custom errors work
The compiler generates a single 4‑byte selector (keccak256("ErrorName(type1,type2)")) that is placed in the revert data. The arguments are ABI‑encoded after the selector. Because the selector is fixed size and the arguments are tightly packed, the EVM spends fewer bytes on the revert data and the contract needs less code to store the error signature.
Worked example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Token {
// Custom error with two uint256 parameters
error InsufficientBalance(uint256 requested, uint256 available);
mapping(address => uint256) public balance;
function deposit() public payable {
balance[msg.sender] += msg.value;
}
function transfer(address to, uint256 amount) public {
if (balance[msg.sender] < amount) {
// Revert with the custom error and its arguments
revert InsufficientBalance(amount, balance[msg.sender]);
}
balance[msg.sender] -= amount;
balance[to] += amount;
}
}
Place the file in a project folder and compile it with a Solidity version ≥ 0.8.4.
Verification steps
Compile the contract and record the bytecode size:
solc --version 0.8.20 solc --bin Token.solCompare the output length with a version that uses
require("Insufficient balance"); the custom‑error variant is typically several tens of bytes shorter.Deploy the contract (any test network or local node) and call
transferwith an amount that exceeds the sender’s balance.Capture the transaction receipt; the
revertfield will contain a hex string. Skip the first 4 bytes (the selector) and decode the rest:// Example using ethers.js const receipt = await provider.getTransactionReceipt(txHash); const data = receipt.revertReason; // "0x08c379a0..." if (data.startsWith("0x")) { const selector = data.slice(0, 10); // first 4 bytes + 0x const abiData = data.slice(10); const [requested, available] = ethers.utils.defaultAbiCoder.decode( ["uint256", "uint256"], abiData ); console.log({ requested, available }); }Check the gas used for the failing transaction (available in the receipt). It will be lower than the gas used for the equivalent string‑based revert.
Limits and common pitfalls
Solidity version: custom errors are unavailable before 0.8.4. Using an older pragma yields a parser error.
Off‑chain decoding: front‑ends, block explorers, or debugging tools must know the error’s ABI to show a readable message; otherwise the revert appears as an opaque hex string.
Upgradeable proxies: if you use a proxy pattern, the error selector and signature must stay identical across all implementations. A mismatch makes the proxy treat the revert as unknown data, which can break error handling.
Mixing revert styles: avoid combining
requirewith a string and a custom error in the same revert path; it can confuse tools that expect a single error encoding.Low‑level callers: when using
callorstaticcall, the caller must manually decode the returned data if it wants to interpret the custom error; the EVM does not do this automatically.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.