diff --git a/packages/marketplace/contracts/Exchange.sol b/packages/marketplace/contracts/Exchange.sol index 3095f5e5a5..0c40cb918f 100644 --- a/packages/marketplace/contracts/Exchange.sol +++ b/packages/marketplace/contracts/Exchange.sol @@ -38,9 +38,13 @@ contract Exchange is /// @return Hash for PAUSER_ROLE. bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - /// @notice Role for TSB owned addresses that list TSB owned assets for sale, forces primary sales conditions. - /// @return Hash for TSB_SELLER_ROLE. - bytes32 public constant TSB_SELLER_ROLE = keccak256("TSB_SELLER_ROLE"); + /// @notice Role for TSB owned addresses that list TSB owned assets for sale, forces primary sales conditions(skips royalties, pays primary protocol fees). + /// @return Hash for TSB_PRIMARY_MARKET_SELLER_ROLE. + bytes32 public constant TSB_PRIMARY_MARKET_SELLER_ROLE = keccak256("TSB_PRIMARY_MARKET_SELLER_ROLE"); + + /// @notice Role for TSB owned addresses that can sell bundled assets including quad in secondary market(pays royalties and primary protocol fees). + /// @return Hash for TSB_SECONDARY_MARKET_SELLER_ROLE. + bytes32 public constant TSB_SECONDARY_MARKET_SELLER_ROLE = keccak256("TSB_SECONDARY_MARKET_SELLER_ROLE"); /// @notice Role for addresses that should be whitelisted from any marketplace fees including royalties. /// @return Hash for FEE_WHITELIST_ROLE. @@ -176,8 +180,15 @@ contract Exchange is /// @dev Check if the address is a TSB seller, which forces primary sales conditions regardless if the seller is the creator of the token. /// @param from Address to check. /// @return True if the address is a TSB seller, false otherwise. - function _isTSBSeller(address from) internal view override returns (bool) { - return hasRole(TSB_SELLER_ROLE, from); + function _isTSBPrimaryMarketSeller(address from) internal view override returns (bool) { + return hasRole(TSB_PRIMARY_MARKET_SELLER_ROLE, from); + } + + /// @dev Check if the address is a TSB Bundle Seller, which can sell bundled assets including quad in secondary market. + /// @param from Address to check. + /// @return True if the address is a TSB bundle seller, false otherwise. + function _isTSBSecondaryMarketSeller(address from) internal view override returns (bool) { + return hasRole(TSB_SECONDARY_MARKET_SELLER_ROLE, from); } function _msgSender() diff --git a/packages/marketplace/contracts/ExchangeCore.sol b/packages/marketplace/contracts/ExchangeCore.sol index ad450ef994..2abe572f07 100644 --- a/packages/marketplace/contracts/ExchangeCore.sol +++ b/packages/marketplace/contracts/ExchangeCore.sol @@ -121,7 +121,7 @@ abstract contract ExchangeCore is Initializable, ITransferManager { uint256 len = matchedOrders.length; require(len > 0, "ExchangeMatch cannot be empty"); require(len <= matchOrdersLimit, "too many ExchangeMatch"); - for (uint256 i; i < len; i++) { + for (uint256 i; i < len; ++i) { ExchangeMatch calldata m = matchedOrders[i]; _validateOrders(sender, m.orderLeft, m.signatureLeft, m.orderRight, m.signatureRight); _matchAndTransfer(sender, m.orderLeft, m.orderRight); @@ -141,15 +141,15 @@ abstract contract ExchangeCore is Initializable, ITransferManager { LibOrder.Order memory orderRight, bytes memory signatureRight ) internal view { - // validate must force order.maker != address(0) - orderValidator.validate(orderLeft, signatureLeft, sender); - orderValidator.validate(orderRight, signatureRight, sender); if (orderLeft.taker != address(0)) { require(orderRight.maker == orderLeft.taker, "leftOrder.taker failed"); } if (orderRight.taker != address(0)) { require(orderRight.taker == orderLeft.maker, "rightOrder.taker failed"); } + // validate must force order.maker != address(0) + orderValidator.validate(orderLeft, signatureLeft, sender); + orderValidator.validate(orderRight, signatureRight, sender); } /// @notice Matches valid orders and transfers the associated assets. diff --git a/packages/marketplace/contracts/OrderValidator.sol b/packages/marketplace/contracts/OrderValidator.sol index 72ea8acd77..96ae0b9131 100644 --- a/packages/marketplace/contracts/OrderValidator.sol +++ b/packages/marketplace/contracts/OrderValidator.sol @@ -85,11 +85,14 @@ contract OrderValidator is IOrderValidator, Initializable, EIP712Upgradeable, ER address makeToken; if (asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE) { LibAsset.Bundle memory bundle = LibAsset.decodeBundle(asset.assetType); - for (uint256 i; i < bundle.bundledERC721.length; i++) { + uint256 bundledERC721Length = bundle.bundledERC721.length; + for (uint256 i; i < bundledERC721Length; ++i) { makeToken = bundle.bundledERC721[i].erc721Address; _verifyWhitelistsRoles(makeToken); } - for (uint256 i; i < bundle.bundledERC1155.length; i++) { + + uint256 bundledERC1155Length = bundle.bundledERC1155.length; + for (uint256 i; i < bundledERC1155Length; ++i) { makeToken = bundle.bundledERC1155[i].erc1155Address; _verifyWhitelistsRoles(makeToken); } diff --git a/packages/marketplace/contracts/RoyaltiesRegistry.sol b/packages/marketplace/contracts/RoyaltiesRegistry.sol index 1607af1430..ccdeea0a44 100644 --- a/packages/marketplace/contracts/RoyaltiesRegistry.sol +++ b/packages/marketplace/contracts/RoyaltiesRegistry.sol @@ -110,7 +110,8 @@ contract RoyaltiesRegistry is OwnableUpgradeable, IRoyaltiesProvider, ERC165Upgr _setRoyaltiesType(token, RoyaltiesType.BY_TOKEN, address(0)); uint256 sumRoyalties = 0; delete royaltiesByToken[token]; - for (uint256 i = 0; i < royalties.length; ++i) { + uint256 royaltiesLength = royalties.length; + for (uint256 i = 0; i < royaltiesLength; ++i) { require(royalties[i].account != address(0x0), "recipient should be present"); require(royalties[i].basisPoints != 0, "basisPoints should be > 0"); royaltiesByToken[token].royalties.push(royalties[i]); @@ -253,7 +254,7 @@ contract RoyaltiesRegistry is OwnableUpgradeable, IRoyaltiesProvider, ERC165Upgr uint256 multiRecipientsLength = multiRecipients.length; Part[] memory royalties = new Part[](multiRecipientsLength); uint256 sum = 0; - for (uint256 i; i < multiRecipientsLength; i++) { + for (uint256 i; i < multiRecipientsLength; ++i) { Recipient memory splitRecipient = multiRecipients[i]; royalties[i].account = splitRecipient.recipient; uint256 splitAmount = (splitRecipient.bps * royaltyAmount) / WEIGHT_VALUE; diff --git a/packages/marketplace/contracts/TransferManager.sol b/packages/marketplace/contracts/TransferManager.sol index b0ec643f5d..3005df9d8f 100644 --- a/packages/marketplace/contracts/TransferManager.sol +++ b/packages/marketplace/contracts/TransferManager.sol @@ -126,7 +126,7 @@ abstract contract TransferManager is Initializable, ITransferManager { // Transfer NFT or left side if FeeSide.NONE // NFT transfer when exchanging more than one bundle of ERC1155s if (nftSide.asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE && nftSide.asset.value > 1) { - for (uint256 i = 0; i < nftSide.asset.value; i++) { + for (uint256 i = 0; i < nftSide.asset.value; ++i) { _transfer(nftSide.asset, nftSide.account, paymentSideRecipient); } } else { @@ -210,7 +210,7 @@ abstract contract TransferManager is Initializable, ITransferManager { bool isBundle = nftSide.asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE; if (isBundle) { - if (!_isTSBSeller(nftSide.account)) { + if (!_isTSBPrimaryMarketSeller(nftSide.account)) { remainder = _doTransfersWithFeesAndRoyaltiesForBundle(paymentSide, nftSide, nftSideRecipient); } else { // No royalties but primary fee should be paid on the total value of the bundle @@ -231,7 +231,7 @@ abstract contract TransferManager is Initializable, ITransferManager { function _calculateFeesAndRoyalties( DealSide memory nftSide ) internal returns (uint256 fees, bool shouldTransferRoyalties) { - if (_isTSBSeller(nftSide.account) || _isPrimaryMarket(nftSide)) { + if (_isTSBPrimaryMarketSeller(nftSide.account) || _isPrimaryMarket(nftSide)) { fees = protocolFeePrimary; shouldTransferRoyalties = false; } else { @@ -265,28 +265,11 @@ abstract contract TransferManager is Initializable, ITransferManager { ) internal returns (uint256 remainder) { remainder = paymentSide.asset.value; uint256 feePrimary = protocolFeePrimary; - uint256 feeSecondary = protocolFeeSecondary; LibAsset.Bundle memory bundle = LibAsset.decodeBundle(nftSide.asset.assetType); - remainder = _processERC721Bundles( - paymentSide, - nftSide, - nftSideRecipient, - remainder, - feePrimary, - feeSecondary, - bundle - ); - remainder = _processERC1155Bundles( - paymentSide, - nftSide, - nftSideRecipient, - remainder, - feePrimary, - feeSecondary, - bundle - ); - remainder = _processQuadBundles(paymentSide, nftSideRecipient, remainder, feeSecondary, bundle); + remainder = _processERC721Bundles(paymentSide, nftSide, nftSideRecipient, remainder, feePrimary, bundle); + remainder = _processERC1155Bundles(paymentSide, nftSide, nftSideRecipient, remainder, feePrimary, bundle); + remainder = _processQuadBundles(paymentSide, nftSide, nftSideRecipient, remainder, feePrimary, bundle); return remainder; } @@ -296,20 +279,19 @@ abstract contract TransferManager is Initializable, ITransferManager { address nftSideRecipient, uint256 remainder, uint256 feePrimary, - uint256 feeSecondary, LibAsset.Bundle memory bundle ) internal returns (uint256) { - for (uint256 i; i < bundle.bundledERC721.length; i++) { + uint256 bundledERC721Length = bundle.bundledERC721.length; + for (uint256 i; i < bundledERC721Length; ++i) { address token = bundle.bundledERC721[i].erc721Address; uint256 idLength = bundle.bundledERC721[i].ids.length; - for (uint256 j; j < idLength; j++) { + for (uint256 j; j < idLength; ++j) { remainder = _processSingleAsset( paymentSide, nftSide, nftSideRecipient, remainder, feePrimary, - feeSecondary, token, bundle.bundledERC721[i].ids[j], bundle.priceDistribution.erc721Prices[i][j] @@ -325,23 +307,21 @@ abstract contract TransferManager is Initializable, ITransferManager { address nftSideRecipient, uint256 remainder, uint256 feePrimary, - uint256 feeSecondary, LibAsset.Bundle memory bundle ) internal returns (uint256) { - for (uint256 i; i < bundle.bundledERC1155.length; i++) { + for (uint256 i; i < bundle.bundledERC1155.length; ++i) { address token = bundle.bundledERC1155[i].erc1155Address; uint256 idLength = bundle.bundledERC1155[i].ids.length; require(idLength == bundle.bundledERC1155[i].supplies.length, "ERC1155 array error"); - for (uint256 j; j < idLength; j++) { - for (uint256 k = 0; k < nftSide.asset.value; k++) { + for (uint256 j; j < idLength; ++j) { + for (uint256 k = 0; k < nftSide.asset.value; ++k) { remainder = _processSingleAsset( paymentSide, nftSide, nftSideRecipient, remainder, feePrimary, - feeSecondary, token, bundle.bundledERC1155[i].ids[j], bundle.priceDistribution.erc1155Prices[i][j] @@ -354,26 +334,28 @@ abstract contract TransferManager is Initializable, ITransferManager { function _processQuadBundles( DealSide memory paymentSide, + DealSide memory nftSide, address nftSideRecipient, uint256 remainder, - uint256 feeSecondary, + uint256 feePrimary, LibAsset.Bundle memory bundle ) internal returns (uint256) { uint256 quadSize = bundle.quads.xs.length; - for (uint256 i = 0; i < quadSize; i++) { + for (uint256 i = 0; i < quadSize; ++i) { uint256 size = bundle.quads.sizes[i]; uint256 x = bundle.quads.xs[i]; uint256 y = bundle.quads.ys[i]; uint256 tokenId = idInPath(0, size, x, y); - remainder = _transferFeesAndRoyaltiesForBundledAsset( + remainder = _processSingleAsset( paymentSide, - address(landContract), + nftSide, nftSideRecipient, remainder, + feePrimary, + address(landContract), tokenId, - bundle.priceDistribution.quadPrices[i], - feeSecondary + bundle.priceDistribution.quadPrices[i] ); } return remainder; @@ -385,7 +367,6 @@ abstract contract TransferManager is Initializable, ITransferManager { address nftSideRecipient, uint256 remainder, uint256 feePrimary, - uint256 feeSecondary, address token, uint256 tokenId, uint256 assetPrice @@ -402,6 +383,7 @@ abstract contract TransferManager is Initializable, ITransferManager { ); } } else { + require(_isTSBSecondaryMarketSeller(nftSide.account), "not TSB secondary market seller"); remainder = _transferFeesAndRoyaltiesForBundledAsset( paymentSide, token, @@ -409,7 +391,7 @@ abstract contract TransferManager is Initializable, ITransferManager { remainder, tokenId, assetPrice, - feeSecondary + feePrimary ); } return remainder; @@ -500,8 +482,8 @@ abstract contract TransferManager is Initializable, ITransferManager { address recipient ) internal returns (uint256) { uint256 totalRoyalties; - uint256 len = royalties.length; - for (uint256 i; i < len; i++) { + uint256 royaltiesLength = royalties.length; + for (uint256 i; i < royaltiesLength; ++i) { IRoyaltiesProvider.Part memory r = royalties[i]; totalRoyalties += r.basisPoints; if (r.account == recipient) { @@ -581,22 +563,22 @@ abstract contract TransferManager is Initializable, ITransferManager { _transferERC1155(token, from, to, tokenId, asset.value); } else if (asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE) { LibAsset.Bundle memory bundle = LibAsset.decodeBundle(asset.assetType); - uint256 erc721Length = bundle.bundledERC721.length; - uint256 erc1155Length = bundle.bundledERC1155.length; + uint256 bundledERC721Length = bundle.bundledERC721.length; + uint256 bundledERC1155Length = bundle.bundledERC1155.length; uint256 quadsLength = bundle.quads.xs.length; - if (erc721Length > 0 || quadsLength > 0) require(asset.value == 1, "bundle value error"); - for (uint256 i; i < erc721Length; i++) { + if (bundledERC721Length > 0 || quadsLength > 0) require(asset.value == 1, "bundle value error"); + for (uint256 i; i < bundledERC721Length; ++i) { address token = bundle.bundledERC721[i].erc721Address; uint256 idLength = bundle.bundledERC721[i].ids.length; - for (uint256 j; j < idLength; j++) { + for (uint256 j; j < idLength; ++j) { _transferERC721(token, from, to, bundle.bundledERC721[i].ids[j]); } } - for (uint256 i; i < erc1155Length; i++) { + for (uint256 i; i < bundledERC1155Length; ++i) { address token = bundle.bundledERC1155[i].erc1155Address; uint256 idLength = bundle.bundledERC1155[i].ids.length; require(idLength == bundle.bundledERC1155[i].supplies.length, "ERC1155 array error"); - for (uint256 j; j < idLength; j++) { + for (uint256 j; j < idLength; ++j) { _transferERC1155( token, from, @@ -668,7 +650,11 @@ abstract contract TransferManager is Initializable, ITransferManager { /// @notice Function deciding if the seller is a TSB seller, to be overridden /// @param from Address to check - function _isTSBSeller(address from) internal virtual returns (bool); + function _isTSBPrimaryMarketSeller(address from) internal virtual returns (bool); + + /// @notice Function deciding if the seller is a TSB bundle seller, to be overridden + /// @param from Address to check + function _isTSBSecondaryMarketSeller(address from) internal virtual returns (bool); // slither-disable-next-line unused-state uint256[49] private __gap; diff --git a/packages/marketplace/contracts/Whitelist.sol b/packages/marketplace/contracts/Whitelist.sol index 0ca9874d65..263eb78f25 100644 --- a/packages/marketplace/contracts/Whitelist.sol +++ b/packages/marketplace/contracts/Whitelist.sol @@ -118,8 +118,9 @@ contract Whitelist is IWhitelist, Initializable, AccessControlEnumerableUpgradea /// @param roles List of role identifiers. /// @param permissions List of desired status for each role. function _setRolesEnabled(bytes32[] memory roles, bool[] memory permissions) internal { - require(roles.length == permissions.length, "Mismatched input lengths"); - for (uint256 i = 0; i < roles.length; ++i) { + uint256 rolesLength = roles.length; + require(rolesLength == permissions.length, "Mismatched input lengths"); + for (uint256 i = 0; i < rolesLength; ++i) { if (isRoleEnabled(roles[i]) != permissions[i]) { if (permissions[i]) { _enableRole(roles[i]); diff --git a/packages/marketplace/contracts/libraries/LibAsset.sol b/packages/marketplace/contracts/libraries/LibAsset.sol index 8e4c44d598..2d14bbd810 100644 --- a/packages/marketplace/contracts/libraries/LibAsset.sol +++ b/packages/marketplace/contracts/libraries/LibAsset.sol @@ -83,6 +83,14 @@ library LibAsset { /// @param rightClass The asset class type of the right side of the trade. /// @return FeeSide representing which side should bear the fee, if any. function getFeeSide(AssetClass leftClass, AssetClass rightClass) internal pure returns (FeeSide) { + if (leftClass == AssetClass.BUNDLE || rightClass == AssetClass.BUNDLE) { + require( + ((leftClass == AssetClass.BUNDLE && rightClass == AssetClass.ERC20) || + (rightClass == AssetClass.BUNDLE && leftClass == AssetClass.ERC20)), + "exchange not allowed" + ); + } + if (leftClass == AssetClass.ERC20 && rightClass != AssetClass.ERC20) { return FeeSide.LEFT; } @@ -165,20 +173,25 @@ library LibAsset { uint256 collectiveBundlePrice = 0; // total price of all bundled ERC721 assets - for (uint256 i = 0; i < priceDistribution.erc721Prices.length; i++) { - for (uint256 j = 0; j < priceDistribution.erc721Prices[i].length; j++) + uint256 erc721PricesLength = priceDistribution.erc721Prices.length; + for (uint256 i = 0; i < erc721PricesLength; ++i) { + uint256 erc721PricesInnerLength = priceDistribution.erc721Prices[i].length; + for (uint256 j = 0; j < erc721PricesInnerLength; ++j) collectiveBundlePrice += priceDistribution.erc721Prices[i][j]; } // total price of all bundled ERC1155 assets - for (uint256 i = 0; i < priceDistribution.erc1155Prices.length; i++) { - for (uint256 j = 0; j < priceDistribution.erc1155Prices[i].length; j++) { + uint256 erc1155PricesLength = priceDistribution.erc1155Prices.length; + for (uint256 i = 0; i < erc1155PricesLength; ++i) { + uint256 erc1155PricesInnerLength = priceDistribution.erc1155Prices[i].length; + for (uint256 j = 0; j < erc1155PricesInnerLength; ++j) { collectiveBundlePrice += priceDistribution.erc1155Prices[i][j]; } } // total price of all bundled Quad assets - for (uint256 i = 0; i < priceDistribution.quadPrices.length; i++) { + uint256 quadPricesLength = priceDistribution.quadPrices.length; + for (uint256 i = 0; i < quadPricesLength; ++i) { collectiveBundlePrice += priceDistribution.quadPrices[i]; } diff --git a/packages/marketplace/test/exchange/Bundle.behavior.ts b/packages/marketplace/test/exchange/Bundle.behavior.ts index f42f28cc94..483cfc65dc 100644 --- a/packages/marketplace/test/exchange/Bundle.behavior.ts +++ b/packages/marketplace/test/exchange/Bundle.behavior.ts @@ -15,6 +15,47 @@ import { import {hashKey, OrderDefault, signOrder, Order} from '../utils/order.ts'; import {ZeroAddress, Contract, Signer} from 'ethers'; +function calculateFinalPrice( + priceDistribution: PriceDistribution, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protocolFees: any +): number { + let finalPrice = 0; + + // ERC721 assets + for (let i = 0; i < priceDistribution.erc721Prices.length; i++) { + for (let j = 0; j < priceDistribution.erc721Prices[i].length; j++) { + const assetPrice = priceDistribution.erc721Prices[i][j]; + const protocolFee = protocolFees.erc721ProtocolFees[i][j]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + finalPrice += assetPrice - deductedProtocolFee; + } + } + + // ERC1155 assets + for (let i = 0; i < priceDistribution.erc1155Prices.length; i++) { + for (let j = 0; j < priceDistribution.erc1155Prices[i].length; j++) { + const assetPrice = priceDistribution.erc1155Prices[i][j]; + const protocolFee = protocolFees.erc1155ProtocolFees[i][j]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + finalPrice += assetPrice - deductedProtocolFee; + } + } + + // Quad assets + for (let i = 0; i < priceDistribution.quadPrices.length; i++) { + const assetPrice = priceDistribution.quadPrices[i]; + const protocolFee = protocolFees.quadProtocolFees[i]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + finalPrice += assetPrice - deductedProtocolFee; + } + + return finalPrice; +} + // eslint-disable-next-line mocha/no-exports export function shouldMatchOrdersForBundle() { describe('Exchange MatchOrders for Bundle', function () { @@ -25,7 +66,7 @@ export function shouldMatchOrdersForBundle() { ERC721Contract: Contract, ERC1155Contract: Contract, LandContract: Contract, - protocolFeeSecondary: number, + protocolFeePrimary: number, defaultFeeReceiver: Signer, maker: Signer, taker: Signer, @@ -43,22 +84,31 @@ export function shouldMatchOrdersForBundle() { orderLeft: Order, orderRight: Order, makerSig: string, - takerSig: string; + takerSig: string, + TSB_SECONDARY_MARKET_SELLER_ROLE: string; describe('Bundle x ERC20', function () { beforeEach(async function () { ({ ExchangeContractAsUser, + ExchangeContractAsAdmin, OrderValidatorAsAdmin, ERC20Contract, ERC721Contract, ERC1155Contract, - protocolFeeSecondary, + protocolFeePrimary, defaultFeeReceiver, user1: maker, user2: taker, + TSB_SECONDARY_MARKET_SELLER_ROLE, } = await loadFixture(deployFixtures)); + // grant tsb bundle seller role to seller + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + priceDistribution = { erc721Prices: [[4000000000]], erc1155Prices: [[6000000000]], @@ -187,6 +237,107 @@ export function shouldMatchOrdersForBundle() { ).to.be.revertedWith('Bundle price mismatch'); }); + it('should not execute match order between Bundle and Bundle', async function () { + // Set up ERC721 for maker + await ERC721Contract.mint(await maker.getAddress(), 10); + await ERC721Contract.connect(maker).approve( + await ExchangeContractAsUser.getAddress(), + 1 + ); + + // Set up ERC1155 for taker + await ERC1155Contract.mint(await taker.getAddress(), 10, 50); + + await ERC1155Contract.connect(taker).setApprovalForAll( + await ExchangeContractAsUser.getAddress(), + true + ); + + // Construct makerAsset bundle + priceDistribution = { + erc721Prices: [[10000000000]], + erc1155Prices: [[]], + quadPrices: [], + }; + + bundledERC721 = [ + { + erc721Address: ERC721Contract.target, + ids: [10], + }, + ]; + + bundledERC1155 = []; + + const bundleDataLeft: BundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + makerAsset = await AssetBundle(bundleDataLeft, 1); + + // Construct takerAsset bundle + priceDistribution = { + erc721Prices: [[]], + erc1155Prices: [[10000000000]], + quadPrices: [], + }; + + bundledERC721 = []; + + bundledERC1155 = [ + { + erc1155Address: ERC1155Contract.target, + ids: [10], + supplies: [10], + }, + ]; + + const bundleDataRight: BundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + takerAsset = await AssetBundle(bundleDataRight, 1); + + orderLeft = await OrderDefault( + maker, + makerAsset, // Bundle + ZeroAddress, + takerAsset, // Bundle + 1, + 0, + 0 + ); + orderRight = await OrderDefault( + taker, + takerAsset, // Bundle + ZeroAddress, + makerAsset, // Bundle + 1, + 0, + 0 + ); + + makerSig = await signOrder(orderLeft, maker, OrderValidatorAsAdmin); + takerSig = await signOrder(orderRight, taker, OrderValidatorAsAdmin); + + await expect( + ExchangeContractAsUser.matchOrders([ + { + orderLeft, // passing Bundle as left order + signatureLeft: makerSig, + orderRight, // passing Bundle as right order + signatureRight: takerSig, + }, + ]) + ).to.be.revertedWith('exchange not allowed'); + }); + it('should execute a complete match order between ERC20 tokens and Bundle containing ERC20, ERC721 and ERC1155', async function () { orderLeft = await OrderDefault( maker, // ERC20 @@ -246,8 +397,19 @@ export function shouldMatchOrdersForBundle() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9750000000 // 10000000000 - protocolFee + expectedFinalReturn // 10000000000 - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -258,9 +420,9 @@ export function shouldMatchOrdersForBundle() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -423,8 +585,19 @@ export function shouldMatchOrdersForBundle() { await ExchangeContractAsUser.fills(hashKey(orderRight)) // newFill.leftValue ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9750000000 + expectedFinalReturn ); expect(await ERC20Contract.balanceOf(taker)).to.be.equal(20000000000); @@ -432,7 +605,7 @@ export function shouldMatchOrdersForBundle() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -618,15 +791,27 @@ export function shouldMatchOrdersForBundle() { expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 10000000000 ); + + const protocolFees = { + erc721ProtocolFees: [[]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 19500000000 + 2 * expectedFinalReturn ); expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( (2 * - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]))) / 10000 ); @@ -721,15 +906,26 @@ export function shouldMatchOrdersForBundle() { await ExchangeContractAsUser.fills(hashKey(rightOrderForFirstMatch)) // newFill.leftValue ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9750000000 + expectedFinalReturn ); expect(await ERC20Contract.balanceOf(taker)).to.be.equal(20000000000); expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); // 1 * partial fills => 1 * fee taken @@ -775,7 +971,7 @@ export function shouldMatchOrdersForBundle() { ).to.be.equal(1); expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 19500000000 + 2 * expectedFinalReturn ); expect(await ERC20Contract.balanceOf(taker)).to.be.equal(10000000000); 0; @@ -783,7 +979,7 @@ export function shouldMatchOrdersForBundle() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) * 2) / 10000 @@ -807,15 +1003,22 @@ export function shouldMatchOrdersForBundle() { ERC20Contract, ERC721Contract, ERC1155Contract, - protocolFeeSecondary, + protocolFeePrimary, defaultFeeReceiver, user1: maker, user2: taker, LandContract, LandAsAdmin, landAdmin, + TSB_SECONDARY_MARKET_SELLER_ROLE, } = await loadFixture(deployFixtures)); + // grant tsb bundle seller role to seller + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + priceDistribution = { erc721Prices: [[4000000000]], // price distribution without ERC721 erc1155Prices: [[5000000000]], @@ -885,7 +1088,7 @@ export function shouldMatchOrdersForBundle() { data: '0x', }; - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -966,8 +1169,19 @@ export function shouldMatchOrdersForBundle() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [protocolFeePrimary, protocolFeePrimary], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9750000000 // 10000000000 - protocolFee + expectedFinalReturn // 10000000000 - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -978,13 +1192,13 @@ export function shouldMatchOrdersForBundle() { expect( await ERC20Contract.balanceOf(await defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[1])) / 10000 ); diff --git a/packages/marketplace/test/exchange/BundleWithRoyalties.behaviour.ts b/packages/marketplace/test/exchange/BundleWithRoyalties.behaviour.ts index 5335bb0b02..ffba6032e2 100644 --- a/packages/marketplace/test/exchange/BundleWithRoyalties.behaviour.ts +++ b/packages/marketplace/test/exchange/BundleWithRoyalties.behaviour.ts @@ -17,6 +17,58 @@ import { import {hashKey, OrderDefault, signOrder, Order} from '../utils/order.ts'; import {ZeroAddress, Contract, Signer} from 'ethers'; +function calculateFinalPrice( + priceDistribution: PriceDistribution, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protocolFees: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + royaltyFees: any +): number { + let finalPrice = 0; + + // ERC721 assets + for (let i = 0; i < priceDistribution.erc721Prices.length; i++) { + for (let j = 0; j < priceDistribution.erc721Prices[i].length; j++) { + const assetPrice = priceDistribution.erc721Prices[i][j]; + const protocolFee = protocolFees.erc721ProtocolFees[i][j]; + const royaltyFee = royaltyFees.erc721RoyaltyFees[i][j]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + const deductedRoyaltyFee = (assetPrice * royaltyFee) / 100; + + finalPrice += assetPrice - deductedProtocolFee - deductedRoyaltyFee; + } + } + + // ERC1155 assets + for (let i = 0; i < priceDistribution.erc1155Prices.length; i++) { + for (let j = 0; j < priceDistribution.erc1155Prices[i].length; j++) { + const assetPrice = priceDistribution.erc1155Prices[i][j]; + const protocolFee = protocolFees.erc1155ProtocolFees[i][j]; + const royaltyFee = royaltyFees.erc1155RoyaltyFees[i][j]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + const deductedRoyaltyFee = (assetPrice * royaltyFee) / 100; + + finalPrice += assetPrice - deductedProtocolFee - deductedRoyaltyFee; + } + } + + // Quad assets + for (let i = 0; i < priceDistribution.quadPrices.length; i++) { + const assetPrice = priceDistribution.quadPrices[i]; + const protocolFee = protocolFees.quadProtocolFees[i]; + const royaltyFee = royaltyFees.quadRoyaltyFees[i]; + + const deductedProtocolFee = (assetPrice * protocolFee) / 10000; + const deductedRoyaltyFee = (assetPrice * royaltyFee) / 100; + + finalPrice += assetPrice - deductedProtocolFee - deductedRoyaltyFee; + } + + return finalPrice; +} + // eslint-disable-next-line mocha/no-exports export function shouldMatchOrdersForBundleWithRoyalty() { describe('Exchange MatchOrders for Bundle with royalty', function () { @@ -33,7 +85,6 @@ export function shouldMatchOrdersForBundleWithRoyalty() { RoyaltiesProvider: Contract, LandAsAdmin: Contract, QuadHelper: Contract, - protocolFeeSecondary: number, protocolFeePrimary: number, defaultFeeReceiver: Signer, maker: Signer, @@ -55,7 +106,8 @@ export function shouldMatchOrdersForBundleWithRoyalty() { orderRight: Order, makerSig: string, takerSig: string, - TSB_SELLER_ROLE: string; + TSB_PRIMARY_MARKET_SELLER_ROLE: string, + TSB_SECONDARY_MARKET_SELLER_ROLE: string; describe('Bundle in primary market', function () { beforeEach(async function () { @@ -64,20 +116,23 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ExchangeContractAsAdmin, OrderValidatorAsAdmin, RoyaltiesRegistryAsDeployer, + LandAsAdmin, ERC20Contract, ERC721Contract, ERC721WithRoyaltyV2981, ERC1155Contract, ERC1155WithRoyaltyV2981, + LandContract, RoyaltiesProvider, protocolFeePrimary, - protocolFeeSecondary, defaultFeeReceiver, deployer: maker, user2: taker, admin: royaltyReceiver, user: royaltyReceiver2, - TSB_SELLER_ROLE, + landAdmin, + TSB_PRIMARY_MARKET_SELLER_ROLE, + TSB_SECONDARY_MARKET_SELLER_ROLE, } = await loadFixture(deployFixtures)); priceDistribution = { @@ -93,7 +148,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { 1 ); - // Set up ERC721 for maker in primary markt + // Set up ERC721 for maker in primary market await ERC721WithRoyaltyV2981.mint(await maker.getAddress(), 1, [ await FeeRecipientsData(maker.getAddress(), 10000), ]); @@ -102,47 +157,596 @@ export function shouldMatchOrdersForBundleWithRoyalty() { 1 ); - // Set up ERC1155 for maker - await ERC1155Contract.mint(await maker.getAddress(), 1, 50); - await ERC1155Contract.mint(await maker.getAddress(), 2, 50); + // Set up ERC1155 for maker + await ERC1155Contract.mint(await maker.getAddress(), 1, 50); + await ERC1155Contract.mint(await maker.getAddress(), 2, 50); + + await ERC1155Contract.connect(maker).setApprovalForAll( + await ExchangeContractAsUser.getAddress(), + true + ); + + // Set up ERC1155 for maker in primary market + await ERC1155WithRoyaltyV2981.mint(await maker.getAddress(), 1, 50, [ + await FeeRecipientsData(maker.getAddress(), 10000), + ]); + await ERC1155WithRoyaltyV2981.connect(maker).setApprovalForAll( + await ExchangeContractAsUser.getAddress(), + true + ); + + // Make sure the land contract address is set on the Exchange + const landContractAddress = await LandContract.getAddress(); + await ExchangeContractAsAdmin.setLandContract(landContractAddress); + + // Land contract setup for maker ------------------------------------------------------------- + + // Set a minter + await LandAsAdmin.setMinter(await landAdmin.getAddress(), true); + + // Ensure that the marketplace contract is an approved operator for mock land contract + await LandContract.connect(maker).setApprovalForAllWithOutFilter( + await ExchangeContractAsUser.getAddress(), + true + ); + + await LandAsAdmin.mintQuad(await maker.getAddress(), 3, 0, 0, '0x'); + await LandAsAdmin.mintQuad(await maker.getAddress(), 3, 0, 3, '0x'); + await LandAsAdmin.mintQuad(await maker.getAddress(), 3, 3, 0, '0x'); + await LandAsAdmin.mintQuad(await maker.getAddress(), 3, 3, 3, '0x'); + expect( + await LandContract.balanceOf(await maker.getAddress()) + ).to.be.equal(36); + + // End land setup for maker ------------------------------------------------------------------ + + // Set up ERC20 for taker + await ERC20Contract.mint(await taker.getAddress(), 30000000000); + await ERC20Contract.connect(taker).approve( + await ExchangeContractAsUser.getAddress(), + 30000000000 + ); + + // Construct takerAsset + takerAsset = await AssetERC20(ERC20Contract, 10000000000); + }); + + it('should execute complete match order for bundle with ERC721, ERC1155 & Quads in primary market without royalty for TSB_PRIMARY_MARKET_SELLER', async function () { + // Construct makerAsset bundle + bundledERC721 = [ + { + erc721Address: ERC721Contract.target, + ids: [1], + }, + ]; + + bundledERC1155 = [ + { + erc1155Address: ERC1155Contract.target, + ids: [1], + supplies: [10], + }, + ]; + + quads = { + sizes: [3, 3], // 3x3, 3x3 = 9+9 lands total + xs: [3, 0], + ys: [0, 3], + data: '0x', + }; + + priceDistribution = { + erc721Prices: [[4000000000]], + erc1155Prices: [[5000000000]], + quadPrices: [400000000, 600000000], + }; + + // Create bundle for passing as left order + bundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + makerAsset = await AssetBundle(bundleData, 1); + + // set up royalties by token for ERC721 token + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await ERC721Contract.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token + ); + + // configuring royalties for ERC1155 tokens + await RoyaltiesProvider.initializeProvider( + await ERC1155Contract.getAddress(), + 1, + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id 1 + ); + await RoyaltiesRegistryAsDeployer.setProviderByToken( + await ERC1155Contract.getAddress(), + RoyaltiesProvider.getAddress() + ); + + // set up royalties by token for quad + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await LandAsAdmin.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for Land token + ); + + // grant tsb primary market seller role to maker + await ExchangeContractAsAdmin.grantRole( + TSB_PRIMARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + + orderLeft = await OrderDefault( + maker, + makerAsset, // Bundle + ZeroAddress, + takerAsset, // ERC20 + 1, + 0, + 0 + ); + orderRight = await OrderDefault( + taker, + takerAsset, // ERC20 + ZeroAddress, + makerAsset, // Bundle + 1, + 0, + 0 + ); + + makerSig = await signOrder(orderLeft, maker, OrderValidatorAsAdmin); + takerSig = await signOrder(orderRight, taker, OrderValidatorAsAdmin); + + const makerAddress = await maker.getAddress(); + const takerAddress = await taker.getAddress(); + + expect(await ERC721Contract.ownerOf(1)).to.be.equal(makerAddress); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal(0); + expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( + 30000000000 + ); + + expect(await ERC1155Contract.balanceOf(makerAddress, 1)).to.be.equal( + 50 + ); + expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); + + await ExchangeContractAsAdmin.matchOrders([ + { + orderLeft, // passing Bundle as left order + signatureLeft: makerSig, + orderRight, // passing ERC20 as right order + signatureRight: takerSig, + }, + ]); + + expect(await ERC721Contract.ownerOf(1)).to.be.equal(takerAddress); + expect(await ERC1155Contract.balanceOf(makerAddress, 1)).to.be.equal( + 40 + ); + expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal( + 10 + ); + + expect( + await ExchangeContractAsUser.fills(hashKey(orderLeft)) + ).to.be.equal(10000000000); + expect( + await ExchangeContractAsUser.fills(hashKey(orderRight)) + ).to.be.equal(1); + + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [protocolFeePrimary, protocolFeePrimary], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [0, 0], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( + expectedFinalReturn // 10000000000 - protocolFee - royalty + ); + + expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( + 20000000000 + ); + + // no royalties paid + expect( + await ERC20Contract.balanceOf(royaltyReceiver.getAddress()) + ).to.be.equal(0); + + // check protocol fee + expect( + await ERC20Contract.balanceOf(await defaultFeeReceiver.getAddress()) + ).to.be.equal( + (Number(protocolFeePrimary) * + Number(priceDistribution.erc721Prices[0][0]) + + Number(protocolFeePrimary) * + Number(priceDistribution.erc1155Prices[0][0]) + + Number(protocolFeePrimary) * + Number(priceDistribution.quadPrices[0]) + + Number(protocolFeePrimary) * + Number(priceDistribution.quadPrices[1])) / + 10000 + ); + + // check maker received quads + expect(await LandContract.balanceOf(takerAddress)).to.be.equal(18); + expect(await LandContract.balanceOf(makerAddress)).to.be.equal(18); + }); + + it('should not execute match order for bundle with ERC721 & ERC1155 for regular user in secondary market', async function () { + // Construct makerAsset bundle + bundledERC721 = [ + { + erc721Address: ERC721Contract.target, + ids: [1], + }, + ]; + + bundledERC1155 = [ + { + erc1155Address: ERC1155Contract.target, + ids: [1], + supplies: [10], + }, + ]; + + quads = { + sizes: [], + xs: [], + ys: [], + data: '0x', + }; // empty quads + + // Create bundle for passing as left order + bundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + makerAsset = await AssetBundle(bundleData, 1); // there can only ever be 1 copy of a bundle that contains ERC721 + + // set up royalties by token + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await ERC721Contract.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token + ); + + // configuring royalties + await RoyaltiesProvider.initializeProvider( + await ERC1155Contract.getAddress(), + 1, + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id:1 + ); + + await RoyaltiesRegistryAsDeployer.setProviderByToken( + await ERC1155Contract.getAddress(), + RoyaltiesProvider.getAddress() + ); + + orderLeft = await OrderDefault( + maker, + makerAsset, // Bundle + ZeroAddress, + takerAsset, // ERC20 + 1, + 0, + 0 + ); + orderRight = await OrderDefault( + taker, + takerAsset, // ERC20 + ZeroAddress, + makerAsset, // Bundle + 1, + 0, + 0 + ); + + makerSig = await signOrder(orderLeft, maker, OrderValidatorAsAdmin); + takerSig = await signOrder(orderRight, taker, OrderValidatorAsAdmin); + + const makerAddress = await maker.getAddress(); + const takerAddress = await taker.getAddress(); + + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal(0); + expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( + 30000000000 + ); + + expect(await ERC721Contract.ownerOf(1)).to.be.equal(makerAddress); + expect(await ERC1155Contract.balanceOf(makerAddress, 1)).to.be.equal( + 50 + ); + expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); + + await expect( + ExchangeContractAsUser.matchOrders([ + { + orderLeft, // passing Bundle as left order + signatureLeft: makerSig, + orderRight, // passing ERC20 as right order + signatureRight: takerSig, + }, + ]) + ).to.be.revertedWith('not TSB secondary market seller'); + }); + + it('should not execute match order for bundle when the seller is not the creator of an asset with royalties', async function () { + const {user: maker} = await loadFixture(deployFixtures); // maker is not creator for ERC721WithRoyaltyV2981 & ERC1155WithRoyaltyV2981 assets + + // Set up ERC721 for maker with royalty + await ERC721WithRoyaltyV2981.mint(await maker.getAddress(), 10, [ + await FeeRecipientsData(maker.getAddress(), 10000), + ]); + await ERC721WithRoyaltyV2981.connect(maker).approve( + await ExchangeContractAsUser.getAddress(), + 10 + ); + + // Set up ERC1155 for maker with royalty + await ERC1155WithRoyaltyV2981.mint(await maker.getAddress(), 10, 50, [ + await FeeRecipientsData(maker.getAddress(), 10000), + ]); + await ERC1155WithRoyaltyV2981.connect(maker).setApprovalForAll( + await ExchangeContractAsUser.getAddress(), + true + ); + + // Construct makerAsset bundle + bundledERC721 = [ + { + erc721Address: ERC721WithRoyaltyV2981.target, + ids: [10], + }, + ]; + + bundledERC1155 = [ + { + erc1155Address: ERC1155WithRoyaltyV2981.target, + ids: [10], + supplies: [10], + }, + ]; + + quads = { + sizes: [], + xs: [], + ys: [], + data: '0x', + }; // non empty quads + + // Create bundle for passing as left order + bundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + makerAsset = await AssetBundle(bundleData, 1); // there can only ever be 1 copy of a bundle that contains ERC721 + + // Set up ERC20 for taker + await ERC20Contract.mint(await taker.getAddress(), 30000000000); + await ERC20Contract.connect(taker).approve( + await ExchangeContractAsUser.getAddress(), + 30000000000 + ); + + // Construct takerAsset + takerAsset = await AssetERC20(ERC20Contract, 10000000000); + + // set up royalties by token + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await ERC721WithRoyaltyV2981.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token + ); + + // configuring royalties + await RoyaltiesProvider.initializeProvider( + await ERC1155WithRoyaltyV2981.getAddress(), + 10, + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id:10 + ); + + await RoyaltiesRegistryAsDeployer.setProviderByToken( + await ERC1155Contract.getAddress(), + RoyaltiesProvider.getAddress() + ); + + orderLeft = await OrderDefault( + maker, + makerAsset, // Bundle + ZeroAddress, + takerAsset, // ERC20 + 1, + 0, + 0 + ); + orderRight = await OrderDefault( + taker, + takerAsset, // ERC20 + ZeroAddress, + makerAsset, // Bundle + 1, + 0, + 0 + ); + + makerSig = await signOrder(orderLeft, maker, OrderValidatorAsAdmin); + takerSig = await signOrder(orderRight, taker, OrderValidatorAsAdmin); + + const makerAddress = await maker.getAddress(); + const takerAddress = await taker.getAddress(); + + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal(0); + expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( + 30000000000 + ); + + expect(await ERC721WithRoyaltyV2981.ownerOf(10)).to.be.equal( + makerAddress + ); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(makerAddress, 10) + ).to.be.equal(50); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(takerAddress, 10) + ).to.be.equal(0); + + await expect( + ExchangeContractAsUser.matchOrders([ + { + orderLeft, // passing Bundle as left order + signatureLeft: makerSig, + orderRight, // passing ERC20 as right order + signatureRight: takerSig, + }, + ]) + ).to.be.revertedWith('not TSB secondary market seller'); + }); + + it('should not execute match order for bundle with quad for creator', async function () { + // Construct makerAsset bundle + bundledERC721 = [ + { + erc721Address: ERC721WithRoyaltyV2981.target, + ids: [1], + }, + ]; + + bundledERC1155 = [ + { + erc1155Address: ERC1155WithRoyaltyV2981.target, + ids: [1], + supplies: [10], + }, + ]; + + quads = { + sizes: [3, 3], + xs: [3, 0], + ys: [0, 3], + data: '0x', + }; // non empty quads + + priceDistribution = { + erc721Prices: [[4000000000]], + erc1155Prices: [[5000000000]], + quadPrices: [400000000, 600000000], + }; + + // Create bundle for passing as left order + bundleData = { + bundledERC721, + bundledERC1155, + quads, + priceDistribution, + }; + + makerAsset = await AssetBundle(bundleData, 1); // there can only ever be 1 copy of a bundle that contains ERC721 + + // set up royalties by token + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await ERC721WithRoyaltyV2981.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token + ); + + // configuring royalties + await RoyaltiesProvider.initializeProvider( + await ERC1155WithRoyaltyV2981.getAddress(), + 1, + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id:1 + ); - await ERC1155Contract.connect(maker).setApprovalForAll( - await ExchangeContractAsUser.getAddress(), - true + await RoyaltiesRegistryAsDeployer.setProviderByToken( + await ERC1155WithRoyaltyV2981.getAddress(), + RoyaltiesProvider.getAddress() ); - // Set up ERC1155 for maker in primary markt - await ERC1155WithRoyaltyV2981.mint(await maker.getAddress(), 1, 50, [ - await FeeRecipientsData(maker.getAddress(), 10000), - ]); - await ERC1155WithRoyaltyV2981.connect(maker).setApprovalForAll( - await ExchangeContractAsUser.getAddress(), - true + orderLeft = await OrderDefault( + maker, + makerAsset, // Bundle + ZeroAddress, + takerAsset, // ERC20 + 1, + 0, + 0 + ); + orderRight = await OrderDefault( + taker, + takerAsset, // ERC20 + ZeroAddress, + makerAsset, // Bundle + 1, + 0, + 0 ); - // Set up ERC20 for taker - await ERC20Contract.mint(await taker.getAddress(), 30000000000); - await ERC20Contract.connect(taker).approve( - await ExchangeContractAsUser.getAddress(), + makerSig = await signOrder(orderLeft, maker, OrderValidatorAsAdmin); + takerSig = await signOrder(orderRight, taker, OrderValidatorAsAdmin); + + const makerAddress = await maker.getAddress(); + const takerAddress = await taker.getAddress(); + + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal(0); + expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 30000000000 ); - // Construct takerAsset - takerAsset = await AssetERC20(ERC20Contract, 10000000000); + expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal( + makerAddress + ); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(makerAddress, 1) + ).to.be.equal(50); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(takerAddress, 1) + ).to.be.equal(0); + + await expect( + ExchangeContractAsUser.matchOrders([ + { + orderLeft, // passing Bundle as left order + signatureLeft: makerSig, + orderRight, // passing ERC20 as right order + signatureRight: takerSig, + }, + ]) + ).to.be.revertedWith('not TSB secondary market seller'); }); - it('should execute complete match order for bundle without royalties but with primary market fees for address with TSB_SELLER_ROLE', async function () { + it('should execute complete match order for bundle with ERC721 & ERC1155 assets without royalties but with primary market fees for creator', async function () { // Construct makerAsset bundle bundledERC721 = [ { - erc721Address: ERC721Contract.target, + erc721Address: ERC721WithRoyaltyV2981.target, ids: [1], }, ]; bundledERC1155 = [ { - erc1155Address: ERC1155Contract.target, + erc1155Address: ERC1155WithRoyaltyV2981.target, ids: [1], supplies: [10], }, @@ -155,7 +759,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { data: '0x', }; // empty quads - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -167,28 +771,22 @@ export function shouldMatchOrdersForBundleWithRoyalty() { // set up royalties by token await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( - await ERC721Contract.getAddress(), + await ERC721WithRoyaltyV2981.getAddress(), [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token ); // configuring royalties await RoyaltiesProvider.initializeProvider( - await ERC1155Contract.getAddress(), + await ERC1155WithRoyaltyV2981.getAddress(), 1, [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id:1 ); await RoyaltiesRegistryAsDeployer.setProviderByToken( - await ERC1155Contract.getAddress(), + await ERC1155WithRoyaltyV2981.getAddress(), RoyaltiesProvider.getAddress() ); - // grant tsb seller role to seller - await ExchangeContractAsAdmin.grantRole( - TSB_SELLER_ROLE, - await maker.getAddress() - ); - orderLeft = await OrderDefault( maker, makerAsset, // Bundle @@ -219,11 +817,15 @@ export function shouldMatchOrdersForBundleWithRoyalty() { 30000000000 ); - expect(await ERC721Contract.ownerOf(1)).to.be.equal(makerAddress); - expect(await ERC1155Contract.balanceOf(makerAddress, 1)).to.be.equal( - 50 + expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal( + makerAddress ); - expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(makerAddress, 1) + ).to.be.equal(50); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(takerAddress, 1) + ).to.be.equal(0); await ExchangeContractAsUser.matchOrders([ { @@ -234,13 +836,15 @@ export function shouldMatchOrdersForBundleWithRoyalty() { }, ]); - expect(await ERC721Contract.ownerOf(1)).to.be.equal(takerAddress); - expect(await ERC1155Contract.balanceOf(makerAddress, 1)).to.be.equal( - 40 - ); - expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal( - 10 + expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal( + takerAddress ); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(makerAddress, 1) + ).to.be.equal(40); + expect( + await ERC1155WithRoyaltyV2981.balanceOf(takerAddress, 1) + ).to.be.equal(10); expect( await ExchangeContractAsUser.fills(hashKey(orderLeft)) @@ -249,8 +853,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9877000000 // 10000000000 - protocolFee + expectedFinalReturn // 10000000000 - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -270,7 +892,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ).to.be.equal(0); }); - it('should execute complete match order for bundle with ERC721 asset in primary market and ERC1155 asset in secondry market', async function () { + it('should execute complete match order for bundle with ERC721 asset in primary market and ERC1155 asset in secondary market for TSB_SECONDARY_MARKET_SELLER', async function () { // Construct makerAsset bundle bundledERC721 = [ { @@ -294,7 +916,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { data: '0x', }; // empty quads - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -304,6 +926,12 @@ export function shouldMatchOrdersForBundleWithRoyalty() { makerAsset = await AssetBundle(bundleData, 1); // there can only ever be 1 copy of a bundle that contains ERC721 + // set up royalties by token + await RoyaltiesRegistryAsDeployer.setRoyaltiesByToken( + await ERC721WithRoyaltyV2981.getAddress(), + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token + ); + // configuring royalties await RoyaltiesProvider.initializeProvider( await ERC1155Contract.getAddress(), @@ -316,6 +944,12 @@ export function shouldMatchOrdersForBundleWithRoyalty() { RoyaltiesProvider.getAddress() ); + // grant tsb secondary market seller role to maker + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + orderLeft = await OrderDefault( maker, makerAsset, // Bundle @@ -354,7 +988,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -380,8 +1014,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[10]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9200800000 // 10000000000 - protocolFee - royalty + expectedFinalReturn // 10000000000 - protocolFee - royalty ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -394,7 +1046,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ).to.be.equal( (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -405,7 +1057,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ).to.be.equal(600000000); // 10% of asset price for ERC1155 token with id:1 }); - it('should execute complete match order for bundle with ERC721 asset in secondry market and ERC1155 asset in primary market', async function () { + it('should execute complete match order for bundle with ERC721 asset in secondary market and ERC1155 asset in primary market for TSB_SECONDARY_MARKET_SELLER', async function () { // Construct makerAsset bundle bundledERC721 = [ { @@ -429,7 +1081,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { data: '0x', }; // empty quads - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -445,6 +1097,24 @@ export function shouldMatchOrdersForBundleWithRoyalty() { [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC721 token ); + // configuring royalties + await RoyaltiesProvider.initializeProvider( + await ERC1155WithRoyaltyV2981.getAddress(), + 1, + [await LibPartData(royaltyReceiver, 1000)] // 10% royalty for ERC1155 token with id:1 + ); + + await RoyaltiesRegistryAsDeployer.setProviderByToken( + await ERC1155WithRoyaltyV2981.getAddress(), + RoyaltiesProvider.getAddress() + ); + + // grant tsb secondary market seller role to maker + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + orderLeft = await OrderDefault( maker, makerAsset, // Bundle @@ -483,7 +1153,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ERC1155WithRoyaltyV2981.balanceOf(takerAddress, 1) ).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -507,8 +1177,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[10]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9426200000 // 10000000000 - protocolFee - royalty + expectedFinalReturn // 10000000000 - protocolFee - royalty ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -519,7 +1207,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / @@ -540,23 +1228,30 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ExchangeContractAsAdmin, OrderValidatorAsAdmin, RoyaltiesRegistryAsDeployer, + LandAsAdmin, ERC20Contract, ERC721Contract, ERC1155Contract, + LandContract, RoyaltiesProvider, QuadHelper, - protocolFeeSecondary, + protocolFeePrimary, defaultFeeReceiver, user1: maker, user2: taker, deployer: royaltyReceiver, admin: royaltyReceiver2, user: royaltyReceiver3, - LandContract, - LandAsAdmin, landAdmin, + TSB_SECONDARY_MARKET_SELLER_ROLE, } = await loadFixture(deployFixtures)); + // grant tsb secondary market seller role to maker + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + priceDistribution = { erc721Prices: [[4000000000]], erc1155Prices: [[6000000000]], @@ -632,7 +1327,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { data: '0x', }; // empty quads - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -696,7 +1391,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); await expect( - ExchangeContractAsUser.matchOrders([ + ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -750,7 +1445,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -774,14 +1469,31 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[20]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 8950000000 // 10000000000 - royalty - protocolFee + expectedFinalReturn // 10000000000 - royalty - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 20000000000 ); - // check paid royalty expect( await ERC20Contract.balanceOf(royaltyReceiver.getAddress()) @@ -791,9 +1503,9 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -873,7 +1585,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -897,8 +1609,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary, protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[10, 20]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 8950000000 // 10000000000 - royalty - protocolFee + expectedFinalReturn // 10000000000 - royalty - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 20000000000 @@ -916,11 +1646,11 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][1]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -991,7 +1721,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); await expect( - ExchangeContractAsUser.matchOrders([ + ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1059,7 +1789,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1083,8 +1813,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[5]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9450000000 // 10000000000 - royalty - protocolFee + expectedFinalReturn // 10000000000 - royalty - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 20000000000 @@ -1099,9 +1847,9 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0])) / 10000 ); @@ -1186,7 +1934,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); expect(await ERC1155Contract.balanceOf(takerAddress, 2)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1214,8 +1962,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary, protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[5, 10]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9400000000 // 10000000000 - royalty - protocolFee + expectedFinalReturn // 10000000000 - royalty - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -1234,11 +2000,11 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][1])) / 10000 ); @@ -1331,7 +2097,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, signatureLeft: makerSig, @@ -1347,8 +2113,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) // newFill.leftValue ).to.be.equal(2); + const protocolFees = { + erc721ProtocolFees: [[]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[10]], + quadRoyaltyFees: [], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 17500000000 // 10000000000 - royalty - protocolFee + 2 * expectedFinalReturn // 20000000000 - royalty - protocolFee ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 10000000000 @@ -1363,7 +2147,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ERC20Contract.balanceOf(defaultFeeReceiver.getAddress()) ).to.be.equal( (2 * - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]))) / 10000 ); @@ -1390,7 +2174,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { quadPrices: [400000000, 600000000], }; - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -1442,7 +2226,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1466,8 +2250,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [protocolFeePrimary, protocolFeePrimary], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [10, 10], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9650000000 // 10000000000 - protocolFee - royalty + expectedFinalReturn // 10000000000 - protocolFee - royalty ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( @@ -1483,13 +2285,13 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(await defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[1])) / 10000 ); @@ -1513,7 +2315,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { quadPrices: [400000000, 600000000], }; - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -1580,7 +2382,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1604,8 +2406,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [protocolFeePrimary, protocolFeePrimary], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[0]], + erc1155RoyaltyFees: [[0]], + quadRoyaltyFees: [10, 20], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 9590000000 // 10000000000 - protocolFee - royalty + expectedFinalReturn // 10000000000 - protocolFee - royalty ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 20000000000 @@ -1623,13 +2443,13 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(await defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[1])) / 10000 ); @@ -1653,7 +2473,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { quadPrices: [1000000000, 3000000000], }; - // Create bundle for passing as right order + // Create bundle for passing as left order bundleData = { bundledERC721, bundledERC1155, @@ -1721,7 +2541,7 @@ export function shouldMatchOrdersForBundleWithRoyalty() { ); expect(await ERC1155Contract.balanceOf(takerAddress, 1)).to.be.equal(0); - await ExchangeContractAsUser.matchOrders([ + await ExchangeContractAsAdmin.matchOrders([ { orderLeft, // passing Bundle as left order signatureLeft: makerSig, @@ -1745,8 +2565,26 @@ export function shouldMatchOrdersForBundleWithRoyalty() { await ExchangeContractAsUser.fills(hashKey(orderRight)) ).to.be.equal(1); + const protocolFees = { + erc721ProtocolFees: [[protocolFeePrimary]], + erc1155ProtocolFees: [[protocolFeePrimary]], + quadProtocolFees: [protocolFeePrimary, protocolFeePrimary], + }; + + const royaltyFees = { + erc721RoyaltyFees: [[10]], + erc1155RoyaltyFees: [[10]], + quadRoyaltyFees: [10, 10], + }; + + const expectedFinalReturn = calculateFinalPrice( + priceDistribution, + protocolFees, + royaltyFees + ); + expect(await ERC20Contract.balanceOf(makerAddress)).to.be.equal( - 8750000000 // 10000000000 - protocolFee - royalty + expectedFinalReturn // 10000000000 - protocolFee - royalty ); expect(await ERC20Contract.balanceOf(takerAddress)).to.be.equal( 20000000000 @@ -1767,13 +2605,13 @@ export function shouldMatchOrdersForBundleWithRoyalty() { expect( await ERC20Contract.balanceOf(await defaultFeeReceiver.getAddress()) ).to.be.equal( - (Number(protocolFeeSecondary) * + (Number(protocolFeePrimary) * Number(priceDistribution.erc721Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.erc1155Prices[0][0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[0]) + - Number(protocolFeeSecondary) * + Number(protocolFeePrimary) * Number(priceDistribution.quadPrices[1])) / 10000 ); diff --git a/packages/marketplace/test/exchange/MatchOrdersWithRoyalties.behavior.ts b/packages/marketplace/test/exchange/MatchOrdersWithRoyalties.behavior.ts index 9db7e9c3ef..a76e7f071a 100644 --- a/packages/marketplace/test/exchange/MatchOrdersWithRoyalties.behavior.ts +++ b/packages/marketplace/test/exchange/MatchOrdersWithRoyalties.behavior.ts @@ -39,7 +39,7 @@ export function shouldMatchOrdersWithRoyalty() { orderRight: Order, makerSig: string, takerSig: string, - TSB_SELLER_ROLE: string, + TSB_PRIMARY_MARKET_SELLER_ROLE: string, FEE_WHITELIST_ROLE: string; beforeEach(async function () { @@ -62,7 +62,7 @@ export function shouldMatchOrdersWithRoyalty() { admin: receiver1, user: receiver2, deployer: royaltyReceiver, - TSB_SELLER_ROLE, + TSB_PRIMARY_MARKET_SELLER_ROLE, FEE_WHITELIST_ROLE, } = await loadFixture(deployFixtures)); @@ -571,7 +571,7 @@ export function shouldMatchOrdersWithRoyalty() { ); }); - it('should execute a complete match order without royalties but with primary market fees for address with TSB_SELLER_ROLE', async function () { + it('should execute a complete match order without royalties but with primary market fees for address with TSB_PRIMARY_MARKET_SELLER_ROLE', async function () { await ERC721WithRoyaltyV2981.mint(maker.getAddress(), 1, [ await FeeRecipientsData(maker.getAddress(), 10000), ]); @@ -589,7 +589,7 @@ export function shouldMatchOrdersWithRoyalty() { // grant exchange admin role to seller await ExchangeContractAsDeployer.connect(admin).grantRole( - TSB_SELLER_ROLE, + TSB_PRIMARY_MARKET_SELLER_ROLE, maker.getAddress() ); diff --git a/packages/marketplace/test/exchange/WhitelistingTokens.behavior.ts b/packages/marketplace/test/exchange/WhitelistingTokens.behavior.ts index c1af42fff9..ed41461857 100644 --- a/packages/marketplace/test/exchange/WhitelistingTokens.behavior.ts +++ b/packages/marketplace/test/exchange/WhitelistingTokens.behavior.ts @@ -22,6 +22,7 @@ import {ZeroAddress, Contract, Signer} from 'ethers'; export function shouldCheckForWhitelisting() { describe('Exchange MatchOrders for Whitelisting tokens', function () { let ExchangeContractAsUser: Contract, + ExchangeContractAsAdmin: Contract, OrderValidatorAsAdmin: Contract, ERC20Contract: Contract, ERC20Contract2: Contract, @@ -40,11 +41,13 @@ export function shouldCheckForWhitelisting() { orderLeft: Order, orderRight: Order, makerSig: string, - takerSig: string; + takerSig: string, + TSB_SECONDARY_MARKET_SELLER_ROLE: string; beforeEach(async function () { ({ ExchangeContractAsUser, + ExchangeContractAsAdmin, OrderValidatorAsAdmin, ERC20Contract, ERC20Contract2, @@ -53,6 +56,7 @@ export function shouldCheckForWhitelisting() { ERC1155Contract, user1: maker, user2: taker, + TSB_SECONDARY_MARKET_SELLER_ROLE, } = await loadFixture(deployFixturesWithoutWhitelist)); }); @@ -492,6 +496,12 @@ export function shouldCheckForWhitelisting() { user2: taker, } = await loadFixture(deployFixturesWithoutWhitelist)); + // grant tsb bundle seller role to seller + await ExchangeContractAsAdmin.grantRole( + TSB_SECONDARY_MARKET_SELLER_ROLE, + await maker.getAddress() + ); + priceDistribution = { erc721Prices: [[4000000000]], erc1155Prices: [[6000000000]], diff --git a/packages/marketplace/test/fixtures/index.ts b/packages/marketplace/test/fixtures/index.ts index 61fb40bb90..91b1377eaa 100644 --- a/packages/marketplace/test/fixtures/index.ts +++ b/packages/marketplace/test/fixtures/index.ts @@ -14,7 +14,10 @@ export async function deployFixturesWithoutWhitelist() { const {ExchangeContractAsAdmin} = exchange; - const TSB_SELLER_ROLE = await ExchangeContractAsAdmin.TSB_SELLER_ROLE(); + const TSB_PRIMARY_MARKET_SELLER_ROLE = + await ExchangeContractAsAdmin.TSB_PRIMARY_MARKET_SELLER_ROLE(); + const TSB_SECONDARY_MARKET_SELLER_ROLE = + await ExchangeContractAsAdmin.TSB_SECONDARY_MARKET_SELLER_ROLE(); const FEE_WHITELIST_ROLE = await ExchangeContractAsAdmin.FEE_WHITELIST_ROLE(); const EXCHANGE_ADMIN_ROLE = await ExchangeContractAsAdmin.EXCHANGE_ADMIN_ROLE(); @@ -27,7 +30,8 @@ export async function deployFixturesWithoutWhitelist() { ...exchange, ...mockAssets, EXCHANGE_ADMIN_ROLE, - TSB_SELLER_ROLE, + TSB_PRIMARY_MARKET_SELLER_ROLE, + TSB_SECONDARY_MARKET_SELLER_ROLE, FEE_WHITELIST_ROLE, DEFAULT_ADMIN_ROLE, ERC1776_OPERATOR_ROLE,