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: 17/108
Findings: 3
Award: $135.24
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: itsmeSTYJ
Also found by: 0x1f8b, 0x52, 0xDjango, Ch_301, Chom, KIntern_NA, PwnedNoMore, Treasure-Seeker, auditor0517, byndooa, cccz, csanuragjain, ladboy233, nine9, shenwilly, thank_you, yixxas, zkhorse
42.8343 USDC - $42.83
https://github.com/code-423n4/2022-08-foundation/blob/792e00df429b0df9ee5d909a0a5a6e72bd07cf79/contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol#L183-L189 https://github.com/code-423n4/2022-08-foundation/blob/792e00df429b0df9ee5d909a0a5a6e72bd07cf79/contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol#L239-L243
limitPerAccount can be bypassed. User can mint and transfer all NFT to intermediate wallet and mint again to bypass the limitPerAccount.
Assume limitPerAccount = 5
First user mint 5 NFT.
Then user transfer all 5 NFT to an intermediate address.
Currently, user is holding 0 NFT. IERC721(nftContract).balanceOf(user) == 0
Then mint 5 NFT again
// Confirm that the buyer will not exceed the limit specified after minting. if (IERC721(nftContract).balanceOf(msg.sender) + count > saleConfig.limitPerAccount) { if (saleConfig.limitPerAccount == 0) { // Provide a more targeted error if the collection has not been listed. revert NFTDropMarketFixedPriceSale_Must_Have_Sale_In_Progress(); } revert NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(saleConfig.limitPerAccount); }
In mintFromFixedPriceSale
function IERC721(nftContract).balanceOf(msg.sender) + count == 0 + 5 <= saleConfig.limitPerAccount
thus not revert and 5 NFT will be minted to the wallet again.
Now, this wallet has minted 10 NFT which is over saleConfig.limitPerAccount = 5
uint256 currentBalance = IERC721(nftContract).balanceOf(user); if (currentBalance >= limitPerAccount) { // User has exhausted their limit. return 0; } uint256 availableToMint = limitPerAccount - currentBalance; if (availableToMint > numberOfTokensAvailableToMint) { // User has more tokens available than the collection has available. return numberOfTokensAvailableToMint; } return availableToMint;
In getAvailableCountFromFixedPriceSale
return availableToMint = limitPerAccount - IERC721(nftContract).balanceOf(user) = 5 - 0 = 5
means this wallet can mint another 5 NFT although this wallet has already minted 5 NFT.
User can repeat this process to mint unlimited NFT.
Manual review
Create a mapping(address => uint256) public mintedCount;
to keep track of NFT minting count of each wallet.
#0 - HardlyDifficult
2022-08-17T20:58:21Z
🌟 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
71.8108 USDC - $71.81
/** * @notice Allows the owner to set a max tokenID. * This provides a guarantee to collectors about the limit of this collection contract. * @dev Once this value has been set, it may be decreased but can never be increased. * This max may be less than the final `totalSupply` if 1 or more tokens were burned. * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value. */ function updateMaxTokenId(uint32 _maxTokenId) external onlyAdmin { _updateMaxTokenId(_maxTokenId); }
If 1 or more tokens were burned, totalSupply
will be decreased.
It should be "This max may be more than the final totalSupply
if 1 or more tokens were burned."
/** * @notice Allows the owner to set a max tokenID. * This provides a guarantee to collectors about the limit of this collection contract. * @dev Once this value has been set, it may be decreased but can never be increased. * This max may be less than the final `totalSupply` if 1 or more tokens were burned. * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value. */ function updateMaxTokenId(uint32 _maxTokenId) external onlyAdmin { _updateMaxTokenId(_maxTokenId); }
updateMaxTokenId is an important event, it should emit an event
#0 - HardlyDifficult
2022-08-18T20:55:40Z
Possibly wrong comment
Good catch, will fix.
updateMaxTokenId doesn't emit event
Invalid, the helper it calls does emit an event already.
🌟 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.5997 USDC - $20.60
This reduce gas cost as show here https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5
Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deployment cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.
Any require statement in your code can be replaced with custom error for example,
require(_postRevealBaseURIHash != bytes32(0), "NFTDropCollection: use `reveal` instead");
Can be replaced with
// declare error before contract declaration error UseRevealInstead(); if (_postRevealBaseURIHash == bytes32(0)) revert UseRevealInstead();
#0 - HardlyDifficult
2022-08-19T15:35:58Z
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.