NFT Dealers

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

Listing Storage Key (tokenId) Mismatches Emitted Event ID (listingsCounter) — Off-Chain Systems Broken

Author Revealed upon completion

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
// ...
@> s_listings[_tokenId] = Listing({...}); // stored at tokenId
listingsCounter++;
@> emit NFT_Dealers_Listed(msg.sender, listingsCounter); // emitted as listingsCounter
}

Risk

Likelihood:

  • Every re-listing of any previously-listed token causes the IDs to diverge , this is guaranteed to happen during normal marketplace operation

Any frontend, subgraph, or indexer relying on the emitted event ID is affected

Impact:

  • Off-chain systems (frontends, indexers, bots) that use the emitted listing ID to call buy(), cancelListing(), or updatePrice() will interact with empty/wrong storage slots

Users following event-based listing IDs are unable to purchase, cancel, or manage listings

  • The marketplace becomes functionally broken for any integration relying on events

Proof of Concept

function test_NM006_ListingKeyMismatch() public {
address alice = makeAddr("alice");
_mintAndDepositCollateral(alice, 1);
// First listing: tokenId=1, listingsCounter becomes 1
vm.prank(alice);
nftDealers.list(1, 100e6);
// Data at s_listings[1] (tokenId) — exists
(address seller1,,,,) = nftDealers.s_listings(1);
assertEq(seller1, alice);
// Cancel and re-list: listingsCounter becomes 2
vm.startPrank(alice);
nftDealers.cancelListing(1);
nftDealers.list(1, 200e6);
vm.stopPrank();
// Event emitted listingsCounter=2, but data is at s_listings[1]
(address sellerAtCounter2,,,,) = nftDealers.s_listings(2);
assertEq(sellerAtCounter2, address(0)); // empty — off-chain systems break
(address sellerAtTokenId,,,,) = nftDealers.s_listings(1);
assertEq(sellerAtTokenId, alice); // actual data is here
}

Recommended Mitigation

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