1. Using Short-Circuiting to Optimize Solidity Operations#
Short-circuiting is a Solidity contract development pattern that uses logical OR/AND to prioritize operations with different gas costs. It places low gas cost operations first and high gas cost operations later, so if the low cost operation is feasible, it can skip (short-circuit) the high cost Ethereum virtual machine operations.
// f(x) is a low gas cost operation
// g(y) is a high gas cost operation
// Sort operations with different gas costs as follows
f(x) || g(y)
f(x) && g(y)
2. Explicitly Declare the Visibility of Solidity Contract Functions#
In Solidity contract development, explicitly declaring the visibility of functions not only improves the security of smart contracts but also optimizes the gas cost of contract execution. For example, by explicitly marking a function as external, the storage location of function parameters can be set to calldata
, which saves the Ethereum gas cost required for each function execution.
External visibility consumes less gas than public.
3. Use Appropriate Data Types#
In Solidity, some data types have higher gas costs than others. It is necessary to understand the gas utilization of available data types in order to choose the most efficient one based on your needs. Here are some rules regarding gas consumption of Solidity data types:
- Avoid using
string
type wheneveruint
type can be used. - Storing
uint256
has lower gas cost than storinguint8
. Why? Click here to find out. - When
bytes
type can be used, avoid usingbyte[]
type in Solidity contracts. - If the length of
bytes
has a predictable upper limit, consider using fixed-length Solidity types such asbytes1
tobytes32
. bytes32
has lower gas cost compared tostring
type.
4. Avoid Dead Code in Solidity Smart Contracts#
Dead code refers to Solidity code that will never be executed, such as code with conditions that can never be satisfied. For example, the following Solidity code block with two contradictory conditional statements consumes Ethereum gas resources but serves no purpose:
function deadCode(uint x) public pure {
if(x < 1 {
if(x > 2) {
return x;
}
}
}
5. Avoid Unnecessary Conditional Statements#
Some conditional assertions can be known without executing Solidity code, so such conditional statements can be simplified. For example, in the following Solidity contract code with nested conditional statements, the inner condition is wasting valuable Ethereum gas resources:
function opaquePredicate(uint x) public pure {
if(x < 1) {
if(x < 0 ) {// uint cannot be less than 0
return x;
}
}
}
6. Avoid High Gas Cost Operations in Loops#
Due to the high cost of SLOAD
and SSTORE
opcodes, managing storage variables has much higher gas cost compared to memory variables. Therefore, avoid operating on storage variables in loops. For example, in the following Solidity code, num
is a storage variable, and performing unknown iterations of operations can result in unexpected Ethereum gas consumption:
uint num = 0;
function expensiveLoop(uint x) public {
for(uint i = 0; i < x; i++) {
num += 1;
}
}
The solution to the above Ethereum contract code problem is to create a temporary Solidity variable to replace the global variable in the loop, and then assign the value of the temporary variable back to the global state variable after the loop ends:
uint num = 0;
function lessExpensiveLoop(uint x) public {
uint temp = num;
for(uint i = 0; i < x; i++) {
temp += 1;
}
num = temp;
}
7. Avoid Loops with Constant Outcomes#
If the result of a loop calculation can be predicted without compiling and executing Solidity code, then the loop should be avoided as it can significantly save gas. For example, the following Ethereum contract code can directly set the value of the num
variable:
function constantOutcome() public pure returns(uint) {
uint num = 0;
for(uint i = 0; i < 100; i++) {
num += 1;
}
return num;
}
8. Avoid Redundant Computations in Loops#
If an expression in a loop produces the same result in each iteration, it can be calculated outside the loop to save additional gas costs. This is even more important if the expression uses a storage variable. For example, in the following smart contract code, the value of the expression a*b
does not need to be recalculated in each iteration:
uint a = 4;
uint b = 5;
function repeatedComputations(uint x) public returns(uint) {
uint sum = 0;
for(uint i = 0; i <= x; i++) {
sum = sum + a * b;
}
}
9. Remove Comparison Operations in Loops#
If a comparison operation is executed in each iteration of a loop, but the result of each comparison is the same, it should be removed from the loop.
function unilateralOutcome(uint x) public returns(uint) {
uint sum = 0;
for(uint i = 0; i <= 100; i++) {
if(x > 1) {
sum += 1;
}
}
return sum;
}
10. Choose a Function Name for Common Functions to Minimize Hash Value#