Under normal protocol operation, a seller lists an NFT, a buyer purchases it, and the seller calls collectUsdcFromSelling — all authenticated via s_listings[tokenId].seller. The system assumes this mapping entry is stable for the lifetime of the seller's claim.
s_listings uses tokenId as its mapping key rather than an independent auto-incrementing listing ID. After a buyer purchases an NFT, they become the new owner and may legitimately re-list it by calling list(tokenId, ...). This call unconditionally overwrites the entire s_listings[tokenId] struct, including the seller field, permanently erasing the original seller's record. All subsequent calls by the original seller to collectUsdcFromSelling fail the onlySeller check, and their sale proceeds plus collateral are locked in the contract with no recovery path.
Likelihood:
Re-listing a purchased NFT is the most natural action in any NFT marketplace; normal, non-malicious usage is sufficient to trigger this bug unintentionally.
A malicious buyer can deliberately exploit this by immediately re-listing after purchase, combined with cancelListing, to seize the original seller's collateral.
Impact:
The original seller suffers a 100% loss of both sale proceeds and minting collateral with no on-chain recovery mechanism.
A malicious buyer nets lockAmount (20 USDC) per targeted NFT, and the attack can be repeated indefinitely across different tokenId values.
Add this to 2026-03-NFT-dealers/test/NFTDealersTest.t.sol,run forge test --match-test testPoC_C04_KeyCollisionLocksSellerFunds -vvvv
Replace tokenId-keyed listings with an auto-incrementing listingId. Each listing gets a unique, immutable identifier that is unaffected by subsequent re-listings of the same token.
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.