The gas fee of Ethereum mainnet has always been a difficult problem, especially when the network is congested. During peak hours, users often need to pay extremely high transaction fees. Therefore, it is particularly important to optimize the gas fee 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 Ethereum Virtual Machine (EVM) Gas fee mechanism, the core concepts of Gas fee optimization, and the best practices for Gas fee optimization when developing smart contracts. We hope that these contents can provide inspiration and practical help for developers, and also help ordinary users better understand how EVM Gas fees work, and jointly cope with challenges in the blockchain ecosystem.

Introduction to EVM's Gas Fee Mechanism

In EVM-compatible networks, "Gas" refers to the 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 reading and writing of memory and storage.

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

 Source: Ethereum official website [1]

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

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

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

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

1. Understanding Gas Optimization in EVM

When a smart contract is compiled with Solidity, it is converted into a series of "operation codes", or opcodes.

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

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

After multiple EIP revisions, the gas costs of some opcodes have been adjusted and may deviate from the yellow paper. For detailed information on the latest opcode costs, please refer to here [3].

2. Basic Concepts of Gas Optimization

The core idea of ​​gas optimization is to prioritize cost-efficient operations on the EVM blockchain and avoid operations with expensive gas costs.

In the EVM, the following operations are cheap:

  • Read and write memory variables
  • Reading constant and immutable variables
  • Reading and writing local variables
  • Read calldata variables, such as calldata arrays and structures
  • Internal function call

Costly operations include:

  • Read and write state variables stored in contract storage
  • External function call
  • Loop Operation

Best Practices for EVM Gas Fee Optimization

Based on the above basic concepts, we have compiled a list of best practices for gas fee optimization for the developer community. By following these practices, developers can reduce gas fee consumption of smart contracts, reduce transaction costs, and build 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 the storage, it incurs a high Gas cost.

According to the definition in the Ethereum Yellow Paper, storage operations are more than 100 times more expensive than memory operations. For example, the OPcodes mload 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 in Ethereum Smart Contracts

Ways to limit storage usage include:

  • Storing non-persistent data in memory
  • Reduce the number of storage modifications: By storing intermediate results in memory, assign the results to storage variables after all calculations are completed.

2. Variable Packaging

The number of Storage slots used in a smart contract 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 packing means arranging variables reasonably so that multiple variables can fit into a single storage slot.

On the left is a less efficient implementation that consumes 3 storage slots; on the right is a more efficient implementation.

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

By adjusting this detail, developers can save 20,000 Gas units (storing an unused storage slot consumes 20,000 Gas), but now only two storage slots are required.

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

3. Optimize data types

A variable can be represented by multiple data types, but different data types have different operation costs. Choosing the right data type helps optimize the use of Gas.

For example, in Solidity, integers can be subdivided into different sizes: uint8, uint16, uint32, etc. Since the EVM performs operations 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 in Ethereum Smart Contracts

We can compare the Gas cost 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.

Individually, using uint256 here is cheaper than uint8. However, this is different when using the variable packing optimization we suggested earlier. If the developer is able to 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 the 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 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 in Ethereum Smart Contracts

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

5. Maps and Arrays

Solidity lists can be represented by two data types: Arrays and Mappings, but their syntax and structure are completely different.

Mapping is more efficient and cheaper 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 packing.

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 the function, while calldata is immutable.

Remember this principle: if the function parameter is read-only, calldata should be used instead of memory. This can avoid unnecessary copy operations from function calldata to memory.

Example 1: Using memory

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

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

Example 2: Using calldata

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

When reading values ​​directly from calldata, the intermediate memory operations are skipped. This optimization reduces the execution cost to only 2,413 gas units, a 35% improvement in gas efficiency.

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, their access cost is much lower than storage, and it is recommended to use the Constant or Immutable keywords whenever possible.

8. Use Unchecked when overflow/underflow is not going to happen

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 unnecessary overflow or underflow checks, thereby saving gas costs.

In the figure below, subject to the condition i

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

In addition, compiler versions 0.8.0 and above no longer require the use of the SafeMath library, as overflow and underflow protection is built into the compiler itself.

9. Optimize 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 the size of the bytecode and increases gas consumption. Here is a way to optimize the gas cost of the modifier:

Before optimization:

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

After optimization:

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

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

10. Short-circuit optimization

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

To optimize gas consumption, conditions with low computational cost should be placed first, so that expensive calculations can be skipped as much as possible.

Additional general advice

1. Delete unused 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 the cost of contract deployment and keep the contract size small.

Here are some practical suggestions:

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

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

Loop optimization: Avoid high-cost 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 is not run on the EVM, but locally on the client node, less Gas is required. Using precompiled contracts can save Gas by reducing the amount of computational work required to execute smart contracts.

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

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

3. Use inline assembly code

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

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

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

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

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

4. Use Layer 2 Solutions

Using Layer 2 solutions can reduce the amount of data that needs to be stored and calculated on the Ethereum mainnet.

Layer 2 solutions like rollups, sidechains, and state channels are able to offload transaction processing from the main Ethereum chain, enabling faster and cheaper transactions.

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

5. Use optimization tools and libraries

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

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

These tools can help minimize the size of bytecode, remove useless 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 to both minimize transaction costs and improve the efficiency of smart contracts on EVM-compatible networks. Developers can effectively reduce the gas consumption of contracts by prioritizing cost-saving operations, reducing storage usage, leveraging inline assembly, and following other best practices discussed in this article.

However, it is important to note that during the optimization process, developers must be careful to prevent the introduction of security vulnerabilities. The inherent security of smart contracts should never be sacrificed in the process of optimizing code and reducing gas consumption.

[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