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?