Track 4 · Solidity foundations · lesson 8
require, revert, custom errors
9 min
Track 4 · Solidity foundations · lesson 8
9 min
A contract that fails quietly is worse than one that crashes. The caller is told everything worked, and finds out otherwise later — usually when the money is gone.
When a transaction reverts, every state change it made is rolled back — including changes made before the failure, and changes made in other contracts it called along the way. The chain behaves as though the transaction only ever spent gas.
This is unusual and worth sitting with. There is no partial success. You never have to write cleanup code for a half-finished operation, because half-finished operations do not exist.
It also means a revert deep inside a chain of calls unwinds the whole thing — which is exactly what makes an external call that reverts a denial-of-service tool in the wrong hands.
require(amount > 0, "amount must be positive"); // check an input
revert NotOwner(); // custom error
assert(totalSupply >= balance); // invariant that must hold
require — validate inputs and conditions. Refunds unused gas.revert with a custom error — the modern, cheapest form. A four-byte
selector rather than a whole string stored in your bytecode.assert — for things that should be impossible. A failing assert
signals a bug in your code, not bad input.Predict
Solidity's low-level .call() does not revert when the call it makes fails.
It returns false, and if you ignore that return value your code carries on as
though nothing happened.
(bool ok, ) = recipient.call{value: amount}("");
require(ok, "transfer failed"); // without this line, failure is silent
Forgetting that require is one of the most common real-world bugs in Solidity,
and you will exploit it in track 7.
Check