Error handling in Solidity is very useful

Reading Time: 3 minutes

Hello guys, here is another blog on Solidity. Check out my previous blog on Value types in Solidity. In this blog, we will see how to do error handling in Solidity. Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the state in the current call and all its sub-calls and reports the error to the caller. When exceptions happen in a sub-call, they are rethrown automatically unless they are caught in a try/catch statement.

Exceptions can contain error data that goes back to the caller in the form of error instances. The built-in errors Error(string) and Panic(uint256) are used by the special functions. Error is used for “regular” error conditions while Panic is used for errors that should not be present in bug-free code.

Solidity – Pure and View Modifiers – Blog | Avantrio

Panic and Error

The convenience functions assert and require can be used to check for conditions and throw an exception if the condition is not met.

The assert function creates an error of type Panic(uint256). We should only use assert to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls that will cause a Panic.

The require function either creates an error without any data or an error of type Error(string). We use require function to ensure valid conditions that we cannot detect until execution time. This includes conditions on inputs or return values from calls to external contracts.

An Error(string) exception or an exception without data is generated by the compiler in the following situations:

  1. Calling require(x) where x evaluates to false.
  2. When you use revert() or revert("description").
  3. If you perform an external function call targeting a contract that contains no code.
  4. If your contract receives Ether via a public function without payable modifier (including the constructor and the fallback function).
  5. When your contract receives Ether via a public getter function.

revert statement

A direct revert can be triggered using the revert statement and the revert function.

The revert statement takes a custom error as a direct argument without parentheses:

revert CustomError(arg1, arg2);

For backwards-compatibility reasons, there is also the revert() function, which uses parentheses and accepts a string:

revert(); revert(“description”);

The error data goes back to the caller and we can catch it there. Using revert() causes a revert without any error data while revert("description") will create an Error(string) error.

The following example shows how to use an error string and a custom error instance together with revert and the equivalent require:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract VendingMachine {
    address owner;
    error Unauthorized();
    function buy(uint amount) public payable {
        if (amount > msg.value / 2 ether)
            revert("Not enough Ether provided.");
        // Alternative way to do it:
        require(
            amount <= msg.value / 2 ether,
            "Not enough Ether provided."
        );
        // Perform the purchase.
    }
    function withdraw() public {
        if (msg.sender != owner)
            revert Unauthorized();

        payable(msg.sender).transfer(address(this).balance);
    }
}

try/catch statement

We can use a try/catch statement to catch a failure in an external call. Here is an example.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;

interface DataFeed { function getData(address token) external returns (uint value); }

contract FeedConsumer {
    DataFeed feed;
    uint errorCount;
    function rate(address token) public returns (uint value, bool success) {
        // Permanently disable the mechanism if there are
        // more than 10 errors.
        require(errorCount < 10);
        try feed.getData(token) returns (uint v) {
            return (v, true);
        } catch Error(string memory /*reason*/) {
            // This is executed in case
            // revert was called inside getData
            // and a reason string was provided.
            errorCount++;
            return (0, false);
        } catch Panic(uint /*errorCode*/) {
            // This is executed in case of a panic,
            // i.e. a serious error like division by zero
            // or overflow. The error code can be used
            // to determine the kind of error.
            errorCount++;
            return (0, false);
        } catch (bytes memory /*lowLevelData*/) {
            // This is executed in case revert() was used.
            errorCount++;
            return (0, false);
        }
    }
}

The try keyword has to be followed by an expression representing an external function call or a contract creation (new ContractName()). For example, if it is a complex expression that also involves internal function calls, only a revert happens inside the external call itself. If the end of the success block is reached, execution continues after the catch blocks.

This was all about error handling in Solidity. Stay tuned for more such blogs.


If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.

rust-times

error handling in solidity