Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

Intermediate1/3/2025, 11:35:24 AM
This article explores the Gas fee issues on the Ethereum mainnet and methods for optimization. It focuses on the Gas mechanism of the EVM, core concepts for optimizing Gas costs, and best practices for developing smart contracts. These include reducing storage usage, variable packing, optimizing data types, and using fixed-size variables.

By following these practices, developers can reduce Gas consumption in smart contracts, lower transaction costs, and create more efficient and user-friendly applications.

Gas fees on the Ethereum mainnet have always been a major issue, especially during periods of network congestion. During peak times, users often need to pay extremely high transaction fees. Therefore, optimizing Gas costs during the smart contract development phase is crucial. Gas optimization can not only effectively reduce transaction costs but also improve transaction efficiency, providing users with a more economical and efficient blockchain experience.

This article will outline the Gas fee mechanism of the Ethereum Virtual Machine (EVM), core concepts related to Gas fee optimization, and best practices for optimizing Gas fees when developing smart contracts. It is hoped that this content will inspire and assist developers, while also helping ordinary users better understand how the EVM Gas fee system works, together addressing the challenges within the blockchain ecosystem.

Overview of the EVM Gas Fee Mechanism

In EVM-compatible networks, “Gas” refers to the unit used to measure the computational power required to execute specific operations.

The diagram below illustrates the structure of the EVM. In the diagram, Gas consumption is divided into three parts: operation execution, external message calls, and memory and storage read/write.

Source: Ethereum Official Website[1]

Since the activation of EIP-1559 (London Hard Fork), Gas fees are calculated using the following formula:

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

The base fee is burned, while the priority fee serves as an incentive to encourage validators to include the transaction in the blockchain. Setting a higher priority fee when sending a transaction increases the likelihood of the transaction being included in the next block. This is similar to a “tip” paid by users to the validators.

1. Understanding Gas Optimization in EVM

When compiling a smart contract with Solidity, the contract is converted into a series of “operation codes,” or opcodes.

Each opcode (such as creating a contract, making message calls, accessing account storage, and executing operations on the virtual machine) has an associated Gas consumption cost, which is documented in the Ethereum Yellow Paper[2].

After multiple EIP modifications, the Gas costs of some opcodes have been adjusted, which may differ from the values in the Yellow Paper. For detailed information on the latest costs of opcodes, please refer to this source[3].

2. Basic Concepts of Gas Optimization

The core concept of Gas optimization is to prioritize cost-efficient operations on the EVM blockchain and avoid operations that incur high Gas costs.

In the EVM, the following operations are relatively low-cost:

  • [ ] Reading and writing memory variables
  • [ ] Reading constant and immutable variables
  • [ ] Reading and writing local variables
  • [ ] Reading calldata variables, such as calldata arrays and structs
  • [ ] Internal function calls

High-cost operations include:

  • [ ] Reading and writing state variables stored in contract storage
  • [ ] External function calls
  • [ ] Loop operations

EVM Gas Fee 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 the Gas consumption of smart contracts, 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 significantly higher than Memory. Each time a smart contract reads from or writes to storage, it incurs a high Gas cost.

According to the definition in the Ethereum Yellow Paper, the cost of storage operations is more than 100 times higher than memory operations. For example, opcodes like sload and sstore cost at least 100 Gas units in the best-case scenario, whereas memory operations like mload and mstore only consume 3 Gas units.

Methods to Limit Storage Usage Include:

  • [ ] Store non-permanent data in memory
  • [ ] Reduce the number of storage modifications: By saving intermediate results in memory, and only assigning the final result to storage variables once all calculations are completed.

2. Variable Packing

The number of Storage slots used in a smart contract and how developers represent data can significantly impact Gas consumption.

The Solidity compiler packs consecutive storage variables during the compilation process, using 32-byte storage slots as the basic unit for variable storage. Variable packing refers to the practice of arranging variables in a way that allows multiple variables to 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.

