DEV Community

SK Sabiruddin
SK Sabiruddin

Posted on • Updated on

Ethereum Gas Optimization Techniques

Gas Units:

  • Making transactions on Ethereum or any other Ethereum backed blockchain will cost you a gas fee. The total gas fee consists of two parts: Gas Units x Gas Price

  • Gas units are a number that depends on the amount of computation required for a transaction. E.g. If you send some Ether (Ethereum’s native cryptocurrency) to someone, it requires 21,000 gas units. It’s the minimum number of units required for any transaction.

  • The number of required gas units increases as the complexity of the transaction increases. E.g. Sending an ERC-20 token might cost around 60,000 gas units. Minting an NFT might cost around 120,000 gas units, and so on.

  • It all depends on how many computations need to be made while processing the transaction.

Gas Price:

  • Gas price is determined by the demand for making transactions. The more the traffic, the higher the price.

  • It’s usually expressed in Gwei which is a billionth part of an Ether. If the gas price is 100 Gwei, it means 1 gas unit currently costs 100 Gwei as per the demand. The gas price will vary throughout the day depending on the total traffic.


  • Let’s combine these two now. If you need to send some Ether to someone at a time when gas price for 1 gas unit is 100 Gwei, you’ll have to pay a total of: 21,000 gas units x 100 Gwei = 2,100,000 Gwei.

  • Since Gwei is 1/1bn of Ether, you’ll pay 0.0021 Ether.

  • To find the amount in dollars, multiply it with the current price of ether i.e. 1200 USD. Hence 0.0021 Ether x 1200 USD = 2.52 USD total gas fee. To summarise, the gas fee depends on:

    • Complexity of transaction (gas unit)
    • Demand for making transactions (gas price)
    • Price of Ether (to calculate dollar value)

Where does my gas fee go?

  • So, we know that we need to pay a gas fee when making a transaction. But where does it go now? Is it burnt? Or is it given to the validator?
  • Actually, it’s both, after the EIP-1559 (an upgrade to Ethereum that happened in September 2022).
  • Total gas fee is made up of:
    • Base Fee (fixed for every transaction of a block) – which gets burnt entirely.
    • Max Priority Fee (decided by the user) – which goes to the validator partially or entirely depending on the Max Total Fee which is again decided by the user.
  • Let’s run through an example:

    • Suppose a user wants to pay a Max Total Fee of 100 Gwei per gas unit consumed for the transaction. It’s inclusive of the Base Fee (burnt) and Max Priority Fee (for validator).
    • While making the transaction, the user doesn’t know what the exact Base Fee will be for the transaction/block.
    • During peak time, a lot of people will be trying to make a transaction, thus congesting the network. This will cause your transaction to take a long time before going through. In order to prioritise his/her transaction, a user may want to pay some (extra) fee to the block validator. This fee is called the Max Priority Fee. Suppose the user wants it to be 30 Gwei per gas unit.
    • Hence, the user enters the values of 100 Gwei and 30 Gwei respectively.
    • When the block is confirmed, there are 2 possibilities depending on what is the final value of Base Fee:
Case 1: Base Fee + Max Priority Fee <= Max Total Fee

    E.g.

    Base Fee = 60, Max Priority Fee = 30, Max Total Fee = 100

    Hence,

        - 60 Gwei is burnt, (per gas unit)

        - 30 Gwei goes to the validator, (per gas unit)

        - The remaining 10 Gwei is refunded to the user (per gas unit)
Enter fullscreen mode Exit fullscreen mode
Case 2: Base Fee + Max Priority Fee > Max Total Fee

    E.g.

    Base Fee = 80, Max Priority Fee = 30, Max Total Fee = 100

    Hence,

        - 80 Gwei is burnt, (per gas unit)

        - Only 20 Gwei goes to the validator (per gas unit), since Max Total Fee is 100 Gwei

        - 0 refund for the user since the max limit is consumed.
Enter fullscreen mode Exit fullscreen mode
  • Note: Base Fee can’t be >= Max Total Fee. Your Metamask wallet will let you know if the Max Total Fee entered by you is not sufficient as per the network conditions.

