How to Optimize Smart Contracts in Solidity

Writing cheaper contracts in Solidity

Kaden
3 min readJan 17, 2020
Image source: Kaden Zipfel

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.

Example from stack exchange.

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.

Example with a redundant library
Example without the library

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 type string whenever possible.
  • Type uint256 takes less gas to store than uint8 (see why).
  • Type bytes should be used over byte[].
  • If the length of bytes can be limited, use the lowest amount possible from bytes1 to bytes32.
  • Type bytes32 is cheaper to use than type string.

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.

If x is less than 1, it cannot be greater than 2, so line 4 will never be executed.

Opaque predicate

The outcome of some conditions can be known without being executed and thus do not need to be evaluated.

If x is greater than 1, it is certainly greater than 0, so line 3 is redundant.

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.

Each iteration of the loop requires costly storage management.

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.

Assign the value to a temporary 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.

The return value will always be the same.

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.

These loops are identical and can thus be combined.

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.

The product of a * b can be computed outside of the loop.

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.

The condition on line 4 has the same outcome each time, so it should be removed from the loop.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Kaden
Kaden

Written by Kaden

evm security engineer/researcher

Responses (2)

What are your thoughts?