collectUsdcFromSelling() transfers USDC to the seller but never resets any state — the listing's seller, price, and collateralForMinting all remain intact after payout. Since the only guard (require(!listing.isActive)) stays satisfied on every subsequent call, a seller can call this function repeatedly to extract (price - fees + collateral) each time, draining USDC belonging to other users.
Likelihood:
Any seller whose listing has been bought can trigger this immediately — no special timing, no admin action, no external dependency required
The vulnerable function is the standard post-sale claim path that every seller must call, so the attack surface is exercised in normal protocol usage
Impact:
Direct theft of the entire contract USDC balance — attacker drains other users' collateral deposits and pending sale proceeds
Victims cannot cancel listings or reclaim collateral because contract balance reaches zero
Drain rate scales linearly with (price + collateral) per call — at max listing price (~4,294 USDC), a 10,000 USDC pool is emptied in 2-3 calls
Setup: 5 users each mint (20 USDC collateral × 5 = 100 USDC). Alice lists token 1 for 50 USDC. Bob buys. Contract holds 150 USDC.
| Step | Action | Alice receives | Contract balance |
|---|---|---|---|
| 0 | 5 mints + Bob buys Alice's listing | — | 150 USDC |
| 1 | Alice calls collectUsdcFromSelling(1) |
69.5 USDC | 80.5 USDC |
| 2 | Alice calls collectUsdcFromSelling(1) again |
69.5 USDC | 11.0 USDC |
Math per call: price(50) - fees(50 × 1% = 0.5) + collateral(20) = 69.5 USDC
Alice's entitlement: 69.5 USDC. Alice's actual extraction: 139.0 USDC. 69.5 USDC stolen from other users.
Output: Alice gained total: 139000000 — confirms 2× extraction from a single sale.
Root cause in collectUsdcFromSelling — no state is cleared after payout:
Fix — zero all claimable state before transfer (CEI pattern):
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.