Gas Optimization Standards in Solidity

Lolu BolarinwaLolu Bolarinwa
8 min read

Introduction

Ever had to reconsider carrying out a blockchain transaction due to high transaction costs? Perhaps the cost of initiating the transaction was so high you began to outweigh the benefit of carrying out the transaction in the first place. You are not alone. High Gas Fees are a common setback that is experienced among folks in the blockchain industry. This is where the need for Gas optimization is important, especially among developers building backend infrastructure that supports modern decentralized applications. It is currently not without challenges, but the future holds exciting possibilities.

What is Gas, and Gas Optimization?

Gas in the blockchain industry refers to the unit of measurement for the amount of computational effort required to carry out specific transactions in the blockchain network. It refers to the fee necessary to successfully conduct a transaction or execute a blockchain contract. It gained popularity due to the increasing cost needed to complete transactions on the Ethereum Virtual Machine, EVM.

The gas fee is used to compensate miners or validators for the computational resources they use to process transactions. The fees are a fraction of the cryptocurrency denomination used in the transaction. In the case of Ethereum, it is called gwei, and for Bitcoin, it is known as satoshi.

Gas optimization simply entails the methods used to reduce the initial cryptocurrency cost associated with sending, delivering, receiving and exchanging cryptocurrency and crypto-related assets in the blockchain industry. Gas optimization is a critical aspect of energy management, aimed at enhancing efficiency and reducing costs.

There are a couple of methods used to carry out gas optimization but the most effective is about making the Solidity smart contract code less expensive to execute.

The effect of gas optimization is significant. It can lead to a reduction in the overall number of operations needed to run a smart contract. Optimized smart contracts not only reduce the gas needed to process transactions they also protect against malicious misuse.

Smart Contract Gas Optimization Techniques

1. USING A SOLIDITY COMPILER OPTIMIZER

The Solidity compiler optimizer works best by minimizing the size of the code and making complex expressions simpler. This results in reduced cost of execution and deployment costs.

When writing Solidity smart contracts, it is recommended to run it through the Solidity optimizer to tell the Solidity compiler to produce highly optimized bytecode. The Solidity compiler uses two different optimizer modules to optimize smart contract code. It uses the opcode level optimizer and the Yul IR code optimizer. You can specify which optimization flag the solidity compiler should use to produce highly optimized bytecode.

The opcode optimizer applies simplification methods that combine equal code sets and remove unused smart contract code. The Yul-based optimizer is much more powerful due to its ability to be effective in cross-function approach.

module.exports = {
  solidity: {
    version: "0.8.19",
    settings: {
      optimizer: {
        enabled: true,
        runs: 10000,
      },
    },
  },
};

The above code packet shows the "runs" which specify how often the opcode will be executed throughout the life of the contract. The 'runs' value is set at '10000' because although deployment cost increases when the code is optimized for more "runs", but it costs less after deployment throughout the lifetime of the contract.

2. Using of uint8

In solidity, every operation is based on 256-bit words. When you use a smaller data type like uint8 which is 8 bits, additional operations are needed to downscale from 256 bits to 8 bits. This is why using uint8 can often be more computationally expensive than just storing data in a uint256.

contract A { uint8 a = 0; }                //contract A will cost more gas

contract B { uint a = 0; // or uint256 }   //contract B will cost less gas

However, the order in which the state variable is declared can affect the number of storage slots the contract uses, as is the case in structs. Where smaller-sized unsigned integers like uint8 are more effective when recommended.

3. Upgrade Pragma to the Latest Solidity Compiler Version

Newer compiler versions have been packaged with additional safety standards and optimizers. Using an outdated solidity compiler version is discouraged. The advantages of avoiding older solidity compiler versions include:

  • Ethereum Hardfork Support for staking contracts after the Ethereum Shanghai hard fork which completed the upgrade of the Ethereum network from proof-of-work to a proof-of-stake consensus mechanism. Solidity v0.8.20 included the first support for Shanghai. This meant that the generated bytecode would include PUSH0 opcodes, which significantly impact the overall gas fee charged during a function call.

  • Solidity v0.8.19 includes an assembler to avoid duplicating subassembly bytecode where possible which significantly cuts double spending on function calls. A custom NatSpec annotation to abstract free functions was also included.

  • The Solidity v0.8.18 was the first version to include support for the Ethereum Paris upgrade as the default EVM version, and also allow for named parameters in mapping types. This version includes the elimination of keccak256 calls if the value was already calculated by a previous call and can be reused. This can significantly affect the gas cost calculation for input data to a SHA3 Keccak-256 operation.

  • In Solidity v0.8.17 Yul Optimizer simplifies the starting offset of zero-length operations to zero. It also prevents the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call, and more efficient overflow checks for multiplication is implemented.

    Nevertheless, it is also not advisable to use the most recently released solidity compiler. This is because the latest compiler version might be in beta mode and could be subject to incompatibility issues with other smart contract-building tools needed to deploy and run the application.

4. Use of Mapping vs Arrays

The choice of using mapping instead of arrays can prove significant in the overall gas consumption cost. Arrays and Maps are the two data types used to hold data in Solidity.

Mappings are essentially hashtables that provide constant-time lookups regardless of their size. They help you store data while still maintaining the same heap size. This means that to access an element in a map it will cost the same amount of gas no matter how many elements are in it. Mappings in Solidity are generally less expensive. Most of the time, it is better to use mapping because of its cheaper operations.

mapping(uint => string) public planets          //specify the Data type of uint
 constructor() public {                         //constructor function
        planets[101] = "Mars";
        planets[102] = "Jupiter";
        planets[103] = "Mercury";
    }
}

