Solidity Fundamentals

Error Handling

Beginner Level

In Solidity, error handling is essential for building secure and reliable smart contracts. When something goes wrong (invalid input, insufficient balance, unauthorized access, etc.), the transaction must stop and revert to prevent unwanted state changes. Solidity provides three main mechanisms for error handling: require, assert, and revert.

When an error occurs:

  • The transaction execution stops.
  • Any changes made during the transaction are reverted.
  • Remaining gas is refunded to the user (except in assert, which consumes all gas).

1. require

The require statement is used to validate inputs and conditions. If the condition fails, it reverts the transaction and returns an error message. It is the most commonly used error handler.

Example (Validating deposit amount):


pragma solidity ^0.8.0;

contract RequireExample {
    uint256 public balance;

    function deposit(uint256 amount) public {
        require(amount > 0, "Deposit must be greater than zero");
        balance += amount;
    }
}

If a user tries to deposit 0, the function reverts with the message "Deposit must be greater than zero".

Best use: Input validation, access control, pre-conditions.

2. assert

The assert statement checks for internal errors and invariants that should never fail. Unlike require, when an assert fails, it means there is a bug in the code, not invalid user input. It consumes all remaining gas and should be used sparingly.

Example (Invariant check):


pragma solidity ^0.8.0;

contract AssertExample {
    uint256 public totalSupply = 1000;
    uint256 public minted = 0;

    function mint(uint256 amount) public {
        minted += amount;
        assert(minted <= totalSupply); // must always hold true
    }
}

If minted ever exceeds totalSupply, something is fundamentally wrong in the contract logic.

Best use: Internal consistency checks, invariants, conditions that should never be false.

3. revert

The revert statement immediately stops execution and reverts all changes, just like require, but it is more flexible. It can be used with custom error messages or as part of more complex logic.

Example (Access control):


pragma solidity ^0.8.0;

contract RevertExample {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function withdraw(uint256 amount) public {
        if (msg.sender != owner) {
            revert("Only owner can withdraw funds");
        }
        // withdraw logic (omitted for simplicity)
    }
}

If someone other than the owner calls withdraw, the function reverts with the error message.

Best use: Complex conditions, cleaner error messages.

Summary

Error Handler When to Use Gas Refund Example Use Case
require Input validation, pre-conditions Refunds unused gas Check valid amounts, permissions
assert Internal checks, invariants Consumes all gas Ensure code correctness
revert Manual error handling, custom logic Refunds unused gas Access control, fallback handling

Real Life Example: Bank Contract with Error Handling


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

contract Bank {
    address public owner;
    mapping(address => uint256) public balances;
    uint256 public totalDeposits;

    constructor() {
        owner = msg.sender;
    }

    // Deposit Ether into the bank
    function deposit() public payable {
        require(msg.value > 0, "Deposit must be greater than zero"); // input validation
        balances[msg.sender] += msg.value;
        totalDeposits += msg.value;

        // Invariant check
        assert(totalDeposits >= balances[msg.sender]);
    }

    // Withdraw Ether from the bank
    function withdraw(uint256 amount) public {
        require(amount > 0, "Withdrawal amount must be greater than zero");
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        totalDeposits -= amount;

        payable(msg.sender).transfer(amount);

        assert(totalDeposits >= 0);
    }

    // Only the owner can close the bank
    function closeBank() public {
        if (msg.sender != owner) {
            revert("Only owner can close the bank");
        }

        uint256 balance = address(this).balance;
        totalDeposits = 0;
        payable(owner).transfer(balance);
    }

    // Check contract balance
    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

Continue Learning

Explore more topics in Solidity or browse other tutorials.