NFTDealers::collectUsdcFromSelling() only checks whether a listing is inactive and does not verify that a sale actually occurred,
a seller can withdraw sale proceeds from a canceled listing as if the NFT had been sold.
The intended behavior is that collectUsdcFromSelling() should only be callable after a successful purchase, when then protocol has actually received the buyer’s USDC and the listing has transitioned into a sold state. A canceled listing should not create any claimable sale proceeds because no sale took place.
The issue is that collectUsdcFromSelling() treats every inactive listing as eligible for settlement, without distinguishing between a listing that was sold through buy() and a listing that was merely deactivated through cancelListing(). As a result, after canceling a listing, the seller can still call collectUsdcFromSelling() and receive listing.price - fees as if the contract had collected payment from a buyer.
Likelihood: High
The issue occurs through normal protocol usage after a seller cancels a listing.
No special privileges are required beyond owning and listing an NFT.
Impact: High
A seller can receive sale proceeds even though no buyer ever paid for the NFT.
This allows direct USDC extraction from the contract and also corrupts fee accounting by increasing totalFeesCollected on a nonexistent sale.
The following test shows that after canceling a listing, the seller can still call collectUsdcFromSelling() and receive a payout despite no purchase having occurred.
Track whether a listing was actually sold, and allow collectUsdcFromSelling() only for listings that were completed through buy(). A canceled listing should never become claimable for sale proceeds. While applying this fix, mark the listing as sold and inactive before external interactions in buy() so the function also follows the checks-effects-interactions pattern more closely.
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.