Gas is calculated via following formula: Gas fee = units of gas used * (base fee + priority fee). Opcodes has universally agreed cost in terms of gas. This is more extensively given in the Ethereum yellow paper.
-
Cache array length before loops.
-
≠ checks are slightly gas optimized than == check.
-
make sure state variables immutable if their value never changes
-
for array elements, array[index] + is cheaper than +=
-
use unchecked when the arithmetic can’t overflow/underflow
-
payable functions are less gas consuming
-
Priotitize functions by lowering method ID: The compiler orders public and external members by their method ID.
Calling function at runtime will be cheaper if the function is positioned earlier in the order because 22 gas are added to the cost for every position that came before it. The average called 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)
-
Variable Packing: Each storage cost gas, packing variables helps in gas optimizations.
contract Unoptimized { uint128 Zero; uint256 One; uint128 Two; } contract Optimized { uint128 Zero; uint128 Two; uint256 One; }
-
Deleting the unused variables helps in gas optimization.
-
Compute possible values offchain.
contract Unoptimized { bytes32 hash = keccak256(abi.encodePacked('HashingExample'); } contract Optimized { bytes32 hash = ''; }
-
Don’t shrink variables: If we use uint8, the evm has to first convert it to uint256 to work on it and conversion costs gas.
-
Using events saves gas by not accessing onchain.
-
Using libraries on different functions reduce gas cost.
-
Fixed sized arrays are cheaper than dynamic arrays.
-
Limiting the string length in ‘require’ statements saves gas. (make it to 32bytes)
-
Mapping over arrays saves gas.
-
Avoid assigning values that has no use.
uint256 value; // cheap uint256 value = 0 // expensive
-
EIP1167 minimal proxy contract is a standardized, gas-efficient way to deploy a bunch of contract clones from a factory.
-
Performing operations on memory and calldata than the storage is always cheaper.
-
Use calldata instead of memory.
-
Using modifiers saves gas.
-
Short-Circuit: In the case of
||
and&&
operators, the evaluation is short-circuited, meaning the second condition is not evaluated if the first condition already determines the result of the logical expression.To optimize for lower gas usage, arrange conditions so that the less expensive computation is placed first. This could potentially bypass the execution of the more expensive computation.
-
If you don’t need ERC721Enumerable, for ERC721 which saves gas: The problem with this extension is that it adds a lot of overhead to any transfer (be it the the contract transferring to the user when the user mints, or any transfer from one user to another).
-
Using ERC721A standard saves gas cost.
-
Start with Token ID 1 not zero, making the first mint much cheaper.
-
Use merkle tree for whitelisting users. [READ MORE ON REF NO: 5)
-
Opt for push over pull payment patterns: When distributing payments, it's generally more gas-efficient to use a
push
pattern, where the contract sends funds to recipients, rather than apull
pattern, where recipients need to withdraw their funds. -
Batch multiple operations: If your contract performs multiple similar operations, try to batch them together in a single transaction, reducing the per-operation gas cost.
-
Favor
external
overpublic
for functions: If a function is only meant to be called from outside the contract, use theexternal
visibility specifier instead ofpublic
.external
functions are more gas-efficient when receiving large amounts of data as arguments. -
To minimize gas consumption, it’s advantageous to load frequently accessed state variables into memory. By doing so, you reduce the number of expensive SSTORE operations and achieve substantial gas savings
uint256 public total; function sumIfEvenAndLessThen99(uint[] calldata nums) external { uint256 _total = total; }
-
Using Pre Increment Operator saves gas [CAREFUL when using it]
-
Loading array elements to memory saves gas.
-
Use view and pure functions to reduce gas.
-
User bit shift operators: Cheaper over arthmetic operators. [CAREFUL: Doesn’t revert on overflow/underflow]
- uint256 alpha = someVar / 256; - uint256 beta = someVar % 256; + uint256 alpha = someVar >> 8; + uint256 beta = someVar & 0xff; - uint 256 alpha = delta / 2; - uint 256 beta = delta / 4; - uint 256 gamma = delta * 8; + uint 256 alpha = delta >> 1; + uint 256 beta = delta >> 2; + uint 256 gamma = delta << 3;
-
Avoid redundant checks
// Unoptimized: require(balance>0, "Insufficient balance"); if (balance>0){ // this check is redundant . . . //some action } // Optimized: require(balance>0, "Insufficient balance"); . . . //some action
-
Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
contract NestedIfTest { //Execution cost: 22334 gas function funcUnoptimized(uint256 input) public pure returns (string memory) { if (input<10 && input>0 && input!=6){ return "If condition passed"; } } //Execution cost: 22294 gas function funcOptimized(uint256 input) public pure returns (string memory) { if (input<10) { if (input>0){ if (input!=6){ return "If condition passed"; } } } } }
-
Using mutiple require statements is cheaper than using
&&
multiple check combinations. There are more advantages, such as easier to read code and better coverage reports. -
Calling Internal functions are cheaper.
-
Packing works on struct too. Pack structs to save gas
-
Don’t use unnecesasay require statements.
-
It is more gas efficient to revert with a custom error than a
require
with a string. -
Don’t operate on expensive operations inside a loop, which can be costly
-
Caching variables that is used once is just waste of gas.
-
Importing entire library while only using one function isn’t necessary.
-
Setting the contructor to payable saves gas.
-
Duplicated
require()
/revert()
Checks Should Be Refactored To A Modifier Or Function. -
Using hardcoded address instread address(this) saves gas. [AS NEEDED]
-
Empty Blocks Should Be Removed Or Emit Something.
-
Using delete statements can save gas.
-
<x> += <y>
Costs More Gas Than<x> = <x> + <y>
For State Variables.
[TIPS]
- Use foundry —gas-report to check gas usage.
- Layer 2 solutions reduces the amount of data that needs to be calculated and to be stored in on chain therefore reducing gas cost.
- Use the optimizer on ‘On’ state to reduce gas.
- Use solidity version 0.8.19 to gain some gas boost.
[REFERENCES]
- https://yamenmerhi.medium.com/gas-optimization-in-solidity-75945e12322f
- https://github.com/kadenzipfel/gas-optimizations
- https://github.com/ZeroEkkusu/re-golf-course
- https://certik.medium.com/gas-optimization-in-ethereum-smart-contracts-10-best-practices-cbd57548bdf0
- https://medium.com/@WallStFam/the-ultimate-guide-to-nft-gas-optimization-7e9289e2d88f
- https://github.com/devanshbatham/Solidity-Gas-Optimization-Tips