Top 10 Best Practices for Gas Optimization in Ethereum Smart Contracts

CN
8 months ago

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

Written by: Certik

The Gas fees on the Ethereum mainnet have always been a significant issue, especially during network congestion. During peak times, users often have to pay extremely high transaction fees. Therefore, optimizing Gas fees during the smart contract development phase is particularly important. Optimizing Gas consumption not only effectively reduces transaction costs but also enhances 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), the 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 provide practical help to developers while also assisting ordinary users in better understanding how EVM's Gas fees operate, collectively addressing the 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 computational power required to execute specific operations.

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

Source: Ethereum Official Website[1]

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

Since the implementation 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 add the transaction to the blockchain. Setting a higher priority fee when sending a transaction can increase the likelihood of the transaction being included in the next block. This is similar to a "tip" paid by users to validators.

1. Understanding Gas Optimization in EVM

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

Each segment of opcodes (such as creating contracts, making message calls, accessing account storage, and executing operations on the virtual machine) has a recognized Gas consumption cost, which is recorded in the Ethereum Yellow Paper[2].

After multiple modifications through EIPs, the Gas costs of some opcodes have been adjusted and may differ from those in the Yellow Paper. For detailed information on the latest costs of opcodes, please refer to this link[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 that incur high Gas costs.

In the EVM, the following operations have lower costs:

  • 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

Higher-cost operations include:

  • Reading and writing state variables stored in contract storage

  • External function calls

  • Loop operations

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 the Gas fee 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 much higher than that of Memory. Each time a smart contract reads or writes data from storage, it incurs high Gas costs.

According to the definition in the Ethereum Yellow Paper, the cost of storage operations is over 100 times higher than that of memory operations. For example, the OPcodes mload and mstore instructions consume only 3 Gas units, while storage operations like sload and sstore cost at least 100 units even under the most ideal conditions.

Methods to limit storage usage include:

  • Storing non-permanent data in memory

  • Reducing the number of storage modifications: by keeping intermediate results in memory and assigning the results to storage variables only after all calculations are complete.

2. Variable Packing

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

The Solidity compiler packs contiguous storage variables during the compilation process, using 32-byte storage slots as the basic unit for variable storage. Variable packing refers to arranging variables in such a way that multiple variables can fit into a single storage slot.

The left side shows an inefficient implementation that consumes 3 storage slots; the right side shows a more efficient implementation.

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

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 the operational costs associated with different data types vary. 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 executes operations in 256-bit units, using uint8 means the EVM must first convert it to uint256, which incurs additional Gas costs.

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

Individually, using uint256 is cheaper than uint8. However, if we apply the variable packing optimization suggested earlier, it would be different. If developers can pack four uint8 variables into a single storage slot, the total cost of iterating through them will be lower than that of four uint256 variables. This way, the smart contract can read and write to a storage slot once and place four uint8 variables into memory/storage in a single 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, fixed-size variables consume less Gas than variable-size variables. If the byte length can be limited, try to choose the smallest length from bytes1 to bytes32.

5. Mappings vs. Arrays

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

Mappings are generally more efficient and cost-effective in most cases, while arrays support iteration and 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 calldata or memory. The main difference is that memory can be modified by the function, while calldata is immutable.

Remember this principle: if the function parameters are read-only, prioritize using calldata over memory. This avoids unnecessary copying 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, improving Gas efficiency by 35%.

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 costs are much lower compared to storage, and it is recommended to use the Constant or Immutable keywords whenever possible.

8. Use Unchecked When Overflow/Underflow Can Be Safely Assumed

When developers can ensure that arithmetic operations will not lead to 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 diagram below, the variable i is constrained by the condition ilength, meaning it can never overflow. Here, length is defined as uint256, which means the maximum value of i is max(uint)-1. Therefore, incrementing i in the unchecked code block is considered safe and saves Gas.

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

9. Optimize Modifiers

The code of modifiers is embedded into the modified functions, and each time a modifier is used, its code is copied. This increases the size of the bytecode and raises Gas consumption. Here is a method to optimize the Gas cost of modifiers:

Before Optimization:

After Optimization:

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

10. Short-Circuit Optimization

For the || and && operators, logical operations undergo short-circuit evaluation, meaning if the first condition can already 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, allowing for the possibility of skipping more expensive calculations.

General Additional Recommendations

1. Remove Unused Code

If there are unused functions or variables in the contract, it is advisable 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 certain calculations are directly used in the contract, then those redundant calculations should be removed. Essentially, any unused calculations should be deleted.

In Ethereum, developers can receive Gas rewards by freeing up 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, merge loops whenever possible, and move repeated calculations out of the loop body.

2. Use Precompiled Contracts

Precompiled contracts provide complex library functions, such as cryptographic and hashing operations. Since the code runs locally on client nodes rather than on the EVM, it requires less Gas. Using precompiled contracts can save Gas by reducing the computational workload required to execute smart contracts.

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 lower Gas costs and improve the efficiency of applications.

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

3. Use Inline Assembly Code

Inline 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 for more precise control over memory and storage usage, further reducing Gas fees. Additionally, inline assembly can perform some complex operations that are difficult to achieve using only Solidity, providing more flexibility for optimizing Gas consumption.

Here is an example of code that saves Gas using inline assembly:

As seen in the image above, the second use case that employs inline assembly technology has higher Gas efficiency compared to the standard use case.

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

4. Use Layer 2 Solutions

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

Layer 2 solutions such as rollups, sidechains, and state channels can 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 lowering Gas fees. Using Layer 2 solutions can also enhance Ethereum's scalability, allowing more users and applications to participate in the network without causing congestion due to network 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 size of bytecode, remove unused code, and reduce the number of operations required to execute smart contracts. In conjunction with other Gas optimization libraries, such as "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 can minimize transaction costs and improve 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 the Gas consumption of contracts.

However, it is crucial to note that during the optimization process, developers must proceed with 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 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

免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。

中奖率100%,每日可抽iPhone 17
Ad
Share To
APP

X

Telegram

Facebook

Reddit

CopyLink