By making this adjustment, developers can save 20,000 Gas units (as storing an unused storage slot costs 20,000 Gas), but now only two storage slots are required.

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 using different data types, but the operation costs vary depending on the type. Choosing the appropriate data type helps 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 the EVM must first convert it to uint256, and this conversion incurs additional Gas costs.

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

On its own, using uint256 is cheaper than uint8. However, if we apply the previously suggested variable packing optimization, it makes a difference. If developers can pack four uint8 variables into a single storage slot, the total cost of iterating over them will be lower than using four uint256 variables. In this case, the smart contract can read and write the storage slot once, and load all four uint8 variables into memory/storage in a single operation.

4. Use Fixed-Size Variables Instead of Dynamic Variables

If the data can be constrained to 32 bytes, it is recommended to use the bytes32 data type instead of bytes or strings. Generally, fixed-size variables consume less Gas than dynamically sized variables. If the byte length can be limited, try to choose the smallest length from bytes1 to bytes32.

5. Mappings vs. Arrays

In Solidity, data lists can be represented using two data types: Arrays and Mappings, each with distinct syntax and structure.

Mappings are generally more efficient and cost-effective in most cases, while arrays are iterable and support data type packing. Therefore, it is recommended to prioritize using mappings 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 either calldata or memory. The main difference is that memory can be modified by the function, while calldata is immutable.

Keep this principle in mind: if function parameters are read-only, prefer using calldata instead of memory. This avoids unnecessary copy operations from function calldata to memory.

Example 1: Using memory

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

Example 2: Using calldata

When reading values directly from calldata, the intermediate memory operation is skipped. This optimization reduces the execution cost to only 2,413 Gas units, resulting in 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 computed at compile-time and stored in the contract’s bytecode. Therefore, their access cost is much lower compared to storage variables. It is recommended to use the Constant or Immutable keywords whenever possible.

8. Use Unchecked When Overflow/Underflow Is Not a Concern

When developers can be certain that arithmetic operations will not result in overflow or underflow, they can use the unchecked keyword introduced in Solidity v0.8.0 to avoid unnecessary overflow or underflow checks, thus saving Gas costs.

In the diagram below, the conditionally constrained i

In addition, compiler versions 0.8.0 and above no longer require the use of the SafeMath library, as the compiler itself now includes built-in overflow and underflow protection.

9. Optimize the modifier

The code of modifiers is embedded into the functions they modify. Each time a modifier is used, its code is duplicated, which increases the bytecode size and raises Gas consumption. Here’s one way to optimize the Gas cost of modifiers:

Before optimization:

After optimization:

In this example, by refactoring the logic into an internal function _checkOwner(), which can be reused in the modifier, the bytecode size is reduced and Gas costs are lowered.

10. Short-Circuit Optimization

For || (OR) and && (AND) operators, logical operations are evaluated with short-circuiting, meaning that if the first condition is enough to determine the result of the logical expression, the second condition will not be evaluated.

To optimize Gas consumption, conditions with lower computation costs should be placed first, so that potentially expensive calculations can be skipped.

General Recommendations

1. Remove 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 contract deployment costs and keep the contract size small.

Here are some practical suggestions:

Use the most efficient algorithms for calculations. If the contract directly uses certain calculation results, redundant calculations should be removed. Essentially, any unused calculations should be deleted. In Ethereum, developers can receive 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 high-cost loop operations, try to merge loops, and move repeated calculations out of the loop body.

2. Use Precompiled Contracts

Precompiled contracts provide complex library functions such as cryptography and hashing operations. Since the code is not executed on the EVM but runs locally on the client node, less Gas is required. Using precompiled contracts can save Gas by reducing the computational workload required to execute the smart contract.

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

For a complete list of precompiled contracts supported by the Ethereum network, refer to this link [4].

3. Use Inline Assembly

Inline assembly allows developers to write low-level but efficient code that can be directly executed by the EVM, without using expensive Solidity opcodes. Inline assembly also allows for more precise control over memory and storage usage, further reducing Gas costs. Additionally, inline assembly can perform some complex operations that are difficult to implement with Solidity alone, offering more flexibility for optimizing Gas consumption.

