Errors Handling in solidity from zero to hero (Require- Assert- Revert)

Mahmoud Mourad Dev
Coinmonks
8 min readJan 27, 2023

--

Errors Handling in solidity

Errors Handling

  • Errors and Exception are the norms in programming
  • Errors can occure at design time or runtime
  • It is important to test the contract for possible runtime
  • Implementing exception handling is good practice as it lead to writing defensive code
  • Until version 4.10 there was single throw statement available for error handling For Example write function take input uint zero from user mathematical calculation error ,Throw statement consumed all gas and reverted to orginal state
//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.0;
contract errorMath{
function addNumber(uint256 myNumber) public pure returns(uint256){
return 1000/myNumber;
}
}

// Error statement and consumed all gas
[vm]from: 0xAb8...35cb2to: errorMath.addNumber(uint256) 0xA83...5B10Avalue:
0 weidata: 0xfce...00000logs: 0hash: 0xa79...4bb8b
  • After version 4.10 of solidity there were (assert- require-revert )function
//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract errorMath{
function addNumber(uint256 myNumber) public pure returns(uint256){
return 1000/myNumber;
}
}
// Error statement and consumed all gas
transact to errorMath.addNumber errored: VM error: revert.

revert
The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and
the value you send should be less than your current balance.
Debug the transaction to get more information

Require

  • Syntax : require(bool condition, string memory message)
  • Is used to validate inputs and conditions befor execution function
  • The unused gas is returned to the caller
  • Require(bool condition) in case condtion is not met,this method call revert to original state ,this method is to be used for errors in input or external components

code Example

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
function addNum(uint256 _num) public pure returns(uint256){
// input have to less than 100
require(_num < 100, "input have to less than 100");
return _num;

}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
//bool variable to check wether sunny
bool public sunny= true;
//finalCalculation zero we dont have any calculation yet
uint finalCalculation= 0;
function solarPanalMachine() public{
require(sunny);
//solarPanel run when sunny true
finalCalculation +=3;
}
}
  • Require(bool condition,”string memory message”) in case condition is not met ,this method call revert to original state ,it provide an option to provide custom message
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
//bool variable to check wether sunny
bool public sunny= false;
//finalCalculation zero we dont have any calculation yet
uint finalCalculation= 0;
function solarPanalMachine() public{
require(sunny ,"it is not sunny today");
//solarPanel is not run becuase it is not sunny today
finalCalculation +=3;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
//bool variable to check wether sunny
bool public sunny= true;
//finalCalculation zero we dont have any calculation yet
uint finalCalculation= 0;
function solarPanalMachine() public{
require(sunny ,"it is not sunny today");
//solarPanel is not run becuase it is not sunny today
finalCalculation +=3;
}
//Imagine we have machine that controls the wether
function changeWether() public{
sunny=!sunny; // little trick to change wether

}
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract errorMath{
function addNumber(uint256 myNumber) public pure returns(uint256){
// input myNumber have to greater than 0
require (myNumber>0);
return 1000/myNumber;
}
}

Return message to the caller and return revert to orginal state without consumed gas

//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract errorMath{
function addNumber(uint256 myNumber) public pure returns(uint256){
require (myNumber>0,"myNumber have to greater than zero");
return 1000/myNumber;
}
}

//execution cost 21913 gas (Cost only applies when called by a contract

//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract requireTest{
function oddNumber(uint256 myNumber) public pure returns(bool){
require (myNumber%2 !=0,"myNumber have to odd number");
return true;
}
}

add more require at one function

//SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract requireTest{
function Number(uint256 myNumber) public pure returns(bool){
require (myNumber>=0,"invalid uint8");
require (myNumber <=255, "invalid uint8");
return true;
}
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract requireTest{
uint256 a=100;
uint256 b=20;
function compare() public returns(uint){
require(a>b && a>50,"error a less than b");
a++;
return a;
}
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract requireTest{
uint256 a=100;
uint256 b=20;
function compare() public returns(uint){
require(a>b || a>50 ,"error a less than b");
a++;
return a;
}
}
// Solidity program to demonstrate require statement
pragma solidity ^0.5.0;

// Creating a contract
contract requireStatement {

// Defining function to check input
function checkInput(uint _input) public view returns(string memory){
require(_input >= 0, "invalid uint8");
require(_input <= 255, "invalid uint8");

return "Input is Uint8";
}

// Defining function to use require statement
function Odd(uint _input) public view returns(bool){
require(_input % 2 != 0);
return true;
}
}
// source geeksForgeeks

Require && Modifier

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract requireTest{
address owner;
string private name;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, 'Not Owner');
_;
}
function setName(string memory newName) public onlyOwner{
name = newName;
}
function getName () public view returns (string memory) {
return name;
}
}

Assert

  • Syntax : assert(bool condition)
  • Assert is used for validating and checking the current state befor move to the next line of code
  • Validate the internal local state of the function
  • Ensure that the state changes are consistant befor and after function execution
  • Assert reverts the state to the orginal state and returns any ethers sent to it however it consumes all gas supplied to it
  • Assert(bool condtion) in case condition is not met,this method call cuases an invalid opcode and any changes done to state got reverted,this method is to be used for internal errors
  • Assert should only be used to test for internal errors

