-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QA Report #127
Comments
Good feedback, thanks.
Agree will fix - for context see our response here.
Yes fair feedback, but as noted they are no-ops at the moment.
Good feedback, will fix.
Agree, will fix.
This is intentional. Top-level contracts do not require gaps since all inheritance is slotted before those files. The Gap10000 is a special case gap leaving room for new mixins to be leveraged in the future vs other gaps which are intended to allow new storage slots within a specific mixin; those can be used for new mixins as well but we wanted Gap10000 for a huge amount of space where new mixins are likely to be required.
Agree that we were inconsistent with these checks. We have moved the NFTDropCollection requirement into the factory so that both collection types are implemented in a similar fashion, and we went with the factory instead of the collection init in order to follow the best practice of fail fast.
Fixed the For
Disagree here - this is a special case where those values are magic / arbitrary values.
Agree, will fix.
Invalid. Visibility is not supported in
Since the params there are not bytes, this would require additional casting. I think encodePacked is better readability here.
Agree, we have opted to use the named returns instead of
Disagree. We intentionally use a floating pragma in order to make integrating with contracts easier. Other contract developers are looking to interact with our contracts and they may be on a different version than we use. The pragma selected for our contracts is the minimum required in order to correctly compile and function. This way integration is easier if they lag a few versions behind, or if they use the latest but we don't bump our packages frequently enough, and when we do upgrade versions unless there was a breaking solidity change -- it should just swap in by incrementing our npm package version. |
Overview
Table of Contents
_safeMint()
should be used rather than_mint()
wherever possible__gap[...]
Symbol is required
checkconstant
insteadbytes.concat()
return
statement when the function defines a named return variable, is redundant1. Low Risk Issues
1.1.
_safeMint()
should be used rather than_mint()
wherever possible_mint()
is discouraged in favor of_safeMint()
which ensures that the recipient is either an EOA or implementsIERC721Receiver
, see the WARNING L278:Both open OpenZeppelin and solmate have versions of this function so that NFTs aren't lost if they're minted to contracts that cannot transfer them back out.
Consider replacing with
_safeMint()
here:Be careful however to respect the CEI pattern or add a re-entrancy guard as
_safeMint
adds a callback-check (_checkOnERC721Received
) and a maliciousonERC721Received
could be exploited if not careful.As an example, here, the CEIP isn't respected:
As
tokenId
is needed L160, the lines L159 and L160 can't be swapped. The only mitigation here is to use anonReentrant
modifier on state-updating functions.Reading material:
1.2. Uninitialized Upgradeable contract
Similar issue in the past: here
Upgradeable dependencies should be initialized.
While not causing any harm at the moment, suppose OZ someday decide to upgrade this contract and the sponsor uses the new version: it is possible that the contract will not work anymore.
Consider calling the
init()
here:NFTDropCollection.sol
:NFTCollection.sol
:1.3. Unclear Code Comment
Issue with comment
The following deserves to be clarified:
Here, the comment can make us believe that the state variable
paymentAddress
should be set to the_creator
argument if theinitialize
function didn't pass a_paymentAddress
argument. We could even think that aelse
statement is missing. However, what it means here is that ifpaymentAddress == address(0)
is true in the solution, it will let theowner
variable (which corresponds to the creator) take precedence, as seen ingetTokenCreatorPaymentAddress()
:The comment can be clearer, maybe like this:
If no payment address was defined, the owner (aka the creator's address) will be returned in getters later on
1.4. Incorrect Code Comment
Issue with comment
Here, while what is meant is understandable, the solidity version used is
0.8.12
and theSafeMath
lib isn't imported:Consider replacing the comment with something along the line of
Can't overflow when dividing by a non-zero constant
1.5. Inconsistent use of
__gap[...]
While some contracts inherit from
Gap10000.sol
, others declare their ownprivate __gap
:Furthermore,
NFTDropCollection
andNFTCollection
don't have their ownprivate __gap
and don't inherit fromGap10000
, while they are upgradable.Consider correcting these inconsistencies.
1.6. Inconsistent use of
Symbol is required
checkFor
NFTCollection
, the check is made inNFTCollectionFactory.createNFTCollection()
.However, for
NFTDropCollection
, the check is made way further, after even the contract's creation (during the initialization):Consider moving the check in
NFTCollectionFactory._createNFTDropCollection()
for consistency:File: NFTCollectionFactory.sol 386: function _createNFTDropCollection( ... 395: ) private returns (address collection) { 396: // This reverts if the NFT was previously created using this implementation version + msg.sender + nonce + 396: require(bytes(symbol).length ! 0, "NFTDropCollection: `symbol` must be set"); 397: collection = implementationNFTDropCollection.cloneDeterministic(_getSalt(msg.sender, nonce)); 398: 399: INFTDropCollectionInitializer(collection).initialize( 400: payable(msg.sender), 401: name, 402: symbol, 403: baseURI, 404: postRevealBaseURIHash, 405: maxTokenId, 406: approvedMinter, 407: paymentAddress 408: );
While more consistent, this would also save gas in case of revert.
2. Non-Critical Issues
2.1. It's better to emit after all processing is done
2.2. A magic value should be documented and explained. Use a
constant
insteadConsider using
constant
variables as this would make the code more maintainable and readable while costing nothing gas-wise (constants are replaced by their value at compile-time).2.3. Open TODOS
The codebase still contains
TODO
statements. Consider resolving and removing the TODOs before deploying.2.4. Default Visibility
The following constants are using the default visibility:
For readability, consider explicitly declaring the visibility.
2.5. Use
bytes.concat()
Solidity version 0.8.4 introduces
bytes.concat()
(vsabi.encodePacked(<bytes>,<bytes>)
)Consider using it here:
Please notice that Solidity version 0.8.12 introduces
string.concat()
(vsabi.encodePacked(<str>,<str>)
) and that it's extensively used in the solution already, which is a good thing:2.6. Adding a
return
statement when the function defines a named return variable, is redundantWhile not consuming more gas with the Optimizer enabled: using both named returns and a return statement isn't necessary. Removing one of those can improve code clarity.
Affected code (using both):
2.7. Non-library/interface files should use fixed compiler versions, not floating ones
The text was updated successfully, but these errors were encountered: