image source head

Top 10 Best Practices for Gas Optimization of Ethereum Smart Contracts

trendx logo

Reprinted from panewslab

12/31/2024·4M

The Gas fee of the Ethereum main network has always been a persistent problem, especially when the network is congested. During peak periods, users often have to pay extremely high transaction fees. Therefore, it is particularly important to optimize Gas fees during the smart contract development stage. Optimizing Gas consumption can not only effectively reduce transaction costs, but also improve transaction efficiency, bringing users a more economical and efficient blockchain experience.

This article will outline the Gas fee mechanism of the Ethereum Virtual Machine (EVM), the core concepts related to Gas fee optimization, and the best practices for Gas fee optimization when developing smart contracts. We hope that through these contents, we can provide inspiration and practical help to developers, and also help ordinary users better understand how EVM's gas fees operate, and jointly cope with challenges in the blockchain ecosystem.

Introduction to EVM’s Gas Fee Mechanism

In an EVM-compliant network, "Gas" refers to a unit used to measure the computing power required to perform a specific operation.

The following figure illustrates the structural layout of the EVM. In the figure, Gas consumption is divided into three parts: operation execution, external message calls, and memory and storage reading and writing.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

 Source: Ethereum official website[1]

Since the execution of each transaction requires computing resources, a fee is charged to prevent infinite loops and denial of service (DoS) attacks. The fee required to complete a transaction is called "gas fee".

Since EIP-1559 (London Hard Fork) came into effect, gas fees are calculated by the following formula:

Gas fee = units of gas used * (base fee + priority fee)

The base fee is burned and the priority fee serves as an incentive for validators to add transactions to the blockchain. Setting a higher priority fee when sending a transaction increases the likelihood that the transaction will be included in the next block. This is similar to a "tip" paid by users to validators.

1. Understand Gas optimization in EVM

When compiling a smart contract with Solidity, the contract is converted into a series of "opcodes", or opcodes.

Any piece of operation code (such as creating a contract, making a message call, accessing account storage, and performing operations on a virtual machine) has a recognized Gas consumption cost, which is recorded in the Ethereum Yellow Paper [2].

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

After many EIP modifications, the Gas costs of some of the opcodes have been adjusted and may deviate from those in the Yellow Book. Details on the latest cost of opcodes can be found here[3].

2.Basic concepts of Gas optimization

The core concept of Gas optimization is to prioritize cost-effective operations on the EVM blockchain and avoid expensive Gas-cost operations.

In EVM, the following operations are less expensive:

  • Read and write memory variables
  • Read constants and immutable variables
  • Read and write local variables
  • Read calldata variables, such as calldata arrays and structures
  • internal function call

Costlier operations include:

  • Read and write state variables stored in contract storage
  • external function call
  • Loop operation

EVM Gas Cost Optimization Best Practices

Based on the above basic concepts, we have compiled a list of gas fee optimization best practices for the developer community. By following these practices, developers can reduce smart contract gas consumption, lower transaction costs, and create more efficient and user-friendly applications.

1. Minimize storage usage

In Solidity, Storage is a limited resource, and its Gas consumption is much higher than Memory. Every time a smart contract reads or writes data from storage, it incurs a high gas cost.

According to the definition of the Ethereum Yellow Paper, the cost of storage operations is more than 100 times higher than that of memory operations. For example, OPcodesmload and mstore instructions consume only 3 Gas units, while storage operations such as sload and sstore cost at least 100 units even in the best case.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

Ways to limit storage usage include:

  • Store non-persistent data in memory
  • Reduce the number of storage modifications: by saving intermediate results in memory, and then assigning the results to storage variables after all calculations are completed.

2. Variable packaging

The number of storage slots used in smart contracts and the way developers represent data will greatly affect the consumption of gas fees.

The Solidity compiler will pack continuous storage variables during the compilation process, and use 32-byte storage slots as the basic unit of variable storage. Variable packaging refers to arranging variables appropriately so that multiple variables can fit into a single storage slot.

The left side is a less efficient implementation that will consume 3 storage slots; the right side is a more efficient implementation.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

By adjusting this detail, developers can save 20,000 Gas units (it costs 20,000 Gas to store an unused storage slot), but now only need two storage slots.

Since each storage slot consumes gas, variable packing optimizes gas usage by reducing the number of required storage slots.

3. Optimize data types

A variable can be represented by multiple data types, but different data types have different operating costs. Choosing the appropriate data type can help optimize Gas usage.

For example, in Solidity, integers can be subdivided into different sizes: uint8, uint16, uint32, etc. Since the EVM operates in 256-bit units, using uint8 means that the EVM must first convert it to uint256, and this conversion consumes additional Gas.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

We can compare the Gas costs of uint8 and uint256 through the code in the figure. The UseUint() function consumes 120,382 Gas units, while the UseUInt8() function consumes 166,111 Gas units.

Taken individually, using uint256 here is cheaper than uint8. However, it's different when using the variable packing optimization we suggested earlier. If the developer can pack four uint8 variables into a single storage slot, the total cost of iterating over them will be lower than four uint256 variables. This way, the smart contract can read and write the storage slot once and put four uint8 variables into memory/storage in one operation.

4. Use fixed-size variables instead of dynamic variables

If the data can be controlled within 32 bytes, it is recommended to use the bytes32 data type instead of bytes or strings. Generally speaking, fixed-size variables consume less gas than variable-size variables. If the byte length can be limited, try to choose the minimum length from bytes1 to bytes32.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

5. Mapping and Arrays

Solidity's data lists can be represented by two data types: arrays (Arrays) and mappings (Mappings), but their syntax and structure are completely different.

Maps are more efficient and less expensive in most cases, but arrays are iterable and support data type packing. Therefore, it is recommended to use mapping first when managing data lists, unless iteration is required or Gas consumption can be optimized through data type packaging.