code Example

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract assertTest{
function addNum(uint256 _num) public pure returns(uint256){
// input have to less than 100
assert(_num < 100);
return _num;

}
}

Error code and cuase

  • 0x00 : Used for generic compiler inserted panics.
  • 0x01 : If you call assert with an argument that evaluates to false.
  • 0x11 : If an arithmetic operation results in underflow or overflow outside of an unchecked { … } block.
  • 0x12 : If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0).
  • 0x21 : If you convert a value that is too big or negative into an enum type.
  • 0x22 : If you access a storage byte array that is incorrectly encoded.
  • 0x31 : If you call .pop() on an empty array.
  • 0x32 : If you access an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
  • 0x41 : If you allocate too much memory or create an array that is too large.
  • 0x51 : If you call a zero-initialized variable of internal function type.
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
//bool variable to check wether sunny
bool public sunny= true;
//finalCalculation zero we dont have any calculation yet
uint finalCalculation= 0;
function solarPanalMachine() public{
require(sunny ,"it is not sunny today");
finalCalculation +=3;
// finalCalculation can not be 6
assert(finalCalculation !=6);
}

function getFinalCalcilation() public view returns(uint256){
return finalCalculation;
}
}
// Solidity program to demonstrate assert statement
pragma solidity ^0.5.0;

// Creating a contract
contract assertStatement {

// Defining a state variable
bool result;

// Defining a function to check condition
function checkOverflow(uint _num1, uint _num2) public {
uint sum = _num1 + _num2;
assert(sum<=255);
result = true;
}

// Defining a function to print result of assert statement
function getResult() public view returns(string memory){
if(result == true){
return "No Overflow";
}
else{
return "Overflow exist";
}
}
}
// source geeksForgeeks

Revert

  • Syntax : revert CustomError(arg1, arg2);
  • Syntax2 : revert(); revert(“description”);
  • This is an out-of-the box function
  • Revert(string memory reason) this method aborts the execution and revert any chamges done to the state

code Example

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

contract VendingMachine {
address owner;

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.");
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.5;
contract requireTest{
//bool variable to check wether sunny
bool public sunny= true;
//it is sunny now so umbrella false we not need bring umbrella
bool public umbrella= false;
//Imagine we have machine that controls the wether
// function to change weather
function changeWether() public{
sunny=!sunny; // little trick to change wether

}
//function to check weather sunny or not to bring umbrella
function bringUmbrella() public{
if(!sunny){
umbrella=true;
}else{
revert("no need to bring umbrella today");
}
}
}
// Solidity program to demonstrate revert statement
pragma solidity ^0.5.0;

// Creating a contract
contract revertStatement {

// Defining a function to check condition
function checkOverflow(uint _num1, uint _num2) public view returns(string memory, uint){
uint sum = _num1 + _num2;
if(sum < 0 || sum > 255){
revert(" Overflow Exist");
}
else{
return ("No Overflow", sum);
}

}

}

}
// source geeksForgeeks

Require , Assert and Revert Gas cost

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
// compare gas cost
contract errorHandling {
uint ipadPrice=1000;
function buyIpad(uint _ipadPrice) external pure{
require(_ipadPrice <=1000);
}
function buyIpad2(uint _ipadPrice) external pure{
assert(_ipadPrice <=1000);
}
function buyIpad3(uint _ipadPrice) external pure{
if(_ipadPrice<=1000){
_ipadPrice;
}
}
function buyIpad4(uint _ipadPrice) external pure{
if(_ipadPrice<=1000){
_ipadPrice;
}else{
revert("price greater than 1000");
}
}

}
pragma solidity ^0.8.0;

contract Vendor {
address public seller;
modifier onlySeller() {
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function sell(uint amount) public payable onlySeller {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Perform the sell operation.
}
}
// source tutorialspoint
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Error {
function testRequire(uint _i) public pure {
// Require should be used to validate conditions such as:
// - inputs
// - conditions before execution
// - return values from calls to other functions
require(_i > 10, "Input must be greater than 10");
}

function testRevert(uint _i) public pure {
// Revert is useful when the condition to check is complex.
// This code does the exact same thing as the example above
if (_i <= 10) {
revert("Input must be greater than 10");
}
}

uint public num;

function testAssert() public view {
// Assert should only be used to test for internal errors,
// and to check invariants.

// Here we assert that num is always equal to 0
// since it is impossible to update the value of num
assert(num == 0);
}

// custom error
error InsufficientBalance(uint balance, uint withdrawAmount);

function testCustomError(uint _withdrawAmount) public view {
uint bal = address(this).balance;
if (bal < _withdrawAmount) {
revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount});
}
}
}
// source solidityByexample
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Account {
uint public balance;
uint public constant MAX_UINT = 2 ** 256 - 1;

function deposit(uint _amount) public {
uint oldBalance = balance;
uint newBalance = balance + _amount;

// balance + _amount does not overflow if balance + _amount >= balance
require(newBalance >= oldBalance, "Overflow");

balance = newBalance;

assert(balance >= oldBalance);
}

function withdraw(uint _amount) public {
uint oldBalance = balance;

// balance - _amount does not underflow if balance >= _amount
require(balance >= _amount, "Underflow");

if (balance < _amount) {
revert("Underflow");
}

balance -= _amount;

assert(balance <= oldBalance);
}
}
// source solidityByexample

--

--