NFT Dealers

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

Mismatch between listingId and tokenId leads to incorrect listing management and potential user errors

Author Revealed upon completion

Root + Impact

Description

Root Cause

The contract emits a listingId using listingsCounter, but internally stores listings using _tokenId as the key in the s_listings mapping.

This creates a mismatch between:

  • The identifier exposed to users via events (listingId)

  • The identifier used internally for storage (tokenId)

As a result, users and off-chain systems may rely on incorrect IDs when interacting with functions like buy() or cancelListing().

Impact

This mismatch can lead to incorrect interactions with listings:

  • Users may attempt to buy or cancel a listing using the emitted `listingId`, which does not correspond to the correct storage key.

  • Frontend or indexing systems relying on events may break or behave incorrectly.

  • Listings may become inaccessible or incorrectly referenced.

While no direct fund theft occurs, the protocol becomes unreliable and inconsistent.

function list(uint256 _tokenId, uint32 _price) external {
...
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:

  • Reason 1 - This issue occurs in every listing and affects all users interacting through events or frontend integrations.

Impact:

  • Impact 1 - Broken marketplace functionality

  • Impact 2 - Poor user experience

  • Impact 3 - Potential denial of service for certain listings.

Proof of Concept

Explanation:

However;

  • First listing is stored at s_listings[1]

  • Second listing is stored at s_listings[5]

If a user tries to interact using listingId (2), it will not match the actual stored listing (tokenId 5).

This leads to incorrect or failed interactions.

1. User lists an NFT with tokenId = 1.
2. listingsCounter becomes 1.
3. Event emits listingId = 1.
4. Another NFT is listed with tokenId = 5.
5. listingsCounter becomes 2, event emits listingId = 2.

Recommended Mitigation

The contract should use a consistent identifier for listings across both storage and external interfaces.

Instead of using `tokenId` as the mapping key, it should use `listingId` (i.e., listingsCounter) as the unique identifier.

This ensures that:

  • Events match storage

  • Users can reliably interact with listings

  • Frontend integrations remain consistent

- s_listings[_tokenId] = Listing({
+ s_listings[listingsCounter] = Listing({
seller: msg.sender,
price: _price,
nft: address(this),
tokenId: _tokenId,
isActive: true
});
Additional adjustments:
function buy(uint256 _listingId) external {
- Listing memory listing = s_listings[_listingId];
+ Listing memory listing = s_listings[_listingId];
...
}

Support

FAQs

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

Give us feedback!