Open code423n4 opened 2 years ago
Great report with a few well considered recs. Thanks
replaceAtIf function
Won't fix. While this may save a bit of gas, we reserve the use of assembly to areas where it would provide significant benefit or accomplish something that's otherwise not possible with Solidity alone.
startsWith function
We are looking into a variation of based this suggestion to realize some benefits while limiting the use of assembly.
adminUpdateNFTCollectionImplementation function
Agree, will fix
- contracts/NFTDropCollection.sol
This is an interesting suggestion, have not seen this one before. I'm inclined to leave it as-is though for readability.
- contracts/mixins/shared/MarketFees.sol
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.
internalGetImmutableRoyalties function
IGetRoyalties is an interface used by some external NFT contracts and marketplaces. So we are unable to change the return type.
- contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol
True, but the savings would only apply to reverting scenarios it seems. Thinking we'll keep it as-is for simplicity.
- contract/mixins/collections/SequentialMintCollection.sol
Valid, but it's small savings and I prefer the current form for readability.
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.
. contracts/mixins/collections/CollectionRoyalties.sol
Agree, but these are here for compatibility with other marketplaces. We cannot change them since they are defacto standards. Our marketplace will only hit .royaltyInfo so the rec wouldn't apply there.
Don't initialize variables with default values.
Invalid. This optimization technique is no longer applicable with the current version of Solidity.
Table of Contents
require
= 0
assignment can be avoided for default values1. contracts/PercentSplitETH.sol (Out of Scope)
Can be improved a lot. Since out of scope, did not allocate time to write a report here.
2. contracts/libraries/BytesLibrary.sol (Out of Scope)
2.1
replaceAtIf
functionWe can avoid
abi.encodePacked
and also the loop inreplaceAtIf
function, by implementing it in an assembly block. Also note that the assertion and replacement each can be done at once. So the lines 21-32 can be changed to:2.2
startsWith
functionAgain, we can avoid the loop, and also multiple memory reads. Here is the implementation using
assembly
blocks:startsWith
is only used in contracts/PercentSplitETH.sol for the external functionproxyCall
. So the gas savings would be noticed there.3. contracts/NFTCollectionFactory.sol
3.1
adminUpdateNFTCollectionImplementation
functionThe
unchecked
block in adminUpdateNFTCollectionImplementation can be rewritten asAlso the
versionNFTCollection.toString()
which is passed toINFTCollectionInitializer(_implementation).initialize
can be factored out into a variable to avoid thetoString()
conversion twice.4. contracts/NFTDropCollection.sol
The loop (L181-L186) in
mintCountTo
ofNFTDropCollection
can be gas optimized by moving the loop condition inside the loop block:By moving the condition inside and modifying it to
i > latestTokenId
instead ofi <= latestTokenId
, the compiler would translate it into only one comparison (gt
) versus two (lt
oreq
). Therefore, it would say gas.Note: with this change, the loop would run at least once, but this would also be the case in the original code, since
$$ \text{firstTokenId} = \text{latestTokenId} + 1 \le \text{latestTokenId} + \text{count} = \text{latestTokenId'} $$
And this is because since we are passed line 172, we know that
count
is greater than0
.5. contracts/mixins/shared/MarketFees.sol
The unchecked loop block in the
_distributeFunds
(line 126-135) can be optimized by cachingcreatorRecipients.length
, it will avoid multiplemload
s per iteration. So here is how it would look like after modification (also note the@audit
comments):The same type of optimization can be applied to the for loop block in
getFeesAndRecipients
function on line 198-200internalGetImmutableRoyalties
functionaddress payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints
can be combined into 1 dynamic array, it would save memory and therefore gas.IGetRoyalties
would also need to be modified.6. contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol
In
mintFromFixedPriceSale
we can cachesaleConfig.limitPerAccount
(line 180) on the stack which has been used 3 times. This would remove 2 extramloads
.7. contract/mixins/collections/SequentialMintCollection.sol
In
_updateMaxTokenId
we can change the condition in the 3rdrequire
fromBefore:
To:
aka:
Since EVM doesn't have a less than or equal opcode the compiler has to translate that into 2 comparisons
LT
andEQ
which both require 2 stack items and both results have to be true to pass. We can save gas here by usingGT
and negate the result byNOT
opcode which would only require the result on the stack fromGT
.8. Use custom errors instead of require
The following
require
statements can be turned into custom errors to save gas. The list of locationsrequire
statement can be changed to custom errors:9. contracts/mixins/collections/CollectionRoyalties.sol
In
CollectionRoyalties
, the functionsgetFeeRecipients
,getFeeBps
,getRoyalties
only return arrays of length 1. It would be best to redesign some contracts so that these return values can be instead just values and not arrays. This would prevent using memory and the gas costs associated with using memory (MLOAD
,MSTORE
, and memory expansion gas costs).10.
= 0
assignment can be avoided for default valuesSince value types have by default the value of
0
, explicit0
assignment can be avoided which actually would require more gas.Example:
List of locations where this change can be applied: