🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity Choosing Errors

Choosing errors means picking the right failure tool for each check. Simple input checks, data-rich failures, and impossible states each get a different tool.

Require fits a simple input rule

Zero is a simple bad input. A short reason string says what went wrong.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract Vault {
    uint256 public balance;

    function deposit(uint256 amount) public {
        require(amount > 0, "amount must be above zero");
        balance += amount;
    }
}

A custom error fits a failure that needs data

The caller needs both amounts to understand the failure. The error carries have and want.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
error InsufficientBalance(uint256 have, uint256 want);

contract Vault {
    uint256 public balance;

    function deposit(uint256 amount) public {
        balance += amount;
    }

    function withdraw(uint256 amount) public {
        if (amount > balance) {
            revert InsufficientBalance(balance, amount);
        }
        balance -= amount;
    }
}

Assert fits an invariant that must hold

The assert checks an invariant after every change. It passes here because the code keeps the invariant true.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract Ledger {
    uint256 public balance;
    uint256 public totalDeposited;

    function deposit(uint256 amount) public {
        balance += amount;
        totalDeposited += amount;
        assert(balance <= totalDeposited);
    }

    function withdraw(uint256 amount) public {
        balance -= amount;
        assert(balance <= totalDeposited);
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
error InsufficientBalance(uint256 have, uint256 want);

contract VaultChoice {
    uint256 public balance;
    uint256 public totalDeposited;

    function deposit(uint256 amount) public {
        require(amount > 0, "amount must be above zero");
        balance += amount;
        totalDeposited += amount;
        assert(balance <= totalDeposited);
    }

    function withdraw(uint256 amount) public {
        require(amount > 0, "amount must be above zero");
        if (amount > balance) {
            revert InsufficientBalance(balance, amount);
        }
        balance -= amount;
        assert(balance <= totalDeposited);
    }
}

Key points

  • Require: a check with a short reason string, best for simple input rules.
  • Custom error: a named failure with data, best when the caller needs the numbers.
  • Assert: a check for a state that must never happen, best for internal invariants.
  • Pick require for a simple rule, a custom error for a data-rich failure, and assert for an invariant.
  • Use assert only for a condition your own code must guarantee, never for input a caller can send.

In this example, VaultChoice uses all three tools in order. Its deposit function uses require for a zero amount and assert for the balance invariant. Its withdraw function uses require for zero, a custom error for an amount above the balance, and assert for the invariant again.

Note

a failure tool does not change the check itself, only how the failure is reported.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

A user sends zero as a deposit amount. Which tool fits best?