NFT Dealers

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

Non-whitelisted holders cannot relist NFTs despite documented secondary-market permissions

Author Revealed upon completion

Root + Impact

NFTDealers::list() is restricted to whitelisted users, non-whitelisted holders cannot relist NFTs despite the documented resaleflow.

Description

  • The intended behavior, according to the protocol documentation, is that users who are not in the whitelist cannot create NFTs, but they can still participate in the secondary market by buying NFTs, relisting them, updating prices, canceling listings, and
    collecting USDC after a sale.

  • The issue is that list() is gated by onlyWhitelisted, which prevents any non-whitelisted holder from creating a resale listing. As a result, secondary owners who are not whitelisted cannot relist NFTs even though this is an explicitly documented user permission, breaking the documented resale flow and reducing the liquidity of already circulating NFTs.

modifier onlyWhitelisted() {
require(whitelistedUsers[msg.sender], "Only whitelisted users can call this function");
_;
}
@> function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
require(s_listings[_tokenId].isActive == false, "NFT is already listed");
require(_price > 0, "Price must be greater than 0");
listingsCounter++;
activeListingsCounter++;
s_listings[_tokenId] =
Listing({seller: msg.sender, price: _price, nft: address(this), tokenId: _tokenId, isActive: true});
emit NFT_Dealers_Listed(msg.sender, listingsCounter);
}

Risk

Likelihood: High

  • The issue occurs during normal protocol usage whenever a non-whitelisted user acquires an NFT and attempts to relist it.

  • The failure is deterministic because the resale entry point is directly blocked by the onlyWhitelisted modifier.

Impact: Medium

  • Secondary owners who are not whitelisted lose access to the resale flow that the documentation explicitly says should remain available to them.

  • This breaks the documented market model and reduces the expected liquidity and utility of NFTs after the first sale.

Proof of Concept

The following test shows that a non-whitelisted user can buy an NFT but cannot relist it, even though the protocol documentation states that non-whitelisted users should still be able to announce NFTs.

function test_NonWhitelistedBuyer_CannotRelistNFT_DespiteDocumentedPermissions() public revealed whitelisted {
uint256 salePrice = 2_000e6;
// user1 mints and lists tokenId 1 while whitelisted.
deal(address(usdc), user1, LOCK_AMOUNT);
vm.startPrank(user1);
usdc.approve(address(nftDealers), LOCK_AMOUNT);
nftDealers.mintNft();
nftDealers.list(1, uint32(salePrice));
vm.stopPrank();
// user2 is not whitelisted but can still buy the NFT.
deal(address(usdc), user2, salePrice);
vm.startPrank(user2);
usdc.approve(address(nftDealers), salePrice);
nftDealers.buy(1);
vm.stopPrank();
assertEq(nftDealers.ownerOf(1), user2);
// The new owner cannot relist the NFT because list() is onlyWhitelisted.
vm.startPrank(user2);
vm.expectRevert("Only whitelisted users can call this function");
nftDealers.list(1, uint32(2_500e6));
vm.stopPrank();
}

Recommended Mitigation

Remove the whitelist restriction from list() so non-whitelisted NFT holders can continue accessing the documented secondary-market resale flow.

- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint32 _price) external {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
require(s_listings[_tokenId].isActive == false, "NFT is already listed");
require(_price > 0, "Price must be greater than 0");
(... )
}

Support

FAQs

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

Give us feedback!