Custom Errors
Solidity 0.8.4+ ships custom errors: cheaper to emit, easier to decode off-chain, more expressive. Use revert with a custom error for every reject; reserve require for the simple boolean checks.
Custom errors, revert, require, try/catch
EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Vault {
mapping(address => uint256) public balanceOf;
// 1) Custom errors — parameters carry context to the caller
error InsufficientBalance(uint256 requested, uint256 available);
error NotOwner(address caller, address required);
error Paused();
error AmountTooLow(uint256 amount, uint256 minimum);
address public owner;
bool public paused;
constructor() { owner = msg.sender; }
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner(msg.sender, owner);
_;
}
modifier whenNotPaused() {
if (paused) revert Paused();
_;
}
function withdraw(uint256 amount) external whenNotPaused {
if (amount < 1 ether) revert AmountTooLow(amount, 1 ether);
uint256 bal = balanceOf[msg.sender];
if (amount > bal) revert InsufficientBalance(amount, bal);
balanceOf[msg.sender] = bal - amount;
(bool ok, ) = msg.sender.call{value: amount}("");
if (!ok) revert("send failed");
}
// 2) require — fine for simple, one-line checks (no params needed)
function setOwner(address next) external onlyOwner {
require(next != address(0), "zero");
owner = next;
}
// 3) try / catch — only on EXTERNAL calls to other contracts
function safePull(IERC20 token, address from, uint256 amount) external {
try token.transferFrom(from, address(this), amount) returns (bool ok) {
require(ok, "erc20 fail");
} catch Error(string memory reason) {
// require(false, “reason”) / revert(“reason”) from the other contract
revert(reason);
} catch Panic(uint256 code) {
// Arithmetic / underflow / out-of-bounds in the other contract
revert("panic");
} catch (bytes memory data) {
// Custom error or low-level failure
revert("unknown");
}
}
}
// 4) Decoding errors off-chain (ethers.js)
try {
await vault.withdraw(amount);
} catch (err) {
if (err.errorName === 'InsufficientBalance') {
const { requested, available } = err.errorArgs;
console.log(\`asked ${requested}, only ${available}\`);
}
}
Why it matters
Custom errors are ~75% cheaper to emit than require(\"long reason string\"). They survive reverts as typed structured data — the front-end can show real error UIs instead of “execution reverted”.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
error InsufficientBalance(uint256 requested, uint256 available);
function withdraw(uint256 amount) external {
if (amount > balances[msg.sender])
revert InsufficientBalance(amount, balances[msg.sender]);
// …
}
Try it Yourself »
Discussion
Loading…