diff --git a/packages/land/contracts/libraries/QuadHelper.sol b/packages/land/contracts/libraries/QuadHelper.sol new file mode 100644 index 0000000000..c6cdd9c9cf --- /dev/null +++ b/packages/land/contracts/libraries/QuadHelper.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +/// @author The Sandbox +/// @title QuadHelper: A library for finding quad IDs +/// @notice This library contains utility functions for identifying quadrants within a predefined grid. +library QuadHelper { + uint256 internal constant GRID_SIZE = 408; + /* solhint-disable const-name-snakecase */ + uint256 internal constant LAYER = 0xFF00000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant LAYER_1x1 = 0x0000000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant LAYER_3x3 = 0x0100000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant LAYER_6x6 = 0x0200000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant LAYER_12x12 = 0x0300000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant LAYER_24x24 = 0x0400000000000000000000000000000000000000000000000000000000000000; + /* solhint-enable const-name-snakecase */ + + /// @notice get the quad id given the layer and coordinates. + /// @param layer the layer of the quad see: _getQuadLayer + /// @param x The bottom left x coordinate of the quad + /// @param y The bottom left y coordinate of the quad + /// @return the tokenId of the quad + /// @dev this method is gas optimized, must be called with verified x,y and size, after a call to _isValidQuad + function getQuadId(uint256 layer, uint256 x, uint256 y) external pure returns (uint256) { + unchecked { + return layer + x + y * GRID_SIZE; + } + } + /// @notice get size related information (there is one-to-one relationship between layer and size) + /// @param size The size of the quad + /// @return layer the layers that corresponds to the size + /// @return parentSize the size of the parent (bigger quad that contains the current one) + /// @return childLayer the layer of the child (smaller quad contained by this one) + function getQuadLayer(uint256 size) external pure returns (uint256 layer, uint256 parentSize, uint256 childLayer) { + if (size == 1) { + layer = LAYER_1x1; + parentSize = 3; + } else if (size == 3) { + layer = LAYER_3x3; + parentSize = 6; + } else if (size == 6) { + layer = LAYER_6x6; + parentSize = 12; + childLayer = LAYER_3x3; + } else if (size == 12) { + layer = LAYER_12x12; + parentSize = 24; + childLayer = LAYER_6x6; + } else { + layer = LAYER_24x24; + childLayer = LAYER_12x12; + } + } +} diff --git a/packages/marketplace/contracts/ExchangeCore.sol b/packages/marketplace/contracts/ExchangeCore.sol index 10d6784ba0..673012773f 100644 --- a/packages/marketplace/contracts/ExchangeCore.sol +++ b/packages/marketplace/contracts/ExchangeCore.sol @@ -173,26 +173,18 @@ abstract contract ExchangeCore is Initializable, ITransferManager { orderRight.makeAsset.assetType ); - // Check if the order is a bundle and validate the bundle price - if (makeMatch.assetClass == LibAsset.AssetClass.BUNDLE) { - uint256 bundlePrice = orderRight.makeAsset.value; // colllective price provided by buyer - LibAsset.Bundle memory bundle = LibAsset.decodeBundle(makeMatch); - require( - bundlePrice == LibAsset.computeBundlePrice(bundle, orderLeft.makeAsset.bundlePriceDistribution), - "Bundle price mismatch" - ); - } + LibAsset.verifyPriceDistribution(orderRight.makeAsset, orderLeft.makeAsset.priceDistribution); LibOrder.FillResult memory newFill = _parseOrdersSetFillEmitMatch(sender, orderLeft, orderRight); doTransfers( ITransferManager.DealSide( - LibAsset.Asset(makeMatch, newFill.leftValue, orderLeft.makeAsset.bundlePriceDistribution), + LibAsset.Asset(makeMatch, newFill.leftValue, orderLeft.makeAsset.priceDistribution), orderLeft.maker, orderLeft.makeRecipient ), ITransferManager.DealSide( - LibAsset.Asset(takeMatch, newFill.rightValue, orderRight.makeAsset.bundlePriceDistribution), + LibAsset.Asset(takeMatch, newFill.rightValue, orderRight.makeAsset.priceDistribution), orderRight.maker, orderRight.makeRecipient ), diff --git a/packages/marketplace/contracts/TransferManager.sol b/packages/marketplace/contracts/TransferManager.sol index fe6d2ec363..63d1624fc5 100644 --- a/packages/marketplace/contracts/TransferManager.sol +++ b/packages/marketplace/contracts/TransferManager.sol @@ -8,6 +8,7 @@ import {IERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IRoyaltyUGC} from "@sandbox-smart-contracts/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol"; +import {QuadHelper} from "@sandbox-smart-contracts/land/contracts/libraries/QuadHelper.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IRoyaltiesProvider, TOTAL_BASIS_POINTS} from "./interfaces/IRoyaltiesProvider.sol"; import {ITransferManager} from "./interfaces/ITransferManager.sol"; @@ -178,7 +179,7 @@ abstract contract TransferManager is Initializable, ITransferManager { } if (remainder > 0) { _transfer( - LibAsset.Asset(paymentSide.asset.assetType, remainder, paymentSide.asset.bundlePriceDistribution), + LibAsset.Asset(paymentSide.asset.assetType, remainder, paymentSide.asset.priceDistribution), paymentSide.account, nftSide.recipient ); @@ -203,19 +204,12 @@ abstract contract TransferManager is Initializable, ITransferManager { DealSide memory paymentSide, DealSide memory nftSide ) internal returns (uint256) { - // code to decode for bundle: done // for bundle royalty is calc for loop // for a same creator club royalties // royalties are getting fetched - // decode royalties - //trasfer royalties if (paymentSide.asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE) { LibAsset.Bundle memory bundle = LibAsset.decodeBundle(nftSide.asset.assetType); - uint256 erc721Length = bundle.bundledERC721.length; - uint256 erc1155Length = bundle.bundledERC1155.length; - uint256 quadsLength = bundle.quads.xs.length; - - for (uint256 i; i < erc721Length; i++) { + for (uint256 i; i < bundle.bundledERC721.length; i++) { address token = bundle.bundledERC721[i].erc721Address; uint256 idLength = bundle.bundledERC721[i].ids.length; for (uint256 j; j < idLength; j++) { @@ -225,22 +219,31 @@ abstract contract TransferManager is Initializable, ITransferManager { } } - for (uint256 i; i < erc1155Length; 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++) { uint256 tokenId = bundle.bundledERC1155[i].ids[j]; - uint256 supply = bundle.bundledERC1155[i].supplies[j]; IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(token, tokenId); remainder = _applyRoyalties(remainder, paymentSide, royalties, nftSide.recipient); } } - if (quadsLength > 0) { - address landTokenAddress = address(landContract); - - // TODO: fetch tokenId and call _applyRoyalties + uint256 quadSize = bundle.quads.xs.length; + if (quadSize > 0) { + 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 layer, , ) = QuadHelper.getQuadLayer(size); + uint256 quadId = QuadHelper.getQuadId(layer, x, y); + IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties( + address(landContract), + quadId + ); + remainder = _applyRoyalties(remainder, paymentSide, royalties, nftSide.recipient); + } } } else { (address token, uint256 tokenId) = LibAsset.decodeToken(nftSide.asset.assetType); @@ -288,7 +291,7 @@ abstract contract TransferManager is Initializable, ITransferManager { LibAsset.Asset memory payment = LibAsset.Asset( paymentSide.asset.assetType, 0, - paymentSide.asset.bundlePriceDistribution + paymentSide.asset.priceDistribution ); uint256 fee = (paymentSide.asset.value * percentage) / multiplier; if (remainder > fee) { diff --git a/packages/marketplace/contracts/libraries/LibAsset.sol b/packages/marketplace/contracts/libraries/LibAsset.sol index 0d8ab45c08..dd7c579f49 100644 --- a/packages/marketplace/contracts/libraries/LibAsset.sol +++ b/packages/marketplace/contracts/libraries/LibAsset.sol @@ -34,7 +34,7 @@ library LibAsset { struct Asset { AssetType assetType; // The type of the asset. uint256 value; // The amount or value of the asset. - BundlePriceDistribution bundlePriceDistribution; // The price distribution for individual assets in bundle. + PriceDistribution priceDistribution; // The price distribution for individual assets in bundle. } /// @dev Represents a group (i.e. bundle) of ERC20 assets on the Ethereum blockchain. @@ -73,7 +73,7 @@ library LibAsset { } /// @dev Represents the price of each asset in a bundle. - struct BundlePriceDistribution { + struct PriceDistribution { uint256[] erc20Prices; uint256[][] erc721Prices; uint256[][] erc1155Prices; @@ -157,36 +157,41 @@ library LibAsset { return abi.decode(assetType.data, (Bundle)); } - /// @dev function to compute the total of individual prices in bundle. - /// @param bundle The bundle. - /// @param bundlePriceDistribution The bundle price details. - /// @return The total price of the bundle. - function computeBundlePrice( - Bundle memory bundle, - BundlePriceDistribution memory bundlePriceDistribution - ) internal pure returns (uint256) { - uint256 totalPrice = 0; - - // total price of all bundled ERC20 assets - for (uint256 i = 0; i < bundlePriceDistribution.erc20Prices.length; i++) { - totalPrice += bundlePriceDistribution.erc20Prices[i]; - } + /// @dev function to verify if the order is bundle and validate the bundle price + /// @param rightMakeAsset The make asset from buyer. + /// @param priceDistribution The price distribution details. + function verifyPriceDistribution( + Asset memory rightMakeAsset, + PriceDistribution memory priceDistribution + ) internal pure { + if (rightMakeAsset.assetType.assetClass == AssetClass.BUNDLE) { + uint256 bundlePrice = rightMakeAsset.value; // bundle price provided by buyer + Bundle memory bundle = LibAsset.decodeBundle(rightMakeAsset.assetType); + uint256 collectiveBundlePrice = 0; + + // total price of all bundled ERC20 assets + for (uint256 i = 0; i < priceDistribution.erc20Prices.length; i++) { + collectiveBundlePrice += priceDistribution.erc20Prices[i]; + } - // calculate the total price of all bundled ERC721 assets - for (uint256 i = 0; i < bundlePriceDistribution.erc721Prices.length; i++) { - for (uint256 j = 0; j < bundlePriceDistribution.erc721Prices[i].length; j++) - totalPrice += bundlePriceDistribution.erc721Prices[i][j]; - } + // calculate the 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++) + collectiveBundlePrice += priceDistribution.erc721Prices[i][j]; + } - // calculate the total price of all bundled ERC1155 assets - for (uint256 i = 0; i < bundlePriceDistribution.erc1155Prices.length; i++) { - for (uint256 j = 0; j < bundlePriceDistribution.erc1155Prices[i].length; j++) { - totalPrice += bundle.bundledERC1155[i].supplies[j] * bundlePriceDistribution.erc1155Prices[i][j]; + // calculate the 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++) { + collectiveBundlePrice += + bundle.bundledERC1155[i].supplies[j] * + priceDistribution.erc1155Prices[i][j]; + } } - } - totalPrice += bundlePriceDistribution.quadPrice; + collectiveBundlePrice += priceDistribution.quadPrice; - return totalPrice; + require(bundlePrice == collectiveBundlePrice, "Bundle price mismatch"); + } } }