Normal behavior ensures that after an NFT is sold (which sets the listing to inactive), the seller can call collectUsdcFromSelling exactly once to retrieve their sale revenue and collateral.
The specific issue is that collectUsdcFromSelling transfers the funds but fails to update any state variable to mark the funds as claimed, allowing the seller to call the function repeatedly and drain the contract.
Solidity
function collectUsdcFromSelling(uint256 _listingId) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
require(!listing.isActive, "Listing must be inactive to collect USDC");
// @> No state is updated before or after these transfers
usdc.safeTransfer(address(this), fees);
usdc.safeTransfer(msg.sender, amountToSeller);
}
Whitelisted users who successfully sell an NFT can exploit this immediately.
It requires zero technical knowledge to exploit, as a simple script or repeated manual calls will trigger the drain.
Complete loss of funds. The attacker can drain all USDC held by the NFTDealers contract, stealing minting collaterals, accumulated fees, and funds belonging to other users.
It completely breaks the core economic security of the marketplace.
Solidity
function test_PoC_InfiniteUSDCDrain() public {
vm.startPrank(attacker);
dealers.mintNft();
dealers.list(1, 100e6);
vm.stopPrank();
Diff
function collectUsdcFromSelling(uint256 _listingId) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
require(!listing.isActive, "Listing must be inactive to collect USDC");
}
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.