Root + Impact
Description
After selling an NFT, the seller calls collectUsdcFromSelling to get their money back (sale price minus fees plus the collateral they locked).
The function sends the USDC but never zeros out the listing price, so the seller can just call it again and drain the entire contract balance.
solidity
function collectUsdcFromSelling(uint256 _listingId) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
require(!listing.isActive, "Listing must be inactive to collect USDC");
@> uint256 fees = _calculateFees(listing.price);
@> uint256 amountToSeller = listing.price - fees;
uint256 collateralToReturn = collateralForMinting[listing.tokenId];
totalFeesCollected += fees;
amountToSeller += collateralToReturn;
usdc.safeTransfer(address(this), fees);
@> usdc.safeTransfer(msg.sender, amountToSeller);
@>
@>
}
Risk
Likelihood:
Every time someone sells an NFT and calls this function, the price stays in storage
The require check only needs listing.isActive to be false, which it already is after the sale
Attacker calls the function multiple times in a loop until the contract is empty
Impact:
Attacker drains all USDC from the contract, stealing funds from other sellers
Legitimate sellers who sold NFTs can't collect their money anymore
Protocol becomes insolvent and unusable
Proof of Concept
solidity
collectUsdcFromSelling(tokenId);
collectUsdcFromSelling(tokenId);
collectUsdcFromSelling(tokenId);
Recommended Mitigation
diff
function collectUsdcFromSelling(uint256 _listingId) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
require(!listing.isActive, "Listing must be inactive to collect USDC");
uint256 fees = _calculateFees(listing.price);
uint256 amountToSeller = listing.price - fees;
uint256 collateralToReturn = collateralForMinting[listing.tokenId];
+ s_listings[_listingId].price = 0;
+ collateralForMinting[listing.tokenId] = 0;
totalFeesCollected += fees;
amountToSeller += collateralToReturn;
- usdc.safeTransfer(address(this), fees);
usdc.safeTransfer(msg.sender, amountToSeller);
}