Gas Optimisation
Economic patterns for gas optimisation
Last updated
Economic patterns for gas optimisation
Last updated
: 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.
: 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.
: 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.
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.
Every transaction sent by an EOA has a minimum of 21,000 gas.
Batching allows more operations to be executed with less 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.
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.
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.
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.
Catch frequently used storage variables in memory/stack, converting multiple SLOAD
into 1 SLOAD
If the string you are dealing with can be limited to max of 32 characters, use bytes[32]
instead of dynamic bytes
array or string
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 condition i < length
, where length
is defined as uint256
. The maximum value i
can reach is max(uint)-1
. Thus, incrementing i
inside unchecked
block is safe and consumes lesser gas.
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.
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.
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.
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.
Reference: