In collectUsdcFromSelling() (L171-183), fees are ostensibly "collected" via usdc.safeTransfer(address(this), fees) at L181 -- a self-transfer where the contract sends USDC to itself. This has zero effect on the contract's USDC balance. The fee amount is only tracked virtually via totalFeesCollected += fees at L179.
The self-transfer is a no-op because the from address (the contract, via SafeERC20's safeTransfer which calls IERC20.transfer) and the to address (address(this)) are the same. The contract's balance does not change -- it was already holding the fees from the buyer's payment in buy() at L148.
The fees are implicitly retained because amountToSeller = listing.price - fees + collateral -- the fee portion is simply not sent to the seller. The self-transfer at L181 adds nothing: the contract already holds the fees. It wastes gas on a redundant ERC-20 transfer call (~7000+ gas for the storage read/write in the token contract) on every collection.
The withdrawFees() function (L195-201) later sends totalFeesCollected to the owner at L199 via usdc.safeTransfer(owner, amount). This works correctly because the fee amount is retained in the contract's balance after collectUsdcFromSelling sends only amountToSeller (which excludes fees). The self-transfer at L181 is entirely redundant to this mechanism.
Note: Under normal operation (without the H-01 repeated-collection bug), the accounting is correct -- totalFeesCollected accurately reflects fees retained in the contract. The self-transfer itself does not cause fund loss or accounting errors. It is a gas-wasting no-op with misleading intent.
Likelihood:
Every call to collectUsdcFromSelling() executes the self-transfer at L181 -- this affects 100% of sale collections
The self-transfer usdc.safeTransfer(address(this), fees) always sends USDC from the contract to itself, producing zero net balance change
Impact:
Wasted gas: Each collection pays for a redundant ERC-20 transfer (~7000+ gas for token storage operations) that has no effect
Misleading code: The self-transfer suggests fees are being "moved" or "segregated" when they are not -- they remain in the same shared USDC balance alongside collateral and pending payouts
No fund loss: Under normal operation, the virtual accounting via totalFeesCollected is sufficient and correct. The self-transfer is redundant, not harmful
The following test proves the self-transfer has zero effect on the contract's USDC balance. Run with forge test --match-test test_feeSelfTransferIsNoOp -vv:
Remove the self-transfer entirely. Fees are already retained in the contract because amountToSeller excludes the fee portion (listing.price - fees). The totalFeesCollected virtual counter is sufficient for withdrawFees() to track and withdraw the correct amount.
This fix:
Removes the redundant self-transfer, saving ~7000+ gas per collection
Does not change the contract's USDC balance behavior -- fees are already retained by not including them in amountToSeller
withdrawFees() (L195-201) continues to work correctly since totalFeesCollected tracks the virtual amount and the actual USDC is still in the contract
No side effects on any other function
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.