Gas Optimization Solutions
1.1 Gas Optimization Overwiew
1.2 Optimization Techniques
1.2.1 Use uint256
Instead of Smaller Integers
uint256
Instead of Smaller IntegersWhile using smaller integers (e.g., uint8
, uint16
) might seem like it would reduce gas costs, it can actually increase costs unless multiple variables are packed into a single storage slot. In general, using uint256
is the most gas-efficient for operations unless explicit storage packing is implemented.
Inefficient
uint8 smallInt; // Avoid if not packed
uint16 anotherSmallInt;
Optimized
uint256 optimizedInt; // Prefer `uint256` for independent storage variables
1.2.3 Pack Storage Variables
Storage on Ethereum is divided into 32-byte slots. By combining variables that are smaller than 32 bytes (like uint8
, uint16
, bool
), multiple variables can share a single storage slot, reducing gas costs.
Optimized
contract StoragePackingExample {
uint128 var1; // Takes up half a storage slot
uint128 var2; // Packed into the same slot with `var1`
uint64 var3; // Fits with `var2` in a new slot
uint64 var4;
}
1.2.4 Use memory
Instead of storage
for Temporary Variables
memory
Instead of storage
for Temporary VariablesWhen working with temporary data, use memory
rather than storage
. memory
variables are cheaper since they don’t persist on the blockchain.
Inefficient
function expensiveFunction() public view returns (uint256) {
uint256[] storage tempArray = longArray; // Using `storage` incurs extra gas costs
return tempArray.length;
}
Optimized
function cheaperFunction() public view returns (uint256) {
uint256[] memory tempArray = longArray; // Using `memory` is cheaper
return tempArray.length;
}
1.2.4 Minimize SSTORE
Operations
SSTORE
OperationsEach time a value is written to storage (SSTORE
), it incurs significant gas costs. Where possible, reduce writes to storage by performing calculations in memory
and only writing the final result.
Inefficient
function updateValue() public {
for (uint256 i = 0; i < 100; i++) {
storageArray[i] = i; // Every assignment is a storage write
}
}
Optimized
function updateValue() public {
uint256;
for (uint256 i = 0; i < 100; i++) {
tempArray[i] = i; // Uses `memory`, cheaper than `storage`
}
storageArray = tempArray; // Single storage assignment
}
1.2.5 Use immutable
and constant
Variables
immutable
and constant
VariablesConstants and immutables are stored in bytecode, saving storage gas costs. Use constant
for values known at compile time and immutable
for values set in the constructor.
contract Example {
uint256 public constant FIXED_FEE = 0.01 ether; // Saves gas, hardcoded in bytecode
address public immutable admin; // Set once in the constructor
constructor(address _admin) {
admin = _admin;
}
}
1.2.6 Efficient Loops and Data Structures
Avoid unbounded loops and consider more efficient data structures. For instance, avoid looping through large arrays stored on-chain.
function getSum(uint256[] memory values) public pure returns (uint256) {
uint256 sum = 0;
uint256 length = values.length;
for (uint256 i = 0; i < length; ++i) {
sum += values[i];
}
return sum;
}
1.3 Code for Gas-Optimized Contracts
Gas-Optimized ERC20 Token Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasOptimizedERC20 {
string public constant name = "GasOptimizedToken";
string public constant symbol = "GOT";
uint8 public constant decimals = 18;
uint256 public immutable totalSupply;
mapping(address => uint256) private balances;
mapping(address => mapping(address => uint256)) private allowances;
constructor(uint256 _totalSupply) {
totalSupply = _totalSupply;
balances[msg.sender] = _totalSupply;
}
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "Invalid sender");
require(recipient != address(0), "Invalid recipient");
require(balances[sender] >= amount, "Insufficient balance");
// Use memory variables to minimize storage access
uint256 senderBalance = balances[sender];
uint256 recipientBalance = balances[recipient];
balances[sender] = senderBalance - amount;
balances[recipient] = recipientBalance + amount;
}
function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function _approve(address owner, address spender, uint256 amount) internal {
allowances[owner][spender] = amount;
}
}
Explanation of Optimizations
Constants:
name
,symbol
, anddecimals
are marked asconstant
, saving gas by storing them in bytecode.Immutable Total Supply: The
totalSupply
is set asimmutable
, reducing storage cost.Efficient Transfers:
balanceOf
andtransfer
methods use memory variables to minimize repeated storage access.
Last updated