Error handling is one of the most important concepts, that should be taken into consideration in programming.
Error handling in programming refers to the process of identifying, anticipating, and managing errors or exceptions that can occur during the execution of a program.
There may be some errors when executing the code itself, or you may want to cause an error if something happens. For example, you can throw an error if the user input is not what you need in order to execute your function.
We will discuss everything about errors in solidity programming languages, and we will explain keywords that are used in error handling purposes too.
Solidity has some keywords that are used in error handling we will explain each of them in detail.
require
The keyword require
is one of the most popular keywords in solidity, It does a check for a condition, and if doesn’t match it throws an error.
Here is an example of a simple function to mint an NFT.
function mint() public payable {
require(msg.value >= 0.1 ether, "Insufficient minting fees");
...
}
As you can see in this snipped function, we check that the ether sent by the minter should be greater than or equal to 0.1 ETH, if it's not, it will throw an error with this message “Insufficient minting fees”.
revert
The keyword revert
is like throw
in other programming languages like JavaScript. it throws an error directly, no condition checking occurs when you wrote it.
since revert
didn’t have any checks, you need to make the condition yourself, and if it occurs, you will throw an error.
We will write the same example we wrote in require
, but using revert
instead.
function mint() public payable {
if (msg.value < 0.1 ether) {
revert("Insufficient minting fees");
}
}
As you can see, we made an if
condition to check if the ETH sent by the minter is greater than or equal to 0.1 ETH.
If the amount of ETH sent is less than 0.1 ETH it will revert (throw an error) with this message “Insufficient minting fees”.
error
We learned how to throw errors, but in the previous examples we used string errors format, there is another error format called custom errors in solidity, that was introduced in solidity v0.8.4.
custom errors have a format similar to the function format, where it can take arguments, and it can provide more information.
error InsufficientMintingFees(); // custom error
function mint() public payable {
if (msg.value < 0.1 ether) {
revert InsufficientMintingFees();
}
}
As you can see in the previous example, we used the revert
keyword and threw an error if the condition didn’t pass. But instead of throwing a string formatted error
, we are throwing custom error
.
require
didn’t support custom error yet, but it may support it in the future.
custom errors are a new concept, it's more gas efficient and can provide more detailed information about the error problem. You can check this article for more information about custom errors.
assert
The keyword assert
is similar to require
, but it is used for checking internal errors, for example:
- invariants
- overflow/underflow
assert
is used for a condition that should not occur, it means a bug in the code if it occurs and throws a Panic error.
We will discuss an example of the SafeMath
library provided by Openzeppelin contracts, which is used to prevent overflow and underflow errors while doing mathematical calculations.
// substracting two numbers
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
As you can see in this example, we return the subtraction value from the two numbers a
and b
.
Since the numbers are uint
, which means only positive numbers (can’t be negative), In making subtraction you should avoid subtracting a big number from a small one.
This kind of error is an internal error, it's not in the logic itself, It must stop the process, as it's considered a bug.
assert
consumes all gas, but require
refunds all the remaining gas fees we paid.
There is no need to use SafeMath
lib in solidity v0.8, as the compiler handles the overflow and underflow now.
You can read more about
assert
keyword, and know when to useassert
, and when to userequire
from the following article.
try/catch
the keywords try
and catch
are used to catch errors when external function calls, or contract creation.
We will make an example of external call checking.
contract A {
// simple function that prints a string if the value is not zero
// and throws error if the value passed equal zero
function printNotZeroNumber(uint256 number)
public
pure
returns (string memory)
{
require(number != 0, "Error: number equals zero");
return "Your number is not equal zero";
}
}
contract B {
event Log(bool success, string text);
A contractA;
constructor() {
contractA = new A(); // creating new instance of `A` smart contract
}
// calling `printNotZeroNumber(uint256)` function and check if any error occuars
function testExternalCall(uint256 number) public {
// the returned value `result` if the function didn't throws an error
try contractA.printNotZeroNumber(number) returns (
string memory result
) {
// If the number Not equal zero it will not catch an error:
// Log(true, "Your number is not equal zero")
emit Log(true, result);
} catch Error(string memory reason) { // catching an error of `string` format
// If the number equals zero it will catch error in `reason`:
// Log(false, "Error: number equals zero")
emit Log(false, reason);
// In some cases the error may not be in string format like custom errors,
// so you need to handle them if they are existing.
// In our case this catch block is not used.
} catch (bytes memory reasons) {
emit Log(false, "Error in execution");
}
}
}
We made a lot of comments in the code, to be more understandable to the beginner developers.
In brief, we made a contract A
and made an instance (contract creation) of it in the contract B
, then we called the function printNotZeroNumber(uint256)
that returns a string
if the number is not equal to zero, otherwise, it throws a string error format.
Errors can be in two formats string formated
or custom errors
, as we explained, so you need to catch the error reason in string
if it is a string formatted error, and catch the error reason in bytes
if it's a custom error.
In our function, the error is in string format, so we don't need to handle catch bytes
, but we wrote it as a reference.
You can use catch without taking the reason for the error if you don’t care about the error reason.
You can read more about try/catch from the following article.
Recap
require
is used to throw string errors if the condition is not met.revert
throws an error, it supports string formatted and custom errors.- You can create custom errors using
error
keywords. assert
is used to catch internal errors that should not occur.try/catch
are used for catching external function call errors and contract creation.