NFT Dealers

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

NFT Transferred to Non-Whitelisted Wallet Becomes Permanently Unlistable

Author Revealed upon completion

NFT Transferred to Non-Whitelisted Wallet Becomes Permanently Unlistable

Description

The list() function allows NFT owners to create marketplace listings. Only whitelisted users can call it, enfored by onlyWhitelisted modifier. NFT transfers via transferFrom have no corresponding whitelist restriction.

When a whitelist user transfers an NFT to a non-whitelisted address, the new owner cannot list the NFT on the marketplace. The NFT is effectively trapped - the owner holds it but has no way to sell it through the protocol.

According to protocol documentation, non whitelisted users should have access to the following actions

buy, update price, cancel listing, list NFT
// transferFrom imposes no whitelist check, allowing NFTs to reach any address
function transferFrom(address from, address to, uint256 tokenId) public // inherited ERC721, no whitelist check
// @> list() blocks non-whitelisted owners from ever creating a listing
function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
// ...
}

Risk

Likelihood: High

  • Any whitelisted user can freely transfer their NFT to a non-whitelisted address with no special conditions.

  • A whitelisted user could be removed from the whitelist after already holding an NFT, leaving them unable to list their own token.

Impact: High

  • The non-whitelisted owner's NFT is permanently unlistable via the protocol, stranding the asset and any associated collateral.

  • User trust and token value are harmed - buyers cannot rely on being able to re-sell tokens acquired through secondary transfers.

Proof of Concept

function test_NonWhitelistedWallets_CannotList() public {
// Initialize test
vm.startBroadcast(owner);
nftDealers.whitelistWallet(user1);
nftDealers.revealCollection();
vm.stopBroadcast();
// Assert state after initialization
assertEq(nftDealers.whitelistedUsers(user1), true);
assertEq(nftDealers.isCollectionRevealed(), true);
// Mint
vm.startBroadcast(user1);
usdc.approve(address(nftDealers), USDC_COLLATERAL);
nftDealers.mintNft();
vm.stopBroadcast();
// Assert state after mint
assertEq(nftDealers.ownerOf(1), user1);
assertEq(nftDealers.collateralForMinting(1), USDC_COLLATERAL);
assertEq(usdc.balanceOf(address(nftDealers)), USDC_COLLATERAL);
assertEq(usdc.balanceOf(user1), INITIAL_USER_BALANCE - USDC_COLLATERAL);
// Transfer NFT to user2
vm.prank(user1);
nftDealers.transferFrom(user1, user2, 1);
// Assert state after transfer
assertEq(nftDealers.ownerOf(1), user2);
// Attempt to list NFT
vm.prank(user2);
vm.expectRevert('Only whitelisted users can call this function');
nftDealers.list(1, 2e6);
}

Recommended Mitigation

Remove teh onlyWhitelisted modifier from the list() and instead verify only that the caller ownns the NFT.

- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint32 _price) external {
// ...
}

Alternatively, if the whitelist is intentional for all interactions, restrict transferFrom/safeTransferFrom to only allow transfers to whitelisted addresses.

Support

FAQs

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

Give us feedback!