Platform: Code4rena
Start Date: 11/08/2022
Pot Size: $40,000 USDC
Total HM: 8
Participants: 108
Period: 4 days
Judge: hickuphh3
Total Solo HM: 2
Id: 152
League: ETH
Rank: 34/108
Findings: 3
Award: $75.06
馃専 Selected for report: 0
馃殌 Solo Findings: 0
馃専 Selected for report: Saw-mon_and_Natalie
Also found by: 0x1f8b, 0x52, 0xDjango, 0xNazgul, 0xSmartContract, 0xSolus, 0xackermann, 0xmatt, 0xsolstars, Aymen0909, Bnke0x0, Chom, Deivitto, DevABDee, Dravee, ElKu, IllIllI, JC, Kumpa, Lambda, LeoS, MiloTruck, PwnedNoMore, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Treasure-Seeker, Vexjon, Waze, Yiko, __141345__, apostle0x01, auditor0517, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, c3phas, carlitox477, cccz, cryptphi, csanuragjain, d3e4, danb, delfin454000, durianSausage, erictee, fatherOfBlocks, gogo, iamwhitelights, joestakey, jonatascm, ladboy233, mics, oyc_109, rbserver, ret2basic, robee, rokinot, rvierdiiev, shenwilly, sikorico, simon135, thank_you, wagmi, yash90, zeesaw, zkhorse
41.2563 USDC - $41.26
Consider changing the variable to be an unnamed one
AddressLibrary.sol, 36-37, named return variable is not used. FoundationTreasuryNode.sol, 59-61, treasuryAddress
AddressLibrary.sol, callAndReturnContractAddress
AdminRole.sol, 43, Checks -> Check
MISSING CHECKS FOR ADDRESS(0X0) WHEN ASSIGNING VALUES TO ADDRESS STATE VARIABLES
AdminRole.sol,28-30,
function grantAdmin(address account) external { grantRole(DEFAULT_ADMIN_ROLE, account); }
_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function
NFTCollection.sol,130,143,159,271
#0 - HardlyDifficult
2022-08-18T21:04:41Z
Use named returns consistently
Agree, we have opted to use the named returns instead of return ..
. This is more consistent with other code in our repo and saves a bit of on the contract size. We also like named returns as a way of improving natspec, and typechain (particularly when a tuple is returned).
Missing natspec comments
Fair feedback -- for natspec we aim for complete coverage of the public interfaces but for internal/private/libraries we have some gaps in order to reduce redundancy, for those we aim to include comments where things are unclear or not obvious from the function and variable names.
Typos
I believe "checks" is correct there.
L01: address variable should check if it is zero
Fair feedback but we wanted to be consistent with the OZ access control which will expose the grant functions publicly as well.
Use safeMint
Agree will fix - for context see our response here.
馃専 Selected for report: Dravee
Also found by: 0x040, 0x1f8b, 0xDjango, 0xHarry, 0xNazgul, 0xSmartContract, 0xbepresent, 0xkatana, Amithuddar, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, DevABDee, Diraco, ElKu, Fitraldys, Funen, IllIllI, JC, LeoS, Metatron, MiloTruck, Noah3o6, ReyAdmirado, Rohan16, Rolezn, Saw-mon_and_Natalie, Sm4rty, SpaceCake, TomJ, Tomio, Trabajo_de_mates, Waze, Yiko, __141345__, ajtra, apostle0x01, bobirichman, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, d3e4, durianSausage, erictee, fatherOfBlocks, gerdusx, gogo, hakerbaya, ignacio, jag, joestakey, ladboy233, medikko, mics, newfork01, oyc_109, pfapostol, robee, rvierdiiev, sach1r0, saian, samruna, sikorico, simon135, wagmi, zeesaw, zkhorse, zuhaibmohd
20.6323 USDC - $20.63
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained https://blog.soliditylang.org/2021/04/21/custom-errors/. Custom errors are defined using the error statement.
libraries/AddressLibrary.sol, 31, require(contractAddress.isContract(), "InternalProxyCall: did not return a contract");
NFTCollection.sol, 158, require(tokenCreatorPaymentAddress != address(0), "NFTCollection: tokenCreatorPaymentAddress is required");
NFTCollection.sol, 263, require(bytes(tokenCID).length != 0, "NFTCollection: tokenCID is required");
NFTCollection.sol, 264, require(!cidToMinted[tokenCID], "NFTCollection: NFT was already minted");
NFTCollection.sol, 268, require(maxTokenId == 0 || tokenId <= maxTokenId, "NFTCollection: Max token count has already been minted");
NFTCollection.sol, 327, require(_exists(tokenId), "NFTCollection: URI query for nonexistent token");
NFTCollectionFactory.sol, 203, require(_implementation.isContract(), "NFTCollectionFactory: Implementation is not a contract");
NFTCollectionFactory.sol, 227, require(_implementation.isContract(), "NFTCollectionFactory: Implementation is not a contract");
NFTCollectionFactory.sol, 262, require(bytes(symbol).length != 0, "NFTCollectionFactory: Symbol is required");
NFTDropCollection.sol, 130, require(bytes(_symbol).length > 0, "NFTDropCollection: _symbol
must be set");
NFTDropCollection.sol, 131, require(_maxTokenId > 0, "NFTDropCollection: _maxTokenId
must be set");
NFTDropCollection.sol, 172, require(count != 0, "NFTDropCollection: count
must be greater than 0");
NFTDropCollection.sol, 179, require(latestTokenId <= maxTokenId, "NFTDropCollection: Exceeds max tokenId");
NFTDropCollection.sol, 238, require(_postRevealBaseURIHash != bytes32(0), "NFTDropCollection: use reveal
instead");
mixins/collections/SequentialMintCollection.sol, 63, require(_creator != address(0), "SequentialMintCollection: Creator cannot be the zero address");
mixins/collections/SequentialMintCollection.sol, 74, require(totalSupply() == 0, "SequentialMintCollection: Any NFTs minted must be burned first");
mixins/collections/SequentialMintCollection.sol, 87, require(_maxTokenId != 0, "SequentialMintCollection: Max token ID may not be cleared");
mixins/collections/SequentialMintCollection.sol, 88, require(maxTokenId == 0 || _maxTokenId < maxTokenId, "SequentialMintCollection: Max token ID may not increase");
mixins/collections/SequentialMintCollection.sol, 89, require(latestTokenId <= _maxTokenId, "SequentialMintCollection: Max token ID must be >= last mint");
0 is less gas efficient than !0 if you enable the optimizer at 10k AND you鈥檙e in a require statement. Detailed explanation with the opcodes https://twitter.com/gzeon/status/1485428085885640706
NFTDropCollection.sol, 130, require(bytes(_symbol).length > 0, "NFTDropCollection: _symbol
must be set");
NFTDropCollection.sol, 131, require(_maxTokenId > 0, "NFTDropCollection: _maxTokenId
must be set");
prefix increment ++i is more cheaper than postfix i++
NFTCollectionFactory.sol, 207, versionNFTCollection++; NFTCollectionFactory.sol, 231, versionNFTDropCollection++;
// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
NFTCollection.sol, 53, mapping(string => bool) private cidToMinted;
resign the default value to the variables will cost more gas.
libraries/BytesLibrary.sol, 25, for (uint256 i = 0; i < 20; ++i) { libraries/BytesLibrary.sol, 44, for (uint256 i = 0; i < 4; ++i) { mixins/shared/MarketFees.sol, 126, for (uint256 i = 0; i < creatorRecipients.length; ++i) { mixins/shared/MarketFees.sol, 198, for (uint256 i = 0; i < creatorShares.length; ++i) { mixins/shared/MarketFees.sol, 484, for (uint256 i = 0; i < creatorRecipients.length; ++i) {
If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
NFTCollectionFactory.sol, adminUpdateNFTCollectionImplementation,adminUpdateNFTDropCollectionImplementation NFTDropCollection.sol,burn,reveal,selfDestruct,updateMaxTokenId,updatePreRevealContent
libraries/BytesLibrary.sol, 25, for (uint256 i = 0; i < 20; ++i) { libraries/BytesLibrary.sol, 44, for (uint256 i = 0; i < 4; ++i) { mixins/shared/MarketFees.sol, 126, for (uint256 i = 0; i < creatorRecipients.length; ++i) { mixins/shared/MarketFees.sol, 198, for (uint256 i = 0; i < creatorShares.length; ++i) { mixins/shared/MarketFees.sol, 484, for (uint256 i = 0; i < creatorRecipients.length; ++i) {
#0 - HardlyDifficult
2022-08-17T14:48:24Z
G01: custom error save more 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.
G02: COMPARISONS WITH ZERO FOR UNSIGNED INTEGERS
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
G03: PREFIX INCREMENT SAVE MORE GAS
Agree and will fix.
G04: 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.
G05: resign the default value to the variables.
Invalid. This optimization technique is no longer applicable with the current version of Solidity.
G06: USE A MORE RECENT VERSION OF SOLIDITY
Invalid - we already on 0.8.16, the latest available.
G07: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.
G08: ++I/I++ SHOULD BE UNCHECKED{++I}/UNCHECKED{I++} WHEN IT IS NOT POSSIBLE FOR THEM TO OVERFLOW, AS IS THE CASE WHEN USED IN FOR- AND WHILE-LOOPS
4 of the 5 examples listed are already unchecked -- invalid. 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.