How to save Gas Cost:

Important: This document contains useful information to optimize your Gas Cost in Ethereum Blockchain. The following document contains 90% of the gas optimization techniques in Ethereum.

  • We will see which method is useful and which method is useless. (or event harmful)

  • We will see how much gas you can save by using each method.

  • We will rank all the methods by using a tier list. (A = excellent, D= useless/harmful)

1. Deployment optimization vs Call optimization:

At first, you need to know that there are 2 ways to optimize gas on a Decentralized Application.

A). Optimizing Deployment costs:

  • You can optimize the cost to deploy a smart contract.

  • The cost = 21000 gas (for creating transaction) + 32000 gas (for creating a contract) + CONSTRUCTOR EXECUTION + BYTES DEPLOYED * 78.125

  • So, for deploying a smart contract, you will spend at least 53000 gas.

  • To minimize gas costs you need to minimize the size of a smart contract and the cost of the constructor.

B). Optimize every function call:

  • You can also optimize the cost of calling every function in your smart contract.

  • The cost is equal to21000 gas (for creating transaction) + the function execution cost).

  • So, the objective will be to reduce the function execution cost.

Note that these 2 means of optimization are not the same!!

  • A lot of times, you may deploy a larger solidity code (so with higher deployment gas cost) but more optimized for every function call. (So with a smaller calling gas cost)

  • If you need to choose which type of optimization, you need to use it wisely.

  • I strongly advise you to optimize every function call as the deployment is made only one time and the user experience will be enhanced. (unless your smart contract size exceeds the limit of 24Kb)


2. Ranking Gas optimization methods:

  • We will rank all the gas optimization methods by using a tier list. (Because there is a difference between saving 2 gas on a transaction of 21000 gas and saving 10000 gas.)

    • Tier A: If you don’t know these rules, you’re not a TRUE solidity developer.
    • Tier B: Essential, should be known by all the developers.
    • Tier C: may be useful sometime, should be known by developers too.
    • Tier D: Useless/harmful, don’t use them (unless you know what you are doing). You may lose, either your time or the security of the smart contract.

  • Tier A:

A1 : Knowing how much each EVM opcodes cost:

  • Here is the 6 of the most costly gas opcodes (among the most used is smart contracts):

    • CREATE/CREATE2 -> Deploys a smart contract. (32,000 gas)
    • SSTORE -> Stores a value in storage. (20,000 gas if the slot hasn’t been accessed, 2900otherwise.)
    • EXTCODESIZE -> Get the size in bytes of a smart contract code. (2600 if not accessed before, 100 gas otherwise)
    • BALANCE (address(this).balance), same as EXTCODESIZE.
    • SLOAD -> Access a value in storage. (2100 gas if not accessed before 100 gas otherwise)
    • LOG4 -> Create an event with 4 topics. (1875 gas)
  • (This is in fact an estimation. Some of them may vary depending on the situation.)

  • The best website which describes how much each opcode is https://www.evm.codes/, it’s very well explained.

  • If you know that, you’ll better understand solidity and you’ll get a “better” intuition of how much each line of code costs you.

A2: Use view the modifier:

function getBalance() external view {
                return balance;
        }
Enter fullscreen mode Exit fullscreen mode
  • You can easily cut your gas costs to 0 when you call the smart contract if you don’t write to the storage. (moreover, you don’t need to wait 5–20 seconds the response)
  • (A3 Understand the solidity programming language & A4 Become good at computer science/algorithms)

  • Tier B:

These tips and tricks can save you a good amount of gas and should be implemented as much as possible. IMPORTANT NOTE BEFORE STARTING:

  • As the not_optimized() function is situated before optimized() function in the byte code, Calling optimized() costs more than not_optimized() with the same code (22 more gas), so I will subtract 22 gas on each call tooptimized() function.

B1: Batching operations (Saved gas: 21000 gas * batched transactions):

  • Submitting a transaction alone on the blockchain costs a lot of gas (21,000 gas to be precise), so if you can find a way to batch transactions for your users, it can save you a good amount of gas.

