Skip to Content
Solidity ExamplesError Handling

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
Last updated on