Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M-05 _isPrimaryMarket Does Not Consider Bundled NFTs #1591

Open
wants to merge 7 commits into
base: marketplace-bundle/M-02-whitelisted-assets-may-be-rejected
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/marketplace/.solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"compiler-version": ["error", "^0.8.0"],
"func-visibility": ["error", {"ignoreConstructors": true}],
"custom-errors": "off",
"func-named-parameters": ["error", 7]
"func-named-parameters": ["error", 10]
}
}
328 changes: 245 additions & 83 deletions packages/marketplace/contracts/TransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ abstract contract TransferManager is Initializable, ITransferManager {

(address paymentSideRecipient, address nftSideRecipient) = _getRecipients(paymentSide, nftSide);

// Transfer ERC20 or right side if FeeSide.NONE
if (feeSide == LibAsset.FeeSide.NONE || _mustSkipFees(nftSide.account)) {
_transfer(paymentSide.asset, paymentSide.account, nftSideRecipient);
} else {
_doTransfersWithFeesAndRoyalties(paymentSide, nftSide);
}

// 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) {
Expand All @@ -124,13 +131,6 @@ abstract contract TransferManager is Initializable, ITransferManager {
} else {
_transfer(nftSide.asset, nftSide.account, paymentSideRecipient);
}

// Transfer ERC20 or right side if FeeSide.NONE
if (feeSide == LibAsset.FeeSide.NONE || _mustSkipFees(nftSide.account)) {
_transfer(paymentSide.asset, paymentSide.account, nftSideRecipient);
} else {
_doTransfersWithFeesAndRoyalties(paymentSide, nftSide);
}
}

/// @notice Sets the royalties registry.
Expand Down Expand Up @@ -202,15 +202,48 @@ abstract contract TransferManager is Initializable, ITransferManager {
/// @param paymentSide DealSide of the fee-side order
/// @param nftSide DealSide of the nft-side order
function _doTransfersWithFeesAndRoyalties(DealSide memory paymentSide, DealSide memory nftSide) internal {
uint256 fees;
uint256 remainder = paymentSide.asset.value;
(, address nftSideRecipient) = _getRecipients(paymentSide, nftSide);

(uint256 fees, bool shouldTransferRoyalties) = _calculateFeesAndRoyalties(nftSide);
bool isBundle = nftSide.asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE;

if (isBundle) {
if (!_isTSBSeller(nftSide.account)) {
remainder = _doTransfersWithFeesAndRoyaltiesForBundle(paymentSide, nftSide, nftSideRecipient);
} else {
// No royalties but primary fee should be paid on the total value of the bundle
remainder = _transferProtocolFees(remainder, paymentSide, fees);
}
} else if (shouldTransferRoyalties) {
remainder = _transferRoyalties(remainder, paymentSide, nftSide);
}
if (!isBundle) {
remainder = _transferProtocolFees(remainder, paymentSide, fees);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing, after we merge other PRs to this lets add a comment above this line about why we do this

// Transfer protocol fees for non bundle asset, bundles pay per item protocol fees

}

if (remainder > 0) {
_transfer(LibAsset.Asset(paymentSide.asset.assetType, remainder), paymentSide.account, nftSideRecipient);
}
}

function _calculateFeesAndRoyalties(
DealSide memory nftSide
) internal returns (uint256 fees, bool shouldTransferRoyalties) {
if (_isTSBSeller(nftSide.account) || _isPrimaryMarket(nftSide)) {
fees = protocolFeePrimary;
// No royalties
shouldTransferRoyalties = false;
} else {
fees = protocolFeeSecondary;
remainder = _transferRoyalties(remainder, paymentSide, nftSide);
shouldTransferRoyalties = true;
}
}

function _transferProtocolFees(
uint256 remainder,
DealSide memory paymentSide,
uint256 fees
) internal returns (uint256) {
if (fees > 0 && remainder > 0) {
remainder = _transferPercentage(
remainder,
Expand All @@ -221,10 +254,190 @@ abstract contract TransferManager is Initializable, ITransferManager {
PROTOCOL_FEE_MULTIPLIER
);
}
if (remainder > 0) {
(, address nftSideRecipient) = _getRecipients(paymentSide, nftSide);
_transfer(LibAsset.Asset(paymentSide.asset.assetType, remainder), paymentSide.account, nftSideRecipient);
return remainder;
}

function _doTransfersWithFeesAndRoyaltiesForBundle(
DealSide memory paymentSide,
DealSide memory nftSide,
address nftSideRecipient
) 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);
return remainder;
}

function _processERC721Bundles(
DealSide memory paymentSide,
DealSide memory nftSide,
address nftSideRecipient,
uint256 remainder,
uint256 feePrimary,
uint256 feeSecondary,
LibAsset.Bundle memory bundle
) internal returns (uint256) {
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++) {
remainder = _processSingleAsset(
paymentSide,
nftSide,
nftSideRecipient,
remainder,
feePrimary,
feeSecondary,
token,
bundle.bundledERC721[i].ids[j],
bundle.priceDistribution.erc721Prices[i][j]
);
}
}
return remainder;
}