B2: change orders of storage slot (20,000 gas saved at deployment):

  • Ethereum storage is composed of slots of 32 bytes, the problem is that writing is expensive. (Up to 20,000 gas using “cold” writing)
  • Let’s say you have 3 variables:

    // These vars use 3 different storage slots
         unit128 a; // slot 0(16 bytes)
         unit256 b; // slot 1(32 bytes)
         unit128 c; // slot 2(16 bytes)

    // These var use 2 different storage slots
         unit256 b; // slot 0(32 bytes)
         unit128 a; // slot 1(16 bytes)
         unit128 c; // slot 1(16 bytes)
Enter fullscreen mode Exit fullscreen mode
  • But if we align the 3 slots well, we can economize 1 slot. (a and c variable will be in the same slot)

  • Therefore, there are 1 less used slot at the deployment. (So 20,000 gas saved)

  • Moreover, if you want to access the c variable, but the b variable is already accessed. It will count as a warm access (accessing a storage slot already accessed) so it will cost you 1000instead of 2900 gas which is pretty significant.

  • If you don’t understand the storage, the documentation may help you: https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html

B3: Use the optimizer (Saved gas: 10–50% on deployment & call):

  • You can use the solidity built-in optimizer.
  • This is a very simple way to save a lot of gas without learning any new concepts. You need to check the “enable optimization” checkbox under Advance configuration section.
  • A value near to 1 will optimize gas cost, but a value near to the max (2³²-1) will optimize calls to the function. In most of the cases you can use the default value (200)

B4: use mapping instead of array (5000 gas saved per value):

  • Mappings are usually less expensive than arrays, but you can’t iterate over them.
      mapping(unit => unit) map;
      unit[] map2;

      function optimized() external{
          map[1] = 10;
      }

       function not_optimized() external{
          map2.push(10);
      }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 48,409 gas
  • optimised() function gas usage: 43,400 gas

B5: Use require instead of assert:

  • Assert should NOT be used by other means than testing purposes. (because when the assertion fails, gas is NOT refunded contrary to require)

B6: Use self-destruct (save up to 24,000 gas):

  • selfdestruct() function destroys the smart contract and refunds 24,000 gas.
  • In a more complex transaction like deploying another smart contract, you can call selfdestruct() on the smart contract to economize some gas.

B7: Make fewer external calls (amount saved: variable):

  • Make a call to another contract only when you’re obligated to do so.

B8: Look for dead code (saves a variable amount of gas on deployment):

  • Sometimes, developers forget to remove useless code like:
      require(a == 0)
      if (a == 0) {
         return true;
      } else {
          return false
      }
Enter fullscreen mode Exit fullscreen mode
  • This is a dump example because it shows you that some devs can forget to remove useless parts.

B9: use immutable variables (saves 15,000 gas on deployment)":

  • Use immutable storage variables whenever it’s possible.
       contract not_optimised{
       unit256 public a;
       constructor(unit256 _a) public{
             a = _a;
         } 
      }
Enter fullscreen mode Exit fullscreen mode
       contract optimised{
       unit256 public immutable a;
       constructor(unit256 _a) public{
            a = _a;
         } 
       }
Enter fullscreen mode Exit fullscreen mode
  • not_optimisedcontract gas usage: 11,6800 gas
  • optimised contract gas usage: 10,1013 gas