Here is an example of using inline assembly to save Gas:

As seen in the above example, the second case, which uses inline assembly, has higher Gas efficiency compared to the standard case.

However, using inline assembly can also introduce risks and is prone to errors. Therefore, it should be used cautiously and is recommended only for experienced developers.

4. Use Layer 2 Solutions

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

Layer 2 solutions like rollups, sidechains, and state channels 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, which in turn lowers Gas fees. Using Layer 2 solutions also enhances Ethereum’s scalability, allowing more users and applications to participate in the network without causing congestion from overload.

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.

These tools can help minimize the bytecode size, remove unused code, and reduce the number of operations required to execute smart contracts. Combined with other Gas optimization libraries like “solmate,” developers can effectively lower Gas costs and improve the efficiency of smart contracts.

Conclusion

Optimizing Gas consumption is an important step for developers, as it not only minimizes transaction costs but also improves the efficiency of smart contracts on EVM-compatible networks. By prioritizing cost-saving operations, reducing storage usage, utilizing inline assembly, and following other best practices discussed in this article, developers can effectively lower Gas consumption of contracts.

However, it is important to note that during the optimization process, developers must exercise caution to avoid introducing security vulnerabilities. In the process of optimizing code and reducing Gas consumption, the inherent security of the smart contract should never be compromised.

