Under normal behavior, a seller should be able to collect the proceeds from a completed sale exactly once. After the payout is made, the sale should reach a terminal settled state so the same proceeds cannot be withdrawn again.
In the current implementation, collectUsdcFromSelling() transfers sale proceeds every time it is called, but it never marks the payout as claimed and never clears the accounting state used to calculate that payout. As a result, the recorded seller can call the function multiple times and repeatedly drain USDC from the contract as long as the contract still holds enough balance.
The root cause is that the function only checks that the listing is inactive and that msg.sender is the current recorded seller, but it does not store any “claimed” flag, delete the listing, or zero the payout basis after transferring funds.
Likelihood:
The bug occurs whenever a seller has an inactive listing and calls collectUsdcFromSelling() more than once.
The exploit path is straightforward because the function has no one-time-use guard and no post-payout state update preventing replay.
Impact:
A seller can withdraw the same sale proceeds multiple times.
Repeated claims can drain pooled USDC belonging to the protocol, including funds originating from buyers, mint collateral, or fees.
Once exploited, the contract can become insolvent for later legitimate withdrawals.
Paste this inside NFTDealersTest.t.sol:
Track sale settlement explicitly and finalize it before transferring funds. At minimum:
distinguish sold listings from canceled listings,
add a proceedsClaimed flag or equivalent settled-state marker,
zero or delete the payout basis before the external token transfer.
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.