- Explain state-reverting exceptions and transaction atomicity.
- Use
require,revertandassertcorrectly. - Define and use custom errors to save gas.
- Handle failures of external calls with
try/catch.
How Does Error Handling Work in Solidity?
- Solidity uses state-reverting exceptions. When an error occurs, all changes made to the state in the current call (and its sub-calls) are undone, and an error is returned to the caller.
- This makes transactions atomic: a transaction either completes fully or has no effect on the state at all.
- The gas used up to the point of failure is still paid; any remaining gas is refunded (for require, revert, custom errors and – since Solidity 0.8.0 – assert).
- Errors raised in an external call normally “bubble up” and revert the caller too, unless the caller catches them with
try/catchor uses a low-level call.
The Main Error-Handling Tools
- require(condition, "message") – the gate check. It validates inputs and conditions before (or during) execution, e.g. caller permissions, amounts and states. If the condition is false the call reverts with an
Error(string)containing the message. Solidity 0.8.26 also introducedrequire(condition, CustomError())(in 0.8.26 only with the via-IR pipeline). - revert("message") or revert CustomError(...) – reverts unconditionally. It is useful inside complex
if/elselogic where a single require would be awkward. - assert(condition) – checks internal invariants – conditions that should never be false if the code is correct (e.g. a total that must always equal the sum of balances). A failing assert indicates a bug and produces a
Panic(uint256)error (code 0x01). Since 0.8.0 it no longer consumes all remaining gas. - Custom errors (Solidity 0.8.4+) – declared with
error InsufficientBalance(uint available, uint required);and used withrevert. They are cheaper than long strings and can carry data.
Require vs Revert vs Assert
| Feature | require | revert | assert |
|---|---|---|---|
| Purpose | Validate inputs, permissions and conditions | Abort from complex branching logic | Check invariants that must never fail |
| Typical place | Start of a function | Inside if/else blocks | End of a function / after critical updates |
| Error type | Error(string) or custom error | Error(string) or custom error | Panic(uint256) |
| Meaning of failure | Invalid input / not allowed | Invalid input / not allowed | A bug in the contract |
| Remaining gas | Refunded | Refunded | Refunded (since 0.8.0) |
Other Panic codes are raised automatically by the compiler’s checks, e.g. 0x11 arithmetic overflow, 0x12 division by zero, 0x21 invalid enum conversion, 0x31 pop on an empty array and 0x32 array index out of bounds.
try / catch
A contract can catch a failure of an external function call or a contract creation instead of reverting itself:
try other.doSomething(x) returns (uint result) {
// success
} catch Error(string memory reason) {
// revert("...") or require(false, "...")
} catch Panic(uint code) {
// assert failure, overflow, division by zero ...
} catch (bytes memory lowLevelData) {
// custom errors and anything else
}
try/catch works only with external calls (and new); it cannot catch errors from internal function calls.
Example : require, revert, assert, custom errors and try/catch
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Bank {
error InsufficientBalance(uint available, uint required);
mapping(address => uint) public balances;
uint public totalDeposits;
function deposit() external payable {
require(msg.value > 0, "Deposit must be greater than zero"); // input check
balances[msg.sender] += msg.value;
totalDeposits += msg.value;
}
function withdraw(uint amount) external {
uint available = balances[msg.sender];
if (amount > available) {
revert InsufficientBalance(available, amount); // custom error with data
}
balances[msg.sender] = available - amount;
totalDeposits -= amount;
assert(address(this).balance >= totalDeposits); // invariant check
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "Transfer failed");
assert(address(this).balance >= totalDeposits); // must still hold after paying
}
}
contract BankUser {
event Failed(string reason);
event FailedLowLevel(bytes data);
function tryWithdraw(Bank bank, uint amount) external returns (bool) {
try bank.withdraw(amount) {
return true;
} catch Error(string memory reason) {
emit Failed(reason);
} catch (bytes memory data) {
emit FailedLowLevel(data); // custom error InsufficientBalance lands here
}
return false;
}
}
Key Takeaways
- An error reverts every state change of the call; transactions are atomic.
- require/revert = invalid input or permission (Error or custom error); assert = bug detection (Panic).
- Custom errors are gas-efficient; try/catch handles failures of external calls.
The PDF E-Notes are the earlier printable version of this chapter. The notes on this page are the latest, revised version.
Question / Answer (15 questions)
require(msg.value > 0, "Send Ether");. A false condition reverts with the given message.if (x > y) revert TooLarge(x);.error InsufficientBalance(uint available, uint required);, and raised with revert. They cost less gas than long error strings and can carry useful data.catch (bytes memory data) block, because custom errors are not Error(string) or Panic(uint); they are delivered as raw encoded error data.MCQ Test : Check Your Understanding (5 questions)
Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.
Bibliography/References
The notes, questions and MCQs on this page are original explanations written for this course. The works listed below were consulted and are cited for attribution and further reading. Product names, logos and trademarks belong to their respective owners. Short quotations, where used, are marked and attributed.
- Solidity Team. (n.d.). Expressions and Control Structures (if/else, loops, checked arithmetic, error handling, try/catch). Solidity Documentation. https://docs.soliditylang.org/en/latest/control-structures.html
- Solidity Team. (n.d.). Contracts (creating contracts, visibility and getters, function modifiers, constant and immutable, functions, events, custom errors, inheritance, abstract contracts, interfaces, libraries). Solidity Documentation. https://docs.soliditylang.org/en/latest/contracts.html
- Solidity Team. (n.d.). Security Considerations. Solidity Documentation. https://docs.soliditylang.org/en/latest/security-considerations.html
- Solidity Team. (n.d.). Solidity Blog (release announcements). https://soliditylang.org/blog/
- Modi, R. (2018). Solidity Programming Essentials: A beginner’s guide to build smart contracts for Ethereum and blockchain. Packt Publishing. ISBN 978-1-78883-138-3.