โฌ…๏ธGas Saving Technique 5: ++i costs less gas compared to i++

Introduction

Gas optimization is a crucial aspect of Ethereum smart contract development due to the inherent costs associated with transaction processing. A seemingly minor yet effective gas-saving technique within loops is using ++i (pre-increment) instead of i++ (post-increment). In the realm of unsigned integers, utilizing pre-increment can yield gas savings without compromising the functionality of the contract.

Vulnerability Details & Impact

Understanding Gas Consumption

  • Increased Costs with Post-Increment: The operation i++ (post-increment) consumes more gas than ++i (pre-increment). The post-increment operation involves incrementing the variable i but returns its initial value, necessitating a temporary variable. This process results in extra gas consumption, approximately an additional 5 gas per iteration.

Gas Savings with Pre-Increment

  • Efficiency of Pre-Increment: With ++i (pre-increment), the variable i is incremented first and the updated value is immediately returned, eliminating the need for a temporary variable and thereby saving gas.

This differentiation in gas cost between i++ (post-increment) and ++i (pre-increment) in Solidity, or in Ethereum smart contracts in general, comes down to how each operation is compiled and executed on the Ethereum Virtual Machine (EVM).

Post-increment (i++):

When you use i++, it is a post-increment operation. What this means is that the value of i is used first and then incremented. This involves temporarily storing the original value of i before it gets incremented, so it can be used or returned by the operation.

Here is a breakdown:

  • j = i; This step involves assigning the current value of i to a temporary variable j. This is an extra operation that consumes gas.

  • i = i + 1; This increments the value of i.

  • return j; Finally, the original value of i (now stored in j) is returned.

Due to the extra steps and the usage of a temporary variable, post-increment tends to consume more gas.

Pre-increment (++i):

On the other hand, ++i is a pre-increment operation. It increments the value of i first and then uses it or returns it, which is more straightforward and involves fewer steps.

Here is a breakdown:

  • i = i + 1; This step increments the value of i.

  • return i; This step returns the new incremented value.

Rational:

The key difference lies in the temporary variable and extra assignment operation used in the post-increment (i++). The EVM charges gas for every computational step, and since post-increment involves more steps, it tends to be costlier in terms of gas.

Optimizing for gas is crucial in Ethereum smart contracts to ensure that they are economical to run. Thus, choosing pre-increment (++i) over post-increment (i++) where possible is a better practice for gas optimization.

How to Implement ++i for Gas Savings

Practical Example: Pre-Increment Optimization

Consider a loop where you're incrementing a counter. Using pre-increment can save gas on each iteration:

Before Optimization:

solidityCopy code// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LoopIncrementOptimizer {
    uint public counter = 0;

    function incrementCounter(uint times) public {
        for (uint i = 0; i < times; i++) {  // Using post-increment
            counter += 1;
        }
    }
}

After Optimization:

solidityCopy code// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LoopIncrementOptimizer {
    uint public counter = 0;

    function incrementCounter(uint times) public {
        for (uint i = 0; i < times; ++i) {  // Using pre-increment
            counter += 1;
        }
    }
}

In the optimized version, ++i is used instead of i++, reducing gas consumption per loop iteration due to the avoidance of temporary variable creation and assignment.

  1. Identify Loop Increments: Review your smart contract for loop structures where a counter variable is incremented.

  2. Use Pre-Increment: Change i++ to ++i in the loop to save gas on each iteration.

  3. Test: Ensure that the change doesnโ€™t affect the contract's functionality by conducting thorough testing.

Conclusion

While minor, the optimization from using pre-increment ++i over post-increment i++ can accumulate into meaningful gas savings, especially in contracts with extensive loop operations. This technique is simple and straightforward to implement but always verify through testing that the optimization does not inadvertently alter the expected behavior of the contract. Every bit of gas saved contributes to a more efficient and economical smart contract execution.

Recommendation

[G-09] Gas: Using the logical NOT operator ! is cheaper than a comparison to the constant boolean value false - NOT operator ! cheaper than boolean FALSE

Custom Slither Detector

Any suggestions? please contact me on Linkedin or Twitter

import re
from typing import List
from slither.detectors.abstract_detector import (
    AbstractDetector,
    DetectorClassification,
    DETECTOR_INFO,
)
from slither.formatters.naming_convention.naming_convention import custom_format
from slither.utils.output import Output


class ZokyoOptimizeIncrement(AbstractDetector):
    ARGUMENT = 'zokyo-optimize-increment'
    HELP = 'Zokyo: Optimize increment and decrement operations'
    IMPACT = DetectorClassification.OPTIMIZATION
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = 'https://zokyo-auditing-tutorials.gitbook.io/zokyo-book-of-gas-savings/tutorials/gas-saving-technique-5-++i-costs-less-gas-compared-to-i++'
    WIKI_TITLE = 'Optimize Increment and Decrement'
    WIKI_DESCRIPTION = 'Detect post-increment (x++) and post-decrement (x--) operations and suggest using pre-increment (++x) and pre-decrement (--x) for optimization.'
    WIKI_EXPLOIT_SCENARIO = ''
    WIKI_RECOMMENDATION = 'Use pre-increment (++x) and pre-decrement (--x) rather than post-increment (x++) and post-decrement (x--).'

    def _detect(self) -> List[Output]:
        """ Detect the postfix increment and decrement operations """
        results = []

        info: DETECTOR_INFO



        for contract in self.contracts:
            for func in contract.functions:
                for node in func.nodes:
                    irs = node.irs
                    for index, ir in enumerate(irs):
                        code = str(ir)
                        # Regex pattern to specifically match post-increment in the IR
                        if re.search(r'TMP_\d+\(uint256\) := \w+\(uint256\)', code) and \
                           re.search(r'\w+\(uint256\) = \w+ \(c\)[+|-] 1', str(irs[index+1])):
                            
                            info = [
                                "Contract ", contract.name, ", function: ",func," has post inctrement/decrement (x++ / x--) \n"
                                'Consider using pre-increment/decrement for gas optimization. (++x / --x) \n'
                            ]

                            res = self.generate_result(info)
                            res.add(contract, {"target": "contract", "convention": "increment"})
                            results.append(res)

        return results

Last updated