No need to explicitly initialize variables with default values
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
Instances number of this issue: 7
// src/ArtGobblers.sol
432: for (uint256 i = 0; i < cost; ++i) {
592: for (uint256 i = 0; i < numGobblers; ++i) {
// src/Pages.sol
251: for (uint256 i = 0; i < numPages; i++) _mint(community, ++lastMintedPageId);
// src/utils/GobblerReserve.sol
37: for (uint256 i = 0; i < ids.length; i++) {
// src/utils/token/GobblersERC721.sol
186: for (uint256 i = 0; i < amount; ++i) {
// rc/utils/token/GobblersERC1155B.sol
114: for (uint256 i = 0; i < owners.length; ++i) {
173: for (uint256 i = 0; i < amount; ++i) {
The demo of the loop gas comparison can be seen here.
++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
Saves 5 gas per loop
Instances number of this issue: 1
// src/utils/GobblerReserve.sol
37: for (uint256 i = 0; i < ids.length; i++) {
For-loop length could be cached
The for loop length can be cached to memory before the loop, even for memory variables.
The demo of the loop gas comparison can be seen here.
Instances number of this issue: 1
// src/utils/GobblerReserve.sol
37: for (uint256 i = 0; i < ids.length; i++) {
Use bytes32 instead of string
Use bytes32 instead of string to save gas whenever possible.
String is a dynamic data structure and therefore is more gas consuming then bytes32.
Instances number of this issue: 3
Bytes32 can be used instead of string in the following:
// src/ArtGobblers.sol
136: string public UNREVEALED_URI;
139: string public BASE_URI;
// src/Pages.sol
96: string public BASE_URI;
Use custom errors rather than revert()/require() strings
Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
Usage of uint/int smaller than 32 bytes (256 bits) incurs overhead
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
State variables only set in the constructor should be declared immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas). If getters are still desired, '_' can be added to the variable name and the getter can be added manually.
Instances number of this issue: 3
The followings can be changed from public to internal or private:
// src/ArtGobblers.sol
136: string public UNREVEALED_URI;
139: string public BASE_URI;
// src/Pages.sol
96: string public BASE_URI;
Making some constants/immutable as non-public
Changing the visibility from public to private or internal can save gas when a constant isn’t used outside of its contract.
Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.
If needed, the value can be read from the verified contract source code.
A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract’s value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don’t get out of sync.
Instances number of this issue: 13
The followings can be changed from public to internal or private:
// src/ArtGobblers.sol
112: uint256 public constant MAX_SUPPLY = 10000;
115: uint256 public constant MINTLIST_SUPPLY = 2000;
118: uint256 public constant LEGENDARY_SUPPLY = 10;
122: uint256 public constant RESERVED_SUPPLY = (MAX_SUPPLY - MINTLIST_SUPPLY - LEGENDARY_SUPPLY) / 5;
126-129:
uint256 public constant MAX_MINTABLE = MAX_SUPPLY
- MINTLIST_SUPPLY
- LEGENDARY_SUPPLY
- RESERVED_SUPPLY;
146: bytes32 public immutable merkleRoot;
156: uint256 public immutable mintStart;
159: uint128 public numMintedFromGoo;
177: uint256 public constant LEGENDARY_GOBBLER_INITIAL_START_PRICE = 69;
180: uint256 public constant FIRST_LEGENDARY_GOBBLER_ID = MAX_SUPPLY - LEGENDARY_SUPPLY + 1;
184: uint256 public constant LEGENDARY_AUCTION_INTERVAL = MAX_MINTABLE / (LEGENDARY_SUPPLY + 1);
// lib/VRGDAs/src/VRGDA.sol
17: int256 public immutable targetPrice;
21: int256 internal immutable decayConstant;
Some variables can be stored as constants
Instances number of this issue: 6
ERC721TokenReceiver.onERC721Received.selector and ERC1155TokenReceiver.onERC1155BatchReceived.selector can be saved as constants.
No need to explicitly initialize variables with default values
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
Instances number of this issue: 7
The demo of the loop gas comparison can be seen here.
++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
Saves 5 gas per loop
Instances number of this issue: 1
For-loop length could be cached
The for loop length can be cached to memory before the loop, even for memory variables. The demo of the loop gas comparison can be seen here. Instances number of this issue: 1
Use bytes32 instead of string
Use bytes32 instead of string to save gas whenever possible. String is a dynamic data structure and therefore is more gas consuming then bytes32.
Instances number of this issue: 3 Bytes32 can be used instead of string in the following:
Use custom errors rather than
revert()/require()
stringsCustom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas
Instances number of this issue: 37
Duplicated require()/assert() checks could be refactored to a modifier or function
Instances number of this issue: 16
address(0)
check: 8ERC721TokenReceiver(to).onERC721Received
check: 8Using
bool
for storage incurs overheadhttps://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past
Instances number of this issue: 1
Usage of uint/int smaller than 32 bytes (256 bits) incurs overhead
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
Reference: Layout of State Variables in Storage
Instances number of this issue: 20
State variables only set in the constructor should be declared immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas). If getters are still desired, '_' can be added to the variable name and the getter can be added manually.
Instances number of this issue: 3 The followings can be changed from public to internal or private:
Making some constants/immutable as non-public
Changing the visibility from public to private or internal can save gas when a constant isn’t used outside of its contract.
Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.
If needed, the value can be read from the verified contract source code.
A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract’s value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don’t get out of sync.
Instances number of this issue: 13 The followings can be changed from public to internal or private:
Some variables can be stored as constants
Instances number of this issue: 6
ERC721TokenReceiver.onERC721Received.selector
andERC1155TokenReceiver.onERC1155BatchReceived.selector
can be saved as constants.X = X + Y / X = X - Y
IS CHEAPER THANX += Y / X -= Y
The demo of the gas comparison can be seen here.
Consider use
X = X + Y / X = X - Y
to save gas.Instances number of this issue: 10