Skip to content

Latest commit

 

History

History
325 lines (214 loc) · 7.56 KB

0-Gas-Optimizations.md

File metadata and controls

325 lines (214 loc) · 7.56 KB

Gas Optimizations

G001 - Don't Initialize Variables with Default Value

Description

Uninitialized variables are assigned with the types default value.

Explicitly initializing a variable with it's default value costs unnecessary gas.

Example

🤦 Bad:

uint256 x = 0;
bool y = false;

🚀 Good:

uint256 x;
bool y;

Background Information

G002 - Cache Array Length Outside of Loop

Description

Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.

Example

🤦 Bad:

for (uint256 i = 0; i < array.length; i++) {
    // invariant: array's length is not changed
}

🚀 Good:

uint256 len = array.length
for (uint256 i = 0; i < len; i++) {
    // invariant: array's length is not changed
}

Background Information

G003 - Use != 0 instead of > 0 for Unsigned Integer Comparison

Description

When dealing with unsigned integer types, comparisons with != 0 are cheaper than with > 0.

Example

🤦 Bad:

// `a` being of type unsigned integer
require(a > 0, "!a > 0");

🚀 Good:

// `a` being of type unsigned integer
require(a != 0, "!a > 0");

Background Information

TODO

G004 - Remove Unused Variables

Description

Removing unused variables saves gas, especially for state variables, i.e. variables saved in storage.

G005 - Make Variable constant/immutable

Description

Making variables constant/immutable, if possible, saves gas as all variables get replaced by the values assigned to them.

Background Information

G006 - Use immutable for OpenZeppelin AccessControl's Roles Declarations

Description

⚡️ Only valid for solidity versions <0.6.12 ⚡️

Access roles marked as constant results in computing the keccak256 operation each time the variable is used because assigned operations for constant variables are re-evaluated every time.

Changing the variables to immutable results in computing the hash only once on deployment, leading to gas savings.

Example

🤦 Bad:

bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");

🚀 Good:

bytes32 public immutable GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");

Background Information

G007 - Long Revert Strings

Description

Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.

If the contract(s) in scope allow using Solidity >=0.8.4, consider using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using NatSpec.

Example

🤦 Bad:

require(condition, "UniswapV3: The reentrancy guard. A transaction cannot re-enter the pool mid-swap");

🚀 Good (with shorter string):

// TODO: Provide link to a reference of error codes
require(condition, "LOK");

🚀 Good (with custom errors):

/// @notice A transaction cannot re-enter the pool mid-swap.
error NoReentrancy();

// ...

if (!condition) {
    revert NoReentrancy();
}

Background Information

G008 - Use Shift Right/Left instead of Division/Multiplication if possible

Description

A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left.

While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.

Example

🤦 Bad:

uint256 b = a / 2;
uint256 c = a / 4;
uint256 d = a * 8;

🚀 Good:

uint256 b = a >> 1;
uint256 c = a >> 2;
uint256 d = a << 3;

Background Information

G009 - Make Function external instead of public

Description

⚡️ Only valid for solidity versions <0.6.9 ⚡️

The restriction that public functions can not take calldata arguments was lifted in version 0.6.9.

For solidity versions <0.6.9, public functions had to copy the arguments to memory.

Background Information

G010 - Make Function payable

Description

⚡️ Community sentiment suggests to not accept this optimization due to security risks ⚡️

Functions marked as payable are slightly cheaper than non-payable ones, because the Solidity compiler inserts a check into non-payable functions requiring msg.value to be zero.

However, keep in mind that this optimization opens the door for a whole set of security considerations involving Ether held in contracts.

Background Information

G011 - Unnecessary checked arithmetic in for loop

Description

A lot of times there is no risk that the loop counter can overflow.

Using Solidity's unchecked block saves the overflow checks.

Example

🤦 Bad:

uint len = supportedTokens.length;
for (uint i; i < len; i++) {
    // ...
}

🚀 Good:

uint len = supportedTokens.length;
for (uint i; i < len; ) {
    // ...

    unchecked { i++; }
}

Background Information

G012 - Use Prefix Increment instead of Postfix Increment if possible

Description

The difference between the prefix increment and postfix increment expression lies in the return value of the expression.

The prefix increment expression (++i) returns the updated value after it's incremented. The postfix increment expression (i++) returns the original value.

The prefix increment expression is cheaper in terms of gas.

Consider using the prefix increment expression whenever the return value is not needed.

Note to be careful using this optimization whenever the expression's return value is used afterwards, e.g. uint a = i++ and uint a = ++i result in different values for a.

Example

🤦 Bad:

for (uint i; i < len; i++) {
    if (i % 2 == 0) {
        counter++;
    }
    // ...
}

🚀 Good:

for (uint i; i < len; ++i) {
    if (i % 2 == 0) {
        ++counter;
    }
    // ...
}

Background information