[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

Disclaimer:

  1. This article is reproduced from [PANewslab]. The copyright belongs to the original author [CertiK]. If you have any objection to the reprint, please contact Gate Learn team, the team will handle it as soon as possible according to relevant procedures.
  2. Disclaimer: The views and opinions expressed in this article represent only the author’s personal views and do not constitute any investment advice.
  3. Other language versions of the article are translated by the Gate Learn team. Unless otherwise stated, the translated article may not be copied, distributed or plagiarized.

Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

Intermediate1/3/2025, 11:35:24 AM
This article explores the Gas fee issues on the Ethereum mainnet and methods for optimization. It focuses on the Gas mechanism of the EVM, core concepts for optimizing Gas costs, and best practices for developing smart contracts. These include reducing storage usage, variable packing, optimizing data types, and using fixed-size variables.

By following these practices, developers can reduce Gas consumption in smart contracts, lower transaction costs, and create more efficient and user-friendly applications.

Gas fees on the Ethereum mainnet have always been a major issue, especially during periods of network congestion. During peak times, users often need to pay extremely high transaction fees. Therefore, optimizing Gas costs during the smart contract development phase is crucial. Gas optimization can not only effectively reduce transaction costs but also improve transaction efficiency, providing users with a more economical and efficient blockchain experience.

This article will outline the Gas fee mechanism of the Ethereum Virtual Machine (EVM), core concepts related to Gas fee optimization, and best practices for optimizing Gas fees when developing smart contracts. It is hoped that this content will inspire and assist developers, while also helping ordinary users better understand how the EVM Gas fee system works, together addressing the challenges within the blockchain ecosystem.

Overview of the EVM Gas Fee Mechanism

In EVM-compatible networks, “Gas” refers to the unit used to measure the computational power required to execute specific operations.

The diagram below illustrates the structure of the EVM. In the diagram, Gas consumption is divided into three parts: operation execution, external message calls, and memory and storage read/write.

Source: Ethereum Official Website[1]

Since the activation of EIP-1559 (London Hard Fork), Gas fees are calculated using the following formula:

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

The base fee is burned, while the priority fee serves as an incentive to encourage validators to include the transaction in the blockchain. Setting a higher priority fee when sending a transaction increases the likelihood of the transaction being included in the next block. This is similar to a “tip” paid by users to the validators.

1. Understanding Gas Optimization in EVM

When compiling a smart contract with Solidity, the contract is converted into a series of “operation codes,” or opcodes.

Each opcode (such as creating a contract, making message calls, accessing account storage, and executing operations on the virtual machine) has an associated Gas consumption cost, which is documented in the Ethereum Yellow Paper[2].

After multiple EIP modifications, the Gas costs of some opcodes have been adjusted, which may differ from the values in the Yellow Paper. For detailed information on the latest costs of opcodes, please refer to this source[3].

2. Basic Concepts of Gas Optimization

The core concept of Gas optimization is to prioritize cost-efficient operations on the EVM blockchain and avoid operations that incur high Gas costs.

In the EVM, the following operations are relatively low-cost:

  • [ ] Reading and writing memory variables
  • [ ] Reading constant and immutable variables
  • [ ] Reading and writing local variables
  • [ ] Reading calldata variables, such as calldata arrays and structs
  • [ ] Internal function calls

High-cost operations include:

  • [ ] Reading and writing state variables stored in contract storage
  • [ ] External function calls
  • [ ] Loop operations

EVM Gas Fee 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 the Gas consumption of smart contracts, 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 significantly higher than Memory. Each time a smart contract reads from or writes to storage, it incurs a high Gas cost.

According to the definition in the Ethereum Yellow Paper, the cost of storage operations is more than 100 times higher than memory operations. For example, opcodes like sload and sstore cost at least 100 Gas units in the best-case scenario, whereas memory operations like mload and mstore only consume 3 Gas units.

Methods to Limit Storage Usage Include:

  • [ ] Store non-permanent data in memory
  • [ ] Reduce the number of storage modifications: By saving intermediate results in memory, and only assigning the final result to storage variables once all calculations are completed.

2. Variable Packing

The number of Storage slots used in a smart contract and how developers represent data can significantly impact Gas consumption.

The Solidity compiler packs consecutive storage variables during the compilation process, using 32-byte storage slots as the basic unit for variable storage. Variable packing refers to the practice of arranging variables in a way that allows multiple variables to 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.

By making this adjustment, developers can save 20,000 Gas units (as storing an unused storage slot costs 20,000 Gas), but now only two storage slots are required.

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 using different data types, but the operation costs vary depending on the type. Choosing the appropriate data type helps 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 the EVM must first convert it to uint256, and this conversion incurs additional Gas costs.

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

On its own, using uint256 is cheaper than uint8. However, if we apply the previously suggested variable packing optimization, it makes a difference. If developers can pack four uint8 variables into a single storage slot, the total cost of iterating over them will be lower than using four uint256 variables. In this case, the smart contract can read and write the storage slot once, and load all four uint8 variables into memory/storage in a single operation.

4. Use Fixed-Size Variables Instead of Dynamic Variables

If the data can be constrained to 32 bytes, it is recommended to use the bytes32 data type instead of bytes or strings. Generally, fixed-size variables consume less Gas than dynamically sized variables. If the byte length can be limited, try to choose the smallest length from bytes1 to bytes32.

5. Mappings vs. Arrays

In Solidity, data lists can be represented using two data types: Arrays and Mappings, each with distinct syntax and structure.

Mappings are generally more efficient and cost-effective in most cases, while arrays are iterable and support data type packing. Therefore, it is recommended to prioritize using mappings 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 either calldata or memory. The main difference is that memory can be modified by the function, while calldata is immutable.

Keep this principle in mind: if function parameters are read-only, prefer using calldata instead of memory. This avoids unnecessary copy operations from function calldata to memory.

Example 1: Using memory

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

Example 2: Using calldata

When reading values directly from calldata, the intermediate memory operation is skipped. This optimization reduces the execution cost to only 2,413 Gas units, resulting in 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 computed at compile-time and stored in the contract’s bytecode. Therefore, their access cost is much lower compared to storage variables. It is recommended to use the Constant or Immutable keywords whenever possible.

8. Use Unchecked When Overflow/Underflow Is Not a Concern

When developers can be certain that arithmetic operations will not result in overflow or underflow, they can use the unchecked keyword introduced in Solidity v0.8.0 to avoid unnecessary overflow or underflow checks, thus saving Gas costs.

In the diagram below, the conditionally constrained i

In addition, compiler versions 0.8.0 and above no longer require the use of the SafeMath library, as the compiler itself now includes built-in overflow and underflow protection.

9. Optimize the modifier

The code of modifiers is embedded into the functions they modify. Each time a modifier is used, its code is duplicated, which increases the bytecode size and raises Gas consumption. Here’s one way to optimize the Gas cost of modifiers:

Before optimization:

After optimization:

In this example, by refactoring the logic into an internal function _checkOwner(), which can be reused in the modifier, the bytecode size is reduced and Gas costs are lowered.

10. Short-Circuit Optimization

For || (OR) and && (AND) operators, logical operations are evaluated with short-circuiting, meaning that if the first condition is enough to determine the result of the logical expression, the second condition will not be evaluated.

To optimize Gas consumption, conditions with lower computation costs should be placed first, so that potentially expensive calculations can be skipped.

General Recommendations

1. Remove 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 contract deployment costs and keep the contract size small.

Here are some practical suggestions:

Use the most efficient algorithms for calculations. If the contract directly uses certain calculation results, redundant calculations should be removed. Essentially, any unused calculations should be deleted. In Ethereum, developers can receive 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 high-cost loop operations, try to merge loops, and move repeated calculations out of the loop body.

2. Use Precompiled Contracts

Precompiled contracts provide complex library functions such as cryptography and hashing operations. Since the code is not executed on the EVM but runs locally on the client node, less Gas is required. Using precompiled contracts can save Gas by reducing the computational workload required to execute the smart contract.

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

For a complete list of precompiled contracts supported by the Ethereum network, refer to this link [4].

3. Use Inline Assembly

Inline assembly allows developers to write low-level but efficient code that can be directly executed by the EVM, without using expensive Solidity opcodes. Inline assembly also allows for more precise control over memory and storage usage, further reducing Gas costs. Additionally, inline assembly can perform some complex operations that are difficult to implement with Solidity alone, offering more flexibility for optimizing Gas consumption.

Here is an example of using inline assembly to save Gas:

As seen in the above example, the second case, which uses inline assembly, has higher Gas efficiency compared to the standard case.

However, using inline assembly can also introduce risks and is prone to errors. Therefore, it should be used cautiously and is recommended only for experienced developers.

4. Use Layer 2 Solutions

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

Layer 2 solutions like rollups, sidechains, and state channels 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, which in turn lowers Gas fees. Using Layer 2 solutions also enhances Ethereum’s scalability, allowing more users and applications to participate in the network without causing congestion from overload.

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.

These tools can help minimize the bytecode size, remove unused code, and reduce the number of operations required to execute smart contracts. Combined with other Gas optimization libraries like “solmate,” developers can effectively lower Gas costs and improve the efficiency of smart contracts.

Conclusion

Optimizing Gas consumption is an important step for developers, as it not only minimizes transaction costs but also improves the efficiency of smart contracts on EVM-compatible networks. By prioritizing cost-saving operations, reducing storage usage, utilizing inline assembly, and following other best practices discussed in this article, developers can effectively lower Gas consumption of contracts.

However, it is important to note that during the optimization process, developers must exercise caution to avoid introducing security vulnerabilities. In the process of optimizing code and reducing Gas consumption, the inherent security of the smart contract should never be compromised.

[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

Disclaimer:

  1. This article is reproduced from [PANewslab]. The copyright belongs to the original author [CertiK]. If you have any objection to the reprint, please contact Gate Learn team, the team will handle it as soon as possible according to relevant procedures.
  2. Disclaimer: The views and opinions expressed in this article represent only the author’s personal views and do not constitute any investment advice.
  3. Other language versions of the article are translated by the Gate Learn team. Unless otherwise stated, the translated article may not be copied, distributed or plagiarized.
Empieza ahora
¡Registrarse y recibe un bono de
$100
!