2- Usage of uints/ints 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 as you can check here.
So use uint256/int256 for state variables and then downcast to lower sizes where needed.
4- Use custom errors rather than require()/revert() strings to save deployments gas :
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information.
5- It costs more gas to initialize variables to zero than to let the default of zero be applied (saves ~3 gas per instance) :
If a variable is not set/initialized, it is assumed to have the default value (0 for uint or int, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
There are 6 instances of this issue:
File: src/utils/token/GobblersERC1155B.sol Line 173
for (uint256 i = 0; i < numPages; i++) _mint(community, ++lastMintedPageId);
7- Using private rather than public for constants, saves gas :
If needed, the value can be read from the verified contract source code. 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.
uint256 public constant LEGENDARY_AUCTION_INTERVAL = MAX_MINTABLE / (LEGENDARY_SUPPLY + 1);
8- Functions guaranteed to revert when called by normal users can be marked payable :
If a function modifier such as onlyAdmin is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for the owner because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are :
CALLVALUE(gas=2), DUP1(gas=3), ISZERO(gas=3), PUSH2(gas=3), JUMPI(gas=10), PUSH1(gas=3), DUP1(gas=3), REVERT(gas=0), JUMPDEST(gas=1), POP(gas=2).
Which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
There are 4 instances of this issue:
File: src/ArtGobblers.sol
560 function upgradeRandProvider(RandProvider newRandProvider) external onlyOwner
File: src/Goo.sol
101 function mintForGobblers(address to, uint256 amount) external only(artGobblers)
108 function burnForGobblers(address from, uint256 amount) external only(artGobblers)
115 function burnForPages(address from, uint256 amount) external only(pages)
Gas Optimizations
Findings
immutable
uints/ints
smaller than 32 bytes (256 bits) incurs overheadx += y/x -= y
costs more gas thanx = x + y/x = x - y
for state variablesrequire()
/revert()
strings to save deployments gas++i
cost less gas thani++
in for loopsFindings
1- 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).
There are 4 instances of this issue:
File: src/utils/token/PagesERC721.sol Line 24
File: src/utils/token/PagesERC721.sol Line 26
File: src/utils/token/GobblersERC721.sol Line 23
File: src/utils/token/GobblersERC721.sol Line 25
2- Usage of
uints/ints
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 as you can check here.
So use
uint256
/int256
for state variables and then downcast to lower sizes where needed.There are 4 instances of this issue:
File: src/Pages.sol Line 107
File: src/Pages.sol Line 114
File: src/ArtGobblers.sol Line 159
File: src/ArtGobblers.sol Line 167
3-
x += y/x -= y
costs more gas thanx = x + y/x = x - y
for state variables :There are 7 instances of this issue:
File: src/ArtGobblers.sol Line 456
File: src/ArtGobblers.sol Line 464
File: src/ArtGobblers.sol Line 662
File: src/ArtGobblers.sol Line 905
File: src/ArtGobblers.sol Line 906
File: src/ArtGobblers.sol Line 912
File: src/ArtGobblers.sol Line 912
4- Use custom errors rather than
require()
/revert()
strings to save deployments gas :Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information.
There are 24 instances of this issue :
5- It costs more gas to initialize variables to zero than to let the default of zero be applied (saves ~3 gas per instance) :
If a variable is not set/initialized, it is assumed to have the default value (0 for uint or int, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
There are 6 instances of this issue:
File: src/utils/token/GobblersERC1155B.sol Line 173
File: src/utils/token/GobblersERC721.sol Line 186
File: src/utils/GobblerReserve.sol Line 37
File: src/Pages.sol Line 251
File: src/ArtGobblers.sol Line 432
File: src/ArtGobblers.sol Line 592
6- Use of ++i cost less gas than i++/i=i+1 in for loops :
There are 2 instances of this issue:
File: src/utils/GobblerReserve.sol Line 37
File: src/Pages.sol Line 251
7- Using private rather than public for constants, saves gas :
If needed, the value can be read from the verified contract source code. 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.
There are 8 instances of this issue:
File: src/ArtGobblers.sol Line 112
File: src/ArtGobblers.sol Line 115
File: src/ArtGobblers.sol Line 118
File: src/ArtGobblers.sol Line 122
File: src/ArtGobblers.sol Line 126
File: src/ArtGobblers.sol Line 177
File: src/ArtGobblers.sol Line 180
File: src/ArtGobblers.sol Line 184
8- Functions guaranteed to revert when called by normal users can be marked
payable
:If a function modifier such as
onlyAdmin
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for the owner because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are :CALLVALUE(gas=2), DUP1(gas=3), ISZERO(gas=3), PUSH2(gas=3), JUMPI(gas=10), PUSH1(gas=3), DUP1(gas=3), REVERT(gas=0), JUMPDEST(gas=1), POP(gas=2). Which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
There are 4 instances of this issue: