Open code423n4 opened 2 years ago
Will give you 400 gas saved because it's better than the average report, however I think benchmarks should be offered on the real code, not via dummy examples.
The dummy examples obfuscate the optimization from the real contest, meaning some of these will not actually work in the in-production code
A good report, but you should customize per contest if you want to win
Gas Optimizations List
return
keyword for gas efficient (for external functions)Shifting
cheaper thandivision
uint256
instead ofbool
in mappings is more gas efficientassembly
to write address storage valuesmethodID
address(0)
constructor
topayable
++index
instead ofindex++
to increment a loop counterCustom Errors
rather than revert() / require() strings to save deployment gasprivate
rather thanpublic
for constants, saves gaspayable
msg.sender
(even if you never use it)Total 13 issues
Suggestions
zero-address
check in `constructorTotal 1 suggestion
[G-01] No need
return
keyword for gas efficient (for external functions) [ 3 gas saved]Context: ArtGobblers.sol#L541 ChainlinkV1RandProvider.sol#L69
Recommendation: You must remove the
return
keyword from the specified contexts.Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report: src/ArtGobblers.sol/requestRandomSeed [ 3 gas saved] src/utils/rand/ChainlinkV1RandProvider.sol/ requestRandomBytes [ 3 gas saved]
First gas test with return parameter (Current Code) Second gas test by removing return parameter (Recommended code)
[G-02]
Shifting
cheaper thandivision
[6 gas saved]Context: ArtGobblers.sol#L462
Description: A division by 2 can be calculated by shifting one to the right. While the
DIV
opcode uses5 gas
, theSHR
opcode only uses3 gas
Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.Recommendation: Instead of dividing by
2
use>> 1
.Proof Of Concept: The optimizer was turned on and set to 10000 runs
Current: ArtGobblers.sol#L462
Recommendation:
Gas Report:
[G-03] Using
uint256
instead ofbool
in mappings is more gas efficient [146 gas per instance]Context: ArtGobblers.sol#L149 ArtGobblers.sol#L212
Description: OpenZeppelin uint256 and bool comparison: 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. The values being non-zero value makes deployment a bit more expensive.
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.
Recommendation: Use uint256(1) and uint256(2) instead of bool.
Proof Of Concept: The optimizer was turned on and set to 10000 runs
Gas Report:
[G-04] Use
assembly
to write address storage values [39 - 399 gas per instance]Context: ArtGobblers.sol#L320 ArtGobblers.sol#L321 ArtGobblers.sol#L314 ArtGobblers.sol#L315 ArtGobblers.sol#L316 ArtGobblers.sol#L317 Goo.sol#L83 Goo.sol#L84 Pages.sol#L179 Pages.sol#L181 Pages.sol#L183 GobblerReserve.sol#L24 ChainlinkV1RandProvider.sol#L55
Description: Use assembly to write address storage values. 399 gas saves per instance for
string
39 gas saves per instance foraddress
uint256 also saves no gasProof Of Concept: (for string) The optimizer was turned on and set to 10000 runs.
Gas Report:
Proof Of Concept: (for address) The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-05] Function ordering via
methodID
[22 gas per instance]Context: All Contracts
Description: Contracts most called functions could simply save gas by function ordering via
Method ID
. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because22 gas
are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.Recommendation: Find a lower
method ID
name for the most called functions for example Call() vs. Call1() is cheaper by22 gas
For example, the function IDs in theArtGobblers.sol
contract will be the most used; A lower method ID may be given.Proof of Consept: https://coinsbench.com/advanced-gas-optimizations-tips-for-solidity-85c47f413dc5
ArtGobblers.sol function names can be named and sorted according to METHOD ID
[G-06] Use assembly to check for
address(0)
[6 gas per instance]Context: ArtGobblers.sol#L887 PagesERC721.sol#L105
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-07] Setting the
constructor
topayable
[13 gas per instance]Context: ArtGobblers.sol#L287 Goo.sol#L82 Pages.sol#L156 GobblerReserve.sol#L23 ChainlinkV1RandProvider.sol#L48 PagesERC721.sol#L36
Description: You can cut out 10 opcodes in the creation-time EVM bytecode if you declare a constructor payable. Making the constructor payable eliminates the need for an initial check of
msg.value == 0
and saves13 gas
on deployment with no security risks.Recommendation: Set the constructor to
payable
.Proof Of Concept: https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5?u=pcaversaccio
The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-08] Catching the array length prior to loop [13 gas per 6 arrays instance]
Context: GobblerReserve.sol#L37
Description: One can save gas by caching the array length (in stack) and using that set variable in the loop. Replace state variable reads and writes within loops with local variable reads and writes. This is done by assigning state variable values to new local variables, reading and/or writing the local variables in a loop, then after the loop assigning any changed local variables to their equivalent state variables.
Recommendation: Simply do something like so before the for loop:
uint length = variable.length
Then add length in place ofvariable.length
in the for loop.Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-09] Use
++index
instead ofindex++
to increment a loop counter [5 gas per instance]Context: Pages.sol#L251
Description: Due to reduced stack operations, using ++index saves 5 gas per iteration.
Recommendation: Use ++index to increment a loop counter.
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-10] Use
Custom Errors
rather than revert() / require() strings to save deployment gas [68 gas per instance]Context: ArtGobblers.sol#L437 ArtGobblers.sol#L885 ArtGobblers.sol#L887 ArtGobblers.sol#L889 PagesERC721.sol#L85 PagesERC721.sol#L103 PagesERC721.sol#L105 PagesERC721.sol#L107 PagesERC721.sol#L135 PagesERC721.sol#L151 GobblersERC721.sol#L95 GobblersERC721.sol#L121 GobblersERC1155B.sol#L149 GobblersERC1155B.sol#L185
Description: Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they’re hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas. https://blog.soliditylang.org/2021/04/21/custom-errors/
Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-11] Using
private
rather thanpublic
for constants, saves gas [ 10 gas per instance]Context: ArtGobblers.sol#L112 ArtGobblers.sol#L115 ArtGobblers.sol#L118 ArtGobblers.sol#L122 ArtGobblers.sol#L177 ArtGobblers.sol#L180 ArtGobblers.sol#L184
Proof Of Concept The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-12] Functions guaranteed to revert when callled by normal users can be marked
payable
[24 gas per instance]Context: ArtGobblers.sol#L560 GobblerReserve.sol#L34
Description: If a function modifier or require such as onlyOwner-admin 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 legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
Recommendation: Functions guaranteed to revert when called by normal users can be marked payable (for only
onlyowner or admin
functions)Proof Of Concept: The optimizer was turned on and set to 10000 runs.
Gas Report:
[G-13] Save gas by just catching to
msg.sender
(even if you never use it) [ 2 gas per function run]Context: ArtGobblers.sol#L339
Proof Of Concept The optimizer was turned on and set to 1000000 runs.
Gas Report:
[S-01] Missing
zero-address
check inconstructor
Context: ArtGobblers.sol#L294 ArtGobblers.sol#L295 ArtGobblers.sol#L292 ArtGobblers.sol#L293 ArtGobblers.sol#L296 Goo.sol#L82 Pages.sol#L160 Pages.sol#L161 Pages.sol#L162
Description: Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly. It also wast gas as it requires the redeployment of the contract.