Under the intended protocol flow, collectUsdcFromSelling should only be callable after an NFT has been genuinely purchased, enabling the seller to withdraw buyer-paid USDC and reclaim collateral.
The function's sole guard is require(!listing.isActive, ...). Because cancelListing also sets isActive = false, a seller who cancels their listing immediately satisfies this condition and can call collectUsdcFromSelling to drain USDC belonging to other protocol users — with no buyer ever having participated.
Likelihood:
Any whitelisted user who has ever created a listing can call collectUsdcFromSelling immediately after cancelListing returns, requiring no external accounts or waiting period.
The attack is profitable whenever the contract holds USDC from other users' collateral deposits or accumulated fees — a condition that is true during normal protocol operation.
Impact:
An attacker can withdraw USDC from the protocol with no capital at risk, up to the full contract balance, directly harming other users and the protocol treasury.
When combined with C-01 (https://codehawks.cyfrin.io/c/2026-03-nft-dealers/s/cmmud2us50005jo04v7800jlu), the attacker can repeat the drain indefinitely, multiplying total damage.
Add this to 2026-03-NFT-dealers/test/NFTDealersTest.t.sol,run forge test --match-test testPoC_C03_CollectAfterCancel -vvvv
Introduce a dedicated isSold flag that is set exclusively inside buy(), and require it in collectUsdcFromSelling. Add an isCollected flag to prevent re-collection (which also addresses C-01(https://codehawks.cyfrin.io/c/2026-03-nft-dealers/s/cmmud2us50005jo04v7800jlu)).
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.