All references to immutable
state variables will be replaced with the assigned values by the compiler, which will remove the need to SLOAD
.
// 🚩 Unoptimized
uint internal four = 4;
// 🏌️ Optimized (-100 gas)
uint internal immutable four = 4;
Caching the array length first saves an SLOAD
on each iteration of the loop.
uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 🚩 Unoptimized
for (uint256 index; index < arr.length; ++index) {}
// 🏌️ Optimized (-1000 gas)
uint256 arrLength = arr.length;
for (uint256 index; index < arrLength; ++index) {}
Due to stack operations, this approach is 25 gas cheaper when dealing with arrays in storage, and 4 gas cheaper for arrays in memory.
uint256[2] public arr = [0, 0];
// 🚩 Unoptimized
array[index] += amount;
// 🏌️ Optimized (-25 gas)
array[index] = array[index] + amount;
In Solidity version 0.8.0 and above, a series of opcodes are added before adding, diving, and multiplying to check for underflow and overflow.
Put code in an unchecked
block when it is impossible for the arithmetics to underflow or overflow.
Warning: Using unchecked
with code that would otherwise underflow or overflow will result in the wrapping behavior. Use with caution.
uint zeroToTen;
// 🚩 Unoptimized
while (zeroToTen < 10) {
++zeroToTen;
}
// 🏌️ Optimized (-660 gas)
while (zeroToTen < 10) {
unchecked {
++zeroToTen;
}
}
Making functions payable
eliminates the need for an initial check of msg.value == 0
and saves 21 gas.
Note: This conservatively assumes the function could be pure
if not for the payable
. When compared against a non-pure
function the savings is 24 gas. When used with a constructor, the savings is on deployment.
Warning: Adding a payable
function where none existed previously could introduce a security risk. Use with caution.
// 🚩 Unoptimized
function doSomething() public {}
// 🏌️ Optimized (-24 gas)
function doSomething() payable public {}
The compiler orders public and external members by their Method ID.
Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost for every position that came before it. The average caller will save on gas if you prioritize most called functions.
// 🚩 Unoptimized
bytes32 public occasionallyCalled;
// Method ID: 0x13216062 (position: 1, gas: 2261)
function mostCalled() public {}
// Method ID: 0xd0755f53 (position: 3, gas: 142)
function leastCalled() public {}
// Method ID: 0x24de5553 (position: 2, gas: 120)
// 🏌️ Optimized
bytes32 public occasionallyCalled;
// Method ID: 0x13216062 (position: 2, gas: 2283)
function mostCalled_41q() public {}
// Method ID: 0x0000a818 (position: 1, gas: 98)
function leastCalled() public {}
// Method ID: 0x24de5553 (position: 3, gas: 142)
Initializing the iterator in a loop to 0, e.g. for(uint256 index = 0; ...)
, will always be optimized.