Gas Optimisation
Economic patterns for gas optimisation
String Equality Comparison
String Equality Comparison: Check for the equality of two provided strings in a way that minimizes average gas consumption for a large number of different inputs.
Solidity does not yet have a native method for comparing strings. The pairwise comparison method may result in high gas consumption for strings that are long and actually equal.
We can use hash functions for comparison, combined with a check for matching length of the provided strings.
Tight Variable Packing
Tight Variable Packing: Optimize gas consumption when storing or loading statically-sized variables.
Gas can be saved when reading multiple storage variables if they are all stored in a single slot.
Memory Array Building
Memory Array Building: Aggregate and retrieve data from contract storage in a gas efficient way.
To reduce gas costs associated with reading storage variables, we store data in an array for efficient data retrieval. A view function can then be implemented to read and return this data.
Example of how a collection of items can be aggregated over its owners:
By labelling a function as view
, we get a ‘free’ query because an external call to a view function would require zero gas, while a non-view function would require a transaction on the blockchain.
Mappings over Arrays
Except where iteration is required or data types can be packed, it is advised to use mappings to manage lists of data in order to conserve gas. This is beneficial for both memory and storage.
An integer index can be used as a key in a mapping to control an ordered list, so you can access any value without having to iterate through an array as would otherwise be necessary.
Batching Transactions
Every transaction sent by an EOA has a minimum of 21,000 gas.
Batching allows more operations to be executed with less gas.
Indexed Events for reducing storage gas
Event data is stored in the transaction receipts trie, which can be queried and stored off-chain if the info is required.
Search for logged events using indexed parameters.
Use calldata
instead of memory
for function params
calldata
instead of memory
for function paramsInstead of copying variables to memory, it is typically more cost-effective to load them immediately from calldata.
If all you need to do is read data, you can conserve gas by saving the data in calldata.
Free up unused storage
Deleting your unused variables helps free up space and earns a gas refund. Deleting unused variables has the same effect as reassigning the value type with its default value, such as the integer's default value of 0, or the address zero for addresses.
Mappings, however, are unaffected by deletion, as the keys of mappings may be arbitrary and are generally unknown. Therefore, if you delete a struct, all of its members that are not mappings will reset and also recurse into its members. However, individual keys and the values they relate to can be removed.
Use immutable
and constant
immutable
and constant
Use immutable if you want to assign a permanent value at construction. Use constants if you already know the permanent value. Both get directly embedded in bytecode, saving
SLOAD.
Local Variable Assignment
Catch frequently used storage variables in memory/stack, converting multiple
SLOAD
into 1SLOAD
Use fixed size bytes array rather than string or bytes[]
If the string you are dealing with can be limited to max of 32 characters, use
bytes[32]
instead of dynamicbytes
array orstring
Use unchecked
unchecked
Use unchecked for arithmetic where you are sure it won't over or underflow, saving gas costs for checks added from solidity v0.8.0.
In the example below, the variable
i
cannot overflow because of the conditioni < length
, wherelength
is defined asuint256
. The maximum valuei
can reach ismax(uint)-1
. Thus, incrementingi
insideunchecked
block is safe and consumes lesser gas.
Use custom errors to save deployment and runtime costs in case of revert
Instead of using strings for error messages (e.g.,
require(msg.sender == owner, “unauthorized”)
), you can use custom errors to reduce both deployment and runtime gas costs. In addition, they are very convenient as you can easily pass dynamic information to them.
Refactor a modifier to call a local function instead of directly having the code in the modifier, saving bytecode size and thereby deployment cost
Modifiers code is copied in all instances where it's used, increasing bytecode size. By doing a refractor to the internal function, one can reduce bytecode size significantly at the cost of one JUMP. Consider doing this only if you are constrained by bytecode size.
Use indexed events as they are less costly compared to non-indexed ones
Using the
indexed
keyword for value types such as uint, bool, and address saves gas costs. However, this is only the case for value types, whereas indexing bytes and strings are more expensive than their unindexed version.This is because you're reading them off the stack instead of putting them in memory.
Use struct when dealing with different input arrays to enforce array length matching
When the length of all input arrays needs to be the same, use a
struct
to combine multiple input arrays so you don't have to manually validate their lengths.
Short-circuit with || and &&
Last updated