Skip to content
Optimizing Gas in Solidity Smart Contracts: Choosing the Right Storage
George Tebrean

Published On - June 19, 2024

Optimizing Gas in Solidity Smart Contracts: Choosing the Right Storage

In recent years, Ethereum Virtual Machine (EVM) networks have gained significant traction. Every day, a growing number of new users join these networks, engaging in numerous transactions. However, this increased activity leads to rising transaction fees, sparking interest in reducing these fees to make Web3 apps more affordable and user-friendly.

One promising solution is optimizing the gas execution of smart contracts. By using the right implementation approach, developers can create more efficient smart contracts, thereby reducing gas fees. This optimization not only makes transactions cheaper but also enhances the overall user experience on EVM networks. As these improvements continue, the future of Web3 applications looks increasingly promising.

Solidity Development

Solidity is the most widely used programming language for developing smart contracts on Ethereum Virtual Machine (EVM) chains. Smart contracts are executed on-chain, and each action in a contract transaction incurs a gas cost. Naturally, complex or resource-intensive operations consume more gas.

The most gas-intensive operations are those related to storage. Adding and reading data from storage can become prohibitively expensive if not handled properly, utilizing all available storage areas. When examining EVM Codes, it is evident that STORE opcodes for storage are significantly more expensive than opcodes for memory usage. Specifically, they are 33 times more costly.

 

Opcode

Gas

Description

SLOAD

100

Load word from storage

SSTORE

100

Save word to storage

MSTORE

3

Load word from memory

MLOAD

3

Save word to memory

Storage Areas 

The EVM offers five storage areas: storage, memory, calldata, stack, and logs. In Solidity, code primarily interacts with the first three because it doesn’t have direct access to the stack. The stack is where EVM processing takes place, and accessing it requires low-level programming techniques. Logs are used by Solidity for events, but contracts cannot access log data once it is created.

Storage
  • A key-value store that maps 256-bit words to 256-bit words;
  • Stores all smart contract’s state variables which are mutable (constants are part of the contract bytecode);
  • Is defined per contract at deployment time.
Memory
  • Created for function calls;
  • Linear and addressable at the byte level;
  • Reads limited to 256 bits width, writes can be 8 or 256 bits wide;
  • Stores all function variables and objects specified with the memory keyword;
  • Recommended for storing mutable data for a short period.


Calldata
  • A temporary location which stores function arguments;
  • It can’t be written and is used only for readings.

Gas Optimization Approaches

To lower gas costs related to storage, prioritize using memory over storage. Consider the following smart contract which uses the storage area exclusively:

 
contract GasCostComparison { uint256[] private s_numbers; uint256 private s_sum; function numberSum()public returns(uint256) { for(uint i=0; i< s_numbers.length; i++){ s_sum+=s_numbers[i]; } return s_sum; } function initNumbers(uint256 n)public { for(uint i=0; i < n; i++){ s_numbers.push(i); } } }

If s_numbers is initialized by calling initNumbers with n=10, the gas usage for numberSum would be 53,010 gas.

Avoid Reading Too Often from Storage

In the `for` statement, we compare the index i with s_numbers.length. Even though we might think the array length is read from storage only once, it is read every time the comparison takes place. To optimize, read the length only once from storage:


function numberSum()public returns(uint256) { uint256 l = s_numbers.length; for(uint i=0; i< l; i++){ s_sum+=s_numbers[i]; } return s_sum; }

We store the length read from the storage in the l variable which is stored in the memory area of the new numberSum() function. 

This reduces gas usage to 51,945 gas, saving 1,065 gas.

Avoid Writing Too Often in Storage

Similarly, storing the final sum only at the end of the for statement in the s_sum state variable (which is in storage) is more efficient. Create a temporary variable sum in memory:


function numberSum()public view returns(uint256) { uint256 l = s_numbers.length; uint256 sum = 0; for(uint i=0; i< l; i++){ sum+=s_numbers[i]; } return sum; }

Gas execution this time is 27,770 gas, almost half of the previous cases.

Choosing the right storage type can significantly reduce blockchain gas fees, as shown in the examples above. Optimizing how data is stored and accessed is crucial for minimizing costs and improving the efficiency of smart contracts on Ethereum Virtual Machine (EVM) chains.

By prioritizing memory over storage for mutable data and understanding the nuances of gas costs associated with different operations, developers can significantly enhance the performance and cost-effectiveness of their applications in the Web3 ecosystem.

References:

Solidity Documentation



The Chainlens Blockchain Explorer provides all of the business metrics you need to support your blockchain and smart contract applications.
We provide SLA-backed production support for Ethereum networks running Quorum and Hyperledger Besu.
Traditional financial markets infrastructure is being redefined by blockchain and DLT technology. We can ensure you’re prepared.
With dedicated support from the creators of Web3j you can ensure you have a trusted partner to support your most critical blockchain applications.
Ethereum, Smart Contracts, Solidity