NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Reentrancy risk

Root + Impact

Description

  • The 'buy' and 'cancelListings' functions do exactly as the function names say they

  • They however do not follow the Checks-Effects-Interactions pattern which could lead to possible reentrancy attacks. They perform external calls—specifically _safeTransfer (NFT) and usdc.safeTransfer (ERC20)—before updating the internal state variable s_listings[_listingId].isActive = false.

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

This occurs when a malicious contract calls the 'buy' and 'cancelListings' functions

Impact:

  • Asset theft

Proof of Concept

Preparation: An attacker deploys a MaliciousBuyer contract.
Step 1: The attacker calls buy(1) via their contract.
Step 2: Your contract calls _safeTransfer. Because it's a "safe" transfer, your contract checks if the MaliciousBuyer can receive NFTs by calling its onERC721Received function.
Step 3 (The Reentrancy): Inside onERC721Received, the attacker's contract calls buy(1) again.
Step 4: Because your code hasn't reached the line s_listings[_listingId].isActive = false yet, the second call passes the isActive check.
The Result: Depending on the logic, the attacker could potentially trigger multiple transfers, mess up the activeListingsCounter, or drain collateral if the cancelListing function is targeted similarly.

Recommended Mitigation

Always update the internal state before interacting with external addresses
'''solidity function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
require(listing.seller != msg.sender, "Seller cannot buy their own NFT");
// 1. EFFECT: Update state FIRST
s_listings[_listingId].isActive = false;
activeListingsCounter--;
// 2. INTERACTION: External calls LAST
bool success = usdc.transferFrom(msg.sender, address(this), listing.price);
require(success, "USDC transfer failed");
_safeTransfer(listing.seller, msg.sender, listing.tokenId, "");
emit NFT_Dealers_Sold(msg.sender, listing.price);
}
'''
or use a Reentrancy Guard like OpenZepellin's or a lock modifier
Updates

Lead Judging Commences

rubik0n Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

reentrancy-risk

This is a real reentrancy point, but not an exploitable one. The transaction will revert on the second ERC721 transfer.

Appeal created

rubik0n Lead Judge 8 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Invalid

Support

FAQs

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

Give us feedback!