collectUsdcFromSelling() transfers listing.price - fees + collateral USDC to the seller on every call, but never updates any state to mark the payout as completed. collateralForMinting[tokenId] is not zeroed, no "collected" flag is set, and s_listings is not modified. The only gate is require(!listing.isActive), which is permanently false after a sale or cancellation and never changes back. A seller can call the function an unlimited number of times and receive the full payout on each call, draining the contract's USDC balance.
Each repeated call also increments totalFeesCollected by fees, inflating the tracked fee balance beyond the USDC actually present in the contract. When the owner later calls withdrawFees(), the transfer may revert or silently drain funds belonging to other sellers.
Likelihood: High
The function is callable by any seller who has a completed or cancelled listing — no special conditions required beyond the listing being inactive.
The repeated-call path requires no exploit tooling; a simple loop of calls is sufficient.
Impact: High
The entire USDC balance of the contract can be drained by a single seller in a single transaction via repeated calls.
totalFeesCollected is inflated, causing withdrawFees() to over-draw — owner fee withdrawals fail or steal funds from other pending sellers.
Every other seller and buyer with funds locked in the contract loses their funds.
Zero out collateralForMinting and mark the listing as collected inside collectUsdcFromSelling() to prevent repeated payouts.
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.