The fee transfer in collectUsdcFromSelling sends USDC back to the contract itself, making it a no-op. Meanwhile totalFeesCollected keeps accumulating, causing withdrawFees to overdraw from the contract balance and steal funds belonging to other users.
usdc.safeTransfer(address(this), fees) transfers to the contract itself. The balance does not change
totalFeesCollected is still incremented by fees on every call, making the accounting inconsistent with the actual isolated fee balance
When the owner calls withdrawFees, it withdraws totalFeesCollected from the general contract balance, which includes USDC that belongs to other sellers as collateral or pending proceeds
Likelihood:
Triggered every time a seller calls collectUsdcFromSelling . This is the normal happy path
No special conditions required
Impact:
withdrawFees drains the USDC that belongs to other users' collateral or pending proceeds
Sellers who have not yet called collectUsdcFromSelling may find the contract balance insufficient when they do, causing their transactions to revert
Protocol fee accounting is entirely broken
The scenario below shows how the self-transfer causes totalFeesCollected to diverge from the actual available fee balance. When withdrawFees is called, it attempts to withdraw an amount larger than what the fees actually represent, pulling USDC from funds that belong to other participants in the protocol.
Remove the self-transfer line entirely. The USDC is already held in the contract's balance when a buyer calls buy(), so there is nothing to move. The fees are implicitly retained by not paying them out to the seller. The totalFeesCollected counter alone is sufficient for withdrawFees to know how much to send to the owner. Removing the no-op line also saves gas on every seller payout.
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.