Arrays on the other hand are more expensive than mappings because the gas cost of using an array increases with the size of the array. However, an array can be the correct choice when using smaller data types.

string planets[];
planets = ["Mars", "Jupiter", "Mercury"];

5. Using Batch Operations

One of the few methods where using arrays is encouraged rather than mapping is batching. Batch operations can significantly reduce gas costs in Ethereum by processing multiple transactions as one. This is done by passing dynamically sized arrays that can execute the same functionality in a single transaction. Conducting multiple transactions individually will rack up the overall cost of transactions.

This makes things more efficient. It is especially important for Ethereum development, where higher gas prices can make small savings add up.

function batchSend(Call[] memory _calls) public payable {
       for(uint256 i = 0; i < _calls.length; i++) {
           (bool _success, bytes memory _data) = _calls[i].recipient.call{gas: _calls[i].gas, value: _calls[i].value}(_calls[i].data);
           if (!_success) {

               assembly { revert(add(0x20, _data), mload(_data)) }
           }
       }
   }

Other External Factors that Affect Gas Prices

Lolu Bolarinwa Gas Optimization Techniques in Smart Contracts

Outside poor solidity coding practices, many factors may influence the gas cost of a blockchain network. Some of these factors are uncontrollable and seem to stem from the network effect associated with the blockchain network. Some of them include;

  1. Network Congestion: This is directly dependent on the number of people participating in the network. An example of this is observed in changes in the gas cost on the Ethereum Network during a busy period in the network volume. This is because each transaction is competing for the same block at the same time.

  2. Time of Transaction: The period in time when the network traffic is lightest often correlates to busy awake time in the region that has the most network users. An example is that the Ethereum network is generally cheaper when North America is asleep. This is because miners or validators prioritize transactions based on their gas price, with higher-priced transactions generally being processed more quickly. Timing your transactions when there is less demand on the network can help reduce gas costs.

  3. Layer-2 Solutions: Layer-2 solutions established on a particular blockchain offer lower gas fees and faster transaction speed finality. They handle more transactions as a bulk rollup and deliver a smoother user experience.

  4. Popular DApps or NFTs: The popularity of certain decentralized applications (DApps) or non-fungible tokens (NFTs) can cause network congestion, leading to higher gas fees. This is common when there is an absence of trusted and reliable alternatives to functional applications. Timing the period you use popular applications can help minimize high gas fees.

  5. Gas Frontrunning: This is a practice where validators or miners, who are responsible for running the software that approves transactions on the network, can reorder, include, or omit transactions in ways that benefit them financially. Certain platforms implement front-running protection that guarantees fair gas slippage for consumers. While applications notorious for front-running should be avoided.

Conclusion

In conclusion, gas optimization is a critical aspect of smart contract development on the blockchain network. It involves a variety of techniques, from choosing the right data types and good solidity optimization practices to using batch operations and efficient algorithms. However, it’s not just about the code. External factors such as network congestion, time of transaction, gas front-running and the popularity of certain DApps or NFTs can also influence gas costs. Therefore, a comprehensive approach to gas optimization should consider both the internal aspects of smart contract development and the external dynamics of the blockchain network. By doing so, developers can ensure that their smart contracts are not only efficient and cost-effective, but also robust and adaptable to the ever-changing landscape of the web3 ecosystem.

0
Subscribe to my newsletter

Read articles from Lolu Bolarinwa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Lolu Bolarinwa
Lolu Bolarinwa