Embracing Custom Errors in Solidity
Published: 2023-04-23 14:09:59

## Introduction

Solidity, the smart contract programming language for the Ethereum blockchain, has always had a way to handle errors and exceptions. The default error handling mechanism, through `require`, `assert`, and `revert` functions, allowed developers to manage exceptions and halt the execution of contracts if specific conditions were not met. However, these default errors lacked the flexibility and expressiveness that developers needed for building robust and complex smart contract systems.

With the introduction of custom errors in Solidity 0.8.4, developers now have a more powerful tool for managing exceptions and crafting more informative error messages, making debugging and troubleshooting easier. This article will explore the benefits of custom errors in Solidity, as well as provide examples and insights on how to effectively use them in your smart contracts.

## Basic Errors in Solidity

Basic errors in Solidity are simple and straightforward. They can be triggered using the `require` or `revert` functions, which take a string argument as an error message. For example, consider the following error:


```solidity
function buyTokens(uint256 amount) public {
    require(amount > 0, "Amount must be greater than zero");
    // Token purchase logic
}
```

In this example, the `require` function checks if the amount is greater than zero. If not, the function call will be reverted, and the specified error message will be returned.

## Custom Errors in Solidity 0.8.4

Solidity 0.8.4 introduced custom errors, allowing developers to define their own error types with specific error codes and parameters. This new feature enhances error handling by providing more context and information about the reason for the error. Custom errors can be declared using the `error` keyword, followed by the error name and an optional list of parameters.

A simple custom error without parameters can be declared as follows:

```solidity
error InsufficientFunds();

function withdraw(uint256 amount) public {
    if (amount > balance[msg.sender]) {
        revert InsufficientFunds();
    }
    // Withdrawal logic
}
```

In this example, the custom error `InsufficientFunds` is defined and used to replace the default error message in the `require` statement. This makes the error handling code more concise and easier to understand.

A more complex custom error with parameters can be declared and used like this:

```solidity
uint256 public maximumTransfer = 1000;
error InvalidAmount(uint256 requested, uint256 maximum);

function transferTokens(uint256 amount) public {
    if (amount > maximumTransfer) {
        revert InvalidAmount(amount, maximumTransfer);
    }
    // Token transfer logic
}
```

In this example, the custom error `InvalidAmount` takes two parameters: `requested` and `maximum`. When the error is triggered, it provides additional information about the invalid amount, making it easier for developers to identify the issue.

## Try/Catch Mechanism in Solidity

Solidity also provides a try/catch mechanism to handle errors and exceptions gracefully. This allows developers to write more robust and fault-tolerant smart contracts by catching and handling errors within the contract's execution.

Combining custom errors with the try/catch mechanism allows for more granular error handling. Here's an example of using custom errors with try/catch:

```solidity
function executeTransfer(address token, uint256 amount) public {
    try tokenContract(token).transferTokens(amount) {
        // Successful transfer
    } catch (bytes memory reason) {
        bytes4 errorSelector = abi.decode(reason, (bytes4));

        // Assuming there are InvalidAmount and InsufficientFunds errors defined
        if (errorSelector == InvalidAmount.selector) {
            // Handle invalid amount error
        } else if (errorSelector == InsufficientFunds.selector) {
            // Handle insufficient funds error
        } else {
            // Handle other errors
        }
    }
}
```

In this revised example, the try/catch statement catches the low-level bytes memory reason returned by the failing function call. We then decode the error selector and use conditional logic to handle the error based on its selector.

It is important to note that custom errors can be spoofed when interacting with untrusted contracts. If your contract calls another contract that you do not control, be cautious when relying on the parsed error type. Malicious contracts could deliberately return custom error data that mimics the expected error format, potentially leading to incorrect error handling or other unintended consequences. In such cases, it is essential to implement additional security measures and verify the authenticity of the error data before proceeding with any error handling logic.

## Parsing Custom Errors on the Client Side

When interacting with a smart contract from a client-side application, you can also parse custom errors manually. Here's an example using JavaScript and the ethers.js library:

```javascript
const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer);

try {
    const tx = await tokenContract.transferTokens(amount);
    await tx.wait();
} catch (error) {
    const reasonBytes = error.data;
    const errorSelector = ethers.utils.hexDataSlice(reasonBytes, 0, 4);

    const invalidAmountSelector = tokenContract.interface.getSighash("InvalidAmount(uint256,uint256)");
    const insufficientFundsSelector = tokenContract.interface.getSighash("InsufficientFunds()");

    if (errorSelector === invalidAmountSelector) {
        console.log("Invalid amount");
    } else if (errorSelector === insufficientFundsSelector) {
        console.log("Insufficient funds for the transfer");
    } else {
        console.log("Unknown error:", error);
    }
}
```

In this example, we use the ethers.js library to interact with the token contract. When an error is encountered, we extract the error selector from the error data and use it to determine the type of error that occurred.


## Conclusion

Custom errors in Solidity provide developers with a more powerful and expressive tool for managing exceptions in their smart contracts. By combining custom errors with the try/catch mechanism in Solidity and parsing them on the client side, developers can build more robust and fault-tolerant applications that are easier to debug and maintain. Embrace custom errors in your smart contracts to improve the overall development experience and the quality of your code.