How to Optimize Smart Contracts in Solidity
On the Ethereum blockchain, gas is an execution fee used to compensate miners for the computational resources required to power smart contracts. Network usage is progressively increasing, with current gas costs being millions of dollars per day. As the ecosystem continues to grow, so too will the value of gas optimization. The following sections will go over some common gas optimization patterns.
Check out the github repo for an updated list of gas optimizations.
Gas-Saving Patterns
The following are patterns you can make use of in your code to reduce gas consumption.
Short-circuiting
Short-circuiting is a strategy we can make use of when an operation makes use of either ||
or &&
. This pattern works by ordering the lower-cost operation first so that the higher-cost operation may be skipped (short-circuited) if the first operation evaluates to true
.
Unnecessary libraries
Libraries are often only imported for a small number of uses, meaning that they can contain a significant amount of code that is redundant to your contract. If you can safely and effectively implement the functionality imported from a library within your contract, it is optimal to do so.
Explicit function visibility
Explicit function visibility can often provide benefits in terms of smart contract security as well as gas optimization. For example, explicitly labeling external functions forces the function parameter storage location to be set as calldata
, which saves gas each time the function is executed.
Proper data types
In Solidity, some data types are more expensive than others. It’s important to be aware of the most efficient type that can be used. Here are a few rules about data types.
- Type
uint
should be used in place of typestring
whenever possible. - Type
uint256
takes less gas to store thanuint8
(see why). - Type
bytes
should be used overbyte[]
. - If the length of
bytes
can be limited, use the lowest amount possible frombytes1
tobytes32
. - Type
bytes32
is cheaper to use than typestring
.
Gas-Costly Patterns
The following patterns were gathered from the paper “Under-Optimized Smart Contracts Devour Your Money.” These patterns increase gas costs and should be avoided.
Dead code
Dead code is code that will never run because it’s evaluation is predicated on a condition that will always return false
.
Opaque predicate
The outcome of some conditions can be known without being executed and thus do not need to be evaluated.
Expensive operations in a loop
Due to the expensive SLOAD and SSTORE opcodes, managing a variable in storage is much more expensive than managing variables in memory. For this reason, storage variables should not be used in loops.
The fix for this pattern would be to create a temporary variable to represent the global variable and once the loop is complete, reassign the value of the temporary variable to the global variable.
Constant outcome of a loop
If the outcome of a loop is a constant that can be inferred during compilation, it should not be used.
Loop fusion
Occasionally in smart contracts, you may find that there are two loops with the same parameters. In the case that the loop parameters are the same, there is no reason to have separate loops.
Repeated computations in a loop
If an expression in a loop produces the same outcome in each iteration, it can be moved out of the loop. This is especially important when the variables(s) used in the expression are stored in storage.
Comparison with unilateral outcome in a loop
When a comparison is executed in each iteration of a loop but the result is the same each time, it should be removed from the loop.
References
- https://medium.com/coinmonks/optimizing-your-solidity-contracts-gas-usage-9d65334db6c7
- https://ethereum.stackexchange.com/questions/28813/how-to-write-an-optimized-gas-cost-smart-contract
- https://arxiv.org/pdf/1703.03994.pdf
- https://ethereum.stackexchange.com/questions/3067/why-does-uint8-cost-more-gas-than-uint256
- https://www.youtube.com/watch?v=qwBkeJ84d2g&feature=youtu.be&t=68
- https://ethereum.stackexchange.com/questions/11556/use-string-type-or-bytes32