Smart Contract Optimization: How to Use Less Gas in Ethereum

Written by iamshvetsov | Published 2023/11/06
Tech Story Tags: ethereum | smart-contracts | gas-optimization | cryptocurrency | solidity | evm | smart-contract-development | gas-fees

TLDRvia the TL;DR App

It is well known that most transactions on Ethereum require a fee to ensure that the network is spam-resistant and doesn’t get stuck in an endless loop of calculations.

The gas fee is the amount of gas required to complete the transaction multiplied by the cost per unit of gas. The fee is paid whether the transaction succeeds or fails. As of November 2023, gas costs about 10 gwei. Although 1 gwei is now equivalent to about US$0.000002, we need to understand that this is just one unit, and you have to pay tens and sometimes hundreds of thousands of gas to complete transactions, which will be a more tangible amount. That is why we need to keep optimization in mind.

As developers, we cannot influence the price of gas, so we need to focus on the units of gas consumed by the code to reduce it as much as possible.

Due to the optimizations, developing on Ethereum is different from developing Web 2.0 applications because we have to give up familiar patterns, such as splitting into small functions, for ease of testing. In this article, I will provide several examples, for each of which I will give an optimized and unoptimized smart contract with the amount of gas consumed.

Optimizations and operation codes

There are two main approaches to optimization:

  • Optimizing the cost of deployment (minimizing the size of the smart contract and the cost of

    executing the constructor)

  • Optimizing each function call

The first thing to check is that the smart contract does not contain unnecessary and unused code, as deployment is compiled to bytecode, and the more of it there is, the more expensive deployment will be.

Here are some expensive gas operation codes in EVM:

  • CREATE/CREATE2 – Deploys a smart contract (32,000 gas)

  • SSTORE – Stores the value in memory (20,000 gas if the slot has not been accessed, 2,900

    otherwise)

  • BALANCE (address(this).balance) – (2,600 gas if not previously accessed)

A complete list of all the codes for the different forks can be found here: https://www.evm.codes.

Optimizing each function call is a better strategy because the smart contract is deployed once.

Tips

Do not initialize variables with default values

If a variable is not initialized, the compiler assumes that it has a default value: 0 for uint, “” for string, false for bool, 0x(0) for address. So, initializing with default values is a waste of gas.

Organize variables

Solidity stores variables in cells of 32 bytes. If a variable is smaller than a cell, it is packed with another variable, but such variables must be declared side by side.

Do not truncate variables

When declaring a single variable, it makes sense to use uint256, because otherwise, the variable has to be truncated, which takes a bit more time.

Use static values

In cases where there is an option to pre-calculate static values, it is better to use it, even if it is hardcoded. For instance, in the example below, you need to call two functions that increase the gas consumption.

Use constant variables

For variables that will be constants, just one immutable keyword will save a lot of gas.

Do not declare temporary variables

It may seem harmless to introduce an extra variable that is stored in memory rather than on the blockchain, but adding an intermediate variable requires more gas.

Use mappings instead of arrays

This is not always possible, but where possible, arrays should be avoided as they are more expensive to work with.

Prefer a fixed-length array to a dynamic one

In some cases, you cannot avoid using arrays. Dynamic arrays are more expensive than fixed-length arrays because you have to change the array length when using the push and pop functions.

Use the data type you need in the array

What applies to variables does not apply to arrays. If you specify the required type, the engine will pack the data in the correct way.

Do not create many functions that call each other

In any other language, the smaller the function, the better it is for debugging and testing, but things are not the same in Solidity. It is also desirable to minimize function calls to third-party smart contracts.

Avoid multiple changes to state variables

If there is a variable in the blockchain whose value needs to be changed multiple times, it is better to create a temporary variable and write the final value to the blockchain after the loop is executed.

Optimize gas by removing unnecessary variables

Ethereum offers gas refunds for deleting variables. Using the delete keyword with a variable name will return 15,000 gas but no more than half of the transaction value.

Use selfdestruct() to compensate for gas

Deleting a contract without the option to restore will return 24,000 gas but no more than half of the transaction value.

Use Solidity Gas Optimizer

The built-in optimizer allows you to adjust the value of iterations depending on your optimization goals. A lower value should be set to minimize deployment costs, and a higher value should be set to optimize runtime costs. Below is the configuration file for the Solidity Optimizer with Hardhat.

Summary

At compile time, contract code is converted to byte code for EVM, and transaction codes have a cost depending on the Ethereum fork. Despite initiatives to reduce the cost of gas (in particular, the introduction of variable size blocks in the London update, which changed the way fees are calculated), as developers, we need to optimize both the deployment and function calls in smart contracts to save money on deployment and execution.


Written by iamshvetsov | I’m a Software Engineer with 5+ yoe, now I'm interested in Web3/Blockchain
Published by HackerNoon on 2023/11/06