function testListingIdsAreInconsistentAndBreakIntegrationsAccounting() public revealed whitelisted {
uint256 tokenId = 1;
uint32 firstPrice = 1000e6;
uint32 secondPrice = 1500e6;
mintNFTForTesting();
vm.startPrank(userWithCash);
nftDealers.list(tokenId, firstPrice);
console2.log("After first list - totalListings:", nftDealers.totalListings());
console2.log("After first list - totalActiveListings:", nftDealers.totalActiveListings());
(
address sellerAfterFirstList,
uint32 storedPriceAfterFirstList,
address nftAfterFirstList,
uint256 storedTokenIdAfterFirstList,
bool isActiveAfterFirstList
) = nftDealers.s_listings(tokenId);
console2.log("Storage key used for active listing after first list:", tokenId);
console2.log("Stored seller after first list:", sellerAfterFirstList);
console2.log("Stored price after first list:", uint256(storedPriceAfterFirstList));
console2.log("Stored nft after first list:", nftAfterFirstList);
console2.log("Stored tokenId after first list:", storedTokenIdAfterFirstList);
console2.log("Stored active flag after first list:", isActiveAfterFirstList ? uint256(1) : uint256(0));
assertEq(nftDealers.totalListings(), 1);
assertEq(nftDealers.totalActiveListings(), 1);
assertEq(storedTokenIdAfterFirstList, tokenId);
assertEq(storedPriceAfterFirstList, firstPrice);
assertTrue(isActiveAfterFirstList);
nftDealers.cancelListing(tokenId);
console2.log("After cancel - totalListings:", nftDealers.totalListings());
console2.log("After cancel - totalActiveListings:", nftDealers.totalActiveListings());
(, uint32 storedPriceAfterCancel,, uint256 storedTokenIdAfterCancel, bool isActiveAfterCancel) =
nftDealers.s_listings(tokenId);
console2.log("Stored price after cancel:", uint256(storedPriceAfterCancel));
console2.log("Stored tokenId after cancel:", storedTokenIdAfterCancel);
console2.log("Stored active flag after cancel:", isActiveAfterCancel ? uint256(1) : uint256(0));
assertEq(nftDealers.totalListings(), 1, "historical listing count remains 1 after cancel");
assertEq(nftDealers.totalActiveListings(), 0, "no active listings after cancel");
assertFalse(isActiveAfterCancel, "listing at storage key tokenId should now be inactive");
nftDealers.list(tokenId, secondPrice);
vm.stopPrank();
console2.log("After second list - totalListings:", nftDealers.totalListings());
console2.log("After second list - totalActiveListings:", nftDealers.totalActiveListings());
(
address sellerAfterSecondList,
uint32 storedPriceAfterSecondList,
address nftAfterSecondList,
uint256 storedTokenIdAfterSecondList,
bool isActiveAfterSecondList
) = nftDealers.s_listings(tokenId);
console2.log("Active listing still stored at key:", tokenId);
console2.log("Stored seller after second list:", sellerAfterSecondList);
console2.log("Stored price after second list:", uint256(storedPriceAfterSecondList));
console2.log("Stored nft after second list:", nftAfterSecondList);
console2.log("Stored tokenId after second list:", storedTokenIdAfterSecondList);
console2.log("Stored active flag after second list:", isActiveAfterSecondList ? uint256(1) : uint256(0));
(address sellerAtKey2, uint32 priceAtKey2, address nftAtKey2, uint256 tokenIdAtKey2, bool isActiveAtKey2) =
nftDealers.s_listings(2);
console2.log("Storage entry at key 2 - seller:", sellerAtKey2);
console2.log("Storage entry at key 2 - price:", uint256(priceAtKey2));
console2.log("Storage entry at key 2 - nft:", nftAtKey2);
console2.log("Storage entry at key 2 - tokenId:", tokenIdAtKey2);
console2.log("Storage entry at key 2 - active flag:", isActiveAtKey2 ? uint256(1) : uint256(0));
assertEq(nftDealers.totalListings(), 2, "contract reports two listings have been created");
assertEq(nftDealers.totalActiveListings(), 1, "there should be one currently active listing");
assertEq(storedTokenIdAfterSecondList, tokenId, "active listing is still keyed by tokenId");
assertEq(storedPriceAfterSecondList, secondPrice, "second listing overwrote the same storage slot");
assertTrue(isActiveAfterSecondList, "tokenId key remains the active listing slot");
assertEq(sellerAtKey2, address(0), "no actual listing record exists at key 2");
assertEq(priceAtKey2, 0, "no actual listing record exists at key 2");
assertEq(tokenIdAtKey2, 0, "no actual listing record exists at key 2");
assertFalse(isActiveAtKey2, "listingId 2 is not a valid active storage entry");
vm.startPrank(userWithEvenMoreCash);
usdc.approve(address(nftDealers), secondPrice);
vm.expectRevert(abi.encodeWithSelector(NFTDealers.ListingNotActive.selector, 2));
nftDealers.buy(2);
nftDealers.buy(1);
vm.stopPrank();
console2.log("Owner of token after buy(1):", nftDealers.ownerOf(tokenId));
assertEq(
nftDealers.ownerOf(tokenId),
userWithEvenMoreCash,
"only buy(1) succeeds because the contract uses tokenId as the real listing key"
);
}
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/NFTDealersTest.t.sol:NFTDealersTest
[PASS] testListingIdsAreInconsistentAndBreakIntegrationsAccounting() (gas: 537885)
Logs:
After first list - totalListings: 1
After first list - totalActiveListings: 1
Storage key used for active listing after first list: 1
Stored seller after first list: 0x22CdC71E987473D657FCe79C9C0C0B1A62148056
Stored price after first list: 1000000000
Stored nft after first list: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Stored tokenId after first list: 1
Stored active flag after first list: 1
After cancel - totalListings: 1
After cancel - totalActiveListings: 0
Stored price after cancel: 1000000000
Stored tokenId after cancel: 1
Stored active flag after cancel: 0
After second list - totalListings: 2
After second list - totalActiveListings: 1
Active listing still stored at key: 1
Stored seller after second list: 0x22CdC71E987473D657FCe79C9C0C0B1A62148056
Stored price after second list: 1500000000
Stored nft after second list: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Stored tokenId after second list: 1
Stored active flag after second list: 1
Storage entry at key 2 - seller: 0x0000000000000000000000000000000000000000
Storage entry at key 2 - price: 0
Storage entry at key 2 - nft: 0x0000000000000000000000000000000000000000
Storage entry at key 2 - tokenId: 0
Storage entry at key 2 - active flag: 0
Owner of token after buy(1): 0x533575789af8F38A73C7747E36C17C1835FDF44a
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.23ms (1.87ms CPU time)
Ran 1 test suite in 15.24ms (4.23ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
- mapping(uint256 => Listing) public s_listings;
+ mapping(uint256 => Listing) public s_listings;
+ mapping(uint256 => uint256) public activeListingIdByTokenId;
struct Listing {
address seller;
uint32 price;
address nft;
uint256 tokenId;
bool isActive;
}
function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
- require(s_listings[_tokenId].isActive == false, "NFT is already listed");
+ uint256 currentListingId = activeListingIdByTokenId[_tokenId];
+ require(!s_listings[currentListingId].isActive, "NFT is already listed");
require(_price > 0, "Price must be greater than 0");
listingsCounter++;
activeListingsCounter++;
- s_listings[_tokenId] =
+ s_listings[listingsCounter] =
Listing({seller: msg.sender, price: _price, nft: address(this), tokenId: _tokenId, isActive: true});
+ activeListingIdByTokenId[_tokenId] = listingsCounter;
emit NFT_Dealers_Listed(msg.sender, listingsCounter);
}
function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
...
s_listings[_listingId].isActive = false;
+ activeListingIdByTokenId[listing.tokenId] = 0;
}
function cancelListing(uint256 _listingId) external {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
require(listing.seller == msg.sender, "Only seller can cancel listing");
s_listings[_listingId].isActive = false;
activeListingsCounter--;
+ activeListingIdByTokenId[listing.tokenId] = 0;
...
}