Open code423n4 opened 2 years ago
Good report, very easy to follow.
- not using the named return variables when a function returns, wastes deployment gas
Agree for code consistency with other parts of our code. And this is a thorough list. Saves 0.013 bytes on the bytecode size in total.
- can make the variable outside the loop to save gas
This technique is new to me.. but it makes sense that it could make a difference.
I tested it with royalties where 5 recipients were paid: Before: // 221331 · 230960 · 225544 After: // 221316 · 230945 · 225529 Savings: 15 gas
And when royalties are just 1 recipient (i.e. loop is not entered): Before: // 129153 · 136830 · 132512 After: // 129157 · 136834 · 132516 Cost: 4 gas
Since the vast majority of sales use just 1 recipient, and that it does complicate the code a tiny bit - I won't be making the change here.
However for your other example, that loop is always run for 20 iterations -- seems like an easy win. Will be making that change.
- it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied
Invalid. This optimization technique is no longer applicable with the current version of Solidity.
++i/i++ should be unchecked{++i}
Forgot example links for this one - but I know what you mean from the other submissions :)
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.
- require()/revert() strings longer than 32 bytes
Agree but won't fix. We use up to 64 bytes, aiming to respect the incremental cost but 32 bytes is a bit too short to provide descriptive error messages for our users.
- use custom errors rather than revert()/require() strings to save deployment gas
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.
- using calldata instead of memory for read-only arguments in external functions saves gas
Updated startsWith
, NFTCollection, and the AddressLibrary
& call
Invalid for capLength
due to how that data is sourced. Same for replaceAtIf
.
- using bools for storage incurs overhead
Valid for cidToMinted
, saving ~200 gas. Not seeing any benefit for assumePrimarySale
, potentially because it's an immutable variable.
Not changing the return values to keep the ABI clean, particularly supportsInterface
for example since that would be a violation of the standard.
- usage of uint/int smaller than 32 bytes (256 bits) incurs overhead
Potentially valid but won't fix. This is uint32 to allow packing storage for a significant gas savings. Accepting the input as uint256 would incur additional overhead in checking for overflow before saving these values. Other inputs are logically limited to the size exposed, huge values cannot be used. Accepting the input as uint256 would incur additional overhead in checking for overflow before using these values.
- expressions for constant values such as a call to keccak256(), should use immutable rather than constant
While it seems this should help, changing to immutable shows a regression in our test suite. Gas costs go up by ~50 gas.
- using private rather than public for constants, saves gas
Agree but won't fix. For ease of use and consistency we will continue to expose some constants publicly.
.length should not be looked up in every loop of a for-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.
1. not using the named return variables when a function returns, wastes deployment gas
AddressLibrary.sol#L36
NFTCollectionFactory.sol#L294
NFTCollectionFactory.sol#L333
NFTCollectionFactory.sol#L372
NFTDropMarketFixedPriceSale.sol#L230
FoundationTreasuryNode.sol#L59
MarketFees.sol#L208
MarketSharedCore.sol#L20
2. can make the variable outside the loop to save gas
BytesLibrary.sol#L26
MarketFees.sol#L504
3. it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied
BytesLibrary.sol#L25
BytesLibrary.sol#L44
MarketFees.sol#L126
MarketFees.sol#L198
MarketFees.sol#L484
4.
++i/i++
should beunchecked{++i}/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for-loop and while-loops5. require()/revert() strings longer than 32 bytes cost extra gas
AddressLibrary.sol#L31
NFTCollection.sol#L158
NFTCollection.sol#L263
NFTCollection.sol#L264
NFTCollection.sol#L268
NFTCollection.sol#L327
NFTCollectionFactory.sol#L173
NFTCollectionFactory.sol#L182
NFTCollectionFactory.sol#L203
NFTCollectionFactory.sol#L227
NFTCollectionFactory.sol#L262
NFTDropCollection.sol#L80
NFTDropCollection.sol#L93
NFTDropCollection.sol#L130
NFTDropCollection.sol#L131
NFTDropCollection.sol#L172
NFTDropCollection.sol#L179
NFTDropCollection.sol#L238
AdminRole.sol#L19
MinterRole.sol#L22
SequentialMintCollection.sol#L58
SequentialMintCollection.sol#L63
SequentialMintCollection.sol#L74
SequentialMintCollection.sol#L87
SequentialMintCollection.sol#L88
SequentialMintCollection.sol#L89
ContractFactory.sol#L22
ContractFactory.sol#L31
6. use custom errors rather than revert()/require() strings to save deployment gas
https://blog.soliditylang.org/2021/04/21/custom-errors/
basically most requires and reverts use strings and need to be changed
AddressLibrary.sol#L31
NFTCollection.sol#L158
NFTCollection.sol#L263
NFTCollection.sol#L264
NFTCollection.sol#L268
NFTCollection.sol#L327
NFTCollectionFactory.sol#L173
NFTCollectionFactory.sol#L182
NFTCollectionFactory.sol#L203
NFTCollectionFactory.sol#L227
NFTCollectionFactory.sol#L262
NFTDropCollection.sol#L80
NFTDropCollection.sol#L93
NFTDropCollection.sol#L130
NFTDropCollection.sol#L131
NFTDropCollection.sol#L172
NFTDropCollection.sol#L179
NFTDropCollection.sol#L238
AdminRole.sol#L19
MinterRole.sol#L22
SequentialMintCollection.sol#L58
SequentialMintCollection.sol#L63
SequentialMintCollection.sol#L74
SequentialMintCollection.sol#L87
SequentialMintCollection.sol#L88
SequentialMintCollection.sol#L89
ContractFactory.sol#L22
ContractFactory.sol#L31
7. using
calldata
instead ofmemory
for read-only arguments in external functions saves gasArrayLibrary.sol#L13
ArrayLibrary.sol#L25
BytesLibrary.sol#L16
BytesLibrary.sol#L38
AddressLibrary.sol#L25
AddressLibrary.sol#L34
NFTCollection.sol#L107
NFTCollection.sol#L108
NFTCollectionFactory.sol#L371
8. using
bools
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
NFTCollection.sol#L53
NFTCollection.sol#L291
NFTCollection.sol#L317
NFTDropCollection.sol#L266
NFTDropCollection.sol#L288
NFTDropMarketFixedPriceSale.sol#L232
FETHNode.sol#L46
MarketFees.sol#L83
MarketFees.sol#L61
9. 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. https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
NFTCollection.sol#L251
NFTCollectionFactory.sol#L80
NFTCollectionFactory.sol#L95
NFTCollectionFactory.sol#L192
NFTCollectionFactory.sol#L291
NFTCollectionFactory.sol#L329
NFTCollectionFactory.sol#L368
NFTCollectionFactory.sol#L391
NFTDropCollection.sol#L126
NFTDropCollection.sol#L171
NFTDropCollection.sol#L220
NFTDropMarketFixedPriceSale.sol#L120
NFTDropMarketFixedPriceSale.sol#L121
NFTDropMarketFixedPriceSale.sol#L172
SequentialMintCollection.sol#L27
SequentialMintCollection.sol#L34
SequentialMintCollection.sol#L40
SequentialMintCollection.sol#L62
SequentialMintCollection.sol#L86
10. expressions for constant values such as a call to keccak256(), should use immutable rather than constant
NFTDropMarketFixedPriceSale.sol#L70
MinterRole.sol#L19
11. using private rather than public for constants, saves gas
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it’s used, and not adding another entry to the method ID table
NFTDropMarketFixedPriceSale.sol#L70
MinterRole.sol#L19
12.
<array>.length
should not be looked up in every loop of a for-loopThis reduce gas cost as show here https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5
MarketFees.sol#L503
MarketFees.sol#L198