iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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, &ldquo;reason&rdquo;) / revert(&ldquo;reason&rdquo;) 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…