function _processERC1155Bundles(
DealSide memory paymentSide,
DealSide memory nftSide,
address nftSideRecipient,
uint256 remainder,
uint256 feePrimary,
uint256 feeSecondary,
LibAsset.Bundle memory bundle
) internal returns (uint256) {
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++) {
remainder = _processSingleAsset(
paymentSide,
nftSide,
nftSideRecipient,
remainder,
feePrimary,
feeSecondary,
token,
bundle.bundledERC1155[i].ids[j],
bundle.priceDistribution.erc1155Prices[i][j]
);
}
}
}
return remainder;
}

function _processQuadBundles(
DealSide memory paymentSide,
address nftSideRecipient,
uint256 remainder,
uint256 feeSecondary,
LibAsset.Bundle memory bundle
) internal returns (uint256) {
uint256 quadSize = bundle.quads.xs.length;
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(
paymentSide,
address(landContract),
nftSideRecipient,
remainder,
tokenId,
bundle.priceDistribution.quadPrices[i],
feeSecondary
);
}
return remainder;
}

function _processSingleAsset(
DealSide memory paymentSide,
DealSide memory nftSide,
address nftSideRecipient,
uint256 remainder,
uint256 feePrimary,
uint256 feeSecondary,
address token,
uint256 tokenId,
uint256 assetPrice
) internal returns (uint256) {
if (_isPrimaryMarketForBundledAsset(nftSide.account, token, tokenId)) {
if (feePrimary > 0 && remainder > 0) {
remainder = _transferPercentage(
remainder,
paymentSide,
assetPrice,
defaultFeeReceiver,
feePrimary,
PROTOCOL_FEE_MULTIPLIER
);
}
} else {
remainder = _transferFeesAndRoyaltiesForBundledAsset(
paymentSide,
token,
nftSideRecipient,
remainder,
tokenId,
assetPrice,
feeSecondary
);
}
return remainder;
}

function _transferFeesAndRoyaltiesForBundledAsset(
DealSide memory paymentSide,
address token,
address nftSideRecipient,
uint256 remainder,
uint256 tokenId,
uint256 assetPrice,
uint256 fees
) internal returns (uint256) {
IRoyaltiesProvider.Part[] memory royalties;

royalties = royaltiesRegistry.getRoyalties(token, tokenId);
remainder = _applyRoyalties(remainder, paymentSide, assetPrice, royalties, nftSideRecipient);
if (fees > 0 && remainder > 0) {
remainder = _transferPercentage(
remainder,
paymentSide,
assetPrice,
defaultFeeReceiver,
fees,
PROTOCOL_FEE_MULTIPLIER
);
}
return remainder;
}

/// @notice Return if this tx is on primary market
Expand All @@ -235,6 +448,22 @@ abstract contract TransferManager is Initializable, ITransferManager {
return creator != address(0) && nftSide.account == creator;
}

/// @notice Return if this tx is on primary market for bundled asset
/// @param token the
/// @param tokenId the
/// @return creator Address or zero if is not able to retrieve it
function _isPrimaryMarketForBundledAsset(
address nftSideAccount,
address token,
uint256 tokenId
) internal view returns (bool) {
address creator;
if (token.supportsInterface(type(IRoyaltyUGC).interfaceId)) {
creator = IRoyaltyUGC(token).getCreatorAddress(tokenId);
}
return creator != address(0) && nftSideAccount == creator;
}

/// @notice Transfer royalties.
/// @param remainder How much of the amount left after previous transfers
/// @param paymentSide DealSide of the fee-side order
Expand All @@ -246,78 +475,11 @@ abstract contract TransferManager is Initializable, ITransferManager {
DealSide memory nftSide
) internal returns (uint256) {
(, address nftSideRecipient) = _getRecipients(paymentSide, nftSide);
if (nftSide.asset.assetType.assetClass == LibAsset.AssetClass.BUNDLE) {
LibAsset.Bundle memory bundle = LibAsset.decodeBundle(nftSide.asset.assetType);

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++) {
IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(
token,
bundle.bundledERC721[i].ids[j]
);

remainder = _applyRoyalties(
remainder,
paymentSide,
bundle.priceDistribution.erc721Prices[i][j],
royalties,
nftSideRecipient
);
}
}
(address token, uint256 tokenId) = LibAsset.decodeToken(nftSide.asset.assetType);
IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(token, tokenId);
remainder = _applyRoyalties(remainder, paymentSide, remainder, royalties, nftSideRecipient);

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++) {
IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(
token,
bundle.bundledERC1155[i].ids[j]
);

// royalty transfer when exchanging one or more than one bundle of ERC1155s
for (uint256 k; k < nftSide.asset.value; k++) {
remainder = _applyRoyalties(
remainder,
paymentSide,
bundle.priceDistribution.erc1155Prices[i][j],
royalties,
nftSideRecipient
);
}
}
}

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 tokenId = idInPath(0, size, x, y);
IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(
address(landContract),
tokenId
);

remainder = _applyRoyalties(
remainder,
paymentSide,
bundle.priceDistribution.quadPrices[i],
royalties,
nftSideRecipient
);
}
}
} else {
(address token, uint256 tokenId) = LibAsset.decodeToken(nftSide.asset.assetType);
IRoyaltiesProvider.Part[] memory royalties = royaltiesRegistry.getRoyalties(token, tokenId);
remainder = _applyRoyalties(remainder, paymentSide, remainder, royalties, nftSideRecipient);
}
return remainder;
}

Expand Down
Loading
Loading