Open code423n4 opened 2 years ago
Use != 0 instead of > 0
Invalid. We tested the recommendation and got the following results:
createNFTDropCollection gas reporter results:
using > 0 (current):
- 319246 · 319578 · 319361
using != 0 (recommendation):
- 319252 · 319584 · 319367
impact: +6 gas
unchecked loop in
getFeesAndRecipients
getFeesAndRecipients
is a read only function not intended to be used on-chain, but as a best practice we will add unchecked there as well.
Cache Array Length Outside of Loop
May be theoretically valid, but won't fix. I tested this: gas-reporter and our gas-stories suite is reporting a small regression using this technique. It also hurts readability a bit so we wouldn't want to include it unless it was a clear win.
[NAZ-G4] Functions Visibility Can Be Declared External
Invalid - this must be public for inheritance.
Functions guaranteed to revert when called by normal users can be marked payable
Disagree. This could save gas but 1) this seems like a poor practice to encourage and could be error prone. and 2) we don't care about optimizing admin-only actions.
Custom errors
Agree but won't fix at this time. We use these in the market but not in collections. Unfortunately custom errors are still not as good of an experience for users (e.g. on etherscan). We used them in the market originally because we were nearing the max contract size limit and this was a good way to reduce the bytecode. We'll consider this in the future as tooling continues to improve.
NAZ-G7] Function Ordering via Method ID
Disagree, this hurts the user experience and saves very little gas.
[NAZ-G1] In
require()
, Use!= 0
Instead of> 0
With Uint ValuesContext:
NFTDropCollection.sol#L131
Description: In a require, when checking a uint, using
!= 0
instead of> 0
saves 6 gas. This will jump over or avoid an extraISZERO
opcode.Recommendation: Use
!= 0
instead of> 0
with uint values but only inrequire()
statements.[NAZ-G2] The Increment In For Loop Post Condition Can Be Made Unchecked
Context:
MarketFees.sol#L198
Description: (This is only relevant if you are using the default solidity checked arithmetic).
i++
involves checked arithmetic, which is not required. This is because the value ofi
is always strictly less than length <= 2256 - 1. Therefore, the theoretical maximum value ofi
to enter the for-loop body is `2256 - 2. This means that the
i++` in the for loop can never overflow. Regardless, the overflow checks are performed by the compiler.Unfortunately, the Solidity optimizer is not smart enough to detect this and remove the checks. One can manually do this by:
Note that it’s important that the call to
unchecked_inc
is inlined. This is only possible for solidity versions starting from0.8.2
.Recommendation: The increment in the for loop post condition can be made unchecked.
[NAZ-G3] Catching The Array Length Prior To Loop
Context:
MarketFees.sol#L198
,MarketFees.sol#L484
,MarketFees.sol#L503
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 addlength
in place ofvariable.length
in the for loop.[NAZ-G4] Functions Visibility Can Be Declared External
Context:
NFTDropCollection.sol#L159
Description: Several functions across multiple contracts have a public visibility and can be marked with external visibility to save gas.
Recommendation: Change the functions visibility to external to save gas.
[NAZ-G5] Setting The Constructor To Payable
Context:
All Contracts
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 saves 21 gas on deployment with no security risks.Recommendation: Set the constructor to payable.
[NAZ-G6] Use of Custom Errors Instead of String
Context:
All Contracts
Description: To save some gas the use of custom errors leads to cheaper deploy time cost and run time cost. The run time cost is only relevant when the revert condition is met.
Recommendation: Use Custom Errors instead of strings.
[NAZ-G7] Function Ordering via Method ID
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) because 22 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. One could use
This tool
to help find alternative function names with lower Method IDs while keeping the original name intact.Recommendation: Find a lower method ID name for the most called functions for example
mostCalled()
vs.mostCalled_41q()
is cheaper by 44 gas.