list() stores the listing in s_listings[_tokenId] but emits NFT_Dealers_Listed(..., listingsCounter). All downstream functions (buy, cancelListing, updatePrice, collectUsdcFromSelling) accept a _listingId parameter and look up s_listings[_listingId]. When a user follows the event-emitted listingId (as any frontend or indexer would), the lookup hits an empty or wrong storage slot — causing reverts or purchasing the wrong NFT.
Likelihood:
Occurs whenever a user lists a token whose tokenId differs from the current listingsCounter value — this is the normal case when users mint multiple tokens before listing, or list tokens in non-sequential order
Any frontend, indexer, or direct contract interaction that reads emitted events will use the wrong ID
Impact:
Core marketplace functions (buy, cancelListing, updatePrice, collectUsdcFromSelling) break for any listing where tokenId ≠ listingsCounter
Collision scenario: if tokenId A and listingsCounter B happen to match a different listing's storage key, a buyer pays for item A but receives item B — wrong NFT purchased
Marketplace is unusable for normal multi-user operation
Scenario: Alice mints tokens 1, 2. Carol mints token 3. Carol lists token 3 — this is listing #1 (listingsCounter = 1), but storage is at s_listings[3].
| What event says | Where it's stored | What buy() looks up |
|---|---|---|
listingId = 1 |
s_listings[3] |
s_listings[1] = empty → reverts |
Output: buy(1) reverts with ListingNotActive(1). buy(3) succeeds — proves storage key is tokenId, not listingsCounter.
Root cause — list() stores by _tokenId but emits listingsCounter:
Fix — use one canonical key for storage, events, and function parameters:
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.