NFT Dealers

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

Listing ID emitted in events differs from storage key — off-chain integrations broken

Author Revealed upon completion

Root + Impact

Description

  • list() stores listings at s_listings[_tokenId] but emits listingsCounter as the listing ID. All other functions (buy, cancelListing, collectUsdcFromSelling, updatePrice) accept _listingId and read s_listings[_listingId].

  • Users who pass the emitted listingsCounter value access the wrong storage slot. After any cancel+relist, the emitted ID and tokenId diverge permanently.

s_listings[_tokenId] = Listing({...}); // @> stored at tokenId
emit NFT_Dealers_Listed(msg.sender, listingsCounter); // @> emits different key

Risk

Likelihood:

  • Every off-chain integration using emitted event data to call contract functions accesses the wrong listing

Impact:

  • buy(emittedId) either reverts or buys the wrong NFT. collectUsdcFromSelling(emittedId) fails or collects from wrong listing

Proof of Concept

function testListingIdMismatch() public {
// Alice lists tokenId=1 → listingsCounter=1, stored at s_listings[1] (match by coincidence)
// Alice cancels, relists → listingsCounter=2, stored at s_listings[1] (diverged)
vm.startPrank(alice);
nftDealers.cancelListing(1);
nftDealers.list(1, 50e6);
vm.stopPrank();
// Bob tries emitted listingId=2 → s_listings[2] is empty
vm.startPrank(bob);
usdc.approve(address(nftDealers), 50e6);
vm.expectRevert(); // ListingNotActive — wrong slot
nftDealers.buy(2);
vm.stopPrank();
}

After Alice cancels and relists tokenId=1, the event emits listingId=2 but the listing is stored at s_listings[1]. Bob's buy(2) reverts because s_listings[2] is empty.

Recommended Mitigation

Use listingsCounter as the storage key consistently, so the emitted ID matches what buy(), cancelListing(), and other functions expect.

function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
listingsCounter++;
- s_listings[_tokenId] =
+ s_listings[listingsCounter] =
Listing({seller: msg.sender, price: _price, nft: address(this), tokenId: _tokenId, isActive: true});
emit NFT_Dealers_Listed(msg.sender, listingsCounter);
}

Support

FAQs

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

Give us feedback!