Reacting to Unwanted Behavior – assert, require, revert
Solidity is a programming language for Ethereum smart contracts. And since smart contracts live on the blockchain, they need some special commands that do not exist in other programming environments.
If a situation occurs that is unwanted, the execution of the smart contract should stop. There are different ways to tell the EVM to stop execution and roll back all state changes that have been done in this transaction.
In his topic, we explain the statements assert, require, revert.
revert
The statement revert will stop the execution and undo all state changes. The remaining gas will be refunded to the caller (but the gas used so far will be gone). Revert allows returning a value. This can be used as an error message to clarify why the revert statement was executed.
There are two ways to trigger a revert.
- revert CustomError(arg1, arg2)
- revert(“description”)
The first way is the go-to way and the second one is for backward compatibility. Using the custom error instance will usually be cheaper than passing a string as a description.
To try it out, copy the following code.
pragma solidity 0.8.20;
contract revertContract{
/// Number too big. Number must be below `threshold` but got
/// `number'.
/// @param threshold max allowed number.
/// @param number number supplied.
error WrongNumber(uint256 threshold, uint256 number);
string public status;
uint256 myThreshold = 100;
function revertString(uint256 _number) public{
status = "not ok";
if(_number > myThreshold){
revert("Number too big");
}
status = "ok";
}
function revertCustomError(uint256 _number) public{
status = "not ok";
if(_number > myThreshold){
revert WrongNumber({
threshold: myThreshold,
number: _number
});
}
status = "ok";
}
}
In line 16, the revert statement is called. It passes a description string.
In line 25 the revert statement is called in combination with custom errors. The custom error is declared in line 9 together with NatSpec in the lines before. It should serve as a more verbose and cheaper description of the error.
If you call the function revertString, with a number bigger than 100, you will see that the transaction fails. The remix ide gives you the reason in its console.
If you call the function revertCustomError the function will fail too. But so far, the ide doesn’t display custom errors. Instead, you will only get the message that the transaction failed.
require
Require is another way to call revert. It combines the if statement, the revert, and the returned description.
The condition must evaluate to false to let the require statement fire.
require(condition, "description");
You can copy the following code and try it out.
pragma solidity 0.8.20;
contract requireContract{
function requireTest(uint256 _number) public {
require(_number < 100, "number too big");
}
}
If you enter a number bigger than 100 the transaction will fail and the IDE will display the description.
The reason to introduce the require statement was to make the code more readable. The require statement should be used in the following cases:
- validating user inputs as we did above.
- validating state conditions before any action
- validating the response from an external contract
- checking for overflows and underflows
assert
The assert statement also undoes all state changes but it consumes all the gas provided in a transaction, even if it was not used. This makes it less forgiving than revert or require and it is used less often. Assert is there for “really bad things” and should be only used for internal errors.
The condition must evaluate to false to let the assert statement fire.
assert(condition);
Assert should be used in the following cases:
- Check invariants like this.balance > totalSupply
- Validate state after execution
- Overflow and underflow checks, if revert/require are not suitable
Solidity creates assert-type exceptions in some cases automatically:
- Dividing or modulo by zero
- Access an array out of its bounds
- Converting too large or negative numbers to enum