The list function stores listing data in s_listings[tokenId] and uses tokenId as the key for all subsequent operations (buy, cancelListing, updatePrice). Off-chain services and buyers are expected to rely on the NFT_Dealers_Listed event to retrieve the correct listingId and interact with the contract.
However, the NFT_Dealers_Listed event emits listingsCounter as the listingId instead of tokenId. This counter increments on every new listing regardless of the associated tokenId, causing the two values to permanently diverge as soon as any NFT is cancelled and relisted. Any buyer or off-chain service relying on the emitted listingId to call buy will reference a non-existent slot in s_listings, causing the transaction to revert with ListingNotActive.
Likelihood:
A seller cancels and relists their NFT, which is a standard and expected marketplace operation, causing the divergence to occur permanently from that point forward
Any off-chain interface or indexer consuming NFT_Dealers_Listed events to populate listings will display incorrect listingId values, leading every buyer interacting through that interface to fail
Impact:
Relisted NFTs become permanently unpurchasable by any buyer relying on event data, effectively locking legitimate listings out of the marketplace
The protocol's public event interface becomes unreliable, undermining trust in any front-end or off-chain integration built on top of it
This test uses the same setup as the NFTDealersTest file, extended with both userWithEvenMoreCash and userWithCash acting as sellers, and maliciousUser as the buyer.
The vulnerability: The list function stores listings under s_listings[tokenId] but emits listingsCounter as the listingId in the NFT_Dealers_Listed event. On the first listing of each NFT the two values coincide, making the bug invisible. They permanently diverge as soon as any NFT is cancelled and relisted, since listingsCounter keeps incrementing while the mapping key stays fixed to the tokenId.
The divergence: Both users mint and list their NFTs — in both cases listingId from the event matches tokenId by coincidence. When userWithEvenMoreCash cancels and relists tokenId=1, the listing is correctly stored at s_listings[1], but listingsCounter has reached 3, so the event emits listingId=3. From this point the two identifiers are permanently misaligned.
Final assertions:
buy(3) reverts with ListingNotActive because s_listings[3] was never created
The real listing is confirmed active at s_listings[1] with the correct price, proving it is permanently inaccessible to any buyer relying on event data
Emit _tokenId instead of listingsCounter in the NFT_Dealers_Listed event, so that the listingId in the event always matches the key used in s_listings. This ensures that any buyer or off-chain service relying on the event can directly use the emitted value to call buy, cancelListing, or updatePrice without any mismatch.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.