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: 5/108
Findings: 4
Award: $2,398.02
🌟 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 https://github.com/code-423n4/2022-08-foundation/blob/792e00df429b0df9ee5d909a0a5a6e72bd07cf79/contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol#L239-L240
Minters can bypass the limitPerAccount
and mint as many token as they want during a drop sale.
In mintFromFixedPriceSale
, there is a check to ensure that an address can only mint as any token as determined in limitPerAccount
:
NFTDropMarketFixedPriceSale.sol#L182-L189
// Confirm that the buyer will not exceed the limit specified after minting. if (IERC721(nftContract).balanceOf(msg.sender) + count > saleConfig.limitPerAccount) { ... revert NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(saleConfig.limitPerAccount); }
However, the contract only checks the current balance of the buyer. Therefore, a buyer can transfer their newly minted token to bypass the check, and mint another batch of tokens. This is especially bad in airdrop-style drops with 0
price, as a single malicious user can potentially mint out the whole collection.
createFixedPriceSale
to start the sale for her NFTDropCollection
with 1 as the limitPerAccount
.It is impossible to enforce a minting limit on a free-for-all sale without resorting to some sort of allowlists/snapshots. In the short term, we can make it more difficult to exploit by:
balanceOf
, preventing users from reusing the same address to mint, and#0 - HardlyDifficult
2022-08-17T20:58:04Z
🌟 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
43.5928 USDC - $43.59
Risk Rating | Number of issues |
---|---|
Low Risk | 5 |
Non-Critical | 2 |
if (buyReferrer != address(0) && buyReferrer != msg.sender && buyReferrer != seller && buyReferrer != creator) { ... }
When processing fees, there is a check to ensure that the inputted buyReferrer
is not the buyer itself, nor it is the seller or the creator themselves.
However, a motivated user (buyer/seller/creator) can easily bypass this check by using a fresh address as the referrer. We recommend to remove this check to save gas, and enforce this constraint on the front-end level instead.
creatorRecipients
// Pay the creator(s) unchecked { for (uint256 i = 0; i < creatorRecipients.length; ++i) { _sendValueWithFallbackWithdraw( creatorRecipients[i], creatorShares[i], SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS ); // Sum the total creator rev from shares // creatorShares is in ETH so creatorRev will not overflow here. creatorRev += creatorShares[i]; } }
When distributing funds to creatorRecipients
, SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
is used as the gas limit to ensure that enough gas is sent to cover 5 recipients. However, as the transfers are being done individually in a for-loop, using SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
will inflate the amount of gas needed to be prepared by the msg.sender
.
Replace SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
with SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT
.
BASIS_POINTS
for (uint256 i = 0; i < creatorRecipients.length; ++i) { if (creatorShares[i] > BASIS_POINTS) { // If the numbers are >100% we ignore the fee recipients and pay just the first instead totalShares = 0; break; } totalShares += creatorShares[i]; }
When calculating totalShares
, there is a check to handle the case where at least one creator has a share of more than 100 percent: creatorShares[i] > BASIS_POINTS
. However, it is still possible that the total shares of all creators will be greater than BASIS_POINTS
.
Therefore, we recommend to just check for the final value of totalShares
, so that:
BASIS_POINTS
.BASIS_POINTS
.Update the code to:
for (uint256 i = 0; i < creatorRecipients.length; ++i) { totalShares += creatorShares[i]; } if (totalShares > BASIS_POINTS) { totalShares = 0; }
_getSellerOrOwnerOf
NatSpec/** * @notice Checks who the seller for an NFT is if listed in this market. */ function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller); /** * @notice Checks who the seller for an NFT is if listed in this market. */ function _getSellerOrOwnerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable sellerOrOwner);
_getSellerOrOwnerOf
has the same @notice
comment as _getSellerOf
. Update it to include owner
.
* @dev This creates and initializes an ERC-1165 minimal proxy pointing to a NFT collection contract implementation.
Minimal proxy should be ERC-1167
.
NFTCollectionFactory.sol#L226-L247
In adminUpdateNFTDropCollectionImplementation
, the event is emitted in the middle of the function. As every other functions in the contract place the emission at the end of the function, it will be more consistent to place the ImplementationNFTDropCollectionUpdated
event emission at the end as well.
NFTDropCollection
name validationNFTDropCollection.sol#L130
During the initialisation of NFTDropCollection
, there is a check to ensure that _symbol
is not empty. However, the same check is missing for _name
.
#0 - HardlyDifficult
2022-08-18T18:33:41Z
[L-01] Unnecessary referrer checks
Agree. We did this to stop common scenarios and may revisit this in the future.
[L-02] Unnecessary gas limit when distributing funds to creatorRecipients
Agree, will fix. Dupe of https://github.com/code-423n4/2022-08-foundation-findings/issues/165
[L-03] Total royalty shares can be greater than BASIS_POINTS
Dupe of https://github.com/code-423n4/2022-08-foundation-findings/issues/34
[L-04] Inaccurate _getSellerOrOwnerOf NatSpec
Agree, will fix.
[L-05] ERC-1165 Typo
Agree, fixed.
[N-01] Inconsistent event emission placement
Agree, will fix.
[N-02] Missing NFTDropCollection name validation
By design -- we choose to require symbols but allow names to be optional...