NFT Dealers

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

s_listings is keyed by tokenId but all events and external calls use an independent listingsCounter as the ID

Author Revealed upon completion

Root + Impact

s_listings mapping uses _tokenId as the key when storing a listing. However, listingsCounter is a separate auto-incrementing value emitted in the NFT_Dealers_Listed event as the listingId. Every function that operates on a listing — buy, cancelListing, collectUsdcFromSelling, updatePrice — takes a _listingId parameter that users and off-chain indexers will naturally populate with the emitted counter value. But the lookup s_listings[_listingId] expects the tokenId, not the counter — resulting in wrong listing lookups or empty struct returns.

function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
listingsCounter++;
s_listings[_tokenId] = Listing({...}); // @> stored at tokenId key
emit NFT_Dealers_Listed(msg.sender, listingsCounter); // @> emits counter as listingId
}
function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId]; // @> looks up by listingId (counter)
if (!listing.isActive) revert ListingNotActive(_listingId); // @> always reverts if listingId != tokenId
}

Risk

Risk

Likelihood:

  • Any user or frontend that calls buy(), cancelListing(), or updatePrice() using the listingId from the emitted event will hit an empty/inactive listing and revert every time

  • tokenId and listingsCounter only coincidentally align for the very first listing of tokenId #1

Impact:

  • Core marketplace functionality — buying and canceling — is broken for all practical use

Proof of Concept

// TokenId = 5, listingsCounter becomes 3 after this call
nftDealers.list(5, 1000e6);
// emits NFT_Dealers_Listed(seller, 3) ← user sees listingId = 3
// User tries to buy using the emitted listingId
nftDealers.buy(3);
// s_listings[3] → empty struct (isActive = false)
// reverts: ListingNotActive(3)
// Only works if called with tokenId directly:
nftDealers.buy(5); // ← not what the event told them

Recommended Mitigation

- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
listingsCounter++;
- s_listings[_tokenId] = Listing({...});
+ s_listings[listingsCounter] = Listing({...tokenId: _tokenId...});
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!