Skip to content

Error Handling in Solidity

This example demonstrates different error handling mechanisms in Solidity including require, assert, revert, try/catch, and custom errors.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Basic error handling example
contract BasicErrorHandling {
    uint256 public value;
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // Example using require()
    function setValue(uint256 _newValue) public {
        // Check caller is owner
        require(msg.sender == owner, "Not the owner");
        // Validate input
        require(_newValue != 0, "Value cannot be zero");
        // Check not same value
        require(_newValue != value, "Same value already set");

        value = _newValue;
    }

    // Example using assert()
    function divide(uint256 _dividend, uint256 _divisor) public pure returns (uint256) {
        // Assert should be used to check for conditions that should never be false
        // Failing assertion probably means there's a bug
        assert(_divisor != 0);
        return _dividend / _divisor;
    }

    // Example using revert()
    function processSomething(uint256 _value) public pure returns (uint256) {
        if (_value == 0) {
            revert("Value cannot be zero");
        }
        if (_value > 100) {
            revert("Value too high");
        }
        return _value * 2;
    }
}

// Advanced error handling with try/catch
contract ExternalContract {
    uint256 public value;

    function setValue(uint256 _value) external {
        require(_value != 0, "Value cannot be zero");
        value = _value;
    }

    function doRevert() external pure {
        revert("Always reverts");
    }
}

contract TryCatchExample {
    event Success(string message, uint256 value);
    event Failure(string message, bytes data);

    ExternalContract public extContract;

    constructor() {
        extContract = new ExternalContract();
    }

    // Try/Catch with external calls
    function tryExternalCall(uint256 _value) public {
        try extContract.setValue(_value) {
            emit Success("Value set successfully", _value);
        } catch Error(string memory reason) {
            // Catches revert() and require() with error message
            emit Failure("Failed with error", bytes(reason));
        } catch Panic(uint errorCode) {
            // Catches assert() failures and division by zero
            emit Failure("Failed with panic", abi.encodePacked(errorCode));
        } catch (bytes memory lowLevelData) {
            // Catches any other errors
            emit Failure("Failed with low level error", lowLevelData);
        }
    }
}

// Custom errors example (Solidity 0.8.4+)
contract CustomErrorsExample {
    // Define custom errors
    error Unauthorized(address caller);
    error InvalidValue(uint256 value);
    error DeadlineExceeded(uint256 deadline, uint256 currentTime);

    address public owner;
    uint256 public value;

    constructor() {
        owner = msg.sender;
    }

    function setValue(uint256 _newValue) public {
        // Using custom error with parameters
        if (msg.sender != owner) {
            revert Unauthorized(msg.sender);
        }

        if (_newValue == 0) {
            revert InvalidValue(_newValue);
        }

        if (_newValue == value) {
            revert("Value already set");  // Traditional revert still works
        }

        value = _newValue;
    }

    function doSomethingBeforeDeadline(uint256 deadline) public view {
        if (block.timestamp > deadline) {
            revert DeadlineExceeded(deadline, block.timestamp);
        }
        // Do something...
    }
}

Key Concepts

  1. Basic Error Handling

    • require(): Input validation and access control
    • assert(): Invariant checking, should never fail
    • revert(): Manual error throwing
  2. Try/Catch

    • Only works with external calls
    • Can catch different types of errors
    • Provides error handling for external interactions
  3. Custom Errors

    • Gas efficient compared to strings
    • Can include parameters
    • Better error reporting

Best Practices

  1. Use require for:

    • Input validation
    • Access control
    • Preconditions
    • External call results
  2. Use assert for:

    • Invariant checking
    • Internal errors
    • Conditions that should never be false
  3. Use try/catch for:

    • External calls
    • Contract creation
    • Error recovery
  4. Use custom errors for:

    • Gas optimization
    • Better error reporting
    • Complex error conditions

Cross-Contract Custom Error Handling

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// File: CustomErrors.sol
// This could be in a separate file
interface ICustomErrors {
    error InvalidAmount(uint256 amount, uint256 maxAmount);
    error Unauthorized(address caller, string message);
    error DeadlineMissed(uint256 deadline, uint256 currentTime);
}

// File: ErrorThrower.sol
contract ErrorThrower is ICustomErrors {
    uint256 public constant MAX_AMOUNT = 1000;

    function throwCustomError(uint256 _amount) external pure {
        if (_amount > MAX_AMOUNT) {
            revert InvalidAmount(_amount, MAX_AMOUNT);
        }
    }

    function throwUnauthorized() external pure {
        revert Unauthorized(msg.sender, "Not allowed");
    }
}

// File: ErrorCatcher.sol
contract ErrorCatcher {
    ErrorThrower public thrower;

    event ErrorCaught(string name, bytes data);

    constructor(address _thrower) {
        thrower = ErrorThrower(_thrower);
    }

    // Catching specific custom errors
    function testCustomErrorCatching(uint256 _amount) external {
        try thrower.throwCustomError(_amount) {
            // This will only execute if no error was thrown
        } catch (bytes memory lowLevelData) {
            // Decode the error
            if (bytes4(lowLevelData) == bytes4(keccak256("InvalidAmount(uint256,uint256)"))) {
                // Custom handling for InvalidAmount error
                emit ErrorCaught("InvalidAmount", lowLevelData);
            } else if (bytes4(lowLevelData) == bytes4(keccak256("Unauthorized(address,string)"))) {
                // Custom handling for Unauthorized error
                emit ErrorCaught("Unauthorized", lowLevelData);
            } else {
                // Handle other errors
                emit ErrorCaught("Unknown", lowLevelData);
            }
        }
    }
}

Key points about cross-contract custom error handling:

  1. Define errors in an interface for reusability
  2. Implement interface in contracts that throw errors
  3. Use try/catch in calling contracts
  4. Decode error signatures to identify specific errors
  5. Handle each error type appropriately