Gas Saving Techniques in Solidity
Tags: solidity, gas, blockchain, calldata
Published: 2022-07-09 20:12:55

`Gas` is a unit of computational measure in Solidity. This is the amount that we pay to interact and transact with a smart contracts. The `gas` is calculated via a simple formulae which is:
`Gas = Gas Price * Gas used`.
The `gas` price is not constant and it is dependent on the congestion of the network. The more users pushing transaction through the network the higher the `gas` price. The amount of `gas` used is determined by the operation performed by the smart contract. 

This blog post will discuss some techniques we might employ to reduce the `gas` spent when sending transaction to a smart contract. We will consider the following listed techniques to reduce the `gas` foot print of a smart contract.

* [Use constant and immutable variables for variable that don't change](#constant) 
* [Cache read variables in memory](#cache) 
* [Incrementing and decrementing by 1](#incrementing) 
* [calldata and memory ](#calldata) 
* [Calling view function ](#view) 
* [Integer overflow/underflow ](#overflow) 
* [Use revert instead of require ](#revert) 
* [Avoid loading too many data in memory](#memory) 
* [Other tips](#tips) 


## <a name="constant"></a> Use constant and immutable variables for variable that don't change

Using the `constant` and the `immutable` keywords for variables that do not change helps to save on `gas` used. The reason been that `constant` and `immutable` variables do not occupy a storage slot when compiled. They are saved inside the contract byte code. Lets see an example.



```js
//SPDX-License-Identifier:MIT
pragma solidity ^0.8.3;

interface IERC20 {    
 function transfer(address to, uint256 value) external returns (bool);    
function approve(address spender, uint256 value) external returns (bool);    
function transferFrom(address from, address to, uint256 value) external returns (bool);   
//rest of the interface code
}

//Gas used: 187302
contract Expensive {
   IERC20 public token;
   uint256 public timeStamp = 566777;

   constructor(address token_address) {
       token = IERC20();
   }

}

//Gas used: 142890
contract GasSaver {
   IERC20 public immutable i_token;
   uint256 public constant TIMESTAMP = 566777;

   constructor(address token_address) {
       i_token = IERC20(token_address);
   }
}

```
The first contract `Expensive` has a public variable of type `IERC20` and a `uint256` also defined as public. When we deployed this contract, the contract deployment used up a total of 187302 `gas` when deployed to Remix. The second contract named `GasSaver` also have the same variable structure as the `Expensive` contract but we made use of `constant` keyword for the `TIMESTAMP` because we will not be changing the value. For the `interface` we used the `immutable` keyword which means we can no longer modify the value of the `i_token` variable after setting it inside the `constructor`. This action alone reduced the gas consumption from 187302 to 142890.


## <a name="constant"></a> Cache read variables in memory

Reading from a variable in storage cost `gas`. When we access a variable for the first time it cost `2100 gas` and subsequent access will cost `100 gas`. If we have an array and want to perform some computation on the array elements. It could be more gas effective to read the `length` of the array and store it in a variable. If the array is not too large we could also read the whole `array` into memory and access it from memory instead of continuously reading from storage.

```js
 //SPDX-License-Identifier:MIT;

pragma solidity ^0.8.3;

contract Expensive {
   uint256[] public numbers;

   constructor(uint256[] memory _numbers) {
       numbers = _numbers;
   }
   
   //Gas used: 40146
   function sum() public view returns (uint256 ){
       uint256 total = 0;
       for (uint256 i=0; i < numbers.length; i++) {
          total += numbers[i];
       }
       return total;
   }
}

contract GasSaver {
   uint256[] public numbers;

   constructor(uint256[] memory _numbers) {
       numbers = _numbers;
   }
   
   //39434
   function sum() public view returns (uint256 ){
       uint256 total = 0;
       uint256 arrLength = numbers.length;
       uint256[] memory _numbersInMemory = numbers;
       for (uint256 i=0; i < arrLength; i++) {
           total += _numbersInMemory[i];
       }
       return total;
   }
}
```
The contract `GasSaver` above saved the length of the array in a variable `arrLength` and also loaded the array into memory. This helps to reduce the `gas` spent inside the function.


## <a name="incrementing"></a> Incrementing and Decrementing by 1

There are four ways to increment and decrement a variable by 1 in Solidity. Let us see an example.

```js
//SPDX-License-Identifier:MIT;

pragma solidity ^0.8.3;

contract One{
   uint256 public number;
  //Gas used : 43800
   function incrementByOne() public returns (uint256){
      number += 1;
      return number;
   }
}

contract Two{
   uint256 public number;
   //Gas used : 43787
   function incrementByOne() public returns (uint256){
     number = number + 1;
     return number;
   }
}

contract Three{
   uint256 public number;
   //Gas used : 43634
   function incrementByOne() public returns (uint256){
       return number++;
   }
}

contract Four{
   uint256 public number;
   //Gas used : 43628
   function incrementByOne() public returns (uint256){
       return ++number;
   }
}

```
The example above list four contracts which shows the way we could increment a number variable by a value of 1. The contract `Four` is more gas effective out of the lot when ran. To save more on `gas` when incrementing a variable use the method of increment in contract `Four`  (`++number`) which is more `gas` efficient.


## <a name="calldata"></a> calldata and memory
When running a function we could pass the function parameters as `calldata` or `memory` for variables such as `strings`, `structs`,`arrays` etc. If we are not modifying the passed parameter we should pass it as `calldata` because `calldata` is more `gas` efficient than `memory`. Let's see an example:

```js
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract GasSaver {
    //Gas used: 22471
    function passParameterAsCallData(string calldata _name) public returns (string memory){}
    //Gas used: 22913
    function passParameterAsMemory(string memory _name) public returns (string memory){}
}
```
Calling these two functions in Remix and passing the same parameter to the functions, you will noticed that setting a function `_name` parameter as `calldata` reduced the `gas` used. 
> You can only used `calldata` when you are not going to modify the value of the variable passed as `calldata` inside the function.

## <a name="view"></a> Calling a `view` function
A `view` function does not use `gas` when called, but if we decide to call a view function inside of another function which is a transaction, it then uses `gas`.

```js
//SPDX-License-Identifier:MIT;

pragma solidity ^0.8.3;
contract GasSaver {
    uint256[] private numbers = [2,3,5,67,34];

    function getNumberAt( uint256 _index ) public view returns (uint256){
        return numbers[_index];
    }
    //Gas used when getNumber was called: 44778

    //Gas used without calling getNumber() 44450
    function sumAndMultiply() public {
       uint256[] memory _numbers = numbers;
       uint256 arrlength = _numbers.length;
       for(uint256 i=0; i < arrlength; ++i){
          numbers[i] = _numbers[i] * i;
       }
       //getNumberAt(2);
    }
}

```
The above is a simple contract to illustrate the point about calling `view` functions inside of a transaction. The function `getNumberAt` is a `view` function that returns the value of the array at the passed index. Calling this function uses no `gas` because it is a view function, but if we decide to call this function inside of the `sumAndMultiply` function, we can see that the `gas` usage of `sumAndMultiply` 
has increased because the `view` function is called within it.

`Gas` used when `sumAndMutiply` functions is called: `44778`.
`Gas` used when `getNumberAt` `view` function is also called inside the `sumAndMultiply` function: `44450`.

So, we can see that calling a `view` function inside a transaction increased the amount of `gas` that should have been spent, if the `view` function was not included in the call.

## <a name="overflow"></a> Integer overflow/underflow 

Prior to Solidity `0.8.0` version, interger overflow and underflow checks were performed by using the `SafeMath` library. From Solidity `0.8.0` upward, the compiler does that check for us. 

This extra check cost `gas`. If we know that the mathematical operations we will perform inside the contract will not overflow or underflow, we can tell the compiler not to check for overflow or underflow in the operation. 

```js
//SPDX-License-Identifier:MIT;

pragma solidity ^0.8.3;

contract OverFlow {
    uint256 public numberOne;
    uint256 public numberTwo;

    //Gas used: 43440
    function setNumberOne() public {
        ++numberOne;
    }
    //Gas used: 43339
     function setNumberTwo() public {
       unchecked {
           ++numberTwo;
       }
    }
}
```

The above contract `OverFlow` has two functions used to set two variables inside the contract. The compiler in `setNumberOne` checks and prevents the number from overflow or underflow. In the second function `setNumberTwo` we are telling the compiler to mind its business and not check for overflow or underflow. Using `unchecked` means that the code block will not be checked for overflow or underflow.

If we know that our mathematics will be safe we could safe a little gas by using `unchecked`.

## <a name="revert"></a> Use revert instead of require
We use `require` to check if a statement is `true`. If the statement evaluates to false the transaction is reverted and the remaining gas returned to the user. Since the advert of custom error in Solidity we could use the `revert` statement to throw a custom error. Using `revert` instead of `require` is more gas efficient. You can run the code below to see the the amount of `gas` saved when `revert` is used instead of `require`.



```js
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
error NotEnough();
contract GasSaver {
   uint256 public number;
   //Gas Used: 21898
    function setNumber(uint256 _number) public {
        require(_number > 10, "number too small please");
        number = _number;
    }
     //Gas Used: 21669
     function setNumberAndRevert(uint256 _number) public {
        if ( _number < 10 ){
            revert NotEnough();
        }
        number = _number;
    }
}
```

## <a name="memory"></a> Avoid loading too many data in memory
Loading data in memory in a single transaction has the effect of increasing `gas`used. The `gas` used increase in a quadratic factor when more than `32kb` of memory is used in a single transaction. Let's see an example.

```js
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract One {
    //Gas used: 29261
    function setArrayInMemory() public pure {
        uint256[1000] memory _array;
    }
}

contract Two {
    //Gas used: 276761
    function setArrayInMemory() public pure {
        uint256[10000] memory _array;
    }
}

//Gas used: 922855
contract Three {
    function setArrayInMemory() public pure {
        uint256[20000] memory _array;
    }
}

contract Four {
    //Gas used: 3386918
    function setArrayInMemory() public pure {
        uint256[40000] memory _array;
    }
}
```
The above example defined four contracts and each contract has a function named `setArrayInMemory` where memory space is reserved for an array of `uint256`. 

Contract `One` loads reserves space in memory for 1000 `uint256` numbers and when the function is executed it uses up 29261 `gas`. Contract `Two` creates memory space for 10000 `uint256` numbers and we can see that the `gas` used is 276761 which is roughly about 10 times of the `gas` used by Contract `One`.

Things gets interesting in Contract `Three` which reserves space for 20000 `uint256` numbers. From the `gas` used which is 922855, we can see that once the memory used in a single transaction exceeds 32 kilobyte, the quadratic factor of the memory expansion cost kicks in thereby increasing the `gas` used in a quadratic way.

> Do not populate arrays with enormous amounts of items in a single transaction to prevent `gas` used increase in a quadratic way.

## <a name="tips"></a> Other tips

+ Always put your `require` statement on top in the function before making any state change so that if the `require` statement fails, the remaining `gas` is refunded to the user on revert.

+ When comparing operation using `&&` or `||` with a `require` statement; you should put the cheaper part of the operation first so that if it fails, the compiler will not compare the second part thereby saving on `gas` used.

In conclusion We can use a combination of these techniques highlighted here to improve the `gas` consumption of our smart contracts thereby saving our users from spending too much on `gas` when interacting with our smart contracts.

Thanks for reading.