buy() is expected to mark a listing as inactive before transferring the NFT to the buyer.
The function calls _safeTransfer before setting s_listings[_listingId].isActive = false. _safeTransfer invokes onERC721Received on the recipient if it is a contract. During that callback, the listing is still marked active, meaning any code in the callback can observe or act on a state that should already be finalized. This violates the Checks-Effects-Interactions (CEI) pattern.
Likelihood: Medium
Exploitation requires the buyer to be a smart contract that implements onERC721Received.
The attacker must also have enough USDC approved to cover each re-entrant buy call, and there must be multiple active listings to target.
In the current codebase, re-entry into buy() for the same listing is accidentally blocked by an activeListingsCounter underflow revert (uint32 going below 0). Any future refactor that adjusts counter logic could remove this protection.
Impact: Medium
A contract buyer can observe the still-active listing state inside onERC721Received and take actions that rely on that stale view (e.g., immediately re-listing at a manipulated price, or calling other functions that read isActive).
The accidental underflow protection is not a reliable security boundary; the design is fragile and can break with minor changes.
A malicious buyer contract receives the NFT via _safeTransfer, and inside onERC721Received reads whether listing.isActive is still true. In the current implementation it is, because s_listings[_listingId].isActive = false has not executed yet.
The following contract and test are in test/ThreatModelTests.t.sol and pass with forge test --match-test test_ceiViolation.
In buy(), move s_listings[_listingId].isActive = false to before the _safeTransfer call so that the listing is fully deactivated before any external code executes.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.