diff --git a/src/marketplaces/seaport-1.6/SeaportOnePointSixConfig.sol b/src/marketplaces/seaport-1.6/SeaportOnePointSixConfig.sol new file mode 100644 index 0000000..166525f --- /dev/null +++ b/src/marketplaces/seaport-1.6/SeaportOnePointSixConfig.sol @@ -0,0 +1,1196 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { BaseMarketConfig } from "../../BaseMarketConfig.sol"; +import { TestCallParameters, TestOrderContext, TestOrderPayload, TestItem721, TestItem1155, TestItem20 } from "../../Types.sol"; +import "./lib/ConsiderationStructs.sol"; +import "./lib/ConsiderationTypeHashes.sol"; +import { ConsiderationInterface as ISeaport } from "./interfaces/ConsiderationInterface.sol"; +import "forge-std/console2.sol"; + +contract SeaportOnePointSixConfig is + BaseMarketConfig, + ConsiderationTypeHashes +{ + function name() external pure override returns (string memory) { + return "Seaport 1.6"; + } + + function market() public pure override returns (address) { + return address(seaport); + } + + ISeaport internal constant seaport = + ISeaport(0x0000000000000068F116a894984e2DB1123eB395); + + function buildBasicOrder( + BasicOrderRouteType routeType, + address offerer, + OfferItem memory offerItem, + ConsiderationItem memory considerationItem + ) + internal + view + returns ( + Order memory order, + BasicOrderParameters memory basicComponents + ) + { + OrderParameters memory components = order.parameters; + components.offerer = offerer; + components.offer = new OfferItem[](1); + components.consideration = new ConsiderationItem[](1); + components.offer[0] = offerItem; + components.consideration[0] = considerationItem; + components.startTime = 0; + components.endTime = block.timestamp + 1; + components.totalOriginalConsiderationItems = 1; + basicComponents.startTime = 0; + basicComponents.endTime = block.timestamp + 1; + basicComponents.considerationToken = considerationItem.token; + basicComponents.considerationIdentifier = considerationItem + .identifierOrCriteria; + basicComponents.considerationAmount = considerationItem.endAmount; + basicComponents.offerer = payable(offerer); + basicComponents.offerToken = offerItem.token; + basicComponents.offerIdentifier = offerItem.identifierOrCriteria; + basicComponents.offerAmount = offerItem.endAmount; + basicComponents.basicOrderType = BasicOrderType(uint256(routeType) * 4); + basicComponents.totalOriginalAdditionalRecipients = 0; + bytes32 digest = _deriveEIP712Digest(_deriveOrderHash(components, 0)); + (uint8 v, bytes32 r, bytes32 s) = _sign(offerer, digest); + bytes memory signature = abi.encodePacked(r, s, v); + basicComponents.signature = (order.signature = signature); + } + + function buildBasicOrder( + BasicOrderRouteType routeType, + address offerer, + OfferItem memory offerItem, + ConsiderationItem memory considerationItem, + AdditionalRecipient[] memory additionalRecipients + ) + internal + view + returns ( + Order memory order, + BasicOrderParameters memory basicComponents + ) + { + OrderParameters memory components = order.parameters; + components.offerer = offerer; + components.offer = new OfferItem[](1); + components.consideration = new ConsiderationItem[]( + 1 + additionalRecipients.length + ); + components.offer[0] = offerItem; + components.consideration[0] = considerationItem; + + // Add additional recipients + address additionalRecipientsToken; + if ( + routeType == BasicOrderRouteType.ERC721_TO_ERC20 || + routeType == BasicOrderRouteType.ERC1155_TO_ERC20 + ) { + additionalRecipientsToken = offerItem.token; + } else { + additionalRecipientsToken = considerationItem.token; + } + ItemType additionalRecipientsItemType; + if ( + routeType == BasicOrderRouteType.ETH_TO_ERC721 || + routeType == BasicOrderRouteType.ETH_TO_ERC1155 + ) { + additionalRecipientsItemType = ItemType.NATIVE; + } else { + additionalRecipientsItemType = ItemType.ERC20; + } + for (uint256 i = 0; i < additionalRecipients.length; i++) { + components.consideration[i + 1] = ConsiderationItem( + additionalRecipientsItemType, + additionalRecipientsToken, + 0, + additionalRecipients[i].amount, + additionalRecipients[i].amount, + additionalRecipients[i].recipient + ); + } + + components.startTime = 0; + components.endTime = block.timestamp + 1; + components.totalOriginalConsiderationItems = + 1 + + additionalRecipients.length; + basicComponents.startTime = 0; + basicComponents.endTime = block.timestamp + 1; + basicComponents.considerationToken = considerationItem.token; + basicComponents.considerationIdentifier = considerationItem + .identifierOrCriteria; + basicComponents.considerationAmount = considerationItem.endAmount; + basicComponents.offerer = payable(offerer); + basicComponents.offerToken = offerItem.token; + basicComponents.offerIdentifier = offerItem.identifierOrCriteria; + basicComponents.offerAmount = offerItem.endAmount; + basicComponents.basicOrderType = BasicOrderType(uint256(routeType) * 4); + basicComponents.additionalRecipients = additionalRecipients; + basicComponents.totalOriginalAdditionalRecipients = additionalRecipients + .length; + bytes32 digest = _deriveEIP712Digest(_deriveOrderHash(components, 0)); + (uint8 v, bytes32 r, bytes32 s) = _sign(offerer, digest); + bytes memory signature = abi.encodePacked(r, s, v); + basicComponents.signature = (order.signature = signature); + } + + function buildOrder( + address offerer, + OfferItem[] memory offerItems, + ConsiderationItem[] memory considerationItems + ) internal view returns (Order memory order) { + OrderParameters memory components = order.parameters; + components.offerer = offerer; + components.offer = offerItems; + components.consideration = considerationItems; + components.orderType = OrderType.FULL_OPEN; + components.startTime = 0; + components.endTime = block.timestamp + 1; + components.totalOriginalConsiderationItems = considerationItems.length; + bytes32 digest = _deriveEIP712Digest(_deriveOrderHash(components, 0)); + (uint8 v, bytes32 r, bytes32 s) = _sign(offerer, digest); + bytes memory signature = abi.encodePacked(r, s, v); + order.signature = signature; + } + + function buildOrderAndFulfillmentManyDistinctOrders( + TestOrderContext[] memory contexts, + address paymentTokenAddress, + TestItem721[] memory nfts, + uint256[] memory amounts + ) + internal + view + returns ( + Order[] memory, + Fulfillment[] memory, + uint256 + ) + { + Order[] memory orders = new Order[](nfts.length + 1); + + ConsiderationItem[] + memory fulfillerConsiderationItems = new ConsiderationItem[]( + nfts.length + ); + + Fulfillment[] memory fullfillments = new Fulfillment[](nfts.length + 1); + + for (uint256 i = 0; i < nfts.length; i++) { + // Build offer orders + OfferItem[] memory offerItems = new OfferItem[](1); + + ConsiderationItem[] + memory considerationItems = new ConsiderationItem[](1); + { + offerItems[0] = OfferItem( + ItemType.ERC721, + nfts[i].token, + nfts[i].identifier, + 1, + 1 + ); + } + { + considerationItems[0] = ConsiderationItem( + paymentTokenAddress != address(0) + ? ItemType.ERC20 + : ItemType.NATIVE, + paymentTokenAddress, + 0, + amounts[i], + amounts[i], + payable(contexts[i].offerer) + ); + } + { + orders[i] = buildOrder( + contexts[i].offerer, + offerItems, + considerationItems + ); + } + { + fulfillerConsiderationItems[i] = ConsiderationItem( + ItemType.ERC721, + nfts[i].token, + nfts[i].identifier, + 1, + 1, + payable(contexts[i].fulfiller) + ); + } + { + // Add fulfillment components for each NFT + + FulfillmentComponent + memory nftConsiderationComponent = FulfillmentComponent( + nfts.length, + i + ); + + FulfillmentComponent + memory nftOfferComponent = FulfillmentComponent(i, 0); + + FulfillmentComponent[] + memory nftOfferComponents = new FulfillmentComponent[](1); + nftOfferComponents[0] = nftOfferComponent; + + FulfillmentComponent[] + memory nftConsiderationComponents = new FulfillmentComponent[]( + 1 + ); + nftConsiderationComponents[0] = nftConsiderationComponent; + fullfillments[i] = Fulfillment( + nftOfferComponents, + nftConsiderationComponents + ); + } + } + + uint256 sumAmounts = 0; + + for (uint256 i = 0; i < nfts.length; i++) { + sumAmounts += amounts[i]; + } + + { + FulfillmentComponent + memory paymentTokenOfferComponent = FulfillmentComponent( + nfts.length, + 0 + ); + + FulfillmentComponent[] + memory paymentTokenOfferComponents = new FulfillmentComponent[]( + 1 + ); + paymentTokenOfferComponents[0] = paymentTokenOfferComponent; + + FulfillmentComponent[] + memory paymentTokenConsiderationComponents = new FulfillmentComponent[]( + nfts.length + ); + for (uint256 i = 0; i < nfts.length; i++) { + { + FulfillmentComponent + memory paymentTokenConsiderationComponent = FulfillmentComponent( + i, + 0 + ); + paymentTokenConsiderationComponents[ + i + ] = paymentTokenConsiderationComponent; + } + } + fullfillments[nfts.length] = Fulfillment( + paymentTokenOfferComponents, + paymentTokenConsiderationComponents + ); + } + + // Build sweep floor order + OfferItem[] memory fulfillerOfferItems = new OfferItem[](1); + fulfillerOfferItems[0] = OfferItem( + paymentTokenAddress != address(0) + ? ItemType.ERC20 + : ItemType.NATIVE, + paymentTokenAddress, + 0, + sumAmounts, + sumAmounts + ); + orders[nfts.length] = buildOrder( + contexts[0].fulfiller, + fulfillerOfferItems, + fulfillerConsiderationItems + ); + orders[nfts.length].signature = ""; // Signature isn't needed since fulfiller is msg.sender + + return (orders, fullfillments, sumAmounts); + } + + function beforeAllPrepareMarketplace(address, address) external override { + buyerNftApprovalTarget = sellerNftApprovalTarget = buyerErc20ApprovalTarget = sellerErc20ApprovalTarget = address( + seaport + ); + } + + function getPayload_BuyOfferedERC721WithEther( + TestOrderContext calldata context, + TestItem721 memory nft, + uint256 ethAmount + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ETH_TO_ERC721, + context.offerer, + OfferItem(ItemType.ERC721, nft.token, nft.identifier, 1, 1), + ConsiderationItem( + ItemType.NATIVE, + address(0), + 0, + ethAmount, + ethAmount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + ethAmount, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC1155WithEther( + TestOrderContext calldata context, + TestItem1155 memory nft, + uint256 ethAmount + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ETH_TO_ERC1155, + context.offerer, + OfferItem( + ItemType.ERC1155, + nft.token, + nft.identifier, + nft.amount, + nft.amount + ), + ConsiderationItem( + ItemType.NATIVE, + address(0), + 0, + ethAmount, + ethAmount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + ethAmount, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC721WithERC20( + TestOrderContext calldata context, + TestItem721 memory nft, + TestItem20 memory erc20 + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC20_TO_ERC721, + context.offerer, + OfferItem(ItemType.ERC721, nft.token, nft.identifier, 1, 1), + ConsiderationItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC721WithWETH( + TestOrderContext calldata context, + TestItem721 memory nft, + TestItem20 memory erc20 + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC20_TO_ERC721, + context.offerer, + OfferItem(ItemType.ERC721, nft.token, nft.identifier, 1, 1), + ConsiderationItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC1155WithERC20( + TestOrderContext calldata context, + TestItem1155 calldata nft, + TestItem20 memory erc20 + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC20_TO_ERC1155, + context.offerer, + OfferItem( + ItemType.ERC1155, + nft.token, + nft.identifier, + nft.amount, + nft.amount + ), + ConsiderationItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC20WithERC721( + TestOrderContext calldata context, + TestItem20 memory erc20, + TestItem721 memory nft + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC721_TO_ERC20, + context.offerer, + OfferItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount + ), + ConsiderationItem( + ItemType.ERC721, + nft.token, + nft.identifier, + 1, + 1, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedWETHWithERC721( + TestOrderContext calldata context, + TestItem20 memory erc20, + TestItem721 memory nft + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC721_TO_ERC20, + context.offerer, + OfferItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount + ), + ConsiderationItem( + ItemType.ERC721, + nft.token, + nft.identifier, + 1, + 1, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC20WithERC1155( + TestOrderContext calldata context, + TestItem20 memory erc20, + TestItem1155 calldata nft + ) external view override returns (TestOrderPayload memory execution) { + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ERC1155_TO_ERC20, + context.offerer, + OfferItem( + ItemType.ERC20, + erc20.token, + 0, + erc20.amount, + erc20.amount + ), + ConsiderationItem( + ItemType.ERC1155, + nft.token, + nft.identifier, + nft.amount, + nft.amount, + payable(context.offerer) + ) + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC721WithERC1155( + TestOrderContext calldata context, + TestItem721 memory sellNft, + TestItem1155 calldata buyNft + ) external view override returns (TestOrderPayload memory execution) { + OfferItem[] memory offerItems = new OfferItem[](1); + ConsiderationItem[] memory considerationItems = new ConsiderationItem[]( + 1 + ); + + offerItems[0] = OfferItem( + ItemType.ERC721, + sellNft.token, + sellNft.identifier, + 1, + 1 + ); + considerationItems[0] = ConsiderationItem( + ItemType.ERC1155, + buyNft.token, + buyNft.identifier, + buyNft.amount, + buyNft.amount, + payable(context.offerer) + ); + + Order memory order = buildOrder( + context.offerer, + offerItems, + considerationItems + ); + + if (context.listOnChain) { + order.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.fulfillOrder.selector, order, 0) + ); + } + + function getPayload_BuyOfferedERC1155WithERC721( + TestOrderContext calldata context, + TestItem1155 memory sellNft, + TestItem721 calldata buyNft + ) external view override returns (TestOrderPayload memory execution) { + OfferItem[] memory offerItems = new OfferItem[](1); + ConsiderationItem[] memory considerationItems = new ConsiderationItem[]( + 1 + ); + + offerItems[0] = OfferItem( + ItemType.ERC1155, + sellNft.token, + sellNft.identifier, + sellNft.amount, + sellNft.amount + ); + considerationItems[0] = ConsiderationItem( + ItemType.ERC721, + buyNft.token, + buyNft.identifier, + 1, + 1, + payable(context.offerer) + ); + + Order memory order = buildOrder( + context.offerer, + offerItems, + considerationItems + ); + + if (context.listOnChain) { + order.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.fulfillOrder.selector, order, 0) + ); + } + + function getPayload_BuyOfferedERC721WithEtherOneFeeRecipient( + TestOrderContext calldata context, + TestItem721 memory nft, + uint256 priceEthAmount, + address feeRecipient, + uint256 feeEthAmount + ) external view override returns (TestOrderPayload memory execution) { + AdditionalRecipient[] + memory additionalRecipients = new AdditionalRecipient[](1); + additionalRecipients[0] = AdditionalRecipient( + feeEthAmount, + payable(feeRecipient) + ); + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ETH_TO_ERC721, + context.offerer, + OfferItem(ItemType.ERC721, nft.token, nft.identifier, 1, 1), + ConsiderationItem( + ItemType.NATIVE, + address(0), + 0, + priceEthAmount, + priceEthAmount, + payable(context.offerer) + ), + additionalRecipients + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + priceEthAmount + feeEthAmount, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedERC721WithEtherTwoFeeRecipient( + TestOrderContext calldata context, + TestItem721 memory nft, + uint256 priceEthAmount, + address feeRecipient1, + uint256 feeEthAmount1, + address feeRecipient2, + uint256 feeEthAmount2 + ) external view override returns (TestOrderPayload memory execution) { + AdditionalRecipient[] + memory additionalRecipients = new AdditionalRecipient[](2); + + additionalRecipients[0] = AdditionalRecipient( + feeEthAmount1, + payable(feeRecipient1) + ); + additionalRecipients[1] = AdditionalRecipient( + feeEthAmount2, + payable(feeRecipient2) + ); + ConsiderationItem memory consideration = ConsiderationItem( + ItemType.NATIVE, + address(0), + 0, + priceEthAmount, + priceEthAmount, + payable(context.offerer) + ); + ( + Order memory order, + BasicOrderParameters memory basicComponents + ) = buildBasicOrder( + BasicOrderRouteType.ETH_TO_ERC721, + context.offerer, + OfferItem(ItemType.ERC721, nft.token, nft.identifier, 1, 1), + consideration, + additionalRecipients + ); + if (context.listOnChain) { + order.signature = ""; + basicComponents.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + execution.executeOrder = TestCallParameters( + address(seaport), + priceEthAmount + feeEthAmount1 + feeEthAmount2, + abi.encodeWithSelector( + ISeaport.fulfillBasicOrder_efficient_6GL6yc.selector, + basicComponents + ) + ); + } + + function getPayload_BuyOfferedManyERC721WithEther( + TestOrderContext calldata context, + TestItem721[] calldata nfts, + uint256 ethAmount + ) external view override returns (TestOrderPayload memory execution) { + OfferItem[] memory offerItems = new OfferItem[](nfts.length); + + for (uint256 i = 0; i < nfts.length; i++) { + offerItems[i] = OfferItem( + ItemType.ERC721, + nfts[i].token, + nfts[i].identifier, + 1, + 1 + ); + } + + ConsiderationItem[] memory considerationItems = new ConsiderationItem[]( + 1 + ); + + considerationItems[0] = ConsiderationItem( + ItemType.NATIVE, + address(0), + 0, + ethAmount, + ethAmount, + payable(context.offerer) + ); + + Order memory order = buildOrder( + context.offerer, + offerItems, + considerationItems + ); + + if (context.listOnChain) { + order.signature = ""; + + Order[] memory orders = new Order[](1); + orders[0] = order; + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector(ISeaport.validate.selector, orders) + ); + } + + execution.executeOrder = TestCallParameters( + address(seaport), + ethAmount, + abi.encodeWithSelector(ISeaport.fulfillOrder.selector, order, 0) + ); + } + + function getPayload_BuyOfferedManyERC721WithEtherDistinctOrders( + TestOrderContext[] calldata contexts, + TestItem721[] calldata nfts, + uint256[] calldata ethAmounts + ) external view override returns (TestOrderPayload memory execution) { + require( + contexts.length == nfts.length && nfts.length == ethAmounts.length, + "SeaportConfig::getPayload_BuyOfferedManyERC721WithEtherDistinctOrders: invalid input" + ); + + ( + Order[] memory orders, + Fulfillment[] memory fullfillments, + uint256 sumEthAmount + ) = buildOrderAndFulfillmentManyDistinctOrders( + contexts, + address(0), + nfts, + ethAmounts + ); + + // Validate all for simplicity for now, could make this combination of on-chain and not + if (contexts[0].listOnChain) { + Order[] memory ordersToValidate = new Order[](orders.length - 1); // Last order is fulfiller order + for (uint256 i = 0; i < orders.length - 1; i++) { + orders[i].signature = ""; + ordersToValidate[i] = orders[i]; + } + + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.validate.selector, + ordersToValidate + ) + ); + } + + execution.executeOrder = TestCallParameters( + address(seaport), + sumEthAmount, + abi.encodeWithSelector( + ISeaport.matchOrders.selector, + orders, + fullfillments + ) + ); + } + + function getPayload_BuyOfferedManyERC721WithErc20DistinctOrders( + TestOrderContext[] calldata contexts, + address erc20Address, + TestItem721[] calldata nfts, + uint256[] calldata erc20Amounts + ) external view override returns (TestOrderPayload memory execution) { + require( + contexts.length == nfts.length && + nfts.length == erc20Amounts.length, + "SeaportConfig::getPayload_BuyOfferedManyERC721WithEtherDistinctOrders: invalid input" + ); + ( + Order[] memory orders, + Fulfillment[] memory fullfillments, + + ) = buildOrderAndFulfillmentManyDistinctOrders( + contexts, + erc20Address, + nfts, + erc20Amounts + ); + + // Validate all for simplicity for now, could make this combination of on-chain and not + if (contexts[0].listOnChain) { + Order[] memory ordersToValidate = new Order[](orders.length - 1); // Last order is fulfiller order + for (uint256 i = 0; i < orders.length - 1; i++) { + orders[i].signature = ""; + ordersToValidate[i] = orders[i]; + } + + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.validate.selector, + ordersToValidate + ) + ); + } + + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.matchOrders.selector, + orders, + fullfillments + ) + ); + } + + function getPayload_BuyOfferedManyERC721WithWETHDistinctOrders( + TestOrderContext[] calldata contexts, + address erc20Address, + TestItem721[] calldata nfts, + uint256[] calldata erc20Amounts + ) external view override returns (TestOrderPayload memory execution) { + require( + contexts.length == nfts.length && + nfts.length == erc20Amounts.length, + "SeaportConfig::getPayload_BuyOfferedManyERC721WithEtherDistinctOrders: invalid input" + ); + ( + Order[] memory orders, + Fulfillment[] memory fullfillments, + + ) = buildOrderAndFulfillmentManyDistinctOrders( + contexts, + erc20Address, + nfts, + erc20Amounts + ); + + // Validate all for simplicity for now, could make this combination of on-chain and not + if (contexts[0].listOnChain) { + Order[] memory ordersToValidate = new Order[](orders.length - 1); // Last order is fulfiller order + for (uint256 i = 0; i < orders.length - 1; i++) { + orders[i].signature = ""; + ordersToValidate[i] = orders[i]; + } + + execution.submitOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.validate.selector, + ordersToValidate + ) + ); + } + + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.matchOrders.selector, + orders, + fullfillments + ) + ); + } + + function getPayload_MatchOrders_ABCA( + TestOrderContext[] calldata contexts, + TestItem721[] calldata nfts + ) external view override returns (TestOrderPayload memory execution) { + require(contexts.length == nfts.length, "invalid input"); + + Order[] memory orders = new Order[](contexts.length); + Fulfillment[] memory fullfillments = new Fulfillment[](nfts.length); + + for (uint256 i = 0; i < nfts.length; i++) { + uint256 wrappedIndex = i + 1 == nfts.length ? 0 : i + 1; // wrap around back to 0 + { + OfferItem[] memory offerItems = new OfferItem[](1); + offerItems[0] = OfferItem( + ItemType.ERC721, + nfts[i].token, + nfts[i].identifier, + 1, + 1 + ); + + ConsiderationItem[] + memory considerationItems = new ConsiderationItem[](1); + considerationItems[0] = ConsiderationItem( + ItemType.ERC721, + nfts[wrappedIndex].token, + nfts[wrappedIndex].identifier, + 1, + 1, + payable(contexts[i].offerer) + ); + orders[i] = buildOrder( + contexts[i].offerer, + offerItems, + considerationItems + ); + } + // Set fulfillment + { + FulfillmentComponent + memory nftConsiderationComponent = FulfillmentComponent( + i, + 0 + ); + + FulfillmentComponent + memory nftOfferComponent = FulfillmentComponent( + wrappedIndex, + 0 + ); + + FulfillmentComponent[] + memory nftOfferComponents = new FulfillmentComponent[](1); + nftOfferComponents[0] = nftOfferComponent; + + FulfillmentComponent[] + memory nftConsiderationComponents = new FulfillmentComponent[]( + 1 + ); + nftConsiderationComponents[0] = nftConsiderationComponent; + fullfillments[i] = Fulfillment( + nftOfferComponents, + nftConsiderationComponents + ); + } + } + + execution.executeOrder = TestCallParameters( + address(seaport), + 0, + abi.encodeWithSelector( + ISeaport.matchOrders.selector, + orders, + fullfillments + ) + ); + } +} diff --git a/src/marketplaces/seaport-1.6/interfaces/ConsiderationInterface.sol b/src/marketplaces/seaport-1.6/interfaces/ConsiderationInterface.sol new file mode 100644 index 0000000..dd43d5e --- /dev/null +++ b/src/marketplaces/seaport-1.6/interfaces/ConsiderationInterface.sol @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { AdvancedOrder, BasicOrderParameters, CriteriaResolver, Execution, Fulfillment, FulfillmentComponent, Order, OrderComponents } from "../lib/ConsiderationStructs.sol"; + +/** + * @title ConsiderationInterface + * @author 0age + * @custom:version 1.2 + * @notice Consideration is a generalized native token/ERC20/ERC721/ERC1155 + * marketplace. It minimizes external calls to the greatest extent + * possible and provides lightweight methods for common routes as well + * as more flexible methods for composing advanced orders. + * + * @dev ConsiderationInterface contains all external function interfaces for + * Consideration. + */ +interface ConsiderationInterface { + /** + * @notice Fulfill an order offering an ERC721 token by supplying Ether (or + * the native token for the given chain) as consideration for the + * order. An arbitrary number of "additional recipients" may also be + * supplied which will each receive native tokens from the fulfiller + * as consideration. + * + * @param parameters Additional information on the fulfilled order. Note + * that the offerer must first approve this contract (or + * their preferred conduit if indicated by the order) for + * their offered ERC721 token to be transferred. + * + * @return fulfilled A boolean indicating whether the order has been + * successfully fulfilled. + */ + function fulfillBasicOrder(BasicOrderParameters calldata parameters) + external + payable + returns (bool fulfilled); + + /** + * @notice Fulfill an order with an arbitrary number of items for offer and + * consideration. Note that this function does not support + * criteria-based orders or partial filling of orders (though + * filling the remainder of a partially-filled order is supported). + * + * @param order The order to fulfill. Note that both the + * offerer and the fulfiller must first approve + * this contract (or the corresponding conduit if + * indicated) to transfer any relevant tokens on + * their behalf and that contracts must implement + * `onERC1155Received` to receive ERC1155 tokens + * as consideration. + * @param fulfillerConduitKey A bytes32 value indicating what conduit, if + * any, to source the fulfiller's token approvals + * from. The zero hash signifies that no conduit + * should be used, with direct approvals set on + * Consideration. + * + * @return fulfilled A boolean indicating whether the order has been + * successfully fulfilled. + */ + function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey) + external + payable + returns (bool fulfilled); + + /** + * @notice Fill an order, fully or partially, with an arbitrary number of + * items for offer and consideration alongside criteria resolvers + * containing specific token identifiers and associated proofs. + * + * @param advancedOrder The order to fulfill along with the fraction + * of the order to attempt to fill. Note that + * both the offerer and the fulfiller must first + * approve this contract (or their preferred + * conduit if indicated by the order) to transfer + * any relevant tokens on their behalf and that + * contracts must implement `onERC1155Received` + * to receive ERC1155 tokens as consideration. + * Also note that all offer and consideration + * components must have no remainder after + * multiplication of the respective amount with + * the supplied fraction for the partial fill to + * be considered valid. + * @param criteriaResolvers An array where each element contains a + * reference to a specific offer or + * consideration, a token identifier, and a proof + * that the supplied token identifier is + * contained in the merkle root held by the item + * in question's criteria element. Note that an + * empty criteria indicates that any + * (transferable) token identifier on the token + * in question is valid and that no associated + * proof needs to be supplied. + * @param fulfillerConduitKey A bytes32 value indicating what conduit, if + * any, to source the fulfiller's token approvals + * from. The zero hash signifies that no conduit + * should be used, with direct approvals set on + * Consideration. + * @param recipient The intended recipient for all received items, + * with `address(0)` indicating that the caller + * should receive the items. + * + * @return fulfilled A boolean indicating whether the order has been + * successfully fulfilled. + */ + function fulfillAdvancedOrder( + AdvancedOrder calldata advancedOrder, + CriteriaResolver[] calldata criteriaResolvers, + bytes32 fulfillerConduitKey, + address recipient + ) external payable returns (bool fulfilled); + + /** + * @notice Attempt to fill a group of orders, each with an arbitrary number + * of items for offer and consideration. Any order that is not + * currently active, has already been fully filled, or has been + * cancelled will be omitted. Remaining offer and consideration + * items will then be aggregated where possible as indicated by the + * supplied offer and consideration component arrays and aggregated + * items will be transferred to the fulfiller or to each intended + * recipient, respectively. Note that a failing item transfer or an + * issue with order formatting will cause the entire batch to fail. + * Note that this function does not support criteria-based orders or + * partial filling of orders (though filling the remainder of a + * partially-filled order is supported). + * + * @param orders The orders to fulfill. Note that both + * the offerer and the fulfiller must first + * approve this contract (or the + * corresponding conduit if indicated) to + * transfer any relevant tokens on their + * behalf and that contracts must implement + * `onERC1155Received` to receive ERC1155 + * tokens as consideration. + * @param offerFulfillments An array of FulfillmentComponent arrays + * indicating which offer items to attempt + * to aggregate when preparing executions. + * @param considerationFulfillments An array of FulfillmentComponent arrays + * indicating which consideration items to + * attempt to aggregate when preparing + * executions. + * @param fulfillerConduitKey A bytes32 value indicating what conduit, + * if any, to source the fulfiller's token + * approvals from. The zero hash signifies + * that no conduit should be used, with + * direct approvals set on this contract. + * @param maximumFulfilled The maximum number of orders to fulfill. + * + * @return availableOrders An array of booleans indicating if each order + * with an index corresponding to the index of the + * returned boolean was fulfillable or not. + * @return executions An array of elements indicating the sequence of + * transfers performed as part of matching the given + * orders. Note that unspent offer item amounts or + * native tokens will not be reflected as part of + * this array. + */ + function fulfillAvailableOrders( + Order[] calldata orders, + FulfillmentComponent[][] calldata offerFulfillments, + FulfillmentComponent[][] calldata considerationFulfillments, + bytes32 fulfillerConduitKey, + uint256 maximumFulfilled + ) + external + payable + returns (bool[] memory availableOrders, Execution[] memory executions); + + /** + * @notice Attempt to fill a group of orders, fully or partially, with an + * arbitrary number of items for offer and consideration per order + * alongside criteria resolvers containing specific token + * identifiers and associated proofs. Any order that is not + * currently active, has already been fully filled, or has been + * cancelled will be omitted. Remaining offer and consideration + * items will then be aggregated where possible as indicated by the + * supplied offer and consideration component arrays and aggregated + * items will be transferred to the fulfiller or to each intended + * recipient, respectively. Note that a failing item transfer or an + * issue with order formatting will cause the entire batch to fail. + * + * @param advancedOrders The orders to fulfill along with the + * fraction of those orders to attempt to + * fill. Note that both the offerer and the + * fulfiller must first approve this + * contract (or their preferred conduit if + * indicated by the order) to transfer any + * relevant tokens on their behalf and that + * contracts must implement + * `onERC1155Received` to enable receipt of + * ERC1155 tokens as consideration. Also + * note that all offer and consideration + * components must have no remainder after + * multiplication of the respective amount + * with the supplied fraction for an + * order's partial fill amount to be + * considered valid. + * @param criteriaResolvers An array where each element contains a + * reference to a specific offer or + * consideration, a token identifier, and a + * proof that the supplied token identifier + * is contained in the merkle root held by + * the item in question's criteria element. + * Note that an empty criteria indicates + * that any (transferable) token + * identifier on the token in question is + * valid and that no associated proof needs + * to be supplied. + * @param offerFulfillments An array of FulfillmentComponent arrays + * indicating which offer items to attempt + * to aggregate when preparing executions. + * @param considerationFulfillments An array of FulfillmentComponent arrays + * indicating which consideration items to + * attempt to aggregate when preparing + * executions. + * @param fulfillerConduitKey A bytes32 value indicating what conduit, + * if any, to source the fulfiller's token + * approvals from. The zero hash signifies + * that no conduit should be used, with + * direct approvals set on this contract. + * @param recipient The intended recipient for all received + * items, with `address(0)` indicating that + * the caller should receive the items. + * @param maximumFulfilled The maximum number of orders to fulfill. + * + * @return availableOrders An array of booleans indicating if each order + * with an index corresponding to the index of the + * returned boolean was fulfillable or not. + * @return executions An array of elements indicating the sequence of + * transfers performed as part of matching the given + * orders. Note that unspent offer item amounts or + * native tokens will not be reflected as part of + * this array. + */ + function fulfillAvailableAdvancedOrders( + AdvancedOrder[] calldata advancedOrders, + CriteriaResolver[] calldata criteriaResolvers, + FulfillmentComponent[][] calldata offerFulfillments, + FulfillmentComponent[][] calldata considerationFulfillments, + bytes32 fulfillerConduitKey, + address recipient, + uint256 maximumFulfilled + ) + external + payable + returns (bool[] memory availableOrders, Execution[] memory executions); + + /** + * @notice Match an arbitrary number of orders, each with an arbitrary + * number of items for offer and consideration along with a set of + * fulfillments allocating offer components to consideration + * components. Note that this function does not support + * criteria-based or partial filling of orders (though filling the + * remainder of a partially-filled order is supported). Any unspent + * offer item amounts or native tokens will be transferred to the + * caller. + * + * @param orders The orders to match. Note that both the offerer and + * fulfiller on each order must first approve this + * contract (or their conduit if indicated by the order) + * to transfer any relevant tokens on their behalf and + * each consideration recipient must implement + * `onERC1155Received` to enable ERC1155 token receipt. + * @param fulfillments An array of elements allocating offer components to + * consideration components. Note that each + * consideration component must be fully met for the + * match operation to be valid. + * + * @return executions An array of elements indicating the sequence of + * transfers performed as part of matching the given + * orders. Note that unspent offer item amounts or + * native tokens will not be reflected as part of this + * array. + */ + function matchOrders( + Order[] calldata orders, + Fulfillment[] calldata fulfillments + ) external payable returns (Execution[] memory executions); + + /** + * @notice Match an arbitrary number of full or partial orders, each with an + * arbitrary number of items for offer and consideration, supplying + * criteria resolvers containing specific token identifiers and + * associated proofs as well as fulfillments allocating offer + * components to consideration components. Any unspent offer item + * amounts will be transferred to the designated recipient (with the + * null address signifying to use the caller) and any unspent native + * tokens will be returned to the caller. + * + * @param orders The advanced orders to match. Note that both the + * offerer and fulfiller on each order must first + * approve this contract (or a preferred conduit if + * indicated by the order) to transfer any relevant + * tokens on their behalf and each consideration + * recipient must implement `onERC1155Received` in + * order to receive ERC1155 tokens. Also note that + * the offer and consideration components for each + * order must have no remainder after multiplying + * the respective amount with the supplied fraction + * in order for the group of partial fills to be + * considered valid. + * @param criteriaResolvers An array where each element contains a reference + * to a specific order as well as that order's + * offer or consideration, a token identifier, and + * a proof that the supplied token identifier is + * contained in the order's merkle root. Note that + * an empty root indicates that any (transferable) + * token identifier is valid and that no associated + * proof needs to be supplied. + * @param fulfillments An array of elements allocating offer components + * to consideration components. Note that each + * consideration component must be fully met in + * order for the match operation to be valid. + * @param recipient The intended recipient for all unspent offer + * item amounts, or the caller if the null address + * is supplied. + * + * @return executions An array of elements indicating the sequence of + * transfers performed as part of matching the given + * orders. Note that unspent offer item amounts or native + * tokens will not be reflected as part of this array. + */ + function matchAdvancedOrders( + AdvancedOrder[] calldata orders, + CriteriaResolver[] calldata criteriaResolvers, + Fulfillment[] calldata fulfillments, + address recipient + ) external payable returns (Execution[] memory executions); + + /** + * @notice Cancel an arbitrary number of orders. Note that only the offerer + * or the zone of a given order may cancel it. Callers should ensure + * that the intended order was cancelled by calling `getOrderStatus` + * and confirming that `isCancelled` returns `true`. + * + * @param orders The orders to cancel. + * + * @return cancelled A boolean indicating whether the supplied orders have + * been successfully cancelled. + */ + function cancel(OrderComponents[] calldata orders) + external + returns (bool cancelled); + + /** + * @notice Validate an arbitrary number of orders, thereby registering their + * signatures as valid and allowing the fulfiller to skip signature + * verification on fulfillment. Note that validated orders may still + * be unfulfillable due to invalid item amounts or other factors; + * callers should determine whether validated orders are fulfillable + * by simulating the fulfillment call prior to execution. Also note + * that anyone can validate a signed order, but only the offerer can + * validate an order without supplying a signature. + * + * @param orders The orders to validate. + * + * @return validated A boolean indicating whether the supplied orders have + * been successfully validated. + */ + function validate(Order[] calldata orders) + external + returns (bool validated); + + /** + * @notice Cancel all orders from a given offerer with a given zone in bulk + * by incrementing a counter. Note that only the offerer may + * increment the counter. + * + * @return newCounter The new counter. + */ + function incrementCounter() external returns (uint256 newCounter); + + /** + * @notice Fulfill an order offering an ERC721 token by supplying Ether (or + * the native token for the given chain) as consideration for the + * order. An arbitrary number of "additional recipients" may also be + * supplied which will each receive native tokens from the fulfiller + * as consideration. Note that this function costs less gas than + * `fulfillBasicOrder` due to the zero bytes in the function + * selector (0x00000000) which also results in earlier function + * dispatch. + * + * @param parameters Additional information on the fulfilled order. Note + * that the offerer must first approve this contract (or + * their preferred conduit if indicated by the order) for + * their offered ERC721 token to be transferred. + * + * @return fulfilled A boolean indicating whether the order has been + * successfully fulfilled. + */ + function fulfillBasicOrder_efficient_6GL6yc( + BasicOrderParameters calldata parameters + ) external payable returns (bool fulfilled); + + /** + * @notice Retrieve the order hash for a given order. + * + * @param order The components of the order. + * + * @return orderHash The order hash. + */ + function getOrderHash(OrderComponents calldata order) + external + view + returns (bytes32 orderHash); + + /** + * @notice Retrieve the status of a given order by hash, including whether + * the order has been cancelled or validated and the fraction of the + * order that has been filled. + * + * @param orderHash The order hash in question. + * + * @return isValidated A boolean indicating whether the order in question + * has been validated (i.e. previously approved or + * partially filled). + * @return isCancelled A boolean indicating whether the order in question + * has been cancelled. + * @return totalFilled The total portion of the order that has been filled + * (i.e. the "numerator"). + * @return totalSize The total size of the order that is either filled or + * unfilled (i.e. the "denominator"). + */ + function getOrderStatus(bytes32 orderHash) + external + view + returns ( + bool isValidated, + bool isCancelled, + uint256 totalFilled, + uint256 totalSize + ); + + /** + * @notice Retrieve the current counter for a given offerer. + * + * @param offerer The offerer in question. + * + * @return counter The current counter. + */ + function getCounter(address offerer) + external + view + returns (uint256 counter); + + /** + * @notice Retrieve configuration information for this contract. + * + * @return version The contract version. + * @return domainSeparator The domain separator for this contract. + * @return conduitController The conduit Controller set for this contract. + */ + function information() + external + view + returns ( + string memory version, + bytes32 domainSeparator, + address conduitController + ); + + function getContractOffererNonce(address contractOfferer) + external + view + returns (uint256 nonce); + + /** + * @notice Retrieve the name of this contract. + * + * @return contractName The name of this contract. + */ + function name() external view returns (string memory contractName); +} diff --git a/src/marketplaces/seaport-1.6/lib/ConsiderationEnums.sol b/src/marketplaces/seaport-1.6/lib/ConsiderationEnums.sol new file mode 100644 index 0000000..33bb3e1 --- /dev/null +++ b/src/marketplaces/seaport-1.6/lib/ConsiderationEnums.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +// prettier-ignore +enum OrderType { + // 0: no partial fills, anyone can execute + FULL_OPEN, + + // 1: partial fills supported, anyone can execute + PARTIAL_OPEN, + + // 2: no partial fills, only offerer or zone can execute + FULL_RESTRICTED, + + // 3: partial fills supported, only offerer or zone can execute + PARTIAL_RESTRICTED +} + +// prettier-ignore +enum BasicOrderType { + // 0: no partial fills, anyone can execute + ETH_TO_ERC721_FULL_OPEN, + + // 1: partial fills supported, anyone can execute + ETH_TO_ERC721_PARTIAL_OPEN, + + // 2: no partial fills, only offerer or zone can execute + ETH_TO_ERC721_FULL_RESTRICTED, + + // 3: partial fills supported, only offerer or zone can execute + ETH_TO_ERC721_PARTIAL_RESTRICTED, + + // 4: no partial fills, anyone can execute + ETH_TO_ERC1155_FULL_OPEN, + + // 5: partial fills supported, anyone can execute + ETH_TO_ERC1155_PARTIAL_OPEN, + + // 6: no partial fills, only offerer or zone can execute + ETH_TO_ERC1155_FULL_RESTRICTED, + + // 7: partial fills supported, only offerer or zone can execute + ETH_TO_ERC1155_PARTIAL_RESTRICTED, + + // 8: no partial fills, anyone can execute + ERC20_TO_ERC721_FULL_OPEN, + + // 9: partial fills supported, anyone can execute + ERC20_TO_ERC721_PARTIAL_OPEN, + + // 10: no partial fills, only offerer or zone can execute + ERC20_TO_ERC721_FULL_RESTRICTED, + + // 11: partial fills supported, only offerer or zone can execute + ERC20_TO_ERC721_PARTIAL_RESTRICTED, + + // 12: no partial fills, anyone can execute + ERC20_TO_ERC1155_FULL_OPEN, + + // 13: partial fills supported, anyone can execute + ERC20_TO_ERC1155_PARTIAL_OPEN, + + // 14: no partial fills, only offerer or zone can execute + ERC20_TO_ERC1155_FULL_RESTRICTED, + + // 15: partial fills supported, only offerer or zone can execute + ERC20_TO_ERC1155_PARTIAL_RESTRICTED, + + // 16: no partial fills, anyone can execute + ERC721_TO_ERC20_FULL_OPEN, + + // 17: partial fills supported, anyone can execute + ERC721_TO_ERC20_PARTIAL_OPEN, + + // 18: no partial fills, only offerer or zone can execute + ERC721_TO_ERC20_FULL_RESTRICTED, + + // 19: partial fills supported, only offerer or zone can execute + ERC721_TO_ERC20_PARTIAL_RESTRICTED, + + // 20: no partial fills, anyone can execute + ERC1155_TO_ERC20_FULL_OPEN, + + // 21: partial fills supported, anyone can execute + ERC1155_TO_ERC20_PARTIAL_OPEN, + + // 22: no partial fills, only offerer or zone can execute + ERC1155_TO_ERC20_FULL_RESTRICTED, + + // 23: partial fills supported, only offerer or zone can execute + ERC1155_TO_ERC20_PARTIAL_RESTRICTED +} + +// prettier-ignore +enum BasicOrderRouteType { + // 0: provide Ether (or other native token) to receive offered ERC721 item. + ETH_TO_ERC721, + + // 1: provide Ether (or other native token) to receive offered ERC1155 item. + ETH_TO_ERC1155, + + // 2: provide ERC20 item to receive offered ERC721 item. + ERC20_TO_ERC721, + + // 3: provide ERC20 item to receive offered ERC1155 item. + ERC20_TO_ERC1155, + + // 4: provide ERC721 item to receive offered ERC20 item. + ERC721_TO_ERC20, + + // 5: provide ERC1155 item to receive offered ERC20 item. + ERC1155_TO_ERC20 +} + +// prettier-ignore +enum ItemType { + // 0: ETH on mainnet, MATIC on polygon, etc. + NATIVE, + + // 1: ERC20 items (ERC777 and ERC20 analogues could also technically work) + ERC20, + + // 2: ERC721 items + ERC721, + + // 3: ERC1155 items + ERC1155, + + // 4: ERC721 items where a number of tokenIds are supported + ERC721_WITH_CRITERIA, + + // 5: ERC1155 items where a number of ids are supported + ERC1155_WITH_CRITERIA +} + +// prettier-ignore +enum Side { + // 0: Items that can be spent + OFFER, + + // 1: Items that must be received + CONSIDERATION +} diff --git a/src/marketplaces/seaport-1.6/lib/ConsiderationStructs.sol b/src/marketplaces/seaport-1.6/lib/ConsiderationStructs.sol new file mode 100644 index 0000000..1b3e57b --- /dev/null +++ b/src/marketplaces/seaport-1.6/lib/ConsiderationStructs.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +// prettier-ignore +import { + OrderType, + BasicOrderType, + ItemType, + Side, + BasicOrderRouteType +} from "./ConsiderationEnums.sol"; + +/** + * @dev An order contains eleven components: an offerer, a zone (or account that + * can cancel the order or restrict who can fulfill the order depending on + * the type), the order type (specifying partial fill support as well as + * restricted order status), the start and end time, a hash that will be + * provided to the zone when validating restricted orders, a salt, a key + * corresponding to a given conduit, a counter, and an arbitrary number of + * offer items that can be spent along with consideration items that must + * be received by their respective recipient. + */ +struct OrderComponents { + address offerer; + address zone; + OfferItem[] offer; + ConsiderationItem[] consideration; + OrderType orderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 conduitKey; + uint256 counter; +} + +/** + * @dev An offer item has five components: an item type (ETH or other native + * tokens, ERC20, ERC721, and ERC1155, as well as criteria-based ERC721 and + * ERC1155), a token address, a dual-purpose "identifierOrCriteria" + * component that will either represent a tokenId or a merkle root + * depending on the item type, and a start and end amount that support + * increasing or decreasing amounts over the duration of the respective + * order. + */ +struct OfferItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; +} + +/** + * @dev A consideration item has the same five components as an offer item and + * an additional sixth component designating the required recipient of the + * item. + */ +struct ConsiderationItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; + address payable recipient; +} + +/** + * @dev A spent item is translated from a utilized offer item and has four + * components: an item type (ETH or other native tokens, ERC20, ERC721, and + * ERC1155), a token address, a tokenId, and an amount. + */ +struct SpentItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; +} + +/** + * @dev A received item is translated from a utilized consideration item and has + * the same four components as a spent item, as well as an additional fifth + * component designating the required recipient of the item. + */ +struct ReceivedItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; + address payable recipient; +} + +/** + * @dev For basic orders involving ETH / native / ERC20 <=> ERC721 / ERC1155 + * matching, a group of six functions may be called that only requires a + * subset of the usual order arguments. Note the use of a "basicOrderType" + * enum; this represents both the usual order type as well as the "route" + * of the basic order (a simple derivation function for the basic order + * type is `basicOrderType = orderType + (4 * basicOrderRoute)`.) + */ +struct BasicOrderParameters { + // calldata offset + address considerationToken; // 0x24 + uint256 considerationIdentifier; // 0x44 + uint256 considerationAmount; // 0x64 + address payable offerer; // 0x84 + address zone; // 0xa4 + address offerToken; // 0xc4 + uint256 offerIdentifier; // 0xe4 + uint256 offerAmount; // 0x104 + BasicOrderType basicOrderType; // 0x124 + uint256 startTime; // 0x144 + uint256 endTime; // 0x164 + bytes32 zoneHash; // 0x184 + uint256 salt; // 0x1a4 + bytes32 offererConduitKey; // 0x1c4 + bytes32 fulfillerConduitKey; // 0x1e4 + uint256 totalOriginalAdditionalRecipients; // 0x204 + AdditionalRecipient[] additionalRecipients; // 0x224 + bytes signature; // 0x244 + // Total length, excluding dynamic array data: 0x264 (580) +} + +/** + * @dev Basic orders can supply any number of additional recipients, with the + * implied assumption that they are supplied from the offered ETH (or other + * native token) or ERC20 token for the order. + */ +struct AdditionalRecipient { + uint256 amount; + address payable recipient; +} + +/** + * @dev The full set of order components, with the exception of the counter, + * must be supplied when fulfilling more sophisticated orders or groups of + * orders. The total number of original consideration items must also be + * supplied, as the caller may specify additional consideration items. + */ +struct OrderParameters { + address offerer; // 0x00 + address zone; // 0x20 + OfferItem[] offer; // 0x40 + ConsiderationItem[] consideration; // 0x60 + OrderType orderType; // 0x80 + uint256 startTime; // 0xa0 + uint256 endTime; // 0xc0 + bytes32 zoneHash; // 0xe0 + uint256 salt; // 0x100 + bytes32 conduitKey; // 0x120 + uint256 totalOriginalConsiderationItems; // 0x140 + // offer.length // 0x160 +} + +/** + * @dev Orders require a signature in addition to the other order parameters. + */ +struct Order { + OrderParameters parameters; + bytes signature; +} + +/** + * @dev Advanced orders include a numerator (i.e. a fraction to attempt to fill) + * and a denominator (the total size of the order) in addition to the + * signature and other order parameters. It also supports an optional field + * for supplying extra data; this data will be included in a staticcall to + * `isValidOrderIncludingExtraData` on the zone for the order if the order + * type is restricted and the offerer or zone are not the caller. + */ +struct AdvancedOrder { + OrderParameters parameters; + uint120 numerator; + uint120 denominator; + bytes signature; + bytes extraData; +} + +/** + * @dev Orders can be validated (either explicitly via `validate`, or as a + * consequence of a full or partial fill), specifically cancelled (they can + * also be cancelled in bulk via incrementing a per-zone counter), and + * partially or fully filled (with the fraction filled represented by a + * numerator and denominator). + */ +struct OrderStatus { + bool isValidated; + bool isCancelled; + uint120 numerator; + uint120 denominator; +} + +/** + * @dev A criteria resolver specifies an order, side (offer vs. consideration), + * and item index. It then provides a chosen identifier (i.e. tokenId) + * alongside a merkle proof demonstrating the identifier meets the required + * criteria. + */ +struct CriteriaResolver { + uint256 orderIndex; + Side side; + uint256 index; + uint256 identifier; + bytes32[] criteriaProof; +} + +/** + * @dev A fulfillment is applied to a group of orders. It decrements a series of + * offer and consideration items, then generates a single execution + * element. A given fulfillment can be applied to as many offer and + * consideration items as desired, but must contain at least one offer and + * at least one consideration that match. The fulfillment must also remain + * consistent on all key parameters across all offer items (same offerer, + * token, type, tokenId, and conduit preference) as well as across all + * consideration items (token, type, tokenId, and recipient). + */ +struct Fulfillment { + FulfillmentComponent[] offerComponents; + FulfillmentComponent[] considerationComponents; +} + +/** + * @dev Each fulfillment component contains one index referencing a specific + * order and another referencing a specific offer or consideration item. + */ +struct FulfillmentComponent { + uint256 orderIndex; + uint256 itemIndex; +} + +/** + * @dev An execution is triggered once all consideration items have been zeroed + * out. It sends the item in question from the offerer to the item's + * recipient, optionally sourcing approvals from either this contract + * directly or from the offerer's chosen conduit if one is specified. An + * execution is not provided as an argument, but rather is derived via + * orders, criteria resolvers, and fulfillments (where the total number of + * executions will be less than or equal to the total number of indicated + * fulfillments) and returned as part of `matchOrders`. + */ +struct Execution { + ReceivedItem item; + address offerer; + bytes32 conduitKey; +} diff --git a/src/marketplaces/seaport-1.6/lib/ConsiderationTypeHashes.sol b/src/marketplaces/seaport-1.6/lib/ConsiderationTypeHashes.sol new file mode 100644 index 0000000..e9b7b95 --- /dev/null +++ b/src/marketplaces/seaport-1.6/lib/ConsiderationTypeHashes.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import "./ConsiderationStructs.sol"; + +uint256 constant EIP712_Order_size = 0x180; +uint256 constant EIP712_OfferItem_size = 0xc0; +uint256 constant EIP712_ConsiderationItem_size = 0xe0; +uint256 constant EIP712_DomainSeparator_offset = 0x02; +uint256 constant EIP712_OrderHash_offset = 0x22; +uint256 constant EIP712_DigestPayload_size = 0x42; +uint256 constant EIP_712_PREFIX = ( + 0x1901000000000000000000000000000000000000000000000000000000000000 +); + +contract ConsiderationTypeHashes { + bytes32 internal immutable _NAME_HASH; + bytes32 internal immutable _VERSION_HASH; + bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH; + bytes32 internal immutable _OFFER_ITEM_TYPEHASH; + bytes32 internal immutable _CONSIDERATION_ITEM_TYPEHASH; + bytes32 internal immutable _ORDER_TYPEHASH; + bytes32 internal immutable _DOMAIN_SEPARATOR; + address internal constant seaportAddress = + address(0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC); + + constructor() { + // Derive hash of the name of the contract. + _NAME_HASH = keccak256(bytes("Seaport")); + + // Derive hash of the version string of the contract. + _VERSION_HASH = keccak256(bytes("1.5")); + + bytes memory offerItemTypeString = abi.encodePacked( + "OfferItem(", + "uint8 itemType,", + "address token,", + "uint256 identifierOrCriteria,", + "uint256 startAmount,", + "uint256 endAmount", + ")" + ); + + // Construct the ConsiderationItem type string. + // prettier-ignore + bytes memory considerationItemTypeString = abi.encodePacked( + "ConsiderationItem(", + "uint8 itemType,", + "address token,", + "uint256 identifierOrCriteria,", + "uint256 startAmount,", + "uint256 endAmount,", + "address recipient", + ")" + ); + + // Construct the OrderComponents type string, not including the above. + // prettier-ignore + bytes memory orderComponentsPartialTypeString = abi.encodePacked( + "OrderComponents(", + "address offerer,", + "address zone,", + "OfferItem[] offer,", + "ConsiderationItem[] consideration,", + "uint8 orderType,", + "uint256 startTime,", + "uint256 endTime,", + "bytes32 zoneHash,", + "uint256 salt,", + "bytes32 conduitKey,", + "uint256 counter", + ")" + ); + // Derive the OfferItem type hash using the corresponding type string. + bytes32 offerItemTypehash = keccak256(offerItemTypeString); + + // Derive ConsiderationItem type hash using corresponding type string. + bytes32 considerationItemTypehash = keccak256( + considerationItemTypeString + ); + + // Construct the primary EIP-712 domain type string. + // prettier-ignore + _EIP_712_DOMAIN_TYPEHASH = keccak256( + abi.encodePacked( + "EIP712Domain(", + "string name,", + "string version,", + "uint256 chainId,", + "address verifyingContract", + ")" + ) + ); + + _OFFER_ITEM_TYPEHASH = offerItemTypehash; + _CONSIDERATION_ITEM_TYPEHASH = considerationItemTypehash; + + // Derive OrderItem type hash via combination of relevant type strings. + _ORDER_TYPEHASH = keccak256( + abi.encodePacked( + orderComponentsPartialTypeString, + considerationItemTypeString, + offerItemTypeString + ) + ); + + _DOMAIN_SEPARATOR = _deriveDomainSeparator(); + } + + /** + * @dev Internal view function to derive the EIP-712 domain separator. + * + * @return The derived domain separator. + */ + function _deriveDomainSeparator() internal view returns (bytes32) { + // prettier-ignore + return keccak256( + abi.encode( + _EIP_712_DOMAIN_TYPEHASH, + _NAME_HASH, + _VERSION_HASH, + 1, + seaportAddress + ) + ); + } + + /** + * @dev Internal pure function to efficiently derive an digest to sign for + * an order in accordance with EIP-712. + * + * @param orderHash The order hash. + * + * @return value The hash. + */ + function _deriveEIP712Digest(bytes32 orderHash) + internal + view + returns (bytes32 value) + { + bytes32 domainSeparator = _DOMAIN_SEPARATOR; + // Leverage scratch space to perform an efficient hash. + assembly { + // Place the EIP-712 prefix at the start of scratch space. + mstore(0, EIP_712_PREFIX) + + // Place the domain separator in the next region of scratch space. + mstore(EIP712_DomainSeparator_offset, domainSeparator) + + // Place the order hash in scratch space, spilling into the first + // two bytes of the free memory pointer — this should never be set + // as memory cannot be expanded to that size, and will be zeroed out + // after the hash is performed. + mstore(EIP712_OrderHash_offset, orderHash) + + // Hash the relevant region (65 bytes). + value := keccak256(0, EIP712_DigestPayload_size) + + // Clear out the dirtied bits in the memory pointer. + mstore(EIP712_OrderHash_offset, 0) + } + } + + /** + * @dev Internal view function to derive the EIP-712 hash for an offer item. + * + * @param offerItem The offered item to hash. + * + * @return The hash. + */ + function _hashOfferItem(OfferItem memory offerItem) + internal + view + returns (bytes32) + { + return + keccak256( + abi.encode( + _OFFER_ITEM_TYPEHASH, + offerItem.itemType, + offerItem.token, + offerItem.identifierOrCriteria, + offerItem.startAmount, + offerItem.endAmount + ) + ); + } + + /** + * @dev Internal view function to derive the EIP-712 hash for a consideration item. + * + * @param considerationItem The consideration item to hash. + * + * @return The hash. + */ + function _hashConsiderationItem(ConsiderationItem memory considerationItem) + internal + view + returns (bytes32) + { + return + keccak256( + abi.encode( + _CONSIDERATION_ITEM_TYPEHASH, + considerationItem.itemType, + considerationItem.token, + considerationItem.identifierOrCriteria, + considerationItem.startAmount, + considerationItem.endAmount, + considerationItem.recipient + ) + ); + } + + /** + * @dev Internal view function to derive the order hash for a given order. + * Note that only the original consideration items are included in the + * order hash, as additional consideration items may be supplied by the + * caller. + * + * @param orderParameters The parameters of the order to hash. + * @param counter The counter of the order to hash. + * + * @return orderHash The hash. + */ + function _deriveOrderHash( + OrderParameters memory orderParameters, + uint256 counter + ) internal view returns (bytes32 orderHash) { + // Designate new memory regions for offer and consideration item hashes. + bytes32[] memory offerHashes = new bytes32[]( + orderParameters.offer.length + ); + bytes32[] memory considerationHashes = new bytes32[]( + orderParameters.totalOriginalConsiderationItems + ); + + // Iterate over each offer on the order. + for (uint256 i = 0; i < orderParameters.offer.length; ++i) { + // Hash the offer and place the result into memory. + offerHashes[i] = _hashOfferItem(orderParameters.offer[i]); + } + + // Iterate over each consideration on the order. + for ( + uint256 i = 0; + i < orderParameters.totalOriginalConsiderationItems; + ++i + ) { + // Hash the consideration and place the result into memory. + considerationHashes[i] = _hashConsiderationItem( + orderParameters.consideration[i] + ); + } + + // Derive and return the order hash as specified by EIP-712. + + return + keccak256( + abi.encode( + _ORDER_TYPEHASH, + orderParameters.offerer, + orderParameters.zone, + keccak256(abi.encodePacked(offerHashes)), + keccak256(abi.encodePacked(considerationHashes)), + orderParameters.orderType, + orderParameters.startTime, + orderParameters.endTime, + orderParameters.zoneHash, + orderParameters.salt, + orderParameters.conduitKey, + counter + ) + ); + } +} diff --git a/test/GenericMarketplaceTest.t.sol b/test/GenericMarketplaceTest.t.sol index 4d07e36..01880dc 100644 --- a/test/GenericMarketplaceTest.t.sol +++ b/test/GenericMarketplaceTest.t.sol @@ -7,6 +7,7 @@ import { BlurV2Config } from "../src/marketplaces/blur-2.0/BlurV2Config.sol"; import { FoundationConfig } from "../src/marketplaces/foundation/FoundationConfig.sol"; import { LooksRareConfig } from "../src/marketplaces/looksRare/LooksRareConfig.sol"; import { SeaportOnePointFiveConfig } from "../src/marketplaces/seaport-1.5/SeaportOnePointFiveConfig.sol"; +import { SeaportOnePointSixConfig } from "../src/marketplaces/seaport-1.6/SeaportOnePointSixConfig.sol"; import { LooksRareV2Config } from "../src/marketplaces/looksRare-v2/LooksRareV2Config.sol"; import { SeaportOnePointOneConfig } from "../src/marketplaces/seaport-1.1/SeaportOnePointOneConfig.sol"; import { SudoswapConfig } from "../src/marketplaces/sudoswap/SudoswapConfig.sol"; @@ -29,6 +30,7 @@ contract GenericMarketplaceTest is BaseOrderTest { BaseMarketConfig looksRareV2Config; BaseMarketConfig seaportOnePointOneConfig; BaseMarketConfig seaportOnePointFiveConfig; + BaseMarketConfig seaportOnePointSixConfig; BaseMarketConfig sudoswapConfig; BaseMarketConfig wyvernConfig; BaseMarketConfig x2y2Config; @@ -46,6 +48,9 @@ contract GenericMarketplaceTest is BaseOrderTest { seaportOnePointFiveConfig = BaseMarketConfig( new SeaportOnePointFiveConfig() ); + seaportOnePointSixConfig = BaseMarketConfig( + new SeaportOnePointSixConfig() + ); sudoswapConfig = BaseMarketConfig(new SudoswapConfig()); wyvernConfig = BaseMarketConfig(new WyvernConfig()); x2y2Config = BaseMarketConfig(new X2Y2Config()); @@ -72,6 +77,10 @@ contract GenericMarketplaceTest is BaseOrderTest { benchmarkMarket(looksRareV2Config); } + function testSeaportOnePointSix() external { + benchmarkMarket(seaportOnePointSixConfig); + } + function testSeaportOnePointFive() external { benchmarkMarket(seaportOnePointFiveConfig); }