From 65bdb1cf173a2c40263a7c36175360ff365a5587 Mon Sep 17 00:00:00 2001 From: Volodymyr Lykhonis Date: Thu, 25 Jan 2024 10:36:45 +0100 Subject: [PATCH] Restrict lsp7 --- src/marketplace/lsp7/LSP7Listings.sol | 26 ++++++++++++--- test/marketplace/lsp7/LSP7Listings.t.sol | 37 ++++++++++++++++++++- test/marketplace/lsp7/LSP7Marketplace.t.sol | 3 +- test/marketplace/lsp7/LSP7Offers.t.sol | 3 +- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/marketplace/lsp7/LSP7Listings.sol b/src/marketplace/lsp7/LSP7Listings.sol index 29eeb1d..e12cdde 100644 --- a/src/marketplace/lsp7/LSP7Listings.sol +++ b/src/marketplace/lsp7/LSP7Listings.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.22; +import { + _LSP4_TOKEN_TYPE_KEY, + _LSP4_TOKEN_TYPE_NFT +} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; import {Module} from "../common/Module.sol"; import {ILSP7Listings, LSP7Listing} from "./ILSP7Listings.sol"; @@ -14,6 +18,7 @@ contract LSP7Listings is ILSP7Listings, Module { error InactiveListing(uint256 id); error InvalidDeduction(uint256 available, uint256 deducted); error InvalidListingAmount(uint256 total, uint256 authorizedAllowance); + error InvalidListingType(address asset, uint256 lsp4TokenType, bool isNonDivisible); uint256 public totalListings; mapping(uint256 => LSP7Listing) private _listings; @@ -55,17 +60,28 @@ contract LSP7Listings is ILSP7Listings, Module { if (itemCount == 0) { revert InvalidListingZeroItems(); } + // verify that the asset is a non-divisible NFT + { + uint256 tokenType = uint256(bytes32(ILSP7DigitalAsset(asset).getData(_LSP4_TOKEN_TYPE_KEY))); + bool divisible = ILSP7DigitalAsset(asset).decimals() != 0; + if (tokenType != _LSP4_TOKEN_TYPE_NFT || divisible) { + revert InvalidListingType(asset, tokenType, !divisible); + } + } address seller = msg.sender; uint256 allowance = ILSP7DigitalAsset(asset).authorizedAmountFor(seller, owner); if (allowance < itemCount) { revert InsufficientAuthorization(seller, itemCount, allowance); } - bytes32 key = _listingKey(asset, owner); - uint256 listedAmount = _listedAmount[key] + itemCount; - if (listedAmount > allowance) { - revert InvalidListingAmount(listedAmount, allowance); + // verify that the listing is valid + { + bytes32 key = _listingKey(asset, owner); + uint256 listedAmount = _listedAmount[key] + itemCount; + if (listedAmount > allowance) { + revert InvalidListingAmount(listedAmount, allowance); + } + _listedAmount[key] = listedAmount; } - _listedAmount[key] = listedAmount; totalListings += 1; uint256 id = totalListings; uint256 endTime = 0; diff --git a/test/marketplace/lsp7/LSP7Listings.t.sol b/test/marketplace/lsp7/LSP7Listings.t.sol index 97ddc42..9fceda0 100644 --- a/test/marketplace/lsp7/LSP7Listings.t.sol +++ b/test/marketplace/lsp7/LSP7Listings.t.sol @@ -7,6 +7,11 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; +import { + _LSP4_TOKEN_TYPE_TOKEN, + _LSP4_TOKEN_TYPE_NFT, + _LSP4_TOKEN_TYPE_COLLECTION +} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; import {Module, MARKETPLACE_ROLE} from "../../../src/marketplace/common/Module.sol"; import {LSP7Listings, LSP7Listing} from "../../../src/marketplace/lsp7/LSP7Listings.sol"; @@ -45,7 +50,7 @@ contract LSP7ListingsTest is Test { admin = vm.addr(1); owner = vm.addr(2); - asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, 0, true); + asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, _LSP4_TOKEN_TYPE_NFT, true); listings = LSP7Listings( address( @@ -459,4 +464,34 @@ contract LSP7ListingsTest is Test { vm.expectRevert(abi.encodeWithSelector(LSP7Listings.InvalidListingAmount.selector, 11, 10)); listings.list(address(asset), address(profile), 4, 1 ether, block.timestamp, 10 days); } + + function test_Revert_ListDivisibleNft() public { + LSP7DigitalAssetMock invalidAsset = new LSP7DigitalAssetMock("Mock", "MCK", owner, _LSP4_TOKEN_TYPE_NFT, false); + + (UniversalProfile profile,) = deployProfile(); + invalidAsset.mint(address(profile), 10, false, ""); + + vm.prank(address(profile)); + vm.expectRevert( + abi.encodeWithSelector( + LSP7Listings.InvalidListingType.selector, address(invalidAsset), _LSP4_TOKEN_TYPE_NFT, false + ) + ); + listings.list(address(invalidAsset), address(profile), 10, 1 ether, block.timestamp, 10 days); + } + + function test_Revert_ListNonNft() public { + LSP7DigitalAssetMock invalidAsset = new LSP7DigitalAssetMock("Mock", "MCK", owner, _LSP4_TOKEN_TYPE_TOKEN, true); + + (UniversalProfile profile,) = deployProfile(); + invalidAsset.mint(address(profile), 10, false, ""); + + vm.prank(address(profile)); + vm.expectRevert( + abi.encodeWithSelector( + LSP7Listings.InvalidListingType.selector, address(invalidAsset), _LSP4_TOKEN_TYPE_TOKEN, true + ) + ); + listings.list(address(invalidAsset), address(profile), 10, 1 ether, block.timestamp, 10 days); + } } diff --git a/test/marketplace/lsp7/LSP7Marketplace.t.sol b/test/marketplace/lsp7/LSP7Marketplace.t.sol index e2a9efb..c214859 100644 --- a/test/marketplace/lsp7/LSP7Marketplace.t.sol +++ b/test/marketplace/lsp7/LSP7Marketplace.t.sol @@ -8,6 +8,7 @@ import { } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {_INTERFACEID_LSP0} from "@lukso/lsp-smart-contracts/contracts/LSP0ERC725Account/LSP0Constants.sol"; import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; +import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; import {Module, MARKETPLACE_ROLE} from "../../../src/marketplace/common/Module.sol"; import {Points} from "../../../src/common/Points.sol"; @@ -51,7 +52,7 @@ contract LSP7MarketplaceTest is Test { owner = vm.addr(2); beneficiary = vm.addr(3); - asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, 0, true); + asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, _LSP4_TOKEN_TYPE_NFT, true); participant = Participant( payable( diff --git a/test/marketplace/lsp7/LSP7Offers.t.sol b/test/marketplace/lsp7/LSP7Offers.t.sol index 4c9b62d..d6620e8 100644 --- a/test/marketplace/lsp7/LSP7Offers.t.sol +++ b/test/marketplace/lsp7/LSP7Offers.t.sol @@ -7,6 +7,7 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; +import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; import {Module, MARKETPLACE_ROLE} from "../../../src/marketplace/common/Module.sol"; import {LSP7Listings, LSP7Listing} from "../../../src/marketplace/lsp7/LSP7Listings.sol"; @@ -31,7 +32,7 @@ contract LSP7OffersTest is Test { admin = vm.addr(1); owner = vm.addr(2); - asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, 0, true); + asset = new LSP7DigitalAssetMock("Mock", "MCK", owner, _LSP4_TOKEN_TYPE_NFT, true); listings = LSP7Listings( address(