When developing smart contracts on the Ethereum blockchain, one of the most crucial aspects to consider is the gas consumption. Gas is a measure of computational work on Ethereum, and it costs real money to users. Therefore, optimizing your smart contracts to use less gas is not just a performance improvement but also a cost-saving strategy.

The total gas cost is calculated as: Total Gas Cost=Gas Used×Gas Price

The gas price fluctuates based on network congestion, while the gas used depends on your smart contract’s operations. Let’s dive into various techniques to optimize the gas usage in a Solidity smart contract.

The Scenario

We’ll start with a simple Solidity smart contract that calculates the sum of even numbers less than 99 from an array of integers. Below is the initial code:

Initial Code

Gas used before any optimization: 297069 (when using [1,2,3,4,5,6,7,8,9,10,53,44,76,32,100,400,500] as the argument).

1. Use calldata Instead of memory

In Solidity, calldata is a data location that is cheaper than memory because it’s read-only and external to the smart contract. This makes it suitable for function parameters that don’t need to be modified within the function.

Optimized Code

Gas used: 59585

2. Use Local Variables Instead of State Variables

State variables are stored on the blockchain, making them more expensive to use than local variables. By using a local variable, you can reduce the gas cost.

Optimized Code

Gas used: 58729

3. Short-Circuit Condition Checks

You can combine multiple conditions into a single if statement to short-circuit unnecessary checks.

Optimized Code

When two conditions are combined with an &&, the second condition won't be evaluated if the first one is false, due to short-circuiting.

This short-circuiting behavior also applies to nested if statements: if the outer if condition is false, the inner if condition won't be evaluated.

There's no significant gas optimization to be gained from using nested if statements over a combined if condition with an && operator. Both will have approximately the same gas cost since they both leverage short-circuiting.

Gas used: 57450

4. Use ++i Instead of i++

Prefix increment (++i) is slightly more efficient than postfix (i++) because it doesn’t require a temporary variable.

Optimized Code

Gas used: 56400

5. Cache Array Length

Caching the length of the array can save gas as it avoids multiple calls to retrieve the array’s length.

Optimized Code

Gas used: 55200

6. Load Array Elements to Memory

Loading array elements into a memory variable can save gas when the element is used multiple times.

Optimized Code

Gas used: 54300

Additional Tools for Optimization

Unchecked Increment

Since we are sure that the loop won’t iterate more than (2^{256} - 1) times, we can use the unchecked block to skip overflow checks, saving some gas.

Use Custom Errors Instead of Revert

Starting from Solidity v0.8.4, custom errors can be defined to provide more information about failures in a gas-efficient way.

Bit Shifting Instead of Division/Multiplication

Bit shifting can be more efficient than division or multiplication when dealing with powers of 2.

Function Prioritization

The order of functions in a contract can impact gas consumption due to how the Ethereum Virtual Machine (EVM) handles method IDs. Place the most frequently used functions earlier in the contract to save some gas.

Solidity compiler’s optimizer

You can also use the Solidity compiler’s optimizer by adding the following snippet to your truffle-config.js if you’re using Truffle:

Or if you’re using Hardhat, add it to your hardhat.config.js:

These settings enable the optimizer and set it to run 200 times, a number that aims to balance deployment cost against runtime costs.

Conclusion

Gas optimization is crucial for the efficient execution and interaction of smart contracts on the Ethereum blockchain. By applying these techniques, you can significantly reduce the gas cost, making your smart contracts more efficient and user-friendly. Always remember to test thoroughly to ensure that optimizations do not introduce bugs or vulnerabilities.