Learn Solidity lesson 26. Error handling.

João Paulo Morais
Coinmonks
Published in
6 min readAug 20, 2022

--

We have already seen how to check a condition in Solidity using the require function. For example, if we want to check whether the balance of an account is greater than a certain amount, we can write the following statement.

require(balanceOf[account] > 100);

Thus, if the expression inside the parentheses is false, the transaction will roll back. In the example above, a generic error will be sent back to the sender. It is possible, however, to return a string indicating which error occurred. For that, just pass a second argument to the require function, as below.

require(balanceOf[account] > 100, "Insufficient Balance");

You can see the error in Remix, along with the information that the transaction rolled back. In the figure below, notice the sentence “Reason provided by the contract: Insufficient Balance’’.

When reverting, the return data will indicate the error sent by the contract.

There is a more gas-efficient way of sending errors back to the sender. In Solidity there is the possibility to define error objects. Unfortunately, they cannot be used in conjunction with the require function.

Errors are created using the error keyword, followed by an identifier and possible parameters. An example of a custom error is as follows.

error InsufficientBalance(uint available, uint required);

Custom errors must be used in conjunction with the revert function, as we will see.

Revert

The revert function is used to reverse a transaction at any time during its execution. It is best used in conjunction with custom errors, but for compatibility with earlier versions of the EVM, the revert function can also be used with an argument of type string, whose purpose is to indicate the reason of reversion.

Revert is often used in conditional expressions. The same example from the previous section, refactored using revert, is as follows.

if (balanceOf[msg.sender] <= 100) revert("Insufficient Balance");

In general, using if (!condition) revert(…) and require(condition,…) have the same effect. However, we can use the revert function with custom errors.

Let’s look again at the custom error declared in the previous section:

error InsufficientBalance(uint available, uint required);

Such custom errors can be instantiated by the revert function as in the statement below.

if (balanceOf[msg.sender] <= 100)
revert InsufficientBalance(balanceOf[msg.sender], 100);

The custom error sent back can be parsed as an object. We can see the result in the figure below, where the error sent is indicated, along with the value of its parameters.

Sending a custom error using the revert function. It can be parsed by the client.

Errors are returned to the sender ABI-encoded, just like functions. Let’s see this by trying to send a transaction to a contract on a real blockchain, using Remix. It will warn that the transaction will most likely roll back. This can be seen in the figure below.

Remix indicates that the transaction is likely to roll back.

Remix, like many wallets, can simulate the transaction and verify that it will (probably) reverse. Note the data field of the return: it is ABI-encoded and indicates which error was throw by the contract. To deserialize the error, you must have the ABI of the contract.

In the example above, the data of the error is as follows:

0xcf479181
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000064

The first line is the error signature. Like the function signature, the error signature is the first 4 bytes of the Keccak-256 hash of the string indicating the error. In this case, the string InsufficientBalance(uint256,uint256). This can be verified using an online encryption tool, as shown in the figure below.

Calculating the hash of the error function.

After the function signature, we have two arguments of type uint256, in hexadecimal: 0 as the first argument and 64 as the second argument, which corresponds to 100 in decimal. One of the advantages of using custom errors is the ability to deserialize the arguments on the client.

Custom errors have only one property, selector, which returns the error signature in 4 bytes.

Assert

A second method of checking a condition in Solidity is using the assert function. Its usage is similar to require, but it is mostly used to make sure that a condition that should be true, is in fact true.

In other words, require is used to check a condition that may or may not be true, depending on the parameters of a function, for example. Assert is used to make sure that there is nothing wrong in the contract logic, and in case assert finds a true condition, this indicates a possible bug in the contract that must be fixed.

While revert sends an error whose type is Error(string), assert sends an error of type Panic(uint256). Unlike require, assert cannot receive a text indicating the reason for the error.

In some books or older articles, you will find the information that the assert function consumes all the gas available for the transaction. This was true until version 0.8.0 of the compiler, when the assert function used an opcode other than revert. Currently both functions use the same opcode and the remaining gas is returned back to the sender for both revert and assert usage.

Try/catch

The use of try/catch in Solidity is more restricted than in other programming languages. Try/catch is only used in two conditions: when invoking external calls and when creating new contracts.

Let’s demonstrate its use with an example. The contract named TryCatch will have two functions: called and caller. The called function will be called by the caller function externally, so we can use try/catch. In general try/catch is mostly used to invoke functions in other contracts, but we want to keep the example simple.

pragma solidity ^0.8.7;contract TryCatch {function called(uint errorType) external pure returns(bool) {
if (errorType==0) {
return true;
} else if (errorType==1) {
revert("Error!");
} else if (errorType==2) {
assert(false);
}
}
function caller(uint errorType) public view returns(string memory){
try this.called(errorType) {
return "Ok";
}
catch Error(string memory) {
return "Error!";
}
catch Panic(uint256) {
return "Panic!";
}
}
}

The caller function, when invoking the called function, passes an argument of type uint. If the argument is the value 0, there will be no error. If the argument is the value 1, called executes the revert statement and the error is caught by the first catch, whose type is Error. In case the argument is the value 2, called executes an assert whose condition is false. Such an error is caught by the last catch, of type Panic.

If you are not interested in the type of error, you can just use catch {…} or, in case you want the error ABI-encoded, you can use catch (bytes memory data).

To see this last feature, change the caller function to the code below.

function caller(uint errorType) public view returns(bytes memory) {
try this.called(errorType) {}
catch (bytes memory data) {
return data;
}
}

When passing the value 2 as an argument, assert(false) will be executed, which will throw an error of type Panic. We can see this in the figure below. The first 4 bytes of the Keccak-256 hash of the string Panic(uint256) is 4e487b71, the same as the signature of the return error.

The error signature indicates that it is of type Panic.

After the error signature, there is an ABI-encoded 32 bytes number. This number indicates the reason for the error; in the above case, for the value of 1, it indicates that the expression inside assert returned false. There are other possibilities that generate a Panic type error, such as division by zero or trying to use the .pop() method on an empty array. Each of these errors has its own numeric code.

A complete list of all errors that can generate a Panic type error, along with the error code, can be found at https://docs.soliditylang.org/en/v0.8.15/control-structures.html\#panic-via-assert-and-error-via-require.

Thanks for reading!

Comments and suggestions about this article are welcome.

Any contribution is welcome. www.buymeacoffee.com/jpmorais.

New to trading? Try crypto trading bots or copy trading

--

--

João Paulo Morais
Coinmonks

Astrophysicist, full-stack developer, blockchain enthusiast. Technical Writer @RareSkills.