Skip to content

Commit

Permalink
Prevent listing same token id
Browse files Browse the repository at this point in the history
  • Loading branch information
lykhonis committed Jan 14, 2024
1 parent a98ca2c commit 96f36b0
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 2 deletions.
11 changes: 11 additions & 0 deletions artifacts/abi/marketplace/lsp8/LSP8Listings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "AlreadyListed",
"type": "error"
},
{
"inputs": [
{
Expand Down
23 changes: 23 additions & 0 deletions src/marketplace/lsp8/LSP8Listings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ contract LSP8Listings is ILSP8Listings, Module {
error NotListed(uint256 id);
error UnathorizedSeller(address account);
error InactiveListing(uint256 id);
error AlreadyListed(uint256 id);

uint256 public totalListings;
// id -> listing
mapping(uint256 => LSP8Listing) private _listings;
// hash(asset, tokenId) -> id
mapping(bytes32 => uint256) private _listingIds;

constructor() {
_disableInitializers();
Expand Down Expand Up @@ -56,6 +60,19 @@ contract LSP8Listings is ILSP8Listings, Module {
}
totalListings += 1;
uint256 id = totalListings;
// verify existing possible listing
{
bytes32 existingKey = _listingKey(asset, tokenId);
uint256 existingId = _listingIds[existingKey];
if (isListed(existingId)) {
if (isActiveListing(existingId) && _listings[existingId].seller == seller) {
revert AlreadyListed(existingId);
}
delete _listings[existingId];
emit Delisted(existingId, asset);
}
_listingIds[existingKey] = id;
}
uint256 endTime = 0;
if (secondsUntilEndTime > 0) {
endTime = startTime + secondsUntilEndTime;
Expand Down Expand Up @@ -109,6 +126,7 @@ contract LSP8Listings is ILSP8Listings, Module {
revert UnathorizedSeller(msg.sender);
}
delete _listings[id];
delete _listingIds[_listingKey(listing.asset, listing.tokenId)];
emit Delisted(id, listing.asset);
}

Expand All @@ -118,6 +136,11 @@ contract LSP8Listings is ILSP8Listings, Module {
revert InactiveListing(id);
}
delete _listings[id];
delete _listingIds[_listingKey(listing.asset, listing.tokenId)];
emit Unlisted(id, listing.asset);
}

function _listingKey(address asset, bytes32 tokenId) private pure returns (bytes32) {
return keccak256(abi.encodePacked(asset, tokenId));
}
}
69 changes: 67 additions & 2 deletions test/marketplace/lsp8/LSP8Listings.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,8 @@ contract LSP8ListingsTest is Test {
listings.unlist(1);
}

function test_Revert_UnlistIfNotActiveListing() public {
function testFuzz_Revert_UnlistIfNotActiveListing(bytes32 tokenId) public {
(UniversalProfile profile,) = deployProfile();
bytes32 tokenId = bytes32(0);
asset.mint(address(profile), tokenId, false, "");

vm.prank(address(profile));
Expand All @@ -370,4 +369,70 @@ contract LSP8ListingsTest is Test {
vm.expectRevert(abi.encodeWithSelector(Module.IllegalAccess.selector, marketplace, MARKETPLACE_ROLE));
listings.unlist(1);
}

function testFuzz_Revert_ListSameTokenForActiveListing(bytes32 tokenId) public {
(UniversalProfile profile,) = deployProfile();
asset.mint(address(profile), tokenId, false, "");

vm.prank(address(profile));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);
assertTrue(listings.isListed(1));
assertTrue(listings.isActiveListing(1));

vm.prank(address(profile));
vm.expectRevert(abi.encodeWithSelector(LSP8Listings.AlreadyListed.selector, 1));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);
}

function test_DelistIfListSameTokenForInactiveListing() public {
(UniversalProfile profile,) = deployProfile();
bytes32 tokenId = bytes32(0);
asset.mint(address(profile), tokenId, false, "");

vm.prank(address(profile));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);
assertTrue(listings.isListed(1));
assertTrue(listings.isActiveListing(1));

vm.warp(block.timestamp + 10 days);
assertTrue(listings.isListed(1));
assertFalse(listings.isActiveListing(1));

vm.prank(address(profile));
vm.expectEmit(address(listings));
emit Delisted(1, address(asset));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);

assertFalse(listings.isListed(1));
assertFalse(listings.isActiveListing(1));
assertTrue(listings.isListed(2));
assertTrue(listings.isActiveListing(2));
}

function test_DelistIfListSameTokenByDifferentSeller() public {
(UniversalProfile alice,) = deployProfile();
bytes32 tokenId = bytes32(0);
asset.mint(address(alice), tokenId, false, "");

vm.prank(address(alice));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);
assertTrue(listings.isListed(1));
assertTrue(listings.isActiveListing(1));
assertEq(listings.getListing(1).seller, address(alice));

(UniversalProfile bob,) = deployProfile();
vm.prank(address(alice));
asset.transfer(address(alice), address(bob), tokenId, false, "");

vm.prank(address(bob));
vm.expectEmit(address(listings));
emit Delisted(1, address(asset));
listings.list(address(asset), tokenId, 1 ether, block.timestamp, 10 days);
assertEq(listings.getListing(2).seller, address(bob));

assertFalse(listings.isListed(1));
assertFalse(listings.isActiveListing(1));
assertTrue(listings.isListed(2));
assertTrue(listings.isActiveListing(2));
}
}

0 comments on commit 96f36b0

Please sign in to comment.