Error handling in solidity

Al-Qa'qa'
Coinmonks
6 min readSep 12, 2023

--

Photo by mamewmy on Freepik

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 use assert , and when to use require 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.

--

--

Coinmonks
Coinmonks

Published in Coinmonks

Coinmonks is a non-profit Crypto Educational Publication.

Al-Qa'qa'
Al-Qa'qa'

Written by Al-Qa'qa'

Smart Contract Auditor | Smart Contract Security Researcher