B10: Storing data in events (up to 20,000 gas saved on function call):

  • If you don’t need to access the data on-chain in solidity, you can store it using events.
                address store;
                event Store(uint256 indexed key, 
                            address indexed data);

                function optimized() external{
                    emit Store(1, 0xaaC532..);
                }

                function not_optimized() external{
                    store = 0xaaC532..;
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 43,353 gas
  • optimised() function gas usage: 22,747 gas

  • Tier C:

  • These tips can save you a fair amount of gas and cost you nothing.

C1: Use static size type in solidity (about 50 gas saved):

  • Static size types (like bool, uint256, bytes5) are cheaper than dynamic size type (like string or bytes).
                function optimized() external{
                    bytes4 str = 0x61626364;
                }

                function not_optimized() external{
                     bytes4 memory str = "abcd";
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,255 gas
  • optimised() function gas usage: 21,227 gas

C2: Cold access vs warm access (70 saved gas):

  • Don’t access the same storage variable twice.
                uint256 a = 100;
                function optimized() external{
                    uint tmp =  a;
                    uint b   =  tmp;
                    uint c   =  tmp;
                }

                function not_optimized() external{
                     uint b   =  a;
                     uint c   =  a;
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 23,412 gas
  • optimised() function gas usage: 23,347 gas

C3: Using calldata instead of memory (450 saved gas per call):

        string str;
        function optimized(string calldata _str) external{

                }

        function not_optimized(string memory _str) external{

                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 22,442 gas
  • optimised() function gas usage: 21,994 gas

C4: Use Indexed events (62 gas saved on function call per topic):

  • You can mark each event as indexed as bellow:
  • Indexed events allow events to be searched more easily.
   event Test1(address a, address b);
   event Test2(address indexed a, address indexed b);

   function optimized(string calldata _str) external{

        }

    function not_optimized() external {
            emit Test1(0x..,0x..)
        }
    function optimized() external {
            emit Test2(0x..,0x..)
        }
Enter fullscreen mode Exit fullscreen mode
  • Here the function optimised() uses 135 less gas than not_optimised().

C5: Changing the order of calls (20–2000 saved gas per call):

  • We can call every smart contract, you supply in msg.data the signature of the function. (the first 4 bytes of the function keccak256 hash).

  • Firstly, the EVM will do a “switch” of this signature, to see which function to execute.

      switch(msg.data[0:4]) { // compare to signature
        case 0x01234567: go to functionA;

        // cost 22 more gas..
        case 0x11111111: go to functionB;

        // cost 22+22 more gas... 
        case 0x4913aaaa: go to functionC; 
              ...
      }
Enter fullscreen mode Exit fullscreen mode

C6: Use i++ instead of i = i+1 (62 gas saved on each call):

  • This sounds like a joke, but it seems to work.:
               function optimized() external{
                    uint i = 10;
                    i++;
                }
                function not_optimized() external{
                    uint i = 10;
                    i = i + 1;
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,401 gas
  • optimised() function gas usage: 21,339 gas

C7: Use uint256 instead of uintXX on storage (55 gas saved per call):

  • To write/access data on storage, the EVM uses 32 bytes (= 256 bits slots), by using smaller types than uint256, the EVM needs to perform additional operations to assure the conversion. So better to use uint256 instead of uint8 for storing data.
                uint256 balance;
                uint8 balance2;
                function not_optimized() external{
                    balance2 = 10;
                }
                function optimized() external{
                    balance = 10;
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 43,353 gas
  • optimised() function gas usage: 43,298 gas

C8: Create a custom error (24 gas saved per call):

  • You can create a custom error on solidity and revert with it like below:
                error err(string test);
                function not_optimized() external{
                   revert("test");
                }
                function optimized() external{
                    revert err("test");
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,476 gas
  • optimised() function gas usage: 21,474 gas

C9: Exchanging 2 variables by using a tuple (a, b) = (b, a) (saves 5 gas on every call):

               function not_optimized() external{
                   uint i = 10 ;
                   uint j = 20 ;
                   uint temp = i ;
                   i = j ;
                   j = temp;
                }
                function optimized() external{
                    uint i = 10;
                    uint j = 20 ;
                    (i,j) = (j,i);
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,241 gas
  • optimised() function gas usage: 21,236 gas
  • We put it on the C tier because the notation is way more readable.

C10: Use Calldata instead of Memory in function arguments in case of not changing the passed data:

  • Using calldata in a summing function that loops over an input array can save about 1829 gas (or 3.5%) on average.
   function not_optimised(uint[] memory nums) external {
     for (uint i = 0; i < nums.length; ++i){
        ...
    }
}

   function optimised(uint[] calldata nums) external {
     for (uint i = 0; i < nums.length; ++i){
        ...
   }
}


Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 50,992 gas
  • optimised() function gas usage: 49,163 gas

  • Tier D: (Useless/harmful)

These optimizing gas tips are useless, don’t lose your time for saving a low amount of gas it will be shadowed anyway by the 21,000gas transaction.

D1: using unchecked (gas saved: 150/operation):

  • Since solidity 0.8.0, operations like + * / — are checked every time if there is an overflow/underflow.

  • But it cost a little bit of gas to verify if there is an overflow/underflow.

  • Unless you are doing a lot of operations in a single function call (like in for loops), you should NOT use unchecked{}.

               function not_optimized() external{
                   uint i = 10 ;
                   uint j = 20 ;
                   uint add;
                   add = a + b;
                }
                function optimized() external{
                   uint i = 10 ;
                   uint j = 20 ;
                   uint add;
                   unchecked {c = a + b};
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,419 gas
  • optimised() function gas usage: 21253 gas

D2: changing and optimizing the bytecode yourself:

  • Don’t do this unless you have a good reason to do so, some of the functions may end up with undefined behavior and security issues.
  • Use the optimizer instead or implement a very strict testing policy.

D3: Delete Errors string messages (about 78.125 gas saved on deployment cost per characters):

  • Don’t do it, the code may be harder to debug for you and for users.
  • For example, someone can replace:
         require(account != address(0), 
           "ERC20: mint to the zero address");
    To:-
         require(account != address(0))
Enter fullscreen mode Exit fullscreen mode

D4: using low level assembly (gas saved: variable):

  • If you don't know what you’re doing, DON’T do solidity assembly. You will end up having a lot more chances to have a security hole on your contract for a marginal saved gas amount.

D5: Use external instead of public:

  • Surprisingly they are not any difference between external and public functions,
       function not_optimized() public returns (uint){
                 return 777;
             }
       function optimized() external returns(uint){
                return 777;
           }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,401 gas
  • optimised() function gas usage: 21,379 gas (22)

D6: Use left shift instead of multiplication >> (150 saved gas):

  • Shifting binary data n times to the left results in multiplying the data by 2^n. Here is an example:
      00000001 = 1 dec
      00000010 = 2 dec
      00000100 = 4 dec
      00001000 = 8 dec

     function not_optimized()  external returns (uint){
                   uint a = 5;
                   uint b = a * 1024;
                   return b;
              }

     function optimized() external returns(uint){
                   uint a = 5;
                   uint b = a >> 10;
                   return b;
              }

Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,615 gas
  • optimised() function gas usage: 21,436 gas
  • Gains are not bad, but the >> operator doesn't check for an overflow.

D7: Delete metadata hash (3000–5000 gas on deployment):

  • On every smart contract byte code a “hash” is appended in the end.
  • This is the hash of all the metadata of the smart contract. (Including abi, comments, code…)
  • As the metadata hash is 32 bytes in length, it can save you a bunch of gas.
  • But this is quite hard to do, because you have to do it by hand.
  • So it may be dangerous if executed badly.

D8: Add payable to every function (24 gas saved per call):

  • Adding payable to a function, removes the verification on msg.value. Therefore, some gas is saved.
              function optimized() external payable{
                  ...
                }

              function not_optimized() external{
                  ...
                }
Enter fullscreen mode Exit fullscreen mode
  • not_optimised() function gas usage: 21,186 gas
  • optimised() function gas usage: 21,184 gas

D9: Doing the important work offchain:

  • Be careful about the work you’re doing off-chain (Like in JS).
  • For example, you can’t do security verification off-chain in the website front end, this is very dangerous as anyone may be able to change the JS code in the browser and send bad input to the smart contract.
  • ALWAYS DO THE SECURITY VERIFICATION ON-CHAIN.!!!

Some other gas saving techniques: -

- Loading state variable to memory
- Replace for loop i++ with ++i
- Caching array elements
- Short circuit
Enter fullscreen mode Exit fullscreen mode

Top comments (0)