NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
Submission Details
Impact: high
Likelihood: medium

`list()` wrongly requires whitelist — secondary buyers can never resell their NFTs

Author Revealed upon completion

Root + Impact

Description

The protocol spec clearly states that non-whitelisted users should be able to "buy, update price, cancel listing, list NFT, collect USDC after selling." Only minting is meant to be whitelist-restricted.

But list() has the onlyWhitelisted modifier. Since buy() has no whitelist check (correctly), anyone can purchase an NFT. The problem: after buying, a non-whitelisted user is stuck. They own the NFT but can't put it back on the marketplace.

// Spec: non-whitelisted can "list NFT"
function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted { // @> blocks non-whitelisted
...
}
function buy(uint256 _listingId) external payable { // @> no whitelist — anyone can buy
...
}

Risk

Likelihood: This hits every non-whitelisted buyer. The protocol is designed for open secondary trading, so this is a common scenario.

Impact: Secondary buyers' investments are locked. They can't resell through the marketplace, killing liquidity and contradicting what the protocol promises.


Proof of Concept

  1. Alice (whitelisted) mints and lists NFT #1 at 100 USDC.

  2. Bob (not whitelisted) buys it — buy() works fine, Bob now owns the NFT.

  3. Bob wants to resell at 150 USDC. Calls list(1, 150e6).

  4. Reverts: "Only whitelisted users can call this function."

  5. Bob is stuck with an NFT he can't sell on the marketplace.

function testM01_NonWhitelistedCantRelist() public {
usdc.mint(alice, 20e6);
vm.startPrank(alice);
usdc.approve(address(nftDealers), 20e6);
nftDealers.mintNft();
nftDealers.list(1, 100e6);
vm.stopPrank();
// Bob (non-whitelisted) buys
usdc.mint(bob, 100e6);
vm.startPrank(bob);
usdc.approve(address(nftDealers), 100e6);
nftDealers.buy(1);
vm.stopPrank();
assertEq(nftDealers.ownerOf(1), bob);
assertFalse(nftDealers.isWhitelisted(bob));
// Bob tries to resell — blocked
vm.prank(bob);
vm.expectRevert("Only whitelisted users can call this function");
nftDealers.list(1, 150e6);
}

Recommended Mitigation

Remove onlyWhitelisted from list(). The ownerOf check already ensures only the NFT owner can list — no extra access control needed.

- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint32 _price) external onlyWhenRevealed {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
...
}

Support

FAQs

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

Give us feedback!