NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
Submission Details
Impact: low
Likelihood: low

Violation of Checks-Effects-Interactions

Author Revealed upon completion

Root + Impact

Description

  • The buy and cancelListing functions violate the Checks-Effects-Interactions (CEI) pattern.

  • They execute external calls (_safeTransfer for NFTs and safeTransfer for USDC) before updating the internal state variables (isActive = false or collateralForMinting = 0).

for buy function:
_safeTransfer(listing.seller, msg.sender, listing.tokenId, "");
s_listings[_listingId].isActive = false;
for cancelListing function:
usdc.safeTransfer(listing.seller, collateralForMinting[listing.tokenId]);
collateralForMinting[listing.tokenId] = 0;

Risk

Likelihood:

  • Every time buy and cancelListing are called

Impact:

  • In cancelListing, a reentrant call can be used to trigger the usdc.safeTransfer multiple times before the collateralForMinting

  • Corruption of the activeListingsCounter state and potential for double-claiming collateral.

Proof Of Concept:

Initial Call: An attacker contract calls buy(ID).

  • The Interaction: NFTDealers executes _safeTransfer, which triggers the attacker's onERC721Received hook before the listing is marked as inactive.

  • The Re-entry: Inside the hook, the attacker calls buy(ID) again.

  • Logic Failure: The second call passes the isActive check because the first call is still "paused" on the stack and hasn't reached the line to flip the bool.

  • Corruption: The activeListingsCounter is decremented a second time (causing a revert/underflow), proving the contract's internal accounting is compromised.

Recommended Mitigation

  • In buy: Move s_listings[_listingId].isActive = false and activeListingsCounter-- above the _safeTransfer call.

  • In cancelListing: Move collateralForMinting[listing.tokenId] = 0 above the usdc.safeTransfer call.

In cancelListing function:
- usdc.safeTransfer(listing.seller, collateralForMinting[listing.tokenId]);
- collateralForMinting[listing.tokenId] = 0;
+ collateralForMinting[listing.tokenId] = 0;
+ usdc.safeTransfer(listing.seller, collateralForMinting[listing.tokenId]);
In buy function:
+ s_listings[_listingId].isActive = false;
+ activeListingsCounter--;
_safeTransfer(listing.seller, msg.sender, listing.tokenId, "");
- s_listings[_listingId].isActive = false;
emit NFT_Dealers_Sold(msg.sender, listing.price);

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!