Open code423n4 opened 2 years ago
Very detailed and thoughtful feedback -- thank you!
supportsInterface can be rewritten to avoid the if/esle branching:
I think I do like this style more, will consider the change.
contracts/interfaces/ICollectionFactory.sol
Agree, fixed.
contracts/mixins/shared/ContractFactory.sol
Not sure I'm following this suggestion. There does not appear to be another .code.length type check included at the moment. Considering a stricter check is compelling but since this is an admin function call I think that may be overkill here.
Shorter inheritance list
True but for top-level contracts I like to expand all inherited contracts to make it clear what all the dependencies are and the lineriazation order they are included in.
4.2 CID need to be unique per tokenID
Agree. This is a primary goal of the NFTDropCollection. As you note there are other more flexible ways we could run with this type of approach and we may consider those in the future.
5.1 .isContract()
Fair feedback. Considering a stricter check is compelling but since this is an admin function call I think that may be overkill here.
5.2 versionNFTDropCollection
By design - the default value of 0 is correct there. NFTCollections were previously created by a different factory contract, we wanted the new factory to pick up version where that left off. But drops are new so starting at 0 is correct.
5.3 a better name can be chosen for rolesContract
Agree, I like that name more and will update.
6.2 The comment on line 175 needs a bit of correction
Good catch -- this was missed after adding a require against count == 0. Will fix.
- contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol
Although minor, this approach was used to save gas for the happy case scenario since it avoids a second if condition.
contracts/FETH.sol (Out of Scope)
Fair feedback, but I think the current approach is easier to reason about. And saving admin-only gas is not a goal for us.
- Line Width
Our linter is configured to require 120... although maybe you mean we are adding new lines too early in some instances (?)
- Hard-coded gas limits
Fair feedback. However the use case requires some gas limit to be defined and it's not clear there is a viable alternative here.
- address.isContract check
This is good feedback. ATM these checks are there to help avoid simple errors by the admin. I'm not sure that the stricter check is worth the complexity to maintain.
Use fixed pragma
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.
- Avoid Nested if Blocks
(out of scope) I agree that style is better, will fix.
Per discussion with @HickupHH3 (judge), regarding severity and validity of above items:
"Slightly disagree with #3. Agree with sponsor that the suggestion isn't clear. The rest is ok!"
Table of Contents
address.isContract
checksupportsInterface
checkif
Blocks1. contracts/mixins/collections/CollectionRoyalties.sol
On line 80,
supportsInterface
can be rewritten to avoid theif/esle
branching:2. contracts/interfaces/ICollectionFactory.sol
In
ICollectionFactory
on line 6,IProxyCall
is never used and can safely be removed. Unless there is a plan to use it in the future. Maybe a comment explaining why it was imported here would be helpful.3. contracts/mixins/shared/ContractFactory.sol
On line 31, there is a check for
_contractFactory
to see if it already has a code. I guess this is an extra check that can be removed. Since if_contractFactory
calls the constructor here in its own constructor by then_contractFactory.isContract() = _contractFactory.code.length == 0
. Also, it is possible that a wrong contract address is passed here, so the check would not really do anything. This will only check against accidental EOA addresses used for_contractFactory
. So we could possibly remove the following lines:If there is a stricter condition for the allowed
contractFactory
addresses, maybe we could use that instead. One possible idea is an array of implementation contract code hashes that we could check. Or maybe contracts that have a function similar tosupportsInterface
that returns a magic number which we could check here.4. contracts/NFTCollection.sol
4.1 Shorter inheritance list
The inheritance contracts on line 29-40 can be consolidated into a shorter list:
Then you would need to adjust the overrides on lines 255 and 316
4.2
CID
need to be unique pertokenID
Different
tokenID
s can not share the sameCID
by design. Although it is possible to design the contract so that some tokens share the sameCID
to save storage and also server space for off-chain contents.5. contracts/NFTCollectionFactory.sol
5.1
.isContract()
On lines 182, and 203 instead of checking if
addr.isContract()
to avoid setting the addresses to EOA by mistake it would be best to pass the code hash instead and check the code hash at those addresses. So for example:Before:
After:
This is a stronger requirement since it would guarantee that the addresses are contracts and also they have the required code hash. For the functions to pass the
require
statements you would need to make 2 mistakes, one for the address and the other for the code hash. The probability of making this mistake should be theoretically lower than just passing a wrong address.5.2
versionNFTDropCollection
Doesn't have an initializer like
versionNFTCollection
5.3 a better name can be chosen for
rolesContract
rolerManager
might be a better name for this immutable variable and would make it easier to remember what it does (ref. line 104).6. contracts/NFTDropCollection.sol
6.1
supportsInterface
functionWe can rewrite
supportsInterface
function (Lines 284-294) like the following block which would make it easier to read and possibly would save some gas.6.2 The comment on line 175 needs a bit of correction
So the current comment says:
But
count
can not be zero if we have reached this line. Since we have already checked for a non-zerocount
on line 172So we can change the comment to
6.3 Shorter inheritance list
Like
NFTCollection
, the inheritence list forNFTDropCollection
contract on lines 28-46 can be consolidated more.The
overrides
on lines 245 and 287 would also need to be modified accordingly.7. contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol
In
mintFromFixedPriceSale
we can avoid the nestedif
blocks on lines 182-189. This would improve readability and analyze and it would have the same effect. On the plus side, it will also save gas for a reverting call wheresaleConfig.limitPerAccount
is zero by avoiding the outerif
block in the original code.contracts/FETH.sol (Out of Scope)
In
constructor
instead of passing_lockupDuration
pass_lockupInterval
to save on the exact division check.So taking that into consideration the
constructor
would look like this:Also, the
_lockupInterval
check is moved up before the assignments to save gas in case of a revert. If there will be no revert, moving up theif
block would not introduce any gas changes, since the check will be performed eventually.9. Line Width
Keep line width to max 120 characters for better readability.
10. Hard-coded gas limits
In
contracts/mixins/shared/Constants.sol
we have 3 gas limit constants:These numbers are not future-proof as some hardforks introduce changes to gas costs. These potential future changes to gas costs might break some of the functionalities of the smart contracts that use these constants. This is something to keep in mind. If some hardfork, would break a smart contract using these numbers you would need to deploy new contracts with adjusted gas limit constants. Or you can also have these gas limits be mutable by admins on-chain. For example, all these 3 values can be stored on-chain in 1 storage slot.
11.
address.isContract
checkLots of the contracts in this project import
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"
and useaddress.isContract()
to check if an address is a contranct and not a EOA. I guess this is only a check if the deployer by mistake provides the wrong address. I think this should be double-checked off-chain. If an on-chain check is needed, there are other checks that can be done that are even more strict than just checking against EOA mistakes. For example, we can provide the contract has as the second input to the constructor and check the address's codehash against that. Here is a template as an example:Not only this checks for address with code, but also pinpoints the contract hash to a specific hash. Another type of check that can be used is to check if the provided contract address supports a specific
interfaceSupport
or call an endpoint of the contract expecting it to return a specific magic number.Here is a list of places
isContract
has been used:12. Simplify
supportsInterface
check12.1 NFTDropCollection.sol
NFTDropCollection.supportsInterface
(lines 284-295) can be changed to:12.2 CollectionRoyalties.sol
CollectionRoyalties.supportsInterface
(lines 80-91) can be changed to:13. Floating Solidity Pragma Version
It's best to use the same compiler version across all project files/team members. So having a fixed version pragma is a good practice. Most contracts use a floating pragma which would allow the patch number to be equal or higher than the specified patch number.
14. Avoid Nested
if
BlocksFor better readability and analysis it is better to avoid nested
if
blocks. Here is an example:14.1 FETH.sol (lines 482-492)
After edit: