This occurs when a malicious contract calls the 'buy' and 'cancelListings' functions
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.
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