6. Use calldata instead of memory

Variables declared in function parameters can be stored in calldata or memory. The main difference between the two is that memory can be modified by functions, while calldata is immutable.

Remember this principle: if the function parameters are read-only, calldata should be used in preference to memory. This avoids unnecessary copying from function calldata to memory.

Example 1: Using memory

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

When using the memory keyword, the array values ​​are copied from the encoded calldata to memory during ABI decoding. The execution cost of this block of code is 3,694 Gas units.

Example 2: Using calldata

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

When reading values ​​directly from calldata, the intermediate memory operations are skipped. This optimization method reduces the execution cost to only 2,413 Gas units, and the Gas efficiency increases by 35%.

7. Use Constant/Immutable keywords whenever possible

Constant/Immutable variables are not stored in the contract's storage. These variables are calculated at compile time and stored in the contract's bytecode. Therefore, they are much less expensive to access than storage, and it is recommended to use the Constant or Immutable keywords whenever possible.

**8. Use Unchecked when ensuring that overflow/underflow does not

occur**

When developers are sure that arithmetic operations will not cause overflow or underflow, they can use the unchecked keyword introduced in Solidity v0.8.0 to avoid redundant overflow or underflow checks, thereby saving Gas costs.

In the figure below, subject to the condition i

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

In addition, compilers version 0.8.0 and above no longer require the use of the SafeMath library because the compiler itself has built-in overflow and underflow protection.

9. Optimize the modifier

The code of the modifier is embedded in the modified function, and its code is copied every time the modifier is used. This increases bytecode size and gas consumption. Here's one way to optimize the gas cost of your modifier:

Before optimization:

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

After optimization:

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

In this case, by refactoring the logic into the internal function _checkOwner(), allowing the internal function to be reused in the modifier, the bytecode size is reduced and the gas cost is reduced.

10. Short circuit optimization

For the || and && operators, logical operations undergo short-circuit evaluation, that is, if the first condition is already capable of determining the result of the logical expression, the second condition is not evaluated.

In order to optimize Gas consumption, conditions with low computational cost should be placed first, so that it is possible to skip costly calculations.

Additional general advice

1. Delete useless code

If there are unused functions or variables in the contract, it is recommended to delete them. This is the most direct way to reduce contract deployment costs and keep contract size small.

Here are some practical suggestions:

Use the most efficient algorithm for calculations. If the results of certain calculations are directly used in the contract, then these redundant calculation processes should be removed. Essentially, any unused calculations should be deleted.

In Ethereum, developers can obtain Gas rewards by releasing storage space. If a variable is no longer needed, it should be deleted using the delete keyword or set to its default value.

Loop optimization: avoid costly loop operations, merge loops as much as possible, and move repeated calculations out of the loop body.

2. Use precompiled contracts

Precompiled contracts provide complex library functions such as encryption and hashing operations. Since the code does not run on the EVM but locally on the client node, less gas is required. Using precompiled contracts can save gas by reducing the computational effort required to execute smart contracts.

Examples of precompiled contracts include Elliptic Curve Digital Signature Algorithm (ECDSA) and SHA2-256 hashing algorithm. By using these precompiled contracts in smart contracts, developers can reduce gas costs and improve application operating efficiency.

For a complete list of precompiled contracts supported by the Ethereum network, see here[4].

3. Use inline assembly code

In-line assembly allows developers to write low-level yet efficient code that can be executed directly by the EVM without using expensive Solidity opcodes. Inline assembly also allows more precise control of memory and storage usage, further reducing gas charges. In addition, inline assembly can perform some complex operations that are difficult to achieve using Solidity alone, providing more flexibility for optimizing Gas consumption.

Here is a code example that uses inline assembly to save gas:

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

As can be seen from the figure above, the second use case using inline assembly technology has higher Gas efficiency compared to the standard use case.

However, using inline assembly can also be risky and error-prone. Therefore, it should be used with caution and only by experienced developers.

4. Use Layer 2 solutions

Using Layer 2 solutions reduces the amount of data that needs to be stored and computed on the Ethereum mainnet.

Layer 2 solutions like rollups, sidechains, and state channels enable faster and cheaper transactions by offloading transaction processing from the main Ethereum chain.

By bundling a large number of transactions together, these solutions reduce the number of on-chain transactions, thereby lowering gas fees. Using Layer 2 solutions can also improve Ethereum’s scalability, allowing more users and applications to participate in the network without overloading the network and causing congestion.

5. Use optimization tools and libraries

Several optimization tools are available, such as the solc optimizer, Truffle's build optimizer, and Remix's Solidity compiler.

Top 10 Best Practices for Gas Optimization of Ethereum Smart
Contracts

These tools can help minimize the size of bytecode, remove dead code, and reduce the number of operations required to execute smart contracts. Combined with other Gas optimization libraries, such as "solmate", developers can effectively reduce Gas costs and improve the efficiency of smart contracts.

in conclusion

Optimizing Gas consumption is an important step for developers, both to minimize transaction costs and to improve the efficiency of smart contracts on EVM-compatible networks. Developers can effectively reduce the gas consumption of their contracts by prioritizing cost-saving operations, reducing storage usage, leveraging inline assembly, and following other best practices discussed in this article.

However, it must be noted that during the optimization process, developers must operate with caution to prevent the introduction of security vulnerabilities. In the process of optimizing code and reducing gas consumption, the inherent security of smart contracts should never be sacrificed.

[1] :https://ethereum.org/en/developers/docs/gas/

[2] :https://ethereum.github.io/yellowpaper/paper.pdf

[3] :https://www.evm.codes/

[4] :https://www.evm.codes/precompiled

more