diff --git a/.gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json b/.gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json
similarity index 80%
rename from .gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json
rename to .gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json
index d09e9b45c..72b1390e5 100644
--- a/.gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json
+++ b/.gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json
@@ -1,14 +1,14 @@
{
- "commitHash": "374278b75e1acc08d85bfacef494b7cfe42fa2d9",
+ "commitHash": "ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3",
"contractReports": {
"Conduit": {
"name": "Conduit",
"methods": [
{
"method": "execute",
- "min": 77435,
- "max": 2373103,
- "avg": 483652,
+ "min": 77459,
+ "max": 2168532,
+ "avg": 449559,
"calls": 6
},
{
@@ -21,8 +21,8 @@
{
"method": "executeWithBatch1155",
"min": 97717,
- "max": 361418,
- "avg": 228764,
+ "max": 361430,
+ "avg": 228767,
"calls": 4
},
{
@@ -55,10 +55,10 @@
},
{
"method": "createConduit",
- "min": 712802,
+ "min": 712826,
"max": 712970,
- "avg": 712930,
- "calls": 51
+ "avg": 712927,
+ "calls": 53
},
{
"method": "transferOwnership",
@@ -71,8 +71,8 @@
"method": "updateChannel",
"min": 34454,
"max": 121098,
- "avg": 117184,
- "calls": 69
+ "avg": 117294,
+ "calls": 71
}
],
"bytecodeSize": 12007,
@@ -85,7 +85,7 @@
"method": "createConduit",
"min": 226092,
"max": 231533,
- "avg": 229598,
+ "avg": 229596,
"calls": 6
}
],
@@ -148,7 +148,7 @@
"method": "cancelOrders",
"min": null,
"max": null,
- "avg": 65315,
+ "avg": 65339,
"calls": 1
}
],
@@ -195,30 +195,30 @@
},
{
"method": "createZone",
- "min": null,
- "max": null,
- "avg": 1154314,
+ "min": 1154302,
+ "max": 1154314,
+ "avg": 1154312,
"calls": 31
},
{
"method": "executeMatchAdvancedOrders",
"min": null,
"max": null,
- "avg": 288298,
+ "avg": 288785,
"calls": 2
},
{
"method": "executeMatchOrders",
"min": null,
"max": null,
- "avg": 281862,
+ "avg": 282325,
"calls": 2
},
{
"method": "pause",
- "min": 32851,
+ "min": 32875,
"max": 35006,
- "avg": 33569,
+ "avg": 33585,
"calls": 3
},
{
@@ -237,14 +237,14 @@
"methods": [
{
"method": "prepare",
- "min": 69404,
- "max": 2350940,
- "avg": 1171827,
- "calls": 20
+ "min": 49267,
+ "max": 2351654,
+ "avg": 1061779,
+ "calls": 26
}
],
- "bytecodeSize": 2459,
- "deployedBytecodeSize": 2431
+ "bytecodeSize": 2726,
+ "deployedBytecodeSize": 2698
},
"Seaport": {
"name": "Seaport",
@@ -253,50 +253,50 @@
"method": "cancel",
"min": 41250,
"max": 58422,
- "avg": 54035,
+ "avg": 54039,
"calls": 16
},
{
"method": "fulfillAdvancedOrder",
- "min": 96312,
- "max": 225169,
- "avg": 159958,
- "calls": 188
+ "min": 96288,
+ "max": 225181,
+ "avg": 159382,
+ "calls": 194
},
{
"method": "fulfillAvailableAdvancedOrders",
- "min": 149979,
- "max": 339949,
- "avg": 207233,
- "calls": 29
+ "min": 149626,
+ "max": 350749,
+ "avg": 214389,
+ "calls": 33
},
{
"method": "fulfillAvailableOrders",
- "min": 165345,
- "max": 216505,
- "avg": 201935,
+ "min": 164998,
+ "max": 215740,
+ "avg": 201364,
"calls": 21
},
{
"method": "fulfillBasicOrder",
"min": 90639,
"max": 1621627,
- "avg": 598708,
+ "avg": 598707,
"calls": 187
},
{
"method": "fulfillBasicOrder_efficient_6GL6yc",
"min": 90261,
- "max": 111444,
- "avg": 100853,
+ "max": 111456,
+ "avg": 100859,
"calls": 6
},
{
"method": "fulfillOrder",
"min": 119409,
"max": 225080,
- "avg": 177756,
- "calls": 105
+ "avg": 176772,
+ "calls": 108
},
{
"method": "incrementCounter",
@@ -307,37 +307,51 @@
},
{
"method": "matchAdvancedOrders",
- "min": 180326,
- "max": 299883,
- "avg": 249683,
+ "min": 179574,
+ "max": 300213,
+ "avg": 248903,
"calls": 77
},
{
"method": "matchOrders",
- "min": 158265,
- "max": 349010,
- "avg": 265283,
+ "min": 157474,
+ "max": 348243,
+ "avg": 264537,
"calls": 151
},
{
"method": "validate",
- "min": 53201,
- "max": 83886,
- "avg": 73544,
+ "min": 53177,
+ "max": 83910,
+ "avg": 73542,
"calls": 29
}
],
- "bytecodeSize": 26712,
- "deployedBytecodeSize": 23553
+ "bytecodeSize": 26099,
+ "deployedBytecodeSize": 24374
+ },
+ "SeaportRouter": {
+ "name": "SeaportRouter",
+ "methods": [
+ {
+ "method": "fulfillAvailableAdvancedOrders",
+ "min": 183942,
+ "max": 311883,
+ "avg": 233578,
+ "calls": 6
+ }
+ ],
+ "bytecodeSize": 6345,
+ "deployedBytecodeSize": 6158
},
"TestContractOfferer": {
"name": "TestContractOfferer",
"methods": [
{
"method": "activate",
- "min": 201531,
+ "min": 201519,
"max": 246674,
- "avg": 205514,
+ "avg": 205512,
"calls": 33
},
{
@@ -385,16 +399,16 @@
{
"method": "mint",
"min": 47235,
- "max": 49915,
- "avg": 49443,
- "calls": 245
+ "max": 49903,
+ "avg": 49427,
+ "calls": 239
},
{
"method": "setApprovalForAll",
"min": 26102,
"max": 46002,
- "avg": 45654,
- "calls": 458
+ "avg": 45645,
+ "calls": 446
}
],
"bytecodeSize": 4173,
@@ -407,8 +421,8 @@
"method": "approve",
"min": 28881,
"max": 46245,
- "avg": 45749,
- "calls": 292
+ "avg": 45807,
+ "calls": 390
},
{
"method": "blockTransfer",
@@ -421,8 +435,8 @@
"method": "mint",
"min": 33994,
"max": 68458,
- "avg": 67348,
- "calls": 141
+ "avg": 67563,
+ "calls": 190
},
{
"method": "setNoReturnData",
@@ -442,15 +456,15 @@
"method": "mint",
"min": 51492,
"max": 68784,
- "avg": 66201,
- "calls": 307
+ "avg": 65745,
+ "calls": 282
},
{
"method": "setApprovalForAll",
"min": 26195,
"max": 46095,
- "avg": 45560,
- "calls": 522
+ "avg": 45506,
+ "calls": 474
}
],
"bytecodeSize": 5238,
@@ -461,9 +475,9 @@
"methods": [
{
"method": "activate",
- "min": null,
- "max": null,
- "avg": 201567,
+ "min": 201543,
+ "max": 201567,
+ "avg": 201555,
"calls": 2
}
],
@@ -490,8 +504,8 @@
{
"method": "bulkTransfer",
"min": 77935,
- "max": 1546854,
- "avg": 673107,
+ "max": 1428982,
+ "avg": 633808,
"calls": 3
}
],
diff --git a/README.md b/README.md
index 039476b01..fcf0f1d3d 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
| Sepolia |
@@ -126,7 +112,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://sepolia.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://sepolia.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -139,7 +129,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://polygonscan.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://polygonscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -152,7 +146,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://mumbai.polygonscan.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://mumbai.polygonscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -165,7 +163,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://scope.klaytn.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://scope.klaytn.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -178,7 +180,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://baobab.scope.klaytn.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://baobab.scope.klaytn.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -191,7 +197,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://optimistic.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://optimistic.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -204,7 +214,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://goerli-optimism.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://goerli-optimism.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -217,7 +231,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -230,7 +248,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://goerli.arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://goerli.arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -243,7 +265,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://nova.arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://nova.arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -256,7 +282,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://snowtrace.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://snowtrace.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -269,7 +299,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://testnet.snowtrace.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://testnet.snowtrace.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -282,7 +316,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://gnosisscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://gnosisscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -295,7 +333,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://bscscan.com/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://bscscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
@@ -308,7 +350,11 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts
|
--
+[0x00000000000006c7676171937C444f6BDe3D6282](https://testnet.bscscan.com/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code)
+
+ |
+
+[0x0000000000000aD24e80fd803C6ac37206a45f15](https://testnet.bscscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code)
|
diff --git a/config/.solcover-reference.js b/config/.solcover-reference.js
index a8e0bab93..84280eeef 100644
--- a/config/.solcover-reference.js
+++ b/config/.solcover-reference.js
@@ -49,6 +49,7 @@ module.exports = {
"test/ConduitMockInvalidMagic.sol",
"test/ConduitMockRevertBytes.sol",
"test/ConduitMockRevertNoReason.sol",
+ "test/TestTransferValidationZoneOfferer.sol",
"../reference/shim/Shim.sol",
],
};
diff --git a/config/.solcover.js b/config/.solcover.js
index 2a8e2f2fd..8cac48297 100644
--- a/config/.solcover.js
+++ b/config/.solcover.js
@@ -19,6 +19,7 @@ module.exports = {
"test/EIP1271Wallet.sol",
"test/ExcessReturnDataRecipient.sol",
"test/ERC1155BatchRecipient.sol",
+ "test/InvalidEthRecipient.sol",
"test/Reenterer.sol",
"test/TestERC1155.sol",
"test/TestERC1155Revert.sol",
@@ -43,6 +44,7 @@ module.exports = {
"test/ConduitMockInvalidMagic.sol",
"test/ConduitMockRevertBytes.sol",
"test/ConduitMockRevertNoReason.sol",
+ "test/TestTransferValidationZoneOfferer.sol",
"zones/PausableZone.sol",
"zones/PausableZoneController.sol",
"zones/interfaces/PausableZoneControllerInterface.sol",
diff --git a/contracts/Seaport.sol b/contracts/Seaport.sol
index c682a49c1..a9ea72be5 100644
--- a/contracts/Seaport.sol
+++ b/contracts/Seaport.sol
@@ -5,7 +5,7 @@ import { Consideration } from "./lib/Consideration.sol";
/**
* @title Seaport
- * @custom:version 1.2
+ * @custom:version 1.3
* @author 0age (0age.eth)
* @custom:coauthor d1ll0n (d1ll0n.eth)
* @custom:coauthor transmissions11 (t11s.eth)
diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol
new file mode 100644
index 000000000..43b57c393
--- /dev/null
+++ b/contracts/helpers/SeaportRouter.sol
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {
+ SeaportRouterInterface
+} from "../interfaces/SeaportRouterInterface.sol";
+
+import { SeaportInterface } from "../interfaces/SeaportInterface.sol";
+
+import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol";
+
+import {
+ Execution,
+ AdvancedOrder,
+ CriteriaResolver,
+ FulfillmentComponent
+} from "../lib/ConsiderationStructs.sol";
+
+/**
+ * @title SeaportRouter
+ * @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth)
+ * @notice A utility contract for fulfilling orders with multiple
+ * Seaport versions. DISCLAIMER: This contract only works when
+ * all consideration items across all listings are native tokens.
+ */
+contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard {
+ /// @dev The allowed v1.1 contract usable through this router.
+ address private immutable _SEAPORT_V1_1;
+ /// @dev The allowed v1.2 contract usable through this router.
+ address private immutable _SEAPORT_V1_2;
+
+ /**
+ * @dev Deploy contract with the supported Seaport contracts.
+ *
+ * @param seaportV1point1 The address of the Seaport v1.1 contract.
+ * @param seaportV1point2 The address of the Seaport v1.2 contract.
+ */
+ constructor(address seaportV1point1, address seaportV1point2) {
+ _SEAPORT_V1_1 = seaportV1point1;
+ _SEAPORT_V1_2 = seaportV1point2;
+ }
+
+ /**
+ * @dev Fallback function to receive excess ether, in case total amount of
+ * ether sent is more than the amount required to fulfill the order.
+ */
+ receive() external payable override {
+ // Ensure we only receive ether from Seaport.
+ _assertSeaportAllowed(msg.sender);
+ }
+
+ /**
+ * @notice Fulfill available advanced orders through multiple Seaport
+ * versions.
+ * See {SeaportInterface-fulfillAvailableAdvancedOrders}
+ *
+ * @param params The parameters for fulfilling available advanced orders.
+ */
+ function fulfillAvailableAdvancedOrders(
+ FulfillAvailableAdvancedOrdersParams calldata params
+ )
+ external
+ payable
+ override
+ returns (
+ bool[][] memory availableOrders,
+ Execution[][] memory executions
+ )
+ {
+ // Ensure this function cannot be triggered during a reentrant call.
+ _setReentrancyGuard(true);
+
+ // Put the number of Seaport contracts on the stack.
+ uint256 seaportContractsLength = params.seaportContracts.length;
+
+ // Set the availableOrders and executions arrays to the correct length.
+ availableOrders = new bool[][](seaportContractsLength);
+ executions = new Execution[][](seaportContractsLength);
+
+ // Track the number of order fulfillments left.
+ uint256 fulfillmentsLeft = params.maximumFulfilled;
+
+ // To help avoid stack too deep errors, we format the calldata
+ // params in a struct and put it on the stack.
+ AdvancedOrder[] memory emptyAdvancedOrders;
+ CriteriaResolver[] memory emptyCriteriaResolvers;
+ FulfillmentComponent[][] memory emptyFulfillmentComponents;
+ CalldataParams memory calldataParams = CalldataParams({
+ advancedOrders: emptyAdvancedOrders,
+ criteriaResolvers: emptyCriteriaResolvers,
+ offerFulfillments: emptyFulfillmentComponents,
+ considerationFulfillments: emptyFulfillmentComponents,
+ fulfillerConduitKey: params.fulfillerConduitKey,
+ recipient: params.recipient,
+ maximumFulfilled: fulfillmentsLeft
+ });
+
+ // Iterate through the provided Seaport contracts.
+ for (uint256 i = 0; i < params.seaportContracts.length; ) {
+ // Ensure the provided Seaport contract is allowed.
+ _assertSeaportAllowed(params.seaportContracts[i]);
+
+ // Put the order params on the stack.
+ AdvancedOrderParams calldata orderParams = params
+ .advancedOrderParams[i];
+
+ // Assign the variables to the calldata params.
+ calldataParams.advancedOrders = orderParams.advancedOrders;
+ calldataParams.criteriaResolvers = orderParams.criteriaResolvers;
+ calldataParams.offerFulfillments = orderParams.offerFulfillments;
+ calldataParams.considerationFulfillments = orderParams
+ .considerationFulfillments;
+
+ // Execute the orders, collecting availableOrders and executions.
+ // This is wrapped in a try/catch in case a single order is
+ // executed that is no longer available, leading to a revert
+ // with `NoSpecifiedOrdersAvailable()`.
+ try
+ SeaportInterface(params.seaportContracts[i])
+ .fulfillAvailableAdvancedOrders{
+ value: orderParams.etherValue
+ }(
+ calldataParams.advancedOrders,
+ calldataParams.criteriaResolvers,
+ calldataParams.offerFulfillments,
+ calldataParams.considerationFulfillments,
+ calldataParams.fulfillerConduitKey,
+ calldataParams.recipient,
+ calldataParams.maximumFulfilled
+ )
+ returns (
+ bool[] memory newAvailableOrders,
+ Execution[] memory newExecutions
+ ) {
+ availableOrders[i] = newAvailableOrders;
+ executions[i] = newExecutions;
+
+ // Subtract the number of orders fulfilled.
+ uint256 newAvailableOrdersLength = newAvailableOrders.length;
+ for (uint256 j = 0; j < newAvailableOrdersLength; ) {
+ if (newAvailableOrders[j]) {
+ unchecked {
+ --fulfillmentsLeft;
+ ++j;
+ }
+ }
+ }
+
+ // Break if the maximum number of executions has been reached.
+ if (fulfillmentsLeft == 0) {
+ break;
+ }
+ } catch {}
+
+ // Update fulfillments left.
+ calldataParams.maximumFulfilled = fulfillmentsLeft;
+
+ unchecked {
+ ++i;
+ }
+ }
+
+ // Return excess ether that may not have been used or was sent back.
+ if (address(this).balance > 0) {
+ _returnExcessEther();
+ }
+
+ // Clear the reentrancy guard.
+ _clearReentrancyGuard();
+ }
+
+ /**
+ * @notice Returns the Seaport contracts allowed to be used through this
+ * router.
+ */
+ function getAllowedSeaportContracts()
+ external
+ view
+ override
+ returns (address[] memory seaportContracts)
+ {
+ seaportContracts = new address[](2);
+ seaportContracts[0] = _SEAPORT_V1_1;
+ seaportContracts[1] = _SEAPORT_V1_2;
+ }
+
+ /**
+ * @dev Reverts if the provided Seaport contract is not allowed.
+ */
+ function _assertSeaportAllowed(address seaport) internal view {
+ if (
+ _cast(seaport == _SEAPORT_V1_1) | _cast(seaport == _SEAPORT_V1_2) ==
+ 0
+ ) {
+ revert SeaportNotAllowed(seaport);
+ }
+ }
+
+ /**
+ * @dev Function to return excess ether, in case total amount of
+ * ether sent is more than the amount required to fulfill the order.
+ */
+ function _returnExcessEther() private {
+ // Send received funds back to msg.sender.
+ (bool success, bytes memory data) = payable(msg.sender).call{
+ value: address(this).balance
+ }("");
+
+ // Revert with an error if the ether transfer failed.
+ if (!success) {
+ revert EtherReturnTransferFailed(
+ msg.sender,
+ address(this).balance,
+ data
+ );
+ }
+ }
+}
diff --git a/contracts/helpers/sol/README.md b/contracts/helpers/sol/README.md
new file mode 100644
index 000000000..d8e08c779
--- /dev/null
+++ b/contracts/helpers/sol/README.md
@@ -0,0 +1,6 @@
+# seaport-sol
+
+## Note
+These helpers are intended for use with Forge tests and scripts. As such, they are highly gas-inefficient.
+
+They are still experimental, not thoroughly-tested, and not intended for production use.
\ No newline at end of file
diff --git a/contracts/helpers/sol/lib/AdditionalRecipientLib.sol b/contracts/helpers/sol/lib/AdditionalRecipientLib.sol
new file mode 100644
index 000000000..18161efd9
--- /dev/null
+++ b/contracts/helpers/sol/lib/AdditionalRecipientLib.sol
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { AdditionalRecipient } from "../../../lib/ConsiderationStructs.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library AdditionalRecipientLib {
+ bytes32 private constant ADDITIONAL_RECIPIENT_MAP_POSITION =
+ keccak256("seaport.AdditionalRecipientDefaults");
+ bytes32 private constant ADDITIONAL_RECIPIENTS_MAP_POSITION =
+ keccak256("seaport.AdditionalRecipientsDefaults");
+
+ /**
+ * @notice clears a default AdditionalRecipient from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => AdditionalRecipient)
+ storage additionalRecipientMap = _additionalRecipientMap();
+ AdditionalRecipient storage item = additionalRecipientMap[defaultName];
+ clear(item);
+ }
+
+ function clear(AdditionalRecipient storage item) internal {
+ // clear all fields
+ item.amount = 0;
+ item.recipient = payable(address(0));
+ }
+
+ function clear(AdditionalRecipient[] storage item) internal {
+ while (item.length > 0) {
+ clear(item[item.length - 1]);
+ item.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default AdditionalRecipient from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (AdditionalRecipient memory item) {
+ mapping(string => AdditionalRecipient)
+ storage additionalRecipientMap = _additionalRecipientMap();
+ item = additionalRecipientMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (AdditionalRecipient[] memory items) {
+ mapping(string => AdditionalRecipient[])
+ storage additionalRecipientsMap = _additionalRecipientsMap();
+ items = additionalRecipientsMap[defaultName];
+ }
+
+ /**
+ * @notice saves an AdditionalRecipient as a named default
+ * @param additionalRecipient the AdditionalRecipient to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ AdditionalRecipient memory additionalRecipient,
+ string memory defaultName
+ ) internal returns (AdditionalRecipient memory _additionalRecipient) {
+ mapping(string => AdditionalRecipient)
+ storage additionalRecipientMap = _additionalRecipientMap();
+ additionalRecipientMap[defaultName] = additionalRecipient;
+ return additionalRecipient;
+ }
+
+ function saveDefaultMany(
+ AdditionalRecipient[] memory additionalRecipients,
+ string memory defaultName
+ ) internal returns (AdditionalRecipient[] memory _additionalRecipients) {
+ mapping(string => AdditionalRecipient[])
+ storage additionalRecipientsMap = _additionalRecipientsMap();
+ StructCopier.setAdditionalRecipients(
+ additionalRecipientsMap[defaultName],
+ additionalRecipients
+ );
+ return additionalRecipients;
+ }
+
+ /**
+ * @notice makes a copy of an AdditionalRecipient in-memory
+ * @param item the AdditionalRecipient to make a copy of in-memory
+ */
+ function copy(
+ AdditionalRecipient memory item
+ ) internal pure returns (AdditionalRecipient memory) {
+ return
+ AdditionalRecipient({
+ amount: item.amount,
+ recipient: item.recipient
+ });
+ }
+
+ function copy(
+ AdditionalRecipient[] memory items
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory copiedItems = new AdditionalRecipient[](
+ items.length
+ );
+ for (uint256 i = 0; i < items.length; i++) {
+ copiedItems[i] = copy(items[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (AdditionalRecipient memory) {
+ return
+ AdditionalRecipient({ amount: 0, recipient: payable(address(0)) });
+ }
+
+ /**
+ * @notice gets the storage position of the default AdditionalRecipient map
+ */
+ function _additionalRecipientMap()
+ private
+ pure
+ returns (
+ mapping(string => AdditionalRecipient)
+ storage additionalRecipientMap
+ )
+ {
+ bytes32 position = ADDITIONAL_RECIPIENT_MAP_POSITION;
+ assembly {
+ additionalRecipientMap.slot := position
+ }
+ }
+
+ function _additionalRecipientsMap()
+ private
+ pure
+ returns (
+ mapping(string => AdditionalRecipient[])
+ storage additionalRecipientsMap
+ )
+ {
+ bytes32 position = ADDITIONAL_RECIPIENTS_MAP_POSITION;
+ assembly {
+ additionalRecipientsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an AdditionalRecipient's fields, which modifies the
+ // AdditionalRecipient in-place and
+ // returns it
+
+ function withAmount(
+ AdditionalRecipient memory item,
+ uint256 amount
+ ) internal pure returns (AdditionalRecipient memory) {
+ item.amount = amount;
+ return item;
+ }
+
+ function withRecipient(
+ AdditionalRecipient memory item,
+ address recipient
+ ) internal pure returns (AdditionalRecipient memory) {
+ item.recipient = payable(recipient);
+ return item;
+ }
+}
diff --git a/contracts/helpers/sol/lib/AdvancedOrderLib.sol b/contracts/helpers/sol/lib/AdvancedOrderLib.sol
new file mode 100644
index 000000000..ee820c1e2
--- /dev/null
+++ b/contracts/helpers/sol/lib/AdvancedOrderLib.sol
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ AdvancedOrder,
+ Order,
+ OrderParameters
+} from "../../../lib/ConsiderationStructs.sol";
+import { OrderParametersLib } from "./OrderParametersLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library AdvancedOrderLib {
+ bytes32 private constant ADVANCED_ORDER_MAP_POSITION =
+ keccak256("seaport.AdvancedOrderDefaults");
+ bytes32 private constant ADVANCED_ORDERS_MAP_POSITION =
+ keccak256("seaport.AdvancedOrdersDefaults");
+
+ using OrderParametersLib for OrderParameters;
+
+ /**
+ * @notice clears a default AdvancedOrder from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => AdvancedOrder)
+ storage advancedOrderMap = _advancedOrderMap();
+ AdvancedOrder storage item = advancedOrderMap[defaultName];
+ clear(item);
+ }
+
+ function clear(AdvancedOrder storage item) internal {
+ // clear all fields
+ item.parameters.clear();
+ item.signature = "";
+ item.numerator = 0;
+ item.denominator = 0;
+ item.extraData = "";
+ }
+
+ function clear(AdvancedOrder[] storage items) internal {
+ while (items.length > 0) {
+ clear(items[items.length - 1]);
+ items.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default AdvancedOrder from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (AdvancedOrder memory item) {
+ mapping(string => AdvancedOrder)
+ storage advancedOrderMap = _advancedOrderMap();
+ item = advancedOrderMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (AdvancedOrder[] memory items) {
+ mapping(string => AdvancedOrder[])
+ storage advancedOrdersMap = _advancedOrdersMap();
+ items = advancedOrdersMap[defaultName];
+ }
+
+ function empty() internal pure returns (AdvancedOrder memory) {
+ return AdvancedOrder(OrderParametersLib.empty(), 0, 0, "", "");
+ }
+
+ /**
+ * @notice saves an AdvancedOrder as a named default
+ * @param advancedOrder the AdvancedOrder to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ AdvancedOrder memory advancedOrder,
+ string memory defaultName
+ ) internal returns (AdvancedOrder memory _advancedOrder) {
+ mapping(string => AdvancedOrder)
+ storage advancedOrderMap = _advancedOrderMap();
+ StructCopier.setAdvancedOrder(
+ advancedOrderMap[defaultName],
+ advancedOrder
+ );
+ return advancedOrder;
+ }
+
+ function saveDefaultMany(
+ AdvancedOrder[] memory advancedOrders,
+ string memory defaultName
+ ) internal returns (AdvancedOrder[] memory _advancedOrders) {
+ mapping(string => AdvancedOrder[])
+ storage advancedOrdersMap = _advancedOrdersMap();
+ StructCopier.setAdvancedOrders(
+ advancedOrdersMap[defaultName],
+ advancedOrders
+ );
+ return advancedOrders;
+ }
+
+ /**
+ * @notice makes a copy of an AdvancedOrder in-memory
+ * @param item the AdvancedOrder to make a copy of in-memory
+ */
+ function copy(
+ AdvancedOrder memory item
+ ) internal pure returns (AdvancedOrder memory) {
+ return
+ AdvancedOrder({
+ parameters: item.parameters.copy(),
+ numerator: item.numerator,
+ denominator: item.denominator,
+ signature: item.signature,
+ extraData: item.extraData
+ });
+ }
+
+ function copy(
+ AdvancedOrder[] memory items
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory copiedItems = new AdvancedOrder[](items.length);
+ for (uint256 i = 0; i < items.length; i++) {
+ copiedItems[i] = copy(items[i]);
+ }
+ return copiedItems;
+ }
+
+ /**
+ * @notice gets the storage position of the default AdvancedOrder map
+ */
+ function _advancedOrderMap()
+ private
+ pure
+ returns (mapping(string => AdvancedOrder) storage advancedOrderMap)
+ {
+ bytes32 position = ADVANCED_ORDER_MAP_POSITION;
+ assembly {
+ advancedOrderMap.slot := position
+ }
+ }
+
+ function _advancedOrdersMap()
+ private
+ pure
+ returns (mapping(string => AdvancedOrder[]) storage advancedOrdersMap)
+ {
+ bytes32 position = ADVANCED_ORDERS_MAP_POSITION;
+ assembly {
+ advancedOrdersMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an AdvancedOrder's fields, which modifies the AdvancedOrder in-place
+ // and
+ // returns it
+
+ function withParameters(
+ AdvancedOrder memory advancedOrder,
+ OrderParameters memory parameters
+ ) internal pure returns (AdvancedOrder memory) {
+ advancedOrder.parameters = parameters.copy();
+ return advancedOrder;
+ }
+
+ function withNumerator(
+ AdvancedOrder memory advancedOrder,
+ uint120 numerator
+ ) internal pure returns (AdvancedOrder memory) {
+ advancedOrder.numerator = numerator;
+ return advancedOrder;
+ }
+
+ function withDenominator(
+ AdvancedOrder memory advancedOrder,
+ uint120 denominator
+ ) internal pure returns (AdvancedOrder memory) {
+ advancedOrder.denominator = denominator;
+ return advancedOrder;
+ }
+
+ function withSignature(
+ AdvancedOrder memory advancedOrder,
+ bytes memory signature
+ ) internal pure returns (AdvancedOrder memory) {
+ advancedOrder.signature = signature;
+ return advancedOrder;
+ }
+
+ function withExtraData(
+ AdvancedOrder memory advancedOrder,
+ bytes memory extraData
+ ) internal pure returns (AdvancedOrder memory) {
+ advancedOrder.extraData = extraData;
+ return advancedOrder;
+ }
+
+ function toOrder(
+ AdvancedOrder memory advancedOrder
+ ) internal pure returns (Order memory order) {
+ order.parameters = advancedOrder.parameters.copy();
+ order.signature = advancedOrder.signature;
+ }
+}
diff --git a/contracts/helpers/sol/lib/ArrayLib.sol b/contracts/helpers/sol/lib/ArrayLib.sol
new file mode 100644
index 000000000..8c8c25abd
--- /dev/null
+++ b/contracts/helpers/sol/lib/ArrayLib.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+library ArrayLib {
+ function setBytes32s(
+ bytes32[] storage array,
+ bytes32[] memory values
+ ) internal {
+ while (array.length > 0) {
+ array.pop();
+ }
+ for (uint256 i = 0; i < values.length; i++) {
+ array.push(values[i]);
+ }
+ }
+
+ function copy(
+ bytes32[] memory array
+ ) internal pure returns (bytes32[] memory) {
+ bytes32[] memory copiedArray = new bytes32[](array.length);
+ for (uint256 i = 0; i < array.length; i++) {
+ copiedArray[i] = array[i];
+ }
+ return copiedArray;
+ }
+}
diff --git a/contracts/helpers/sol/lib/BasicOrderParametersLib.sol b/contracts/helpers/sol/lib/BasicOrderParametersLib.sol
new file mode 100644
index 000000000..fb8deb965
--- /dev/null
+++ b/contracts/helpers/sol/lib/BasicOrderParametersLib.sol
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ BasicOrderParameters,
+ OrderComponents,
+ OrderParameters,
+ ConsiderationItem,
+ OrderParameters,
+ OfferItem,
+ AdditionalRecipient
+} from "../../../lib/ConsiderationStructs.sol";
+import {
+ OrderType,
+ ItemType,
+ BasicOrderType
+} from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+import { AdditionalRecipientLib } from "./AdditionalRecipientLib.sol";
+
+library BasicOrderParametersLib {
+ using BasicOrderParametersLib for BasicOrderParameters;
+ using AdditionalRecipientLib for AdditionalRecipient[];
+
+ bytes32 private constant BASIC_ORDER_PARAMETERS_MAP_POSITION =
+ keccak256("seaport.BasicOrderParametersDefaults");
+ bytes32 private constant BASIC_ORDER_PARAMETERS_ARRAY_MAP_POSITION =
+ keccak256("seaport.BasicOrderParametersArrayDefaults");
+
+ function clear(BasicOrderParameters storage basicParameters) internal {
+ // uninitialized pointers take up no new memory (versus one word for initializing length-0)
+ AdditionalRecipient[] memory additionalRecipients;
+
+ basicParameters.considerationToken = address(0);
+ basicParameters.considerationIdentifier = 0;
+ basicParameters.considerationAmount = 0;
+ basicParameters.offerer = payable(address(0));
+ basicParameters.zone = address(0);
+ basicParameters.offerToken = address(0);
+ basicParameters.offerIdentifier = 0;
+ basicParameters.offerAmount = 0;
+ basicParameters.basicOrderType = BasicOrderType(0);
+ basicParameters.startTime = 0;
+ basicParameters.endTime = 0;
+ basicParameters.zoneHash = bytes32(0);
+ basicParameters.salt = 0;
+ basicParameters.offererConduitKey = bytes32(0);
+ basicParameters.fulfillerConduitKey = bytes32(0);
+ basicParameters.totalOriginalAdditionalRecipients = 0;
+ StructCopier.setAdditionalRecipients(
+ basicParameters.additionalRecipients,
+ additionalRecipients
+ );
+ basicParameters.signature = new bytes(0);
+ }
+
+ function clear(
+ BasicOrderParameters[] storage basicParametersArray
+ ) internal {
+ while (basicParametersArray.length > 0) {
+ basicParametersArray[basicParametersArray.length - 1].clear();
+ basicParametersArray.pop();
+ }
+ }
+
+ /**
+ * @notice clears a default BasicOrderParameters from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => BasicOrderParameters)
+ storage orderComponentsMap = _orderComponentsMap();
+ BasicOrderParameters storage basicParameters = orderComponentsMap[
+ defaultName
+ ];
+ basicParameters.clear();
+ }
+
+ function empty() internal pure returns (BasicOrderParameters memory item) {
+ AdditionalRecipient[] memory additionalRecipients;
+ item = BasicOrderParameters({
+ considerationToken: address(0),
+ considerationIdentifier: 0,
+ considerationAmount: 0,
+ offerer: payable(address(0)),
+ zone: address(0),
+ offerToken: address(0),
+ offerIdentifier: 0,
+ offerAmount: 0,
+ basicOrderType: BasicOrderType(0),
+ startTime: 0,
+ endTime: 0,
+ zoneHash: bytes32(0),
+ salt: 0,
+ offererConduitKey: bytes32(0),
+ fulfillerConduitKey: bytes32(0),
+ totalOriginalAdditionalRecipients: 0,
+ additionalRecipients: additionalRecipients,
+ signature: new bytes(0)
+ });
+ }
+
+ /**
+ * @notice gets a default BasicOrderParameters from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (BasicOrderParameters memory item) {
+ mapping(string => BasicOrderParameters)
+ storage orderComponentsMap = _orderComponentsMap();
+ item = orderComponentsMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (BasicOrderParameters[] memory items) {
+ mapping(string => BasicOrderParameters[])
+ storage orderComponentsArrayMap = _orderComponentsArrayMap();
+ items = orderComponentsArrayMap[defaultName];
+ }
+
+ /**
+ * @notice saves an BasicOrderParameters as a named default
+ * @param orderComponents the BasicOrderParameters to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ BasicOrderParameters memory orderComponents,
+ string memory defaultName
+ ) internal returns (BasicOrderParameters memory _orderComponents) {
+ mapping(string => BasicOrderParameters)
+ storage orderComponentsMap = _orderComponentsMap();
+ BasicOrderParameters storage destination = orderComponentsMap[
+ defaultName
+ ];
+ StructCopier.setBasicOrderParameters(destination, orderComponents);
+ return orderComponents;
+ }
+
+ function saveDefaultMany(
+ BasicOrderParameters[] memory orderComponents,
+ string memory defaultName
+ ) internal returns (BasicOrderParameters[] memory _orderComponents) {
+ mapping(string => BasicOrderParameters[])
+ storage orderComponentsArrayMap = _orderComponentsArrayMap();
+ BasicOrderParameters[] storage destination = orderComponentsArrayMap[
+ defaultName
+ ];
+ StructCopier.setBasicOrderParameters(destination, orderComponents);
+ return orderComponents;
+ }
+
+ /**
+ * @notice makes a copy of an BasicOrderParameters in-memory
+ * @param item the BasicOrderParameters to make a copy of in-memory
+ */
+ function copy(
+ BasicOrderParameters memory item
+ ) internal pure returns (BasicOrderParameters memory) {
+ return
+ BasicOrderParameters({
+ considerationToken: item.considerationToken,
+ considerationIdentifier: item.considerationIdentifier,
+ considerationAmount: item.considerationAmount,
+ offerer: item.offerer,
+ zone: item.zone,
+ offerToken: item.offerToken,
+ offerIdentifier: item.offerIdentifier,
+ offerAmount: item.offerAmount,
+ basicOrderType: item.basicOrderType,
+ startTime: item.startTime,
+ endTime: item.endTime,
+ zoneHash: item.zoneHash,
+ salt: item.salt,
+ offererConduitKey: item.offererConduitKey,
+ fulfillerConduitKey: item.fulfillerConduitKey,
+ totalOriginalAdditionalRecipients: item
+ .totalOriginalAdditionalRecipients,
+ additionalRecipients: item.additionalRecipients.copy(),
+ signature: item.signature
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default BasicOrderParameters map
+ */
+ function _orderComponentsMap()
+ private
+ pure
+ returns (
+ mapping(string => BasicOrderParameters) storage orderComponentsMap
+ )
+ {
+ bytes32 position = BASIC_ORDER_PARAMETERS_MAP_POSITION;
+ assembly {
+ orderComponentsMap.slot := position
+ }
+ }
+
+ function _orderComponentsArrayMap()
+ private
+ pure
+ returns (
+ mapping(string => BasicOrderParameters[])
+ storage orderComponentsArrayMap
+ )
+ {
+ bytes32 position = BASIC_ORDER_PARAMETERS_ARRAY_MAP_POSITION;
+ assembly {
+ orderComponentsArrayMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an in-memory BasicOrderParameters's fields, which modifies the
+ // BasicOrderParameters in-memory and returns it
+
+ function withConsiderationToken(
+ BasicOrderParameters memory item,
+ address value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.considerationToken = value;
+ return item;
+ }
+
+ function withConsiderationIdentifier(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.considerationIdentifier = value;
+ return item;
+ }
+
+ function withConsiderationAmount(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.considerationAmount = value;
+ return item;
+ }
+
+ function withOfferer(
+ BasicOrderParameters memory item,
+ address value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.offerer = payable(value);
+ return item;
+ }
+
+ function withZone(
+ BasicOrderParameters memory item,
+ address value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.zone = value;
+ return item;
+ }
+
+ function withOfferToken(
+ BasicOrderParameters memory item,
+ address value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.offerToken = value;
+ return item;
+ }
+
+ function withOfferIdentifier(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.offerIdentifier = value;
+ return item;
+ }
+
+ function withOfferAmount(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.offerAmount = value;
+ return item;
+ }
+
+ function withBasicOrderType(
+ BasicOrderParameters memory item,
+ BasicOrderType value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.basicOrderType = value;
+ return item;
+ }
+
+ function withStartTime(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.startTime = value;
+ return item;
+ }
+
+ function withEndTime(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.endTime = value;
+ return item;
+ }
+
+ function withZoneHash(
+ BasicOrderParameters memory item,
+ bytes32 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.zoneHash = value;
+ return item;
+ }
+
+ function withSalt(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.salt = value;
+ return item;
+ }
+
+ function withOffererConduitKey(
+ BasicOrderParameters memory item,
+ bytes32 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.offererConduitKey = value;
+ return item;
+ }
+
+ function withFulfillerConduitKey(
+ BasicOrderParameters memory item,
+ bytes32 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.fulfillerConduitKey = value;
+ return item;
+ }
+
+ function withTotalOriginalAdditionalRecipients(
+ BasicOrderParameters memory item,
+ uint256 value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.totalOriginalAdditionalRecipients = value;
+ return item;
+ }
+
+ function withAdditionalRecipients(
+ BasicOrderParameters memory item,
+ AdditionalRecipient[] memory value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.additionalRecipients = value;
+ return item;
+ }
+
+ function withSignature(
+ BasicOrderParameters memory item,
+ bytes memory value
+ ) internal pure returns (BasicOrderParameters memory) {
+ item.signature = value;
+ return item;
+ }
+}
diff --git a/contracts/helpers/sol/lib/ConsiderationItemLib.sol b/contracts/helpers/sol/lib/ConsiderationItemLib.sol
new file mode 100644
index 000000000..dbce2fb99
--- /dev/null
+++ b/contracts/helpers/sol/lib/ConsiderationItemLib.sol
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ ConsiderationItem,
+ ReceivedItem
+} from "../../../lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library ConsiderationItemLib {
+ bytes32 private constant CONSIDERATION_ITEM_MAP_POSITION =
+ keccak256("seaport.ConsiderationItemDefaults");
+ bytes32 private constant CONSIDERATION_ITEMS_MAP_POSITION =
+ keccak256("seaport.ConsiderationItemsDefaults");
+
+ function _clear(ConsiderationItem storage item) internal {
+ // clear all fields
+ item.itemType = ItemType.NATIVE;
+ item.token = address(0);
+ item.identifierOrCriteria = 0;
+ item.startAmount = 0;
+ item.endAmount = 0;
+ item.recipient = payable(address(0));
+ }
+
+ /**
+ * @notice clears a default ConsiderationItem from storage
+ * @param defaultName the name of the default to clear
+ */
+
+ function clear(string memory defaultName) internal {
+ mapping(string => ConsiderationItem)
+ storage considerationItemMap = _considerationItemMap();
+ ConsiderationItem storage item = considerationItemMap[defaultName];
+ _clear(item);
+ }
+
+ function clearMany(string memory defaultsName) internal {
+ mapping(string => ConsiderationItem[])
+ storage considerationItemsMap = _considerationItemsMap();
+ ConsiderationItem[] storage items = considerationItemsMap[defaultsName];
+ while (items.length > 0) {
+ _clear(items[items.length - 1]);
+ items.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default ConsiderationItem from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (ConsiderationItem memory item) {
+ mapping(string => ConsiderationItem)
+ storage considerationItemMap = _considerationItemMap();
+ item = considerationItemMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultsName
+ ) internal view returns (ConsiderationItem[] memory items) {
+ mapping(string => ConsiderationItem[])
+ storage considerationItemsMap = _considerationItemsMap();
+ items = considerationItemsMap[defaultsName];
+ }
+
+ function empty() internal pure returns (ConsiderationItem memory) {
+ return
+ ConsiderationItem({
+ itemType: ItemType(0),
+ token: address(0),
+ identifierOrCriteria: 0,
+ startAmount: 0,
+ endAmount: 0,
+ recipient: payable(address(0))
+ });
+ }
+
+ /**
+ * @notice saves an ConsiderationItem as a named default
+ * @param considerationItem the ConsiderationItem to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ ConsiderationItem memory considerationItem,
+ string memory defaultName
+ ) internal returns (ConsiderationItem memory _considerationItem) {
+ mapping(string => ConsiderationItem)
+ storage considerationItemMap = _considerationItemMap();
+ considerationItemMap[defaultName] = considerationItem;
+ return considerationItem;
+ }
+
+ function saveDefaultMany(
+ ConsiderationItem[] memory considerationItems,
+ string memory defaultsName
+ ) internal returns (ConsiderationItem[] memory _considerationItems) {
+ mapping(string => ConsiderationItem[])
+ storage considerationItemsMap = _considerationItemsMap();
+ ConsiderationItem[] storage items = considerationItemsMap[defaultsName];
+ clearMany(defaultsName);
+ StructCopier.setConsiderationItems(items, considerationItems);
+ return considerationItems;
+ }
+
+ /**
+ * @notice makes a copy of an ConsiderationItem in-memory
+ * @param item the ConsiderationItem to make a copy of in-memory
+ */
+ function copy(
+ ConsiderationItem memory item
+ ) internal pure returns (ConsiderationItem memory) {
+ return
+ ConsiderationItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifierOrCriteria: item.identifierOrCriteria,
+ startAmount: item.startAmount,
+ endAmount: item.endAmount,
+ recipient: item.recipient
+ });
+ }
+
+ function copy(
+ ConsiderationItem[] memory item
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory copies = new ConsiderationItem[](
+ item.length
+ );
+ for (uint256 i = 0; i < item.length; i++) {
+ copies[i] = ConsiderationItem({
+ itemType: item[i].itemType,
+ token: item[i].token,
+ identifierOrCriteria: item[i].identifierOrCriteria,
+ startAmount: item[i].startAmount,
+ endAmount: item[i].endAmount,
+ recipient: item[i].recipient
+ });
+ }
+ return copies;
+ }
+
+ /**
+ * @notice gets the storage position of the default ConsiderationItem map
+ */
+ function _considerationItemMap()
+ private
+ pure
+ returns (
+ mapping(string => ConsiderationItem) storage considerationItemMap
+ )
+ {
+ bytes32 position = CONSIDERATION_ITEM_MAP_POSITION;
+ assembly {
+ considerationItemMap.slot := position
+ }
+ }
+
+ function _considerationItemsMap()
+ private
+ pure
+ returns (
+ mapping(string => ConsiderationItem[]) storage considerationItemsMap
+ )
+ {
+ bytes32 position = CONSIDERATION_ITEMS_MAP_POSITION;
+ assembly {
+ considerationItemsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an ConsiderationItem's fields, which modifies the ConsiderationItem
+ // in-place and
+ // returns it
+
+ /**
+ * @notice sets the item type
+ * @param item the ConsiderationItem to modify
+ * @param itemType the item type to set
+ * @return the modified ConsiderationItem
+ */
+ function withItemType(
+ ConsiderationItem memory item,
+ ItemType itemType
+ ) internal pure returns (ConsiderationItem memory) {
+ item.itemType = itemType;
+ return item;
+ }
+
+ /**
+ * @notice sets the token address
+ * @param item the ConsiderationItem to modify
+ * @param token the token address to set
+ * @return the modified ConsiderationItem
+ */
+ function withToken(
+ ConsiderationItem memory item,
+ address token
+ ) internal pure returns (ConsiderationItem memory) {
+ item.token = token;
+ return item;
+ }
+
+ /**
+ * @notice sets the identifier or criteria
+ * @param item the ConsiderationItem to modify
+ * @param identifierOrCriteria the identifier or criteria to set
+ * @return the modified ConsiderationItem
+ */
+ function withIdentifierOrCriteria(
+ ConsiderationItem memory item,
+ uint256 identifierOrCriteria
+ ) internal pure returns (ConsiderationItem memory) {
+ item.identifierOrCriteria = identifierOrCriteria;
+ return item;
+ }
+
+ /**
+ * @notice sets the start amount
+ * @param item the ConsiderationItem to modify
+ * @param startAmount the start amount to set
+ * @return the modified ConsiderationItem
+ */
+ function withStartAmount(
+ ConsiderationItem memory item,
+ uint256 startAmount
+ ) internal pure returns (ConsiderationItem memory) {
+ item.startAmount = startAmount;
+ return item;
+ }
+
+ /**
+ * @notice sets the end amount
+ * @param item the ConsiderationItem to modify
+ * @param endAmount the end amount to set
+ * @return the modified ConsiderationItem
+ */
+ function withEndAmount(
+ ConsiderationItem memory item,
+ uint256 endAmount
+ ) internal pure returns (ConsiderationItem memory) {
+ item.endAmount = endAmount;
+ return item;
+ }
+
+ /**
+ * @notice sets the recipient
+ * @param item the ConsiderationItem to modify
+ * @param recipient the recipient to set
+ * @return the modified ConsiderationItem
+ */
+ function withRecipient(
+ ConsiderationItem memory item,
+ address recipient
+ ) internal pure returns (ConsiderationItem memory) {
+ item.recipient = payable(recipient);
+ return item;
+ }
+
+ function toReceivedItem(
+ ConsiderationItem memory item
+ ) internal pure returns (ReceivedItem memory) {
+ return
+ ReceivedItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifier: item.identifierOrCriteria,
+ amount: item.startAmount,
+ recipient: item.recipient
+ });
+ }
+}
diff --git a/contracts/helpers/sol/lib/CriteriaResolverLib.sol b/contracts/helpers/sol/lib/CriteriaResolverLib.sol
new file mode 100644
index 000000000..e2dd1c840
--- /dev/null
+++ b/contracts/helpers/sol/lib/CriteriaResolverLib.sol
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ CriteriaResolver,
+ OfferItem
+} from "../../../lib/ConsiderationStructs.sol";
+import { Side } from "../../../lib/ConsiderationEnums.sol";
+import { ArrayLib } from "./ArrayLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library CriteriaResolverLib {
+ bytes32 private constant CRITERIA_RESOLVER_MAP_POSITION =
+ keccak256("seaport.CriteriaResolverDefaults");
+ bytes32 private constant CRITERIA_RESOLVERS_MAP_POSITION =
+ keccak256("seaport.CriteriaResolversDefaults");
+
+ using ArrayLib for bytes32[];
+
+ /**
+ * @notice clears a default CriteriaResolver from storage
+ * @param defaultName the name of the default to clear
+ */
+
+ function clear(string memory defaultName) internal {
+ mapping(string => CriteriaResolver)
+ storage criteriaResolverMap = _criteriaResolverMap();
+ CriteriaResolver storage resolver = criteriaResolverMap[defaultName];
+ // clear all fields
+ clear(resolver);
+ }
+
+ function clear(CriteriaResolver storage resolver) internal {
+ bytes32[] memory criteriaProof;
+ resolver.orderIndex = 0;
+ resolver.side = Side(0);
+ resolver.index = 0;
+ resolver.identifier = 0;
+ ArrayLib.setBytes32s(resolver.criteriaProof, criteriaProof);
+ }
+
+ function clear(CriteriaResolver[] storage resolvers) internal {
+ while (resolvers.length > 0) {
+ clear(resolvers[resolvers.length - 1]);
+ resolvers.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default CriteriaResolver from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (CriteriaResolver memory resolver) {
+ mapping(string => CriteriaResolver)
+ storage criteriaResolverMap = _criteriaResolverMap();
+ resolver = criteriaResolverMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultsName
+ ) internal view returns (CriteriaResolver[] memory resolvers) {
+ mapping(string => CriteriaResolver[])
+ storage criteriaResolversMap = _criteriaResolversMap();
+ resolvers = criteriaResolversMap[defaultsName];
+ }
+
+ /**
+ * @notice saves an CriteriaResolver as a named default
+ * @param criteriaResolver the CriteriaResolver to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ CriteriaResolver memory criteriaResolver,
+ string memory defaultName
+ ) internal returns (CriteriaResolver memory _criteriaResolver) {
+ mapping(string => CriteriaResolver)
+ storage criteriaResolverMap = _criteriaResolverMap();
+ CriteriaResolver storage resolver = criteriaResolverMap[defaultName];
+ resolver.orderIndex = criteriaResolver.orderIndex;
+ resolver.side = criteriaResolver.side;
+ resolver.index = criteriaResolver.index;
+ resolver.identifier = criteriaResolver.identifier;
+ ArrayLib.setBytes32s(
+ resolver.criteriaProof,
+ criteriaResolver.criteriaProof
+ );
+ return criteriaResolver;
+ }
+
+ function saveDefaultMany(
+ CriteriaResolver[] memory criteriaResolvers,
+ string memory defaultName
+ ) internal returns (CriteriaResolver[] memory _criteriaResolvers) {
+ mapping(string => CriteriaResolver[])
+ storage criteriaResolversMap = _criteriaResolversMap();
+ CriteriaResolver[] storage resolvers = criteriaResolversMap[
+ defaultName
+ ];
+ // todo: make sure we do this elsewhere
+ clear(resolvers);
+ StructCopier.setCriteriaResolvers(resolvers, criteriaResolvers);
+ return criteriaResolvers;
+ }
+
+ /**
+ * @notice makes a copy of an CriteriaResolver in-memory
+ * @param resolver the CriteriaResolver to make a copy of in-memory
+ */
+ function copy(
+ CriteriaResolver memory resolver
+ ) internal pure returns (CriteriaResolver memory) {
+ return
+ CriteriaResolver({
+ orderIndex: resolver.orderIndex,
+ side: resolver.side,
+ index: resolver.index,
+ identifier: resolver.identifier,
+ criteriaProof: resolver.criteriaProof.copy()
+ });
+ }
+
+ function copy(
+ CriteriaResolver[] memory resolvers
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory copiedItems = new CriteriaResolver[](
+ resolvers.length
+ );
+ for (uint256 i = 0; i < resolvers.length; i++) {
+ copiedItems[i] = copy(resolvers[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (CriteriaResolver memory) {
+ bytes32[] memory proof;
+ return
+ CriteriaResolver({
+ orderIndex: 0,
+ side: Side(0),
+ index: 0,
+ identifier: 0,
+ criteriaProof: proof
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default CriteriaResolver map
+ */
+ function _criteriaResolverMap()
+ private
+ pure
+ returns (
+ mapping(string => CriteriaResolver) storage criteriaResolverMap
+ )
+ {
+ bytes32 position = CRITERIA_RESOLVER_MAP_POSITION;
+ assembly {
+ criteriaResolverMap.slot := position
+ }
+ }
+
+ function _criteriaResolversMap()
+ private
+ pure
+ returns (
+ mapping(string => CriteriaResolver[]) storage criteriaResolversMap
+ )
+ {
+ bytes32 position = CRITERIA_RESOLVERS_MAP_POSITION;
+ assembly {
+ criteriaResolversMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an CriteriaResolver's fields, which modifies the CriteriaResolver
+ // in-place and
+ // returns it
+
+ function withOrderIndex(
+ CriteriaResolver memory resolver,
+ uint256 orderIndex
+ ) internal pure returns (CriteriaResolver memory) {
+ resolver.orderIndex = orderIndex;
+ return resolver;
+ }
+
+ function withSide(
+ CriteriaResolver memory resolver,
+ Side side
+ ) internal pure returns (CriteriaResolver memory) {
+ resolver.side = side;
+ return resolver;
+ }
+
+ function withIndex(
+ CriteriaResolver memory resolver,
+ uint256 index
+ ) internal pure returns (CriteriaResolver memory) {
+ resolver.index = index;
+ return resolver;
+ }
+
+ function withIdentifier(
+ CriteriaResolver memory resolver,
+ uint256 identifier
+ ) internal pure returns (CriteriaResolver memory) {
+ resolver.identifier = identifier;
+ return resolver;
+ }
+
+ function withCriteriaProof(
+ CriteriaResolver memory resolver,
+ bytes32[] memory criteriaProof
+ ) internal pure returns (CriteriaResolver memory) {
+ // todo: consider copying?
+ resolver.criteriaProof = criteriaProof;
+ return resolver;
+ }
+}
diff --git a/contracts/helpers/sol/lib/ExecutionLib.sol b/contracts/helpers/sol/lib/ExecutionLib.sol
new file mode 100644
index 000000000..a77356237
--- /dev/null
+++ b/contracts/helpers/sol/lib/ExecutionLib.sol
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { Execution, ReceivedItem } from "../../../lib/ConsiderationStructs.sol";
+import { ReceivedItemLib } from "./ReceivedItemLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library ExecutionLib {
+ bytes32 private constant EXECUTION_MAP_POSITION =
+ keccak256("seaport.ExecutionDefaults");
+ bytes32 private constant EXECUTIONS_MAP_POSITION =
+ keccak256("seaport.ExecutionsDefaults");
+
+ using ReceivedItemLib for ReceivedItem;
+ using ReceivedItemLib for ReceivedItem[];
+
+ /**
+ * @notice clears a default Execution from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => Execution) storage executionMap = _executionMap();
+ Execution storage item = executionMap[defaultName];
+ clear(item);
+ }
+
+ function clear(Execution storage execution) internal {
+ // clear all fields
+ execution.item = ReceivedItemLib.empty();
+ execution.offerer = address(0);
+ execution.conduitKey = bytes32(0);
+ }
+
+ function clear(Execution[] storage executions) internal {
+ while (executions.length > 0) {
+ clear(executions[executions.length - 1]);
+ executions.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default Execution from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (Execution memory item) {
+ mapping(string => Execution) storage executionMap = _executionMap();
+ item = executionMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (Execution[] memory items) {
+ mapping(string => Execution[]) storage executionsMap = _executionsMap();
+ items = executionsMap[defaultName];
+ }
+
+ /**
+ * @notice saves an Execution as a named default
+ * @param execution the Execution to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ Execution memory execution,
+ string memory defaultName
+ ) internal returns (Execution memory _execution) {
+ mapping(string => Execution) storage executionMap = _executionMap();
+ executionMap[defaultName] = execution;
+ return execution;
+ }
+
+ function saveDefaultMany(
+ Execution[] memory executions,
+ string memory defaultName
+ ) internal returns (Execution[] memory _executions) {
+ mapping(string => Execution[]) storage executionsMap = _executionsMap();
+ StructCopier.setExecutions(executionsMap[defaultName], executions);
+ return executions;
+ }
+
+ /**
+ * @notice makes a copy of an Execution in-memory
+ * @param item the Execution to make a copy of in-memory
+ */
+ function copy(
+ Execution memory item
+ ) internal pure returns (Execution memory) {
+ return
+ Execution({
+ item: item.item.copy(),
+ offerer: item.offerer,
+ conduitKey: item.conduitKey
+ });
+ }
+
+ function copy(
+ Execution[] memory item
+ ) internal pure returns (Execution[] memory) {
+ Execution[] memory copies = new Execution[](item.length);
+ for (uint256 i = 0; i < item.length; i++) {
+ copies[i] = copy(item[i]);
+ }
+ return copies;
+ }
+
+ function empty() internal pure returns (Execution memory) {
+ return
+ Execution({
+ item: ReceivedItemLib.empty(),
+ offerer: address(0),
+ conduitKey: bytes32(0)
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default Execution map
+ */
+ function _executionMap()
+ private
+ pure
+ returns (mapping(string => Execution) storage executionMap)
+ {
+ bytes32 position = EXECUTION_MAP_POSITION;
+ assembly {
+ executionMap.slot := position
+ }
+ }
+
+ function _executionsMap()
+ private
+ pure
+ returns (mapping(string => Execution[]) storage executionsMap)
+ {
+ bytes32 position = EXECUTIONS_MAP_POSITION;
+ assembly {
+ executionsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an Execution's fields, which modifies the Execution
+ // in-place and
+ // returns it
+
+ function withItem(
+ Execution memory execution,
+ ReceivedItem memory item
+ ) internal pure returns (Execution memory) {
+ execution.item = item.copy();
+ return execution;
+ }
+
+ function withOfferer(
+ Execution memory execution,
+ address offerer
+ ) internal pure returns (Execution memory) {
+ execution.offerer = offerer;
+ return execution;
+ }
+
+ function withConduitKey(
+ Execution memory execution,
+ bytes32 conduitKey
+ ) internal pure returns (Execution memory) {
+ execution.conduitKey = conduitKey;
+ return execution;
+ }
+}
diff --git a/contracts/helpers/sol/lib/FulfillmentComponentLib.sol b/contracts/helpers/sol/lib/FulfillmentComponentLib.sol
new file mode 100644
index 000000000..e5577a0d3
--- /dev/null
+++ b/contracts/helpers/sol/lib/FulfillmentComponentLib.sol
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ FulfillmentComponent,
+ OfferItem
+} from "../../../lib/ConsiderationStructs.sol";
+import { Side } from "../../../lib/ConsiderationEnums.sol";
+import { ArrayLib } from "./ArrayLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library FulfillmentComponentLib {
+ bytes32 private constant FULFILLMENT_COMPONENT_MAP_POSITION =
+ keccak256("seaport.FulfillmentComponentDefaults");
+ bytes32 private constant FULFILLMENT_COMPONENTS_MAP_POSITION =
+ keccak256("seaport.FulfillmentComponentsDefaults");
+
+ using ArrayLib for bytes32[];
+
+ /**
+ * @notice clears a default FulfillmentComponent from storage
+ * @param defaultName the name of the default to clear
+ */
+
+ function clear(string memory defaultName) internal {
+ mapping(string => FulfillmentComponent)
+ storage fulfillmentComponentMap = _fulfillmentComponentMap();
+ FulfillmentComponent storage component = fulfillmentComponentMap[
+ defaultName
+ ];
+ clear(component);
+ }
+
+ function clear(FulfillmentComponent storage component) internal {
+ component.orderIndex = 0;
+ component.itemIndex = 0;
+ }
+
+ function clear(FulfillmentComponent[] storage components) internal {
+ while (components.length > 0) {
+ clear(components[components.length - 1]);
+ components.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default FulfillmentComponent from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (FulfillmentComponent memory component) {
+ mapping(string => FulfillmentComponent)
+ storage fulfillmentComponentMap = _fulfillmentComponentMap();
+ component = fulfillmentComponentMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (FulfillmentComponent[] memory components) {
+ mapping(string => FulfillmentComponent[])
+ storage fulfillmentComponentMap = _fulfillmentComponentsMap();
+ components = fulfillmentComponentMap[defaultName];
+ }
+
+ /**
+ * @notice saves an FulfillmentComponent as a named default
+ * @param fulfillmentComponent the FulfillmentComponent to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ FulfillmentComponent memory fulfillmentComponent,
+ string memory defaultName
+ ) internal returns (FulfillmentComponent memory _fulfillmentComponent) {
+ mapping(string => FulfillmentComponent)
+ storage fulfillmentComponentMap = _fulfillmentComponentMap();
+ FulfillmentComponent storage component = fulfillmentComponentMap[
+ defaultName
+ ];
+ component.orderIndex = fulfillmentComponent.orderIndex;
+ component.itemIndex = fulfillmentComponent.itemIndex;
+ return fulfillmentComponent;
+ }
+
+ function saveDefaultMany(
+ FulfillmentComponent[] memory fulfillmentComponents,
+ string memory defaultName
+ ) internal returns (FulfillmentComponent[] memory _fulfillmentComponents) {
+ mapping(string => FulfillmentComponent[])
+ storage fulfillmentComponentsMap = _fulfillmentComponentsMap();
+ FulfillmentComponent[] storage components = fulfillmentComponentsMap[
+ defaultName
+ ];
+ clear(components);
+ StructCopier.setFulfillmentComponents(
+ components,
+ fulfillmentComponents
+ );
+
+ return fulfillmentComponents;
+ }
+
+ /**
+ * @notice makes a copy of an FulfillmentComponent in-memory
+ * @param component the FulfillmentComponent to make a copy of in-memory
+ */
+ function copy(
+ FulfillmentComponent memory component
+ ) internal pure returns (FulfillmentComponent memory) {
+ return
+ FulfillmentComponent({
+ orderIndex: component.orderIndex,
+ itemIndex: component.itemIndex
+ });
+ }
+
+ function copy(
+ FulfillmentComponent[] memory components
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory copiedItems = new FulfillmentComponent[](
+ components.length
+ );
+ for (uint256 i = 0; i < components.length; i++) {
+ copiedItems[i] = copy(components[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (FulfillmentComponent memory) {
+ return FulfillmentComponent({ orderIndex: 0, itemIndex: 0 });
+ }
+
+ /**
+ * @notice gets the storage position of the default FulfillmentComponent map
+ */
+ function _fulfillmentComponentMap()
+ private
+ pure
+ returns (
+ mapping(string => FulfillmentComponent)
+ storage fulfillmentComponentMap
+ )
+ {
+ bytes32 position = FULFILLMENT_COMPONENT_MAP_POSITION;
+ assembly {
+ fulfillmentComponentMap.slot := position
+ }
+ }
+
+ function _fulfillmentComponentsMap()
+ private
+ pure
+ returns (
+ mapping(string => FulfillmentComponent[])
+ storage fulfillmentComponentsMap
+ )
+ {
+ bytes32 position = FULFILLMENT_COMPONENTS_MAP_POSITION;
+ assembly {
+ fulfillmentComponentsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an FulfillmentComponent's fields, which modifies the
+ // FulfillmentComponent
+ // in-place and
+ // returns it
+
+ function withOrderIndex(
+ FulfillmentComponent memory component,
+ uint256 orderIndex
+ ) internal pure returns (FulfillmentComponent memory) {
+ component.orderIndex = orderIndex;
+ return component;
+ }
+
+ function withItemIndex(
+ FulfillmentComponent memory component,
+ uint256 itemIndex
+ ) internal pure returns (FulfillmentComponent memory) {
+ component.itemIndex = itemIndex;
+ return component;
+ }
+}
diff --git a/contracts/helpers/sol/lib/FulfillmentLib.sol b/contracts/helpers/sol/lib/FulfillmentLib.sol
new file mode 100644
index 000000000..e7c493a70
--- /dev/null
+++ b/contracts/helpers/sol/lib/FulfillmentLib.sol
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ Fulfillment,
+ FulfillmentComponent
+} from "../../../lib/ConsiderationStructs.sol";
+import { Side } from "../../../lib/ConsiderationEnums.sol";
+import { ArrayLib } from "./ArrayLib.sol";
+import { FulfillmentComponentLib } from "./FulfillmentComponentLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library FulfillmentLib {
+ bytes32 private constant FULFILLMENT_MAP_POSITION =
+ keccak256("seaport.FulfillmentDefaults");
+ bytes32 private constant FULFILLMENTS_MAP_POSITION =
+ keccak256("seaport.FulfillmentsDefaults");
+
+ using FulfillmentComponentLib for FulfillmentComponent[];
+ using StructCopier for FulfillmentComponent[];
+
+ /**
+ * @notice clears a default Fulfillment from storage
+ * @param defaultName the name of the default to clear
+ */
+
+ function clear(string memory defaultName) internal {
+ mapping(string => Fulfillment)
+ storage fulfillmentMap = _fulfillmentMap();
+ Fulfillment storage _fulfillment = fulfillmentMap[defaultName];
+ // clear all fields
+ FulfillmentComponent[] memory components;
+ _fulfillment.offerComponents.setFulfillmentComponents(components);
+ _fulfillment.considerationComponents.setFulfillmentComponents(
+ components
+ );
+ }
+
+ /**
+ * @notice gets a default Fulfillment from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (Fulfillment memory _fulfillment) {
+ mapping(string => Fulfillment)
+ storage fulfillmentMap = _fulfillmentMap();
+ _fulfillment = fulfillmentMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (Fulfillment[] memory _fulfillments) {
+ mapping(string => Fulfillment[])
+ storage fulfillmentsMap = _fulfillmentsMap();
+ _fulfillments = fulfillmentsMap[defaultName];
+ }
+
+ /**
+ * @notice saves an Fulfillment as a named default
+ * @param fulfillment the Fulfillment to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ Fulfillment memory fulfillment,
+ string memory defaultName
+ ) internal returns (Fulfillment memory _fulfillment) {
+ mapping(string => Fulfillment)
+ storage fulfillmentMap = _fulfillmentMap();
+ StructCopier.setFulfillment(fulfillmentMap[defaultName], fulfillment);
+
+ return fulfillment;
+ }
+
+ function saveDefaultMany(
+ Fulfillment[] memory fulfillments,
+ string memory defaultName
+ ) internal returns (Fulfillment[] memory _fulfillments) {
+ mapping(string => Fulfillment[])
+ storage fulfillmentsMap = _fulfillmentsMap();
+ StructCopier.setFulfillments(
+ fulfillmentsMap[defaultName],
+ fulfillments
+ );
+ return fulfillments;
+ }
+
+ /**
+ * @notice makes a copy of an Fulfillment in-memory
+ * @param _fulfillment the Fulfillment to make a copy of in-memory
+ */
+ function copy(
+ Fulfillment memory _fulfillment
+ ) internal pure returns (Fulfillment memory) {
+ return
+ Fulfillment({
+ offerComponents: _fulfillment.offerComponents.copy(),
+ considerationComponents: _fulfillment
+ .considerationComponents
+ .copy()
+ });
+ }
+
+ function copy(
+ Fulfillment[] memory _fulfillments
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory copiedItems = new Fulfillment[](
+ _fulfillments.length
+ );
+ for (uint256 i = 0; i < _fulfillments.length; i++) {
+ copiedItems[i] = copy(_fulfillments[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (Fulfillment memory) {
+ FulfillmentComponent[] memory components;
+ return
+ Fulfillment({
+ offerComponents: components,
+ considerationComponents: components
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default Fulfillment map
+ */
+ function _fulfillmentMap()
+ private
+ pure
+ returns (mapping(string => Fulfillment) storage fulfillmentMap)
+ {
+ bytes32 position = FULFILLMENT_MAP_POSITION;
+ assembly {
+ fulfillmentMap.slot := position
+ }
+ }
+
+ function _fulfillmentsMap()
+ private
+ pure
+ returns (mapping(string => Fulfillment[]) storage fulfillmentsMap)
+ {
+ bytes32 position = FULFILLMENTS_MAP_POSITION;
+ assembly {
+ fulfillmentsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an Fulfillment's fields, which modifies the
+ // Fulfillment
+ // in-place and
+ // returns it
+
+ function withOfferComponents(
+ Fulfillment memory _fulfillment,
+ FulfillmentComponent[] memory components
+ ) internal pure returns (Fulfillment memory) {
+ _fulfillment.offerComponents = components.copy();
+ return _fulfillment;
+ }
+
+ function withConsiderationComponents(
+ Fulfillment memory _fulfillment,
+ FulfillmentComponent[] memory components
+ ) internal pure returns (Fulfillment memory) {
+ _fulfillment.considerationComponents = components.copy();
+ return _fulfillment;
+ }
+}
diff --git a/contracts/helpers/sol/lib/OfferItemLib.sol b/contracts/helpers/sol/lib/OfferItemLib.sol
new file mode 100644
index 000000000..944445a40
--- /dev/null
+++ b/contracts/helpers/sol/lib/OfferItemLib.sol
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { OfferItem, SpentItem } from "../../../lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library OfferItemLib {
+ bytes32 private constant OFFER_ITEM_MAP_POSITION =
+ keccak256("seaport.OfferItemDefaults");
+ bytes32 private constant OFFER_ITEMS_MAP_POSITION =
+ keccak256("seaport.OfferItemsDefaults");
+
+ function _clear(OfferItem storage item) internal {
+ // clear all fields
+ item.itemType = ItemType.NATIVE;
+ item.token = address(0);
+ item.identifierOrCriteria = 0;
+ item.startAmount = 0;
+ item.endAmount = 0;
+ }
+
+ /**
+ * @notice clears a default OfferItem from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => OfferItem) storage offerItemMap = _offerItemMap();
+ OfferItem storage item = offerItemMap[defaultName];
+ _clear(item);
+ }
+
+ function clearMany(string memory defaultsName) internal {
+ mapping(string => OfferItem[]) storage offerItemsMap = _offerItemsMap();
+ OfferItem[] storage items = offerItemsMap[defaultsName];
+ while (items.length > 0) {
+ _clear(items[items.length - 1]);
+ items.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default OfferItem from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (OfferItem memory item) {
+ mapping(string => OfferItem) storage offerItemMap = _offerItemMap();
+ item = offerItemMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultsName
+ ) internal view returns (OfferItem[] memory items) {
+ mapping(string => OfferItem[]) storage offerItemsMap = _offerItemsMap();
+ items = offerItemsMap[defaultsName];
+ }
+
+ /**
+ * @notice saves an OfferItem as a named default
+ * @param offerItem the OfferItem to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ OfferItem memory offerItem,
+ string memory defaultName
+ ) internal returns (OfferItem memory _offerItem) {
+ mapping(string => OfferItem) storage offerItemMap = _offerItemMap();
+ offerItemMap[defaultName] = offerItem;
+ return offerItem;
+ }
+
+ function saveDefaultMany(
+ OfferItem[] memory offerItems,
+ string memory defaultsName
+ ) internal returns (OfferItem[] memory _offerItems) {
+ mapping(string => OfferItem[]) storage offerItemsMap = _offerItemsMap();
+ OfferItem[] storage items = offerItemsMap[defaultsName];
+ clearMany(defaultsName);
+ StructCopier.setOfferItems(items, offerItems);
+ return offerItems;
+ }
+
+ /**
+ * @notice makes a copy of an OfferItem in-memory
+ * @param item the OfferItem to make a copy of in-memory
+ */
+ function copy(
+ OfferItem memory item
+ ) internal pure returns (OfferItem memory) {
+ return
+ OfferItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifierOrCriteria: item.identifierOrCriteria,
+ startAmount: item.startAmount,
+ endAmount: item.endAmount
+ });
+ }
+
+ function copy(
+ OfferItem[] memory items
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory copiedItems = new OfferItem[](items.length);
+ for (uint256 i = 0; i < items.length; i++) {
+ copiedItems[i] = copy(items[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (OfferItem memory) {
+ return
+ OfferItem({
+ itemType: ItemType.NATIVE,
+ token: address(0),
+ identifierOrCriteria: 0,
+ startAmount: 0,
+ endAmount: 0
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default OfferItem map
+ */
+ function _offerItemMap()
+ private
+ pure
+ returns (mapping(string => OfferItem) storage offerItemMap)
+ {
+ bytes32 position = OFFER_ITEM_MAP_POSITION;
+ assembly {
+ offerItemMap.slot := position
+ }
+ }
+
+ /**
+ * @notice gets the storage position of the default OfferItem[] map
+ */
+ function _offerItemsMap()
+ private
+ pure
+ returns (mapping(string => OfferItem[]) storage offerItemMap)
+ {
+ bytes32 position = OFFER_ITEMS_MAP_POSITION;
+ assembly {
+ offerItemMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an OfferItem's fields, which modifies the OfferItem in-place and
+ // returns it
+
+ /**
+ * @notice sets the item type
+ * @param item the OfferItem to modify
+ * @param itemType the item type to set
+ * @return the modified OfferItem
+ */
+ function withItemType(
+ OfferItem memory item,
+ ItemType itemType
+ ) internal pure returns (OfferItem memory) {
+ item.itemType = itemType;
+ return item;
+ }
+
+ /**
+ * @notice sets the token address
+ * @param item the OfferItem to modify
+ * @param token the token address to set
+ * @return the modified OfferItem
+ */
+ function withToken(
+ OfferItem memory item,
+ address token
+ ) internal pure returns (OfferItem memory) {
+ item.token = token;
+ return item;
+ }
+
+ /**
+ * @notice sets the identifier or criteria
+ * @param item the OfferItem to modify
+ * @param identifierOrCriteria the identifier or criteria to set
+ * @return the modified OfferItem
+ */
+ function withIdentifierOrCriteria(
+ OfferItem memory item,
+ uint256 identifierOrCriteria
+ ) internal pure returns (OfferItem memory) {
+ item.identifierOrCriteria = identifierOrCriteria;
+ return item;
+ }
+
+ /**
+ * @notice sets the start amount
+ * @param item the OfferItem to modify
+ * @param startAmount the start amount to set
+ * @return the modified OfferItem
+ */
+ function withStartAmount(
+ OfferItem memory item,
+ uint256 startAmount
+ ) internal pure returns (OfferItem memory) {
+ item.startAmount = startAmount;
+ return item;
+ }
+
+ /**
+ * @notice sets the end amount
+ * @param item the OfferItem to modify
+ * @param endAmount the end amount to set
+ * @return the modified OfferItem
+ */
+ function withEndAmount(
+ OfferItem memory item,
+ uint256 endAmount
+ ) internal pure returns (OfferItem memory) {
+ item.endAmount = endAmount;
+ return item;
+ }
+
+ function toSpentItem(
+ OfferItem memory item
+ ) internal pure returns (SpentItem memory) {
+ return
+ SpentItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifier: item.identifierOrCriteria,
+ amount: item.startAmount
+ });
+ }
+}
diff --git a/contracts/helpers/sol/lib/OrderComponentsLib.sol b/contracts/helpers/sol/lib/OrderComponentsLib.sol
new file mode 100644
index 000000000..375c01cd5
--- /dev/null
+++ b/contracts/helpers/sol/lib/OrderComponentsLib.sol
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ BasicOrderParameters,
+ OrderComponents,
+ ConsiderationItem,
+ OrderParameters,
+ OfferItem,
+ AdditionalRecipient
+} from "../../../lib/ConsiderationStructs.sol";
+import {
+ OrderType,
+ ItemType,
+ BasicOrderType
+} from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+import { OfferItemLib } from "./OfferItemLib.sol";
+import { ConsiderationItemLib } from "./ConsiderationItemLib.sol";
+
+library OrderComponentsLib {
+ using OrderComponentsLib for OrderComponents;
+ using OfferItemLib for OfferItem[];
+ using ConsiderationItemLib for ConsiderationItem[];
+
+ bytes32 private constant ORDER_COMPONENTS_MAP_POSITION =
+ keccak256("seaport.OrderComponentsDefaults");
+ bytes32 private constant ORDER_COMPONENTS_ARRAY_MAP_POSITION =
+ keccak256("seaport.OrderComponentsArrayDefaults");
+
+ function clear(OrderComponents storage components) internal {
+ // uninitialized pointers take up no new memory (versus one word for initializing length-0)
+ OfferItem[] memory offer;
+ ConsiderationItem[] memory consideration;
+
+ // clear all fields
+ components.offerer = address(0);
+ components.zone = address(0);
+ StructCopier.setOfferItems(components.offer, offer);
+ StructCopier.setConsiderationItems(
+ components.consideration,
+ consideration
+ );
+ components.orderType = OrderType(0);
+ components.startTime = 0;
+ components.endTime = 0;
+ components.zoneHash = bytes32(0);
+ components.salt = 0;
+ components.conduitKey = bytes32(0);
+ components.counter = 0;
+ }
+
+ function clear(OrderComponents[] storage components) internal {
+ while (components.length > 0) {
+ clear(components[components.length - 1]);
+ components.pop();
+ }
+ }
+
+ /**
+ * @notice clears a default OrderComponents from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => OrderComponents)
+ storage orderComponentsMap = _orderComponentsMap();
+ OrderComponents storage components = orderComponentsMap[defaultName];
+ components.clear();
+ }
+
+ function empty() internal pure returns (OrderComponents memory item) {
+ OfferItem[] memory offer;
+ ConsiderationItem[] memory consideration;
+ item = OrderComponents({
+ offerer: address(0),
+ zone: address(0),
+ offer: offer,
+ consideration: consideration,
+ orderType: OrderType(0),
+ startTime: 0,
+ endTime: 0,
+ zoneHash: bytes32(0),
+ salt: 0,
+ conduitKey: bytes32(0),
+ counter: 0
+ });
+ }
+
+ /**
+ * @notice gets a default OrderComponents from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (OrderComponents memory item) {
+ mapping(string => OrderComponents)
+ storage orderComponentsMap = _orderComponentsMap();
+ item = orderComponentsMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (OrderComponents[] memory items) {
+ mapping(string => OrderComponents[])
+ storage orderComponentsArrayMap = _orderComponentsArrayMap();
+ items = orderComponentsArrayMap[defaultName];
+ }
+
+ /**
+ * @notice saves an OrderComponents as a named default
+ * @param orderComponents the OrderComponents to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ OrderComponents memory orderComponents,
+ string memory defaultName
+ ) internal returns (OrderComponents memory _orderComponents) {
+ mapping(string => OrderComponents)
+ storage orderComponentsMap = _orderComponentsMap();
+ OrderComponents storage destination = orderComponentsMap[defaultName];
+ StructCopier.setOrderComponents(destination, orderComponents);
+ return orderComponents;
+ }
+
+ function saveDefaultMany(
+ OrderComponents[] memory orderComponents,
+ string memory defaultName
+ ) internal returns (OrderComponents[] memory _orderComponents) {
+ mapping(string => OrderComponents[])
+ storage orderComponentsArrayMap = _orderComponentsArrayMap();
+ OrderComponents[] storage destination = orderComponentsArrayMap[
+ defaultName
+ ];
+ StructCopier.setOrderComponents(destination, orderComponents);
+ return orderComponents;
+ }
+
+ /**
+ * @notice makes a copy of an OrderComponents in-memory
+ * @param item the OrderComponents to make a copy of in-memory
+ */
+ function copy(
+ OrderComponents memory item
+ ) internal pure returns (OrderComponents memory) {
+ return
+ OrderComponents({
+ offerer: item.offerer,
+ zone: item.zone,
+ offer: item.offer.copy(),
+ consideration: item.consideration.copy(),
+ orderType: item.orderType,
+ startTime: item.startTime,
+ endTime: item.endTime,
+ zoneHash: item.zoneHash,
+ salt: item.salt,
+ conduitKey: item.conduitKey,
+ counter: item.counter
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default OrderComponents map
+ */
+ function _orderComponentsMap()
+ private
+ pure
+ returns (mapping(string => OrderComponents) storage orderComponentsMap)
+ {
+ bytes32 position = ORDER_COMPONENTS_MAP_POSITION;
+ assembly {
+ orderComponentsMap.slot := position
+ }
+ }
+
+ function _orderComponentsArrayMap()
+ private
+ pure
+ returns (
+ mapping(string => OrderComponents[]) storage orderComponentsArrayMap
+ )
+ {
+ bytes32 position = ORDER_COMPONENTS_ARRAY_MAP_POSITION;
+ assembly {
+ orderComponentsArrayMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an in-memory OrderComponents's fields, which modifies the
+ // OrderComponents in-memory and returns it
+
+ function withOfferer(
+ OrderComponents memory components,
+ address offerer
+ ) internal pure returns (OrderComponents memory) {
+ components.offerer = offerer;
+ return components;
+ }
+
+ function withZone(
+ OrderComponents memory components,
+ address zone
+ ) internal pure returns (OrderComponents memory) {
+ components.zone = zone;
+ return components;
+ }
+
+ function withOffer(
+ OrderComponents memory components,
+ OfferItem[] memory offer
+ ) internal pure returns (OrderComponents memory) {
+ components.offer = offer;
+ return components;
+ }
+
+ function withConsideration(
+ OrderComponents memory components,
+ ConsiderationItem[] memory consideration
+ ) internal pure returns (OrderComponents memory) {
+ components.consideration = consideration;
+ return components;
+ }
+
+ function withOrderType(
+ OrderComponents memory components,
+ OrderType orderType
+ ) internal pure returns (OrderComponents memory) {
+ components.orderType = orderType;
+ return components;
+ }
+
+ function withStartTime(
+ OrderComponents memory components,
+ uint256 startTime
+ ) internal pure returns (OrderComponents memory) {
+ components.startTime = startTime;
+ return components;
+ }
+
+ function withEndTime(
+ OrderComponents memory components,
+ uint256 endTime
+ ) internal pure returns (OrderComponents memory) {
+ components.endTime = endTime;
+ return components;
+ }
+
+ function withZoneHash(
+ OrderComponents memory components,
+ bytes32 zoneHash
+ ) internal pure returns (OrderComponents memory) {
+ components.zoneHash = zoneHash;
+ return components;
+ }
+
+ function withSalt(
+ OrderComponents memory components,
+ uint256 salt
+ ) internal pure returns (OrderComponents memory) {
+ components.salt = salt;
+ return components;
+ }
+
+ function withConduitKey(
+ OrderComponents memory components,
+ bytes32 conduitKey
+ ) internal pure returns (OrderComponents memory) {
+ components.conduitKey = conduitKey;
+ return components;
+ }
+
+ function withCounter(
+ OrderComponents memory components,
+ uint256 counter
+ ) internal pure returns (OrderComponents memory) {
+ components.counter = counter;
+ return components;
+ }
+
+ function toOrderParameters(
+ OrderComponents memory components
+ ) internal pure returns (OrderParameters memory parameters) {
+ parameters.offerer = components.offerer;
+ parameters.zone = components.zone;
+ parameters.offer = components.offer.copy();
+ parameters.consideration = components.consideration.copy();
+ parameters.orderType = components.orderType;
+ parameters.startTime = components.startTime;
+ parameters.endTime = components.endTime;
+ parameters.zoneHash = components.zoneHash;
+ parameters.salt = components.salt;
+ parameters.conduitKey = components.conduitKey;
+ parameters.totalOriginalConsiderationItems = components
+ .consideration
+ .length;
+ }
+}
diff --git a/contracts/helpers/sol/lib/OrderLib.sol b/contracts/helpers/sol/lib/OrderLib.sol
new file mode 100644
index 000000000..59dfd56a4
--- /dev/null
+++ b/contracts/helpers/sol/lib/OrderLib.sol
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ Order,
+ AdvancedOrder,
+ OrderParameters
+} from "../../../lib/ConsiderationStructs.sol";
+import { OrderParametersLib } from "./OrderParametersLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library OrderLib {
+ bytes32 private constant ORDER_MAP_POSITION =
+ keccak256("seaport.OrderDefaults");
+ bytes32 private constant ORDERS_MAP_POSITION =
+ keccak256("seaport.OrdersDefaults");
+
+ using OrderParametersLib for OrderParameters;
+
+ /**
+ * @notice clears a default Order from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => Order) storage orderMap = _orderMap();
+ Order storage item = orderMap[defaultName];
+ clear(item);
+ }
+
+ function clear(Order storage order) internal {
+ // clear all fields
+ order.parameters.clear();
+ order.signature = "";
+ }
+
+ function clear(Order[] storage order) internal {
+ while (order.length > 0) {
+ clear(order[order.length - 1]);
+ order.pop();
+ }
+ }
+
+ /**
+ * @notice gets a default Order from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (Order memory item) {
+ mapping(string => Order) storage orderMap = _orderMap();
+ item = orderMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (Order[] memory) {
+ mapping(string => Order[]) storage ordersMap = _ordersMap();
+ Order[] memory items = ordersMap[defaultName];
+ return items;
+ }
+
+ /**
+ * @notice saves an Order as a named default
+ * @param order the Order to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ Order memory order,
+ string memory defaultName
+ ) internal returns (Order memory _order) {
+ mapping(string => Order) storage orderMap = _orderMap();
+ StructCopier.setOrder(orderMap[defaultName], order);
+ return order;
+ }
+
+ function saveDefaultMany(
+ Order[] memory orders,
+ string memory defaultName
+ ) internal returns (Order[] memory _orders) {
+ mapping(string => Order[]) storage ordersMap = _ordersMap();
+ StructCopier.setOrders(ordersMap[defaultName], orders);
+ return orders;
+ }
+
+ /**
+ * @notice makes a copy of an Order in-memory
+ * @param item the Order to make a copy of in-memory
+ */
+ function copy(Order memory item) internal pure returns (Order memory) {
+ return
+ Order({
+ parameters: item.parameters.copy(),
+ signature: item.signature
+ });
+ }
+
+ function copy(Order[] memory items) internal pure returns (Order[] memory) {
+ Order[] memory copiedItems = new Order[](items.length);
+ for (uint256 i = 0; i < items.length; i++) {
+ copiedItems[i] = copy(items[i]);
+ }
+ return copiedItems;
+ }
+
+ function empty() internal pure returns (Order memory) {
+ return Order({ parameters: OrderParametersLib.empty(), signature: "" });
+ }
+
+ /**
+ * @notice gets the storage position of the default Order map
+ */
+ function _orderMap()
+ private
+ pure
+ returns (mapping(string => Order) storage orderMap)
+ {
+ bytes32 position = ORDER_MAP_POSITION;
+ assembly {
+ orderMap.slot := position
+ }
+ }
+
+ function _ordersMap()
+ private
+ pure
+ returns (mapping(string => Order[]) storage ordersMap)
+ {
+ bytes32 position = ORDERS_MAP_POSITION;
+ assembly {
+ ordersMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an Order's fields, which modifies the Order in-place and
+ // returns it
+
+ function withParameters(
+ Order memory order,
+ OrderParameters memory parameters
+ ) internal pure returns (Order memory) {
+ order.parameters = parameters.copy();
+ return order;
+ }
+
+ function withSignature(
+ Order memory order,
+ bytes memory signature
+ ) internal pure returns (Order memory) {
+ order.signature = signature;
+ return order;
+ }
+
+ function toAdvancedOrder(
+ Order memory order,
+ uint120 numerator,
+ uint120 denominator,
+ bytes memory extraData
+ ) internal pure returns (AdvancedOrder memory advancedOrder) {
+ advancedOrder.parameters = order.parameters.copy();
+ advancedOrder.numerator = numerator;
+ advancedOrder.denominator = denominator;
+ advancedOrder.signature = order.signature;
+ advancedOrder.extraData = extraData;
+ }
+}
diff --git a/contracts/helpers/sol/lib/OrderParametersLib.sol b/contracts/helpers/sol/lib/OrderParametersLib.sol
new file mode 100644
index 000000000..9beb886fe
--- /dev/null
+++ b/contracts/helpers/sol/lib/OrderParametersLib.sol
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ BasicOrderParameters,
+ OrderComponents,
+ ConsiderationItem,
+ OrderParameters,
+ OfferItem,
+ AdditionalRecipient
+} from "../../../lib/ConsiderationStructs.sol";
+import {
+ OrderType,
+ ItemType,
+ BasicOrderType
+} from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+import { OfferItemLib } from "./OfferItemLib.sol";
+import { ConsiderationItemLib } from "./ConsiderationItemLib.sol";
+
+library OrderParametersLib {
+ using OrderParametersLib for OrderParameters;
+ using OfferItemLib for OfferItem[];
+ using ConsiderationItemLib for ConsiderationItem[];
+ using ConsiderationItemLib for ConsiderationItem;
+ using OfferItemLib for OfferItem;
+
+ bytes32 private constant ORDER_PARAMETERS_MAP_POSITION =
+ keccak256("seaport.OrderParametersDefaults");
+ bytes32 private constant ORDER_PARAMETERS_ARRAY_MAP_POSITION =
+ keccak256("seaport.OrderParametersArrayDefaults");
+
+ function clear(OrderParameters storage parameters) internal {
+ // uninitialized pointers take up no new memory (versus one word for initializing length-0)
+ OfferItem[] memory offer;
+ ConsiderationItem[] memory consideration;
+
+ // clear all fields
+ parameters.offerer = address(0);
+ parameters.zone = address(0);
+ StructCopier.setOfferItems(parameters.offer, offer);
+ StructCopier.setConsiderationItems(
+ parameters.consideration,
+ consideration
+ );
+ parameters.orderType = OrderType(0);
+ parameters.startTime = 0;
+ parameters.endTime = 0;
+ parameters.zoneHash = bytes32(0);
+ parameters.salt = 0;
+ parameters.conduitKey = bytes32(0);
+ parameters.totalOriginalConsiderationItems = 0;
+ }
+
+ function clear(OrderParameters[] storage parameters) internal {
+ while (parameters.length > 0) {
+ clear(parameters[parameters.length - 1]);
+ parameters.pop();
+ }
+ }
+
+ /**
+ * @notice clears a default OrderParameters from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => OrderParameters)
+ storage orderParametersMap = _orderParametersMap();
+ OrderParameters storage parameters = orderParametersMap[defaultName];
+ parameters.clear();
+ }
+
+ function empty() internal pure returns (OrderParameters memory item) {
+ OfferItem[] memory offer;
+ ConsiderationItem[] memory consideration;
+ item = OrderParameters({
+ offerer: address(0),
+ zone: address(0),
+ offer: offer,
+ consideration: consideration,
+ orderType: OrderType(0),
+ startTime: 0,
+ endTime: 0,
+ zoneHash: bytes32(0),
+ salt: 0,
+ conduitKey: bytes32(0),
+ totalOriginalConsiderationItems: 0
+ });
+ }
+
+ /**
+ * @notice gets a default OrderParameters from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (OrderParameters memory item) {
+ mapping(string => OrderParameters)
+ storage orderParametersMap = _orderParametersMap();
+ item = orderParametersMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultName
+ ) internal view returns (OrderParameters[] memory items) {
+ mapping(string => OrderParameters[])
+ storage orderParametersArrayMap = _orderParametersArrayMap();
+ items = orderParametersArrayMap[defaultName];
+ }
+
+ /**
+ * @notice saves an OrderParameters as a named default
+ * @param orderParameters the OrderParameters to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ OrderParameters memory orderParameters,
+ string memory defaultName
+ ) internal returns (OrderParameters memory _orderParameters) {
+ mapping(string => OrderParameters)
+ storage orderParametersMap = _orderParametersMap();
+ OrderParameters storage destination = orderParametersMap[defaultName];
+ StructCopier.setOrderParameters(destination, orderParameters);
+ return orderParameters;
+ }
+
+ function saveDefaultMany(
+ OrderParameters[] memory orderParameters,
+ string memory defaultName
+ ) internal returns (OrderParameters[] memory _orderParameters) {
+ mapping(string => OrderParameters[])
+ storage orderParametersArrayMap = _orderParametersArrayMap();
+ OrderParameters[] storage destination = orderParametersArrayMap[
+ defaultName
+ ];
+ StructCopier.setOrderParameters(destination, orderParameters);
+ return orderParameters;
+ }
+
+ /**
+ * @notice makes a copy of an OrderParameters in-memory
+ * @param item the OrderParameters to make a copy of in-memory
+ */
+ function copy(
+ OrderParameters memory item
+ ) internal pure returns (OrderParameters memory) {
+ return
+ OrderParameters({
+ offerer: item.offerer,
+ zone: item.zone,
+ offer: item.offer.copy(),
+ consideration: item.consideration.copy(),
+ orderType: item.orderType,
+ startTime: item.startTime,
+ endTime: item.endTime,
+ zoneHash: item.zoneHash,
+ salt: item.salt,
+ conduitKey: item.conduitKey,
+ totalOriginalConsiderationItems: item
+ .totalOriginalConsiderationItems
+ });
+ }
+
+ /**
+ * @notice gets the storage position of the default OrderParameters map
+ */
+ function _orderParametersMap()
+ private
+ pure
+ returns (mapping(string => OrderParameters) storage orderParametersMap)
+ {
+ bytes32 position = ORDER_PARAMETERS_MAP_POSITION;
+ assembly {
+ orderParametersMap.slot := position
+ }
+ }
+
+ function _orderParametersArrayMap()
+ private
+ pure
+ returns (
+ mapping(string => OrderParameters[]) storage orderParametersArrayMap
+ )
+ {
+ bytes32 position = ORDER_PARAMETERS_ARRAY_MAP_POSITION;
+ assembly {
+ orderParametersArrayMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an in-memory OrderParameters's fields, which modifies the
+ // OrderParameters in-memory and returns it
+
+ function withOfferer(
+ OrderParameters memory parameters,
+ address offerer
+ ) internal pure returns (OrderParameters memory) {
+ parameters.offerer = offerer;
+ return parameters;
+ }
+
+ function withZone(
+ OrderParameters memory parameters,
+ address zone
+ ) internal pure returns (OrderParameters memory) {
+ parameters.zone = zone;
+ return parameters;
+ }
+
+ function withOffer(
+ OrderParameters memory parameters,
+ OfferItem[] memory offer
+ ) internal pure returns (OrderParameters memory) {
+ parameters.offer = offer;
+ return parameters;
+ }
+
+ function withConsideration(
+ OrderParameters memory parameters,
+ ConsiderationItem[] memory consideration
+ ) internal pure returns (OrderParameters memory) {
+ parameters.consideration = consideration;
+ return parameters;
+ }
+
+ function withOrderType(
+ OrderParameters memory parameters,
+ OrderType orderType
+ ) internal pure returns (OrderParameters memory) {
+ parameters.orderType = orderType;
+ return parameters;
+ }
+
+ function withStartTime(
+ OrderParameters memory parameters,
+ uint256 startTime
+ ) internal pure returns (OrderParameters memory) {
+ parameters.startTime = startTime;
+ return parameters;
+ }
+
+ function withEndTime(
+ OrderParameters memory parameters,
+ uint256 endTime
+ ) internal pure returns (OrderParameters memory) {
+ parameters.endTime = endTime;
+ return parameters;
+ }
+
+ function withZoneHash(
+ OrderParameters memory parameters,
+ bytes32 zoneHash
+ ) internal pure returns (OrderParameters memory) {
+ parameters.zoneHash = zoneHash;
+ return parameters;
+ }
+
+ function withSalt(
+ OrderParameters memory parameters,
+ uint256 salt
+ ) internal pure returns (OrderParameters memory) {
+ parameters.salt = salt;
+ return parameters;
+ }
+
+ function withConduitKey(
+ OrderParameters memory parameters,
+ bytes32 conduitKey
+ ) internal pure returns (OrderParameters memory) {
+ parameters.conduitKey = conduitKey;
+ return parameters;
+ }
+
+ function withTotalOriginalConsiderationItems(
+ OrderParameters memory parameters,
+ uint256 totalOriginalConsiderationItems
+ ) internal pure returns (OrderParameters memory) {
+ parameters
+ .totalOriginalConsiderationItems = totalOriginalConsiderationItems;
+ return parameters;
+ }
+
+ function toOrderComponents(
+ OrderParameters memory parameters,
+ uint256 counter
+ ) internal pure returns (OrderComponents memory components) {
+ components.offerer = parameters.offerer;
+ components.zone = parameters.zone;
+ components.offer = parameters.offer.copy();
+ components.consideration = parameters.consideration.copy();
+ components.orderType = parameters.orderType;
+ components.startTime = parameters.startTime;
+ components.endTime = parameters.endTime;
+ components.zoneHash = parameters.zoneHash;
+ components.salt = parameters.salt;
+ components.conduitKey = parameters.conduitKey;
+ components.counter = counter;
+ }
+}
diff --git a/contracts/helpers/sol/lib/ReceivedItemLib.sol b/contracts/helpers/sol/lib/ReceivedItemLib.sol
new file mode 100644
index 000000000..49dee2fe3
--- /dev/null
+++ b/contracts/helpers/sol/lib/ReceivedItemLib.sol
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ ReceivedItem,
+ ConsiderationItem
+} from "../../../lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library ReceivedItemLib {
+ bytes32 private constant RECEIVED_ITEM_MAP_POSITION =
+ keccak256("seaport.ReceivedItemDefaults");
+ bytes32 private constant RECEIVED_ITEMS_MAP_POSITION =
+ keccak256("seaport.ReceivedItemsDefaults");
+
+ /**
+ * @notice clears a default ReceivedItem from storage
+ * @param defaultName the name of the default to clear
+ */
+ function clear(string memory defaultName) internal {
+ mapping(string => ReceivedItem)
+ storage receivedItemMap = _receivedItemMap();
+ ReceivedItem storage item = receivedItemMap[defaultName];
+ clear(item);
+ }
+
+ function clear(ReceivedItem storage item) internal {
+ // clear all fields
+ item.itemType = ItemType.NATIVE;
+ item.token = address(0);
+ item.identifier = 0;
+ item.amount = 0;
+ item.recipient = payable(address(0));
+ }
+
+ function clearMany(string memory defaultsName) internal {
+ mapping(string => ReceivedItem[])
+ storage receivedItemsMap = _receivedItemsMap();
+ ReceivedItem[] storage items = receivedItemsMap[defaultsName];
+ clearMany(items);
+ }
+
+ function clearMany(ReceivedItem[] storage items) internal {
+ while (items.length > 0) {
+ clear(items[items.length - 1]);
+ items.pop();
+ }
+ }
+
+ function empty() internal pure returns (ReceivedItem memory) {
+ return
+ ReceivedItem({
+ itemType: ItemType(0),
+ token: address(0),
+ identifier: 0,
+ amount: 0,
+ recipient: payable(address(0))
+ });
+ }
+
+ /**
+ * @notice gets a default ReceivedItem from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (ReceivedItem memory item) {
+ mapping(string => ReceivedItem)
+ storage receivedItemMap = _receivedItemMap();
+ item = receivedItemMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultsName
+ ) internal view returns (ReceivedItem[] memory items) {
+ mapping(string => ReceivedItem[])
+ storage receivedItemsMap = _receivedItemsMap();
+ items = receivedItemsMap[defaultsName];
+ }
+
+ /**
+ * @notice saves an ReceivedItem as a named default
+ * @param receivedItem the ReceivedItem to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ ReceivedItem memory receivedItem,
+ string memory defaultName
+ ) internal returns (ReceivedItem memory _receivedItem) {
+ mapping(string => ReceivedItem)
+ storage receivedItemMap = _receivedItemMap();
+ receivedItemMap[defaultName] = receivedItem;
+ return receivedItem;
+ }
+
+ function saveDefaultMany(
+ ReceivedItem[] memory receivedItems,
+ string memory defaultsName
+ ) internal returns (ReceivedItem[] memory _receivedItems) {
+ mapping(string => ReceivedItem[])
+ storage receivedItemsMap = _receivedItemsMap();
+ ReceivedItem[] storage items = receivedItemsMap[defaultsName];
+ setReceivedItems(items, receivedItems);
+ return receivedItems;
+ }
+
+ function setReceivedItems(
+ ReceivedItem[] storage items,
+ ReceivedItem[] memory newItems
+ ) internal {
+ clearMany(items);
+ for (uint256 i = 0; i < newItems.length; i++) {
+ items.push(newItems[i]);
+ }
+ }
+
+ /**
+ * @notice makes a copy of an ReceivedItem in-memory
+ * @param item the ReceivedItem to make a copy of in-memory
+ */
+ function copy(
+ ReceivedItem memory item
+ ) internal pure returns (ReceivedItem memory) {
+ return
+ ReceivedItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifier: item.identifier,
+ amount: item.amount,
+ recipient: item.recipient
+ });
+ }
+
+ function copy(
+ ReceivedItem[] memory item
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory copies = new ReceivedItem[](item.length);
+ for (uint256 i = 0; i < item.length; i++) {
+ copies[i] = ReceivedItemLib.copy(item[i]);
+ }
+ return copies;
+ }
+
+ /**
+ * @notice gets the storage position of the default ReceivedItem map
+ */
+ function _receivedItemMap()
+ private
+ pure
+ returns (mapping(string => ReceivedItem) storage receivedItemMap)
+ {
+ bytes32 position = RECEIVED_ITEM_MAP_POSITION;
+ assembly {
+ receivedItemMap.slot := position
+ }
+ }
+
+ /**
+ * @notice gets the storage position of the default ReceivedItem map
+ */
+ function _receivedItemsMap()
+ private
+ pure
+ returns (mapping(string => ReceivedItem[]) storage receivedItemsMap)
+ {
+ bytes32 position = RECEIVED_ITEMS_MAP_POSITION;
+ assembly {
+ receivedItemsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an ReceivedItem's fields, which modifies the ReceivedItem
+ // in-place and
+ // returns it
+
+ /**
+ * @notice sets the item type
+ * @param item the ReceivedItem to modify
+ * @param itemType the item type to set
+ * @return the modified ReceivedItem
+ */
+ function withItemType(
+ ReceivedItem memory item,
+ ItemType itemType
+ ) internal pure returns (ReceivedItem memory) {
+ item.itemType = itemType;
+ return item;
+ }
+
+ /**
+ * @notice sets the token address
+ * @param item the ReceivedItem to modify
+ * @param token the token address to set
+ * @return the modified ReceivedItem
+ */
+ function withToken(
+ ReceivedItem memory item,
+ address token
+ ) internal pure returns (ReceivedItem memory) {
+ item.token = token;
+ return item;
+ }
+
+ /**
+ * @notice sets the identifier or criteria
+ * @param item the ReceivedItem to modify
+ * @param identifier the identifier or criteria to set
+ * @return the modified ReceivedItem
+ */
+ function withIdentifier(
+ ReceivedItem memory item,
+ uint256 identifier
+ ) internal pure returns (ReceivedItem memory) {
+ item.identifier = identifier;
+ return item;
+ }
+
+ /**
+ * @notice sets the start amount
+ * @param item the ReceivedItem to modify
+ * @param amount the start amount to set
+ * @return the modified ReceivedItem
+ */
+ function withAmount(
+ ReceivedItem memory item,
+ uint256 amount
+ ) internal pure returns (ReceivedItem memory) {
+ item.amount = amount;
+ return item;
+ }
+
+ /**
+ * @notice sets the recipient
+ * @param item the ReceivedItem to modify
+ * @param recipient the recipient to set
+ * @return the modified ReceivedItem
+ */
+ function withRecipient(
+ ReceivedItem memory item,
+ address recipient
+ ) internal pure returns (ReceivedItem memory) {
+ item.recipient = payable(recipient);
+ return item;
+ }
+
+ function toConsiderationItem(
+ ReceivedItem memory item
+ ) internal pure returns (ConsiderationItem memory) {
+ return
+ ConsiderationItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifierOrCriteria: item.identifier,
+ startAmount: item.amount,
+ endAmount: item.amount,
+ recipient: item.recipient
+ });
+ }
+}
diff --git a/contracts/helpers/sol/lib/SeaportArrays.sol b/contracts/helpers/sol/lib/SeaportArrays.sol
new file mode 100644
index 000000000..40685a808
--- /dev/null
+++ b/contracts/helpers/sol/lib/SeaportArrays.sol
@@ -0,0 +1,1390 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ OrderComponents,
+ OfferItem,
+ ConsiderationItem,
+ SpentItem,
+ ReceivedItem,
+ BasicOrderParameters,
+ AdditionalRecipient,
+ OrderParameters,
+ Order,
+ AdvancedOrder,
+ CriteriaResolver,
+ Fulfillment,
+ FulfillmentComponent
+} from "../../../lib/ConsiderationStructs.sol";
+
+library SeaportArrays {
+ function Orders(Order memory a) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b,
+ Order memory c
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b,
+ Order memory c,
+ Order memory d
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b,
+ Order memory c,
+ Order memory d,
+ Order memory e
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b,
+ Order memory c,
+ Order memory d,
+ Order memory e,
+ Order memory f
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function Orders(
+ Order memory a,
+ Order memory b,
+ Order memory c,
+ Order memory d,
+ Order memory e,
+ Order memory f,
+ Order memory g
+ ) internal pure returns (Order[] memory) {
+ Order[] memory arr = new Order[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b,
+ AdvancedOrder memory c
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b,
+ AdvancedOrder memory c,
+ AdvancedOrder memory d
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b,
+ AdvancedOrder memory c,
+ AdvancedOrder memory d,
+ AdvancedOrder memory e
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b,
+ AdvancedOrder memory c,
+ AdvancedOrder memory d,
+ AdvancedOrder memory e,
+ AdvancedOrder memory f
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function AdvancedOrders(
+ AdvancedOrder memory a,
+ AdvancedOrder memory b,
+ AdvancedOrder memory c,
+ AdvancedOrder memory d,
+ AdvancedOrder memory e,
+ AdvancedOrder memory f,
+ AdvancedOrder memory g
+ ) internal pure returns (AdvancedOrder[] memory) {
+ AdvancedOrder[] memory arr = new AdvancedOrder[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b,
+ OrderComponents memory c
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b,
+ OrderComponents memory c,
+ OrderComponents memory d
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b,
+ OrderComponents memory c,
+ OrderComponents memory d,
+ OrderComponents memory e
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b,
+ OrderComponents memory c,
+ OrderComponents memory d,
+ OrderComponents memory e,
+ OrderComponents memory f
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function OrderComponentsArray(
+ OrderComponents memory a,
+ OrderComponents memory b,
+ OrderComponents memory c,
+ OrderComponents memory d,
+ OrderComponents memory e,
+ OrderComponents memory f,
+ OrderComponents memory g
+ ) internal pure returns (OrderComponents[] memory) {
+ OrderComponents[] memory arr = new OrderComponents[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b,
+ OrderParameters memory c
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b,
+ OrderParameters memory c,
+ OrderParameters memory d
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b,
+ OrderParameters memory c,
+ OrderParameters memory d,
+ OrderParameters memory e
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b,
+ OrderParameters memory c,
+ OrderParameters memory d,
+ OrderParameters memory e,
+ OrderParameters memory f
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function OrderParametersArray(
+ OrderParameters memory a,
+ OrderParameters memory b,
+ OrderParameters memory c,
+ OrderParameters memory d,
+ OrderParameters memory e,
+ OrderParameters memory f,
+ OrderParameters memory g
+ ) internal pure returns (OrderParameters[] memory) {
+ OrderParameters[] memory arr = new OrderParameters[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b,
+ OfferItem memory c
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b,
+ OfferItem memory c,
+ OfferItem memory d
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b,
+ OfferItem memory c,
+ OfferItem memory d,
+ OfferItem memory e
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b,
+ OfferItem memory c,
+ OfferItem memory d,
+ OfferItem memory e,
+ OfferItem memory f
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function OfferItems(
+ OfferItem memory a,
+ OfferItem memory b,
+ OfferItem memory c,
+ OfferItem memory d,
+ OfferItem memory e,
+ OfferItem memory f,
+ OfferItem memory g
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory arr = new OfferItem[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b,
+ ConsiderationItem memory c
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b,
+ ConsiderationItem memory c,
+ ConsiderationItem memory d
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b,
+ ConsiderationItem memory c,
+ ConsiderationItem memory d,
+ ConsiderationItem memory e
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b,
+ ConsiderationItem memory c,
+ ConsiderationItem memory d,
+ ConsiderationItem memory e,
+ ConsiderationItem memory f
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function ConsiderationItems(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b,
+ ConsiderationItem memory c,
+ ConsiderationItem memory d,
+ ConsiderationItem memory e,
+ ConsiderationItem memory f,
+ ConsiderationItem memory g
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory arr = new ConsiderationItem[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b,
+ SpentItem memory c
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b,
+ SpentItem memory c,
+ SpentItem memory d
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b,
+ SpentItem memory c,
+ SpentItem memory d,
+ SpentItem memory e
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b,
+ SpentItem memory c,
+ SpentItem memory d,
+ SpentItem memory e,
+ SpentItem memory f
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function SpentItems(
+ SpentItem memory a,
+ SpentItem memory b,
+ SpentItem memory c,
+ SpentItem memory d,
+ SpentItem memory e,
+ SpentItem memory f,
+ SpentItem memory g
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory arr = new SpentItem[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b,
+ ReceivedItem memory c
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b,
+ ReceivedItem memory c,
+ ReceivedItem memory d
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b,
+ ReceivedItem memory c,
+ ReceivedItem memory d,
+ ReceivedItem memory e
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b,
+ ReceivedItem memory c,
+ ReceivedItem memory d,
+ ReceivedItem memory e,
+ ReceivedItem memory f
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function ReceivedItems(
+ ReceivedItem memory a,
+ ReceivedItem memory b,
+ ReceivedItem memory c,
+ ReceivedItem memory d,
+ ReceivedItem memory e,
+ ReceivedItem memory f,
+ ReceivedItem memory g
+ ) internal pure returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory arr = new ReceivedItem[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b,
+ FulfillmentComponent memory c
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b,
+ FulfillmentComponent memory c,
+ FulfillmentComponent memory d
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b,
+ FulfillmentComponent memory c,
+ FulfillmentComponent memory d,
+ FulfillmentComponent memory e
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b,
+ FulfillmentComponent memory c,
+ FulfillmentComponent memory d,
+ FulfillmentComponent memory e,
+ FulfillmentComponent memory f
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function FulfillmentComponents(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b,
+ FulfillmentComponent memory c,
+ FulfillmentComponent memory d,
+ FulfillmentComponent memory e,
+ FulfillmentComponent memory f,
+ FulfillmentComponent memory g
+ ) internal pure returns (FulfillmentComponent[] memory) {
+ FulfillmentComponent[] memory arr = new FulfillmentComponent[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b,
+ FulfillmentComponent[] memory c
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b,
+ FulfillmentComponent[] memory c,
+ FulfillmentComponent[] memory d
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b,
+ FulfillmentComponent[] memory c,
+ FulfillmentComponent[] memory d,
+ FulfillmentComponent[] memory e
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b,
+ FulfillmentComponent[] memory c,
+ FulfillmentComponent[] memory d,
+ FulfillmentComponent[] memory e,
+ FulfillmentComponent[] memory f
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function FulfillmentComponentArrays(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b,
+ FulfillmentComponent[] memory c,
+ FulfillmentComponent[] memory d,
+ FulfillmentComponent[] memory e,
+ FulfillmentComponent[] memory f,
+ FulfillmentComponent[] memory g
+ ) internal pure returns (FulfillmentComponent[][] memory) {
+ FulfillmentComponent[][] memory arr = new FulfillmentComponent[][](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b,
+ CriteriaResolver memory c
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b,
+ CriteriaResolver memory c,
+ CriteriaResolver memory d
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b,
+ CriteriaResolver memory c,
+ CriteriaResolver memory d,
+ CriteriaResolver memory e
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b,
+ CriteriaResolver memory c,
+ CriteriaResolver memory d,
+ CriteriaResolver memory e,
+ CriteriaResolver memory f
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function CriteriaResolvers(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b,
+ CriteriaResolver memory c,
+ CriteriaResolver memory d,
+ CriteriaResolver memory e,
+ CriteriaResolver memory f,
+ CriteriaResolver memory g
+ ) internal pure returns (CriteriaResolver[] memory) {
+ CriteriaResolver[] memory arr = new CriteriaResolver[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b,
+ AdditionalRecipient memory c
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b,
+ AdditionalRecipient memory c,
+ AdditionalRecipient memory d
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b,
+ AdditionalRecipient memory c,
+ AdditionalRecipient memory d,
+ AdditionalRecipient memory e
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b,
+ AdditionalRecipient memory c,
+ AdditionalRecipient memory d,
+ AdditionalRecipient memory e,
+ AdditionalRecipient memory f
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function AdditionalRecipients(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b,
+ AdditionalRecipient memory c,
+ AdditionalRecipient memory d,
+ AdditionalRecipient memory e,
+ AdditionalRecipient memory f,
+ AdditionalRecipient memory g
+ ) internal pure returns (AdditionalRecipient[] memory) {
+ AdditionalRecipient[] memory arr = new AdditionalRecipient[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b,
+ BasicOrderParameters memory c
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b,
+ BasicOrderParameters memory c,
+ BasicOrderParameters memory d
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b,
+ BasicOrderParameters memory c,
+ BasicOrderParameters memory d,
+ BasicOrderParameters memory e
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b,
+ BasicOrderParameters memory c,
+ BasicOrderParameters memory d,
+ BasicOrderParameters memory e,
+ BasicOrderParameters memory f
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function BasicOrderParametersArray(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b,
+ BasicOrderParameters memory c,
+ BasicOrderParameters memory d,
+ BasicOrderParameters memory e,
+ BasicOrderParameters memory f,
+ BasicOrderParameters memory g
+ ) internal pure returns (BasicOrderParameters[] memory) {
+ BasicOrderParameters[] memory arr = new BasicOrderParameters[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](1);
+ arr[0] = a;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](2);
+ arr[0] = a;
+ arr[1] = b;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b,
+ Fulfillment memory c
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](3);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b,
+ Fulfillment memory c,
+ Fulfillment memory d
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](4);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b,
+ Fulfillment memory c,
+ Fulfillment memory d,
+ Fulfillment memory e
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](5);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b,
+ Fulfillment memory c,
+ Fulfillment memory d,
+ Fulfillment memory e,
+ Fulfillment memory f
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](6);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ return arr;
+ }
+
+ function Fulfillments(
+ Fulfillment memory a,
+ Fulfillment memory b,
+ Fulfillment memory c,
+ Fulfillment memory d,
+ Fulfillment memory e,
+ Fulfillment memory f,
+ Fulfillment memory g
+ ) internal pure returns (Fulfillment[] memory) {
+ Fulfillment[] memory arr = new Fulfillment[](7);
+ arr[0] = a;
+ arr[1] = b;
+ arr[2] = c;
+ arr[3] = d;
+ arr[4] = e;
+ arr[5] = f;
+ arr[6] = g;
+ return arr;
+ }
+}
diff --git a/contracts/helpers/sol/lib/SeaportEnumsLib.sol b/contracts/helpers/sol/lib/SeaportEnumsLib.sol
new file mode 100644
index 000000000..da3159957
--- /dev/null
+++ b/contracts/helpers/sol/lib/SeaportEnumsLib.sol
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {
+ BasicOrderParameters,
+ OrderParameters
+} from "../../../lib/ConsiderationStructs.sol";
+import {
+ OrderType,
+ BasicOrderType,
+ ItemType,
+ BasicOrderRouteType
+} from "../../../lib/ConsiderationEnums.sol";
+
+library SeaportEnumsLib {
+ function parseBasicOrderType(
+ BasicOrderType basicOrderType
+ )
+ internal
+ pure
+ returns (
+ OrderType orderType,
+ ItemType offerType,
+ ItemType considerationType,
+ ItemType additionalRecipientsType,
+ bool offerTypeIsAdditionalRecipientsType
+ )
+ {
+ assembly {
+ // Mask all but 2 least-significant bits to derive the order type.
+ orderType := and(basicOrderType, 3)
+
+ // Divide basicOrderType by four to derive the route.
+ let route := shr(2, basicOrderType)
+ offerTypeIsAdditionalRecipientsType := gt(route, 3)
+
+ // If route > 1 additionalRecipient items are ERC20 (1) else Eth (0)
+ additionalRecipientsType := gt(route, 1)
+
+ // If route > 2, receivedItemType is route - 2. If route is 2,
+ // the receivedItemType is ERC20 (1). Otherwise, it is Eth (0).
+ considerationType := add(
+ mul(sub(route, 2), gt(route, 2)),
+ eq(route, 2)
+ )
+
+ // If route > 3, offeredItemType is ERC20 (1). Route is 2 or 3,
+ // offeredItemType = route. Route is 0 or 1, it is route + 2.
+ offerType := add(route, mul(iszero(additionalRecipientsType), 2))
+ }
+ }
+}
diff --git a/contracts/helpers/sol/lib/SeaportStructLib.sol b/contracts/helpers/sol/lib/SeaportStructLib.sol
new file mode 100644
index 000000000..fca841117
--- /dev/null
+++ b/contracts/helpers/sol/lib/SeaportStructLib.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { AdditionalRecipientLib } from "./AdditionalRecipientLib.sol";
+import { AdvancedOrderLib } from "./AdvancedOrderLib.sol";
+import { ArrayLib } from "./ArrayLib.sol";
+import { BasicOrderParametersLib } from "./BasicOrderParametersLib.sol";
+import { ConsiderationItemLib } from "./ConsiderationItemLib.sol";
+import { CriteriaResolverLib } from "./CriteriaResolverLib.sol";
+import { ExecutionLib } from "./ExecutionLib.sol";
+import { FulfillmentComponentLib } from "./FulfillmentComponentLib.sol";
+import { FulfillmentLib } from "./FulfillmentLib.sol";
+import { OfferItemLib } from "./OfferItemLib.sol";
+import { OrderComponentsLib } from "./OrderComponentsLib.sol";
+import { OrderLib } from "./OrderLib.sol";
+import { OrderParametersLib } from "./OrderParametersLib.sol";
+import { ReceivedItemLib } from "./ReceivedItemLib.sol";
+import { SpentItemLib } from "./SpentItemLib.sol";
+import { StructCopier } from "./StructCopier.sol";
+import { SeaportArrays } from "./SeaportArrays.sol";
diff --git a/contracts/helpers/sol/lib/SpentItemLib.sol b/contracts/helpers/sol/lib/SpentItemLib.sol
new file mode 100644
index 000000000..3aa2f2da1
--- /dev/null
+++ b/contracts/helpers/sol/lib/SpentItemLib.sol
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { SpentItem, OfferItem } from "../../../lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../lib/ConsiderationEnums.sol";
+import { StructCopier } from "./StructCopier.sol";
+
+library SpentItemLib {
+ bytes32 private constant SPENT_ITEM_MAP_POSITION =
+ keccak256("seaport.SpentItemDefaults");
+ bytes32 private constant SPENT_ITEMS_MAP_POSITION =
+ keccak256("seaport.SpentItemsDefaults");
+
+ function empty() internal pure returns (SpentItem memory) {
+ return SpentItem(ItemType(0), address(0), 0, 0);
+ }
+
+ function clear(SpentItem storage item) internal {
+ // clear all fields
+ item.itemType = ItemType(0);
+ item.token = address(0);
+ item.identifier = 0;
+ item.amount = 0;
+ }
+
+ function clearMany(SpentItem[] storage items) internal {
+ while (items.length > 0) {
+ clear(items[items.length - 1]);
+ items.pop();
+ }
+ }
+
+ /**
+ * @notice clears a default SpentItem from storage
+ * @param defaultName the name of the default to clear
+ */
+
+ function clear(string memory defaultName) internal {
+ mapping(string => SpentItem) storage spentItemMap = _spentItemMap();
+ SpentItem storage item = spentItemMap[defaultName];
+ clear(item);
+ }
+
+ function clearMany(string memory defaultsName) internal {
+ mapping(string => SpentItem[]) storage spentItemsMap = _spentItemsMap();
+ SpentItem[] storage items = spentItemsMap[defaultsName];
+ clearMany(items);
+ }
+
+ /**
+ * @notice gets a default SpentItem from storage
+ * @param defaultName the name of the default for retrieval
+ */
+ function fromDefault(
+ string memory defaultName
+ ) internal view returns (SpentItem memory item) {
+ mapping(string => SpentItem) storage spentItemMap = _spentItemMap();
+ item = spentItemMap[defaultName];
+ }
+
+ function fromDefaultMany(
+ string memory defaultsName
+ ) internal view returns (SpentItem[] memory items) {
+ mapping(string => SpentItem[]) storage spentItemsMap = _spentItemsMap();
+ items = spentItemsMap[defaultsName];
+ }
+
+ /**
+ * @notice saves an SpentItem as a named default
+ * @param spentItem the SpentItem to save as a default
+ * @param defaultName the name of the default for retrieval
+ */
+ function saveDefault(
+ SpentItem memory spentItem,
+ string memory defaultName
+ ) internal returns (SpentItem memory _spentItem) {
+ mapping(string => SpentItem) storage spentItemMap = _spentItemMap();
+ spentItemMap[defaultName] = spentItem;
+ return spentItem;
+ }
+
+ function saveDefaultMany(
+ SpentItem[] memory spentItems,
+ string memory defaultsName
+ ) internal returns (SpentItem[] memory _spentItems) {
+ mapping(string => SpentItem[]) storage spentItemsMap = _spentItemsMap();
+ SpentItem[] storage items = spentItemsMap[defaultsName];
+ setSpentItems(items, spentItems);
+ return spentItems;
+ }
+
+ function setSpentItems(
+ SpentItem[] storage items,
+ SpentItem[] memory newItems
+ ) internal {
+ clearMany(items);
+ for (uint256 i = 0; i < newItems.length; i++) {
+ items.push(newItems[i]);
+ }
+ }
+
+ /**
+ * @notice makes a copy of an SpentItem in-memory
+ * @param item the SpentItem to make a copy of in-memory
+ */
+ function copy(
+ SpentItem memory item
+ ) internal pure returns (SpentItem memory) {
+ return
+ SpentItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifier: item.identifier,
+ amount: item.amount
+ });
+ }
+
+ function copy(
+ SpentItem[] memory items
+ ) internal pure returns (SpentItem[] memory) {
+ SpentItem[] memory copiedItems = new SpentItem[](items.length);
+ for (uint256 i = 0; i < items.length; i++) {
+ copiedItems[i] = copy(items[i]);
+ }
+ return copiedItems;
+ }
+
+ /**
+ * @notice gets the storage position of the default SpentItem map
+ */
+ function _spentItemMap()
+ private
+ pure
+ returns (mapping(string => SpentItem) storage spentItemMap)
+ {
+ bytes32 position = SPENT_ITEM_MAP_POSITION;
+ assembly {
+ spentItemMap.slot := position
+ }
+ }
+
+ function _spentItemsMap()
+ private
+ pure
+ returns (mapping(string => SpentItem[]) storage spentItemsMap)
+ {
+ bytes32 position = SPENT_ITEMS_MAP_POSITION;
+ assembly {
+ spentItemsMap.slot := position
+ }
+ }
+
+ // methods for configuring a single of each of an SpentItem's fields, which modifies the SpentItem in-place and
+ // returns it
+
+ /**
+ * @notice sets the item type
+ * @param item the SpentItem to modify
+ * @param itemType the item type to set
+ * @return the modified SpentItem
+ */
+ function withItemType(
+ SpentItem memory item,
+ ItemType itemType
+ ) internal pure returns (SpentItem memory) {
+ item.itemType = itemType;
+ return item;
+ }
+
+ /**
+ * @notice sets the token address
+ * @param item the SpentItem to modify
+ * @param token the token address to set
+ * @return the modified SpentItem
+ */
+ function withToken(
+ SpentItem memory item,
+ address token
+ ) internal pure returns (SpentItem memory) {
+ item.token = token;
+ return item;
+ }
+
+ /**
+ * @notice sets the identifier or criteria
+ * @param item the SpentItem to modify
+ * @param identifier the identifier or criteria to set
+ * @return the modified SpentItem
+ */
+ function withIdentifier(
+ SpentItem memory item,
+ uint256 identifier
+ ) internal pure returns (SpentItem memory) {
+ item.identifier = identifier;
+ return item;
+ }
+
+ /**
+ * @notice sets the start amount
+ * @param item the SpentItem to modify
+ * @param amount the start amount to set
+ * @return the modified SpentItem
+ */
+ function withAmount(
+ SpentItem memory item,
+ uint256 amount
+ ) internal pure returns (SpentItem memory) {
+ item.amount = amount;
+ return item;
+ }
+
+ function toOfferItem(
+ SpentItem memory item
+ ) internal pure returns (OfferItem memory) {
+ return
+ OfferItem({
+ itemType: item.itemType,
+ token: item.token,
+ identifierOrCriteria: item.identifier,
+ startAmount: item.amount,
+ endAmount: item.amount
+ });
+ }
+}
diff --git a/contracts/helpers/sol/lib/StructCopier.sol b/contracts/helpers/sol/lib/StructCopier.sol
new file mode 100644
index 000000000..36436af05
--- /dev/null
+++ b/contracts/helpers/sol/lib/StructCopier.sol
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {
+ BasicOrderParameters,
+ CriteriaResolver,
+ AdvancedOrder,
+ AdditionalRecipient,
+ OfferItem,
+ Order,
+ ConsiderationItem,
+ Fulfillment,
+ FulfillmentComponent,
+ OrderParameters,
+ OrderComponents,
+ Execution
+} from "../../../lib/ConsiderationStructs.sol";
+import {
+ ConsiderationInterface
+} from "../../../interfaces/ConsiderationInterface.sol";
+import { ArrayLib } from "./ArrayLib.sol";
+
+library StructCopier {
+ function _basicOrderParameters()
+ private
+ pure
+ returns (BasicOrderParameters storage empty)
+ {
+ bytes32 position = keccak256("StructCopier.EmptyBasicOrderParameters");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function _criteriaResolver()
+ private
+ pure
+ returns (CriteriaResolver storage empty)
+ {
+ bytes32 position = keccak256("StructCopier.EmptyCriteriaResolver");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function _fulfillment() private pure returns (Fulfillment storage empty) {
+ bytes32 position = keccak256("StructCopier.EmptyFulfillment");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function _orderComponents()
+ private
+ pure
+ returns (OrderComponents storage empty)
+ {
+ bytes32 position = keccak256("StructCopier.EmptyOrderComponents");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function _orderParameters()
+ private
+ pure
+ returns (OrderParameters storage empty)
+ {
+ bytes32 position = keccak256("StructCopier.EmptyOrderParameters");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function _order() private pure returns (Order storage empty) {
+ bytes32 position = keccak256("StructCopier.EmptyOrder");
+ assembly {
+ empty.slot := position
+ }
+ return empty;
+ }
+
+ function setBasicOrderParameters(
+ BasicOrderParameters storage dest,
+ BasicOrderParameters memory src
+ ) internal {
+ dest.considerationToken = src.considerationToken;
+ dest.considerationIdentifier = src.considerationIdentifier;
+ dest.considerationAmount = src.considerationAmount;
+ dest.offerer = src.offerer;
+ dest.zone = src.zone;
+ dest.offerToken = src.offerToken;
+ dest.offerIdentifier = src.offerIdentifier;
+ dest.offerAmount = src.offerAmount;
+ dest.basicOrderType = src.basicOrderType;
+ dest.startTime = src.startTime;
+ dest.endTime = src.endTime;
+ dest.zoneHash = src.zoneHash;
+ dest.salt = src.salt;
+ dest.offererConduitKey = src.offererConduitKey;
+ dest.fulfillerConduitKey = src.fulfillerConduitKey;
+ dest.totalOriginalAdditionalRecipients = src
+ .totalOriginalAdditionalRecipients;
+ setAdditionalRecipients(
+ dest.additionalRecipients,
+ src.additionalRecipients
+ );
+ dest.signature = src.signature;
+ }
+
+ function setOrderComponents(
+ OrderComponents storage dest,
+ OrderComponents memory src
+ ) internal {
+ dest.offerer = src.offerer;
+ dest.zone = src.zone;
+ setOfferItems(dest.offer, src.offer);
+ setConsiderationItems(dest.consideration, src.consideration);
+ dest.orderType = src.orderType;
+ dest.startTime = src.startTime;
+ dest.endTime = src.endTime;
+ dest.zoneHash = src.zoneHash;
+ dest.salt = src.salt;
+ dest.conduitKey = src.conduitKey;
+ dest.counter = src.counter;
+ }
+
+ function setOrderComponents(
+ OrderComponents[] storage dest,
+ OrderComponents[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ OrderComponents storage empty = _orderComponents();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setOrderComponents(dest[i], src[i]);
+ }
+ }
+
+ function setBasicOrderParameters(
+ BasicOrderParameters[] storage dest,
+ BasicOrderParameters[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ BasicOrderParameters storage empty = _basicOrderParameters();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setBasicOrderParameters(dest[i], src[i]);
+ }
+ }
+
+ function setAdditionalRecipients(
+ AdditionalRecipient[] storage dest,
+ AdditionalRecipient[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(src[i]);
+ }
+ }
+
+ function setCriteriaResolver(
+ CriteriaResolver storage dest,
+ CriteriaResolver memory src
+ ) internal {
+ dest.orderIndex = src.orderIndex;
+ dest.side = src.side;
+ dest.index = src.index;
+ dest.identifier = src.identifier;
+ ArrayLib.setBytes32s(dest.criteriaProof, src.criteriaProof);
+ }
+
+ function setCriteriaResolvers(
+ CriteriaResolver[] storage dest,
+ CriteriaResolver[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ CriteriaResolver storage empty = _criteriaResolver();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setCriteriaResolver(dest[i], src[i]);
+ }
+ }
+
+ function setOrder(Order storage dest, Order memory src) internal {
+ setOrderParameters(dest.parameters, src.parameters);
+ dest.signature = src.signature;
+ }
+
+ bytes32 constant TEMP_ORDER = keccak256("seaport-sol.temp.Order");
+ bytes32 constant TEMP_COUNTER_SLOT = keccak256("seaport-sol.temp.Counter");
+
+ /**
+ * @notice Get a counter used to derive a temporary storage slot.
+ * @dev Solidity does not allow copying dynamic types from memory to storage.
+ * We need a "clean" (empty) temp pointer to make an exact copy of a struct with dynamic members, but
+ * Solidity does not allow calling "delete" on a storage pointer either.
+ * By hashing a struct's temp slot with a monotonically increasing counter, we can derive a new temp slot
+ * that is basically "guaranteed" to have all successive storage slots empty.
+ * TODO: We can revisit adding "clear" methods that definitively wipe all dynamic components of a struct,
+ * but that will require an equal amount of SSTOREs; this is obviously more expensive gas-wise, but may not
+ * make a difference performance-wise when running simiulations locally (though this needs to be tested)
+ */
+ function _getAndIncrementTempCounter() internal returns (uint256 counter) {
+ // get counter slot
+ bytes32 counterSlot = TEMP_COUNTER_SLOT;
+ assembly {
+ // load current value
+ counter := sload(counterSlot)
+ // store incremented value
+ sstore(counterSlot, add(counter, 1))
+ }
+ // return original value
+ return counter;
+ }
+
+ function _deriveTempSlotWithCounter(
+ bytes32 libSlot
+ ) internal returns (uint256 derivedSlot) {
+ uint256 counter = _getAndIncrementTempCounter();
+ assembly {
+ // store lib slot in first mem position
+ mstore(0x0, libSlot)
+ // store temp counter in second position
+ mstore(0x20, counter)
+ // hash original slot with counter to get new temp slot, which has a low probability of being dirty
+ // (~1/2**256)
+ derivedSlot := keccak256(0x0, 0x40)
+ }
+ }
+
+ function _getTempOrder() internal returns (Order storage _tempOrder) {
+ uint256 position = _deriveTempSlotWithCounter(TEMP_ORDER);
+ assembly {
+ _tempOrder.slot := position
+ }
+ }
+
+ function setOrders(Order[] storage dest, Order[] memory src) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ Order storage empty = _order();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setOrder(dest[i], src[i]);
+ }
+ }
+
+ function setAdvancedOrder(
+ AdvancedOrder storage dest,
+ AdvancedOrder memory src
+ ) internal {
+ setOrderParameters(dest.parameters, src.parameters);
+ dest.numerator = src.numerator;
+ dest.denominator = src.denominator;
+ dest.signature = src.signature;
+ dest.extraData = src.extraData;
+ }
+
+ bytes32 constant TEMP_ADVANCED_ORDER =
+ keccak256("seaport-sol.temp.AdvancedOrder");
+
+ function _getTempAdvancedOrder()
+ internal
+ returns (AdvancedOrder storage _tempAdvancedOrder)
+ {
+ uint256 position = _deriveTempSlotWithCounter(TEMP_ADVANCED_ORDER);
+ assembly {
+ _tempAdvancedOrder.slot := position
+ }
+ }
+
+ function setAdvancedOrders(
+ AdvancedOrder[] storage dest,
+ AdvancedOrder[] memory src
+ ) internal {
+ AdvancedOrder storage _tempAdvancedOrder = _getTempAdvancedOrder();
+
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ setAdvancedOrder(_tempAdvancedOrder, src[i]);
+ dest.push(_tempAdvancedOrder);
+ }
+ }
+
+ function setOrderParameters(
+ OrderParameters storage dest,
+ OrderParameters memory src
+ ) internal {
+ dest.offerer = src.offerer;
+ dest.zone = src.zone;
+ setOfferItems(dest.offer, src.offer);
+ setConsiderationItems(dest.consideration, src.consideration);
+ dest.orderType = src.orderType;
+ dest.startTime = src.startTime;
+ dest.endTime = src.endTime;
+ dest.zoneHash = src.zoneHash;
+ dest.salt = src.salt;
+ dest.conduitKey = src.conduitKey;
+ dest.totalOriginalConsiderationItems = src
+ .totalOriginalConsiderationItems;
+ }
+
+ function setOfferItems(
+ OfferItem[] storage dest,
+ OfferItem[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(src[i]);
+ }
+ }
+
+ function setConsiderationItems(
+ ConsiderationItem[] storage dest,
+ ConsiderationItem[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(src[i]);
+ }
+ }
+
+ function setFulfillment(
+ Fulfillment storage dest,
+ Fulfillment memory src
+ ) internal {
+ setFulfillmentComponents(dest.offerComponents, src.offerComponents);
+ setFulfillmentComponents(
+ dest.considerationComponents,
+ src.considerationComponents
+ );
+ }
+
+ function setFulfillments(
+ Fulfillment[] storage dest,
+ Fulfillment[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ Fulfillment storage empty = _fulfillment();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setFulfillment(dest[i], src[i]);
+ }
+ }
+
+ function setFulfillmentComponents(
+ FulfillmentComponent[] storage dest,
+ FulfillmentComponent[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(src[i]);
+ }
+ }
+
+ bytes32 constant TEMP_FULFILLMENT_COMPONENTS =
+ keccak256("seaport-sol.temp.FulfillmentComponents");
+
+ function _getTempFulfillmentComponents()
+ internal
+ pure
+ returns (FulfillmentComponent[] storage _tempFulfillmentComponents)
+ {
+ bytes32 position = TEMP_FULFILLMENT_COMPONENTS;
+ assembly {
+ _tempFulfillmentComponents.slot := position
+ }
+ }
+
+ function pushFulFillmentComponents(
+ FulfillmentComponent[][] storage dest,
+ FulfillmentComponent[] memory src
+ ) internal {
+ FulfillmentComponent[]
+ storage _tempFulfillmentComponents = _getTempFulfillmentComponents();
+ setFulfillmentComponents(_tempFulfillmentComponents, src);
+ dest.push(_tempFulfillmentComponents);
+ }
+
+ function setFulfillmentComponentsArray(
+ FulfillmentComponent[][] storage dest,
+ FulfillmentComponent[][] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ pushFulFillmentComponents(dest, src[i]);
+ }
+ }
+
+ function toConsiderationItems(
+ OfferItem[] memory _offerItems,
+ address payable receiver
+ ) internal pure returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory considerationItems = new ConsiderationItem[](
+ _offerItems.length
+ );
+ for (uint256 i = 0; i < _offerItems.length; ++i) {
+ considerationItems[i] = ConsiderationItem(
+ _offerItems[i].itemType,
+ _offerItems[i].token,
+ _offerItems[i].identifierOrCriteria,
+ _offerItems[i].startAmount,
+ _offerItems[i].endAmount,
+ receiver
+ );
+ }
+ return considerationItems;
+ }
+
+ function toOfferItems(
+ ConsiderationItem[] memory _considerationItems
+ ) internal pure returns (OfferItem[] memory) {
+ OfferItem[] memory _offerItems = new OfferItem[](
+ _considerationItems.length
+ );
+ for (uint256 i = 0; i < _offerItems.length; i++) {
+ _offerItems[i] = OfferItem(
+ _considerationItems[i].itemType,
+ _considerationItems[i].token,
+ _considerationItems[i].identifierOrCriteria,
+ _considerationItems[i].startAmount,
+ _considerationItems[i].endAmount
+ );
+ }
+ return _offerItems;
+ }
+
+ function createMirrorOrderParameters(
+ OrderParameters memory orderParameters,
+ address payable offerer,
+ address zone,
+ bytes32 conduitKey
+ ) public pure returns (OrderParameters memory) {
+ OfferItem[] memory _offerItems = toOfferItems(
+ orderParameters.consideration
+ );
+ ConsiderationItem[] memory _considerationItems = toConsiderationItems(
+ orderParameters.offer,
+ offerer
+ );
+
+ OrderParameters memory _mirrorOrderParameters = OrderParameters(
+ offerer,
+ zone,
+ _offerItems,
+ _considerationItems,
+ orderParameters.orderType,
+ orderParameters.startTime,
+ orderParameters.endTime,
+ orderParameters.zoneHash,
+ orderParameters.salt,
+ conduitKey,
+ _considerationItems.length
+ );
+ return _mirrorOrderParameters;
+ }
+
+ function setExecutions(
+ Execution[] storage dest,
+ Execution[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(src[i]);
+ }
+ }
+
+ function setOrderParameters(
+ OrderParameters[] storage dest,
+ OrderParameters[] memory src
+ ) internal {
+ while (dest.length != 0) {
+ dest.pop();
+ }
+ OrderParameters storage empty = _orderParameters();
+ for (uint256 i = 0; i < src.length; ++i) {
+ dest.push(empty);
+ setOrderParameters(dest[i], src[i]);
+ }
+ }
+}
diff --git a/contracts/interfaces/AbridgedTokenInterfaces.sol b/contracts/interfaces/AbridgedTokenInterfaces.sol
index 914279c7b..14d1000f3 100644
--- a/contracts/interfaces/AbridgedTokenInterfaces.sol
+++ b/contracts/interfaces/AbridgedTokenInterfaces.sol
@@ -30,10 +30,20 @@ interface ERC20Interface {
*
* @return success True if the approval was successful.
*/
+
function approve(
address spender,
uint256 value
) external returns (bool success);
+
+ /**
+ * @dev Returns the amount of tokens owned by `account`.
+ *
+ * @param account The address of the account to check the balance of.
+ *
+ * @return balance The amount of tokens owned by `account`.
+ */
+ function balanceOf(address account) external view returns (uint256);
}
/**
@@ -116,4 +126,18 @@ interface ERC1155Interface {
* @param approved Whether the operator is approved.
*/
function setApprovalForAll(address to, bool approved) external;
+
+ /**
+ * @dev Returns the owner of a given token ID.
+ *
+ * @param account The address of the account to check the balance of.
+ * @param id The token ID.
+ *
+ * @return balance The balance of the token.
+ */
+
+ function balanceOf(
+ address account,
+ uint256 id
+ ) external view returns (uint256);
}
diff --git a/contracts/interfaces/ConsiderationInterface.sol b/contracts/interfaces/ConsiderationInterface.sol
index 4132fad1b..3eaff8ce1 100644
--- a/contracts/interfaces/ConsiderationInterface.sol
+++ b/contracts/interfaces/ConsiderationInterface.sol
@@ -15,7 +15,7 @@ import {
/**
* @title ConsiderationInterface
* @author 0age
- * @custom:version 1.2
+ * @custom:version 1.3
* @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
diff --git a/contracts/interfaces/SeaportInterface.sol b/contracts/interfaces/SeaportInterface.sol
index f44a3d0d7..aede7b94a 100644
--- a/contracts/interfaces/SeaportInterface.sol
+++ b/contracts/interfaces/SeaportInterface.sol
@@ -15,7 +15,7 @@ import {
/**
* @title SeaportInterface
* @author 0age
- * @custom:version 1.2
+ * @custom:version 1.3
* @notice Seaport 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
diff --git a/contracts/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol
new file mode 100644
index 000000000..108761496
--- /dev/null
+++ b/contracts/interfaces/SeaportRouterInterface.sol
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {
+ AdvancedOrder,
+ CriteriaResolver,
+ FulfillmentComponent
+} from "../lib/ConsiderationStructs.sol";
+
+import { Execution } from "../lib/ConsiderationStructs.sol";
+
+/**
+ * @title SeaportRouterInterface
+ * @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth)
+ * @notice A utility contract for fulfilling orders with multiple
+ * Seaport versions. DISCLAIMER: This contract only works when
+ * all consideration items across all listings are native tokens.
+ */
+interface SeaportRouterInterface {
+ /**
+ * @dev Advanced order parameters for use through the
+ * FulfillAvailableAdvancedOrdersParams struct.
+ */
+ struct AdvancedOrderParams {
+ AdvancedOrder[] advancedOrders;
+ CriteriaResolver[] criteriaResolvers;
+ FulfillmentComponent[][] offerFulfillments;
+ FulfillmentComponent[][] considerationFulfillments;
+ uint256 etherValue; /// The ether value to send with the set of orders.
+ }
+
+ /**
+ * @dev Parameters for using fulfillAvailableAdvancedOrders
+ * through SeaportRouter.
+ */
+ struct FulfillAvailableAdvancedOrdersParams {
+ address[] seaportContracts;
+ AdvancedOrderParams[] advancedOrderParams;
+ bytes32 fulfillerConduitKey;
+ address recipient;
+ uint256 maximumFulfilled;
+ }
+
+ /**
+ * @dev Calldata params for calling FulfillAvailableAdvancedOrders.
+ */
+ struct CalldataParams {
+ AdvancedOrder[] advancedOrders;
+ CriteriaResolver[] criteriaResolvers;
+ FulfillmentComponent[][] offerFulfillments;
+ FulfillmentComponent[][] considerationFulfillments;
+ bytes32 fulfillerConduitKey;
+ address recipient;
+ uint256 maximumFulfilled;
+ }
+
+ /**
+ * @dev Revert with an error if a provided Seaport contract is not allowed
+ * to be used in the router.
+ */
+ error SeaportNotAllowed(address seaport);
+
+ /**
+ * @dev Revert with an error if an ether transfer back to the fulfiller
+ * fails.
+ */
+ error EtherReturnTransferFailed(
+ address recipient,
+ uint256 amount,
+ bytes returnData
+ );
+
+ /**
+ * @dev Fallback function to receive excess ether, in case total amount of
+ * ether sent is more than the amount required to fulfill the order.
+ */
+ receive() external payable;
+
+ /**
+ * @notice Fulfill available advanced orders through multiple Seaport
+ * versions.
+ * See {SeaportInterface-fulfillAvailableAdvancedOrders}
+ *
+ * @param params The parameters for fulfilling available advanced orders.
+ */
+ function fulfillAvailableAdvancedOrders(
+ FulfillAvailableAdvancedOrdersParams calldata params
+ )
+ external
+ payable
+ returns (
+ bool[][] memory availableOrders,
+ Execution[][] memory executions
+ );
+
+ /**
+ * @notice Returns the Seaport contracts allowed to be used through this
+ * router.
+ */
+ function getAllowedSeaportContracts()
+ external
+ view
+ returns (address[] memory);
+}
diff --git a/contracts/lib/Consideration.sol b/contracts/lib/Consideration.sol
index 96d66a639..e465c0831 100644
--- a/contracts/lib/Consideration.sol
+++ b/contracts/lib/Consideration.sol
@@ -42,7 +42,7 @@ import {
* @custom:coauthor d1ll0n (d1ll0n.eth)
* @custom:coauthor transmissions11 (t11s.eth)
* @custom:coauthor James Wenzel (emo.eth)
- * @custom:version 1.2
+ * @custom:version 1.3
* @notice Consideration is a generalized native token/ERC20/ERC721/ERC1155
* marketplace that provides lightweight methods for common routes as
* well as more flexible methods for composing advanced orders or groups
diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol
index 074fe2cf9..a8e1f0609 100644
--- a/contracts/lib/ConsiderationBase.sol
+++ b/contracts/lib/ConsiderationBase.sol
@@ -10,6 +10,30 @@ import {
} from "../interfaces/ConsiderationEventsAndErrors.sol";
import {
+ BulkOrder_Typehash_Height_One,
+ BulkOrder_Typehash_Height_Two,
+ BulkOrder_Typehash_Height_Three,
+ BulkOrder_Typehash_Height_Four,
+ BulkOrder_Typehash_Height_Five,
+ BulkOrder_Typehash_Height_Six,
+ BulkOrder_Typehash_Height_Seven,
+ BulkOrder_Typehash_Height_Eight,
+ BulkOrder_Typehash_Height_Nine,
+ BulkOrder_Typehash_Height_Ten,
+ BulkOrder_Typehash_Height_Eleven,
+ BulkOrder_Typehash_Height_Twelve,
+ BulkOrder_Typehash_Height_Thirteen,
+ BulkOrder_Typehash_Height_Fourteen,
+ BulkOrder_Typehash_Height_Fifteen,
+ BulkOrder_Typehash_Height_Sixteen,
+ BulkOrder_Typehash_Height_Seventeen,
+ BulkOrder_Typehash_Height_Eighteen,
+ BulkOrder_Typehash_Height_Nineteen,
+ BulkOrder_Typehash_Height_Twenty,
+ BulkOrder_Typehash_Height_TwentyOne,
+ BulkOrder_Typehash_Height_TwentyTwo,
+ BulkOrder_Typehash_Height_TwentyThree,
+ BulkOrder_Typehash_Height_TwentyFour,
EIP712_domainData_chainId_offset,
EIP712_domainData_nameHash_offset,
EIP712_domainData_size,
@@ -28,8 +52,6 @@ import {
import { ConsiderationDecoder } from "./ConsiderationDecoder.sol";
import { ConsiderationEncoder } from "./ConsiderationEncoder.sol";
-import { TypehashDirectory } from "./TypehashDirectory.sol";
-
/**
* @title ConsiderationBase
* @author 0age
@@ -53,9 +75,6 @@ contract ConsiderationBase is
// Allow for interaction with the conduit controller.
ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER;
- // BulkOrder typehash storage
- TypehashDirectory internal immutable _BULK_ORDER_TYPEHASH_DIRECTORY;
-
// Cache the conduit creation code hash used by the conduit controller.
bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
@@ -78,8 +97,6 @@ contract ConsiderationBase is
_ORDER_TYPEHASH
) = _deriveTypehashes();
- _BULK_ORDER_TYPEHASH_DIRECTORY = new TypehashDirectory();
-
// Store the current chainId and derive the current domain separator.
_CHAIN_ID = block.chainid;
_DOMAIN_SEPARATOR = _deriveDomainSeparator();
@@ -207,45 +224,45 @@ contract ConsiderationBase is
nameHash = keccak256(bytes(_nameString()));
// Derive hash of the version string of the contract.
- versionHash = keccak256(bytes("1.2"));
+ versionHash = keccak256(bytes("1.3"));
// Construct the OfferItem type string.
bytes memory offerItemTypeString = bytes(
"OfferItem("
- "uint8 itemType,"
- "address token,"
- "uint256 identifierOrCriteria,"
- "uint256 startAmount,"
- "uint256 endAmount"
+ "uint8 itemType,"
+ "address token,"
+ "uint256 identifierOrCriteria,"
+ "uint256 startAmount,"
+ "uint256 endAmount"
")"
);
// Construct the ConsiderationItem type string.
bytes memory considerationItemTypeString = bytes(
"ConsiderationItem("
- "uint8 itemType,"
- "address token,"
- "uint256 identifierOrCriteria,"
- "uint256 startAmount,"
- "uint256 endAmount,"
- "address recipient"
+ "uint8 itemType,"
+ "address token,"
+ "uint256 identifierOrCriteria,"
+ "uint256 startAmount,"
+ "uint256 endAmount,"
+ "address recipient"
")"
);
// Construct the OrderComponents type string, not including the above.
bytes memory orderComponentsPartialTypeString = bytes(
"OrderComponents("
- "address offerer,"
- "address zone,"
- "OfferItem[] offer,"
- "ConsiderationItem[] consideration,"
- "uint8 orderType,"
- "uint256 startTime,"
- "uint256 endTime,"
- "bytes32 zoneHash,"
- "uint256 salt,"
- "bytes32 conduitKey,"
- "uint256 counter"
+ "address offerer,"
+ "address zone,"
+ "OfferItem[] offer,"
+ "ConsiderationItem[] consideration,"
+ "uint8 orderType,"
+ "uint256 startTime,"
+ "uint256 endTime,"
+ "bytes32 zoneHash,"
+ "uint256 salt,"
+ "bytes32 conduitKey,"
+ "uint256 counter"
")"
);
@@ -253,10 +270,10 @@ contract ConsiderationBase is
eip712DomainTypehash = keccak256(
bytes(
"EIP712Domain("
- "string name,"
- "string version,"
- "uint256 chainId,"
- "address verifyingContract"
+ "string name,"
+ "string version,"
+ "uint256 chainId,"
+ "address verifyingContract"
")"
)
);
@@ -277,14 +294,188 @@ contract ConsiderationBase is
orderTypehash = keccak256(orderTypeString);
}
- function _lookupBulkOrderTypehash(
- uint256 treeHeight
- ) internal view returns (bytes32 typeHash) {
- TypehashDirectory directory = _BULK_ORDER_TYPEHASH_DIRECTORY;
+ /**
+ * @dev Internal pure function to look up one of twenty-four potential bulk
+ * order typehash constants based on the height of the bulk order tree.
+ * Note that values between one and twenty-four are supported, which is
+ * enforced by _isValidBulkOrderSize.
+ *
+ * @param _treeHeight The height of the bulk order tree. The value must be
+ * between one and twenty-four.
+ *
+ * @return _typeHash The EIP-712 typehash for the bulk order type with the
+ * given height.
+ */
+ function _lookupBulkOrderTypehash(uint256 _treeHeight)
+ internal
+ pure
+ returns (bytes32 _typeHash)
+ {
+ // Utilize assembly to efficiently retrieve correct bulk order typehash.
assembly {
- let typeHashOffset := add(1, shl(OneWordShift, sub(treeHeight, 1)))
- extcodecopy(directory, 0, typeHashOffset, OneWord)
- typeHash := mload(0)
+ // Use a Yul function to enable use of the `leave` keyword
+ // to stop searching once the appropriate type hash is found.
+ function lookupTypeHash(treeHeight) -> typeHash {
+ // Handle tree heights one through eight.
+ if lt(treeHeight, 9) {
+ // Handle tree heights one through four.
+ if lt(treeHeight, 5) {
+ // Handle tree heights one and two.
+ if lt(treeHeight, 3) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 1),
+ BulkOrder_Typehash_Height_One,
+ BulkOrder_Typehash_Height_Two
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle height three and four via branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 3),
+ BulkOrder_Typehash_Height_Three,
+ BulkOrder_Typehash_Height_Four
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle tree height five and six.
+ if lt(treeHeight, 7) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 5),
+ BulkOrder_Typehash_Height_Five,
+ BulkOrder_Typehash_Height_Six
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle height seven and eight via branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 7),
+ BulkOrder_Typehash_Height_Seven,
+ BulkOrder_Typehash_Height_Eight
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle tree height nine through sixteen.
+ if lt(treeHeight, 17) {
+ // Handle tree height nine through twelve.
+ if lt(treeHeight, 13) {
+ // Handle tree height nine and ten.
+ if lt(treeHeight, 11) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 9),
+ BulkOrder_Typehash_Height_Nine,
+ BulkOrder_Typehash_Height_Ten
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle height eleven and twelve via branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 11),
+ BulkOrder_Typehash_Height_Eleven,
+ BulkOrder_Typehash_Height_Twelve
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle tree height thirteen and fourteen.
+ if lt(treeHeight, 15) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 13),
+ BulkOrder_Typehash_Height_Thirteen,
+ BulkOrder_Typehash_Height_Fourteen
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+ // Handle height fifteen and sixteen via branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 15),
+ BulkOrder_Typehash_Height_Fifteen,
+ BulkOrder_Typehash_Height_Sixteen
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle tree height seventeen through twenty.
+ if lt(treeHeight, 21) {
+ // Handle tree height seventeen and eighteen.
+ if lt(treeHeight, 19) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 17),
+ BulkOrder_Typehash_Height_Seventeen,
+ BulkOrder_Typehash_Height_Eighteen
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle height nineteen and twenty via branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 19),
+ BulkOrder_Typehash_Height_Nineteen,
+ BulkOrder_Typehash_Height_Twenty
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle tree height twenty-one and twenty-two.
+ if lt(treeHeight, 23) {
+ // Utilize branchless logic to determine typehash.
+ typeHash := ternary(
+ eq(treeHeight, 21),
+ BulkOrder_Typehash_Height_TwentyOne,
+ BulkOrder_Typehash_Height_TwentyTwo
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Handle height twenty-three & twenty-four w/ branchless logic.
+ typeHash := ternary(
+ eq(treeHeight, 23),
+ BulkOrder_Typehash_Height_TwentyThree,
+ BulkOrder_Typehash_Height_TwentyFour
+ )
+
+ // Exit the function once typehash has been located.
+ leave
+ }
+
+ // Implement ternary conditional using branchless logic.
+ function ternary(cond, ifTrue, ifFalse) -> c {
+ c := xor(ifFalse, mul(cond, xor(ifFalse, ifTrue)))
+ }
+
+ // Look up the typehash using the supplied tree height.
+ _typeHash := lookupTypeHash(_treeHeight)
}
}
}
diff --git a/contracts/lib/ConsiderationConstants.sol b/contracts/lib/ConsiderationConstants.sol
index 23848c64d..39f9f84af 100644
--- a/contracts/lib/ConsiderationConstants.sol
+++ b/contracts/lib/ConsiderationConstants.sol
@@ -46,7 +46,7 @@ uint256 constant information_version_cd_offset = 0x60;
uint256 constant information_domainSeparator_offset = 0x20;
uint256 constant information_conduitController_offset = 0x40;
uint256 constant information_versionLengthPtr = 0x63;
-uint256 constant information_versionWithLength = 0x03312e32; // 1.2
+uint256 constant information_versionWithLength = 0x03312e33; // 1.3
uint256 constant information_length = 0xa0;
uint256 constant _NOT_ENTERED = 1;
@@ -186,6 +186,79 @@ uint256 constant BulkOrderProof_lengthRangeAfterMask = 0x2;
uint256 constant BulkOrderProof_keyShift = 0xe8;
uint256 constant BulkOrderProof_keySize = 0x3;
+uint256 constant BulkOrder_Typehash_Height_One = (
+ 0x3ca2711d29384747a8f61d60aad3c450405f7aaff5613541dee28df2d6986d32
+);
+uint256 constant BulkOrder_Typehash_Height_Two = (
+ 0xbf8e29b89f29ed9b529c154a63038ffca562f8d7cd1e2545dda53a1b582dde30
+);
+uint256 constant BulkOrder_Typehash_Height_Three = (
+ 0x53c6f6856e13104584dd0797ca2b2779202dc2597c6066a42e0d8fe990b0024d
+);
+uint256 constant BulkOrder_Typehash_Height_Four = (
+ 0xa02eb7ff164c884e5e2c336dc85f81c6a93329d8e9adf214b32729b894de2af1
+);
+uint256 constant BulkOrder_Typehash_Height_Five = (
+ 0x39c9d33c18e050dda0aeb9a8086fb16fc12d5d64536780e1da7405a800b0b9f6
+);
+uint256 constant BulkOrder_Typehash_Height_Six = (
+ 0x1c19f71958cdd8f081b4c31f7caf5c010b29d12950be2fa1c95070dc47e30b55
+);
+uint256 constant BulkOrder_Typehash_Height_Seven = (
+ 0xca74fab2fece9a1d58234a274220ad05ca096a92ef6a1ca1750b9d90c948955c
+);
+uint256 constant BulkOrder_Typehash_Height_Eight = (
+ 0x7ff98d9d4e55d876c5cfac10b43c04039522f3ddfb0ea9bfe70c68cfb5c7cc14
+);
+uint256 constant BulkOrder_Typehash_Height_Nine = (
+ 0xbed7be92d41c56f9e59ac7a6272185299b815ddfabc3f25deb51fe55fe2f9e8a
+);
+uint256 constant BulkOrder_Typehash_Height_Ten = (
+ 0xd1d97d1ef5eaa37a4ee5fbf234e6f6d64eb511eb562221cd7edfbdde0848da05
+);
+uint256 constant BulkOrder_Typehash_Height_Eleven = (
+ 0x896c3f349c4da741c19b37fec49ed2e44d738e775a21d9c9860a69d67a3dae53
+);
+uint256 constant BulkOrder_Typehash_Height_Twelve = (
+ 0xbb98d87cc12922b83759626c5f07d72266da9702d19ffad6a514c73a89002f5f
+);
+uint256 constant BulkOrder_Typehash_Height_Thirteen = (
+ 0xe6ae19322608dd1f8a8d56aab48ed9c28be489b689f4b6c91268563efc85f20e
+);
+uint256 constant BulkOrder_Typehash_Height_Fourteen = (
+ 0x6b5b04cbae4fcb1a9d78e7b2dfc51a36933d023cf6e347e03d517b472a852590
+);
+uint256 constant BulkOrder_Typehash_Height_Fifteen = (
+ 0xd1eb68309202b7106b891e109739dbbd334a1817fe5d6202c939e75cf5e35ca9
+);
+uint256 constant BulkOrder_Typehash_Height_Sixteen = (
+ 0x1da3eed3ecef6ebaa6e5023c057ec2c75150693fd0dac5c90f4a142f9879fde8
+);
+uint256 constant BulkOrder_Typehash_Height_Seventeen = (
+ 0xeee9a1392aa395c7002308119a58f2582777a75e54e0c1d5d5437bd2e8bf6222
+);
+uint256 constant BulkOrder_Typehash_Height_Eighteen = (
+ 0xc3939feff011e53ab8c35ca3370aad54c5df1fc2938cd62543174fa6e7d85877
+);
+uint256 constant BulkOrder_Typehash_Height_Nineteen = (
+ 0x0efca7572ac20f5ae84db0e2940674f7eca0a4726fa1060ffc2d18cef54b203d
+);
+uint256 constant BulkOrder_Typehash_Height_Twenty = (
+ 0x5a4f867d3d458dabecad65f6201ceeaba0096df2d0c491cc32e6ea4e64350017
+);
+uint256 constant BulkOrder_Typehash_Height_TwentyOne = (
+ 0x80987079d291feebf21c2230e69add0f283cee0b8be492ca8050b4185a2ff719
+);
+uint256 constant BulkOrder_Typehash_Height_TwentyTwo = (
+ 0x3bd8cff538aba49a9c374c806d277181e9651624b3e31111bc0624574f8bca1d
+);
+uint256 constant BulkOrder_Typehash_Height_TwentyThree = (
+ 0x5d6a3f098a0bc373f808c619b1bb4028208721b3c4f8d6bc8a874d659814eb76
+);
+uint256 constant BulkOrder_Typehash_Height_TwentyFour = (
+ 0x1d51df90cba8de7637ca3e8fe1e3511d1dc2f23487d05dbdecb781860c21ac1c
+);
+
uint256 constant receivedItemsHash_ptr = 0x60;
/*
diff --git a/contracts/lib/ConsiderationDecoder.sol b/contracts/lib/ConsiderationDecoder.sol
index 9d1ce5c84..bca09a333 100644
--- a/contracts/lib/ConsiderationDecoder.sol
+++ b/contracts/lib/ConsiderationDecoder.sol
@@ -1339,7 +1339,7 @@ contract ConsiderationDecoder {
*
* @return receivedItem The received item.
*/
- function _convertOfferItemToReceivedItemWithRecipient(
+ function _fromOfferItemToReceivedItemWithRecipient(
OfferItem memory offerItem,
address recipient
) internal pure returns (ReceivedItem memory receivedItem) {
diff --git a/contracts/lib/FulfillmentApplier.sol b/contracts/lib/FulfillmentApplier.sol
index 86ee261e6..3ed34ccb9 100644
--- a/contracts/lib/FulfillmentApplier.sol
+++ b/contracts/lib/FulfillmentApplier.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
-import { Side } from "./ConsiderationEnums.sol";
+import { ItemType, Side } from "./ConsiderationEnums.sol";
import {
AdvancedOrder,
@@ -104,10 +104,12 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
// Skip aggregating offer items if no consideration items are available.
if (considerationItem.amount == 0) {
- // Set the offerer and recipient to null address if execution
- // amount is zero. This will cause the execution item to be skipped.
+ // Set the offerer and recipient to null address and the item type
+ // to a non-native item type if the execution amount is zero. This
+ // will cause the execution item to be skipped.
considerationExecution.offerer = address(0);
considerationExecution.item.recipient = payable(0);
+ considerationExecution.item.itemType = ItemType.ERC20;
return considerationExecution;
}
@@ -213,10 +215,13 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
_revertMissingFulfillmentComponentOnAggregation(side);
}
+ // Retrieve the received item on the execution being returned.
+ ReceivedItem memory item = execution.item;
+
// If the fulfillment components are offer components...
if (side == Side.OFFER) {
// Set the supplied recipient on the execution item.
- execution.item.recipient = payable(recipient);
+ item.recipient = payable(recipient);
// Return execution for aggregated items provided by offerer.
_aggregateValidFulfillmentOfferItems(
@@ -241,11 +246,13 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
execution.conduitKey = fulfillerConduitKey;
}
- // Set the offerer and recipient to null address if execution
- // amount is zero. This will cause the execution item to be skipped.
- if (execution.item.amount == 0) {
+ // Set the offerer and recipient to null address and the item type
+ // to a non-native item type if the execution amount is zero. This
+ // will cause the execution item to be skipped.
+ if (item.amount == 0) {
execution.offerer = address(0);
- execution.item.recipient = payable(0);
+ item.recipient = payable(0);
+ item.itemType = ItemType.ERC20;
}
}
}
@@ -273,9 +280,10 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
// Declare a variable to track errors encountered with amount.
let errorBuffer
- // Declare a variable for the hash of itemType, token, identifier
+ // Declare a variable for the hash of itemType, token, & identifier.
let dataHash
+ // Iterate over each offer component.
for {
// Create variable to track position in offerComponents head.
let fulfillmentHeadPtr := offerComponents
@@ -373,11 +381,11 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
// Retrieve ReceivedItem pointer from Execution.
let receivedItem := mload(execution)
- // Check if this is the first valid fulfillment item
+ // Check if this is the first valid fulfillment item.
switch iszero(dataHash)
case 1 {
- // On first valid item, populate the received item in
- // memory for later comparison.
+ // On first valid item, populate the received item in memory
+ // for later comparison.
// Set the item type on the received item.
mstore(receivedItem, mload(offerItemPtr))
@@ -425,7 +433,7 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
}
}
default {
- // Compare every subsequent item to the first
+ // Compare every subsequent item to the first.
if or(
or(
// The offerer must match on both items.
@@ -543,6 +551,7 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
// Declare variable for hash(itemType, token, identifier, recipient)
let dataHash
+ // Iterate over each consideration component.
for {
// Track position in considerationComponents head.
let fulfillmentHeadPtr := considerationComponents
@@ -613,7 +622,7 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
)
}
- // Declare a separate scope for the amount update
+ // Declare a separate scope for the amount update.
{
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(
@@ -666,9 +675,9 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
)
)
- // Set the recipient on the received item.
- // Note that this depends on the memory layout affected by
- // _validateOrdersAndPrepareToFulfill.
+ // Set the recipient on the received item. Note that this
+ // depends on the memory layout established by the
+ // _validateOrdersAndPrepareToFulfill function.
mstore(
add(receivedItem, ReceivedItem_recipient_offset),
mload(
@@ -704,8 +713,8 @@ contract FulfillmentApplier is FulfillmentApplicationErrors {
}
}
default {
- // Compare every subsequent item to the first
- // The itemType, token, identifier and recipient must match.
+ // Compare every subsequent item to the first; the item
+ // type, token, identifier and recipient must match.
if xor(
dataHash,
// Calculate the hash of (itemType, token, identifier,
diff --git a/contracts/lib/LowLevelHelpers.sol b/contracts/lib/LowLevelHelpers.sol
index 199c44784..882410510 100644
--- a/contracts/lib/LowLevelHelpers.sol
+++ b/contracts/lib/LowLevelHelpers.sol
@@ -108,24 +108,4 @@ contract LowLevelHelpers {
u := b
}
}
-
- /**
- * @dev Internal pure function to compare two addresses without first
- * masking them. Note that dirty upper bits will cause otherwise equal
- * addresses to be recognized as unequal.
- *
- * @param a The first address.
- * @param b The second address
- *
- * @return areEqual A boolean representing whether the addresses are equal.
- */
- function _unmaskedAddressComparison(
- address a,
- address b
- ) internal pure returns (bool areEqual) {
- // Utilize assembly to perform the comparison without masking.
- assembly {
- areEqual := eq(a, b)
- }
- }
}
diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol
index fc47d4b07..648625a86 100644
--- a/contracts/lib/OrderCombiner.sol
+++ b/contracts/lib/OrderCombiner.sol
@@ -29,6 +29,7 @@ import {
import {
AccumulatorDisarmed,
ConsiderationItem_recipient_offset,
+ Execution_offerer_offset,
NonMatchSelector_InvalidErrorValue,
NonMatchSelector_MagicMask,
OneWord,
@@ -136,13 +137,16 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
)
{
// Validate orders, apply amounts, & determine if they utilize conduits.
- bytes32[] memory orderHashes = _validateOrdersAndPrepareToFulfill(
- advancedOrders,
- criteriaResolvers,
- false, // Signifies that invalid orders should NOT revert.
- maximumFulfilled,
- recipient
- );
+ (
+ bytes32[] memory orderHashes,
+ bool containsNonOpen
+ ) = _validateOrdersAndPrepareToFulfill(
+ advancedOrders,
+ criteriaResolvers,
+ false, // Signifies that invalid orders should NOT revert.
+ maximumFulfilled,
+ recipient
+ );
// Aggregate used offer and consideration items and execute transfers.
return
@@ -152,7 +156,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
considerationFulfillments,
fulfillerConduitKey,
recipient,
- orderHashes
+ orderHashes,
+ containsNonOpen
);
}
@@ -182,7 +187,10 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
* already have a designated recipient and are not
* already used as part of a provided fulfillment.
*
- * @return orderHashes The hashes of the orders being fulfilled.
+ * @return orderHashes The hashes of the orders being fulfilled.
+ * @return containsNonOpen A boolean indicating whether any restricted or
+ * contract orders are present within the provided
+ * array of advanced orders.
*/
function _validateOrdersAndPrepareToFulfill(
AdvancedOrder[] memory advancedOrders,
@@ -190,7 +198,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
bool revertOnInvalid,
uint256 maximumFulfilled,
address recipient
- ) internal returns (bytes32[] memory orderHashes) {
+ ) internal returns (bytes32[] memory orderHashes, bool containsNonOpen) {
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard(true); // Native tokens accepted during execution.
@@ -299,16 +307,25 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
uint256 totalOfferItems = offer.length;
{
- // Create a variable indicating if the order is not a
- // contract order. Cache in scratch space to avoid stack
- // depth errors.
+ // Determine the order type, used to check for eligibility
+ // for native token offer items as well as for the presence
+ // of restricted and contract orders (or non-open orders).
OrderType orderType = advancedOrder.parameters.orderType;
+
+ // Utilize assembly to efficiently check for order types.
+ // Note that these checks expect that there are no order
+ // types beyond the current set (0-4) and will need to be
+ // modified if more order types are added.
assembly {
- // Note that this check requires that there are no order
- // types beyond the current set (0-4). It will need to
- // be modified if more order types are added.
+ // Declare a variable indicating if the order is not a
+ // contract order. Cache in scratch space to avoid stack
+ // depth errors.
let isNonContract := lt(orderType, 4)
mstore(0, isNonContract)
+
+ // Update the variable indicating if the order is not an
+ // open order, remaining set if it has been set already.
+ containsNonOpen := or(containsNonOpen, gt(orderType, 1))
}
}
@@ -545,6 +562,10 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
* recipient and are not already used as
* part of a provided fulfillment.
* @param orderHashes An array of order hashes for each order.
+ * @param containsNonOpen A boolean indicating whether any
+ * restricted or contract orders are
+ * present within the provided array of
+ * advanced orders.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
@@ -559,7 +580,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
FulfillmentComponent[][] memory considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
- bytes32[] memory orderHashes
+ bytes32[] memory orderHashes,
+ bool containsNonOpen
)
internal
returns (bool[] memory availableOrders, Execution[] memory executions)
@@ -593,13 +615,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
recipient
);
- // If offerer and recipient on the execution are the same...
- if (
- _unmaskedAddressComparison(
- execution.item.recipient,
- execution.offerer
- )
- ) {
+ // If the execution is filterable...
+ if (_isFilterableExecution(execution)) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
@@ -622,13 +639,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
address(0) // unused
);
- // If offerer and recipient on the execution are the same...
- if (
- _unmaskedAddressComparison(
- execution.item.recipient,
- execution.offerer
- )
- ) {
+ // If the execution is filterable...
+ if (_isFilterableExecution(execution)) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
@@ -664,7 +676,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
advancedOrders,
executions,
orderHashes,
- recipient
+ recipient,
+ containsNonOpen
);
return (availableOrders, executions);
@@ -675,28 +688,29 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
* item for an arbitrary number of fulfilled orders has been met and to
* trigger associated executions, transferring the respective items.
*
- * @param advancedOrders The orders to check and perform executions for.
- * @param executions An array of elements indicating the sequence of
- * transfers to perform when fulfilling the given
- * orders.
- * @param orderHashes An array of order hashes for each order.
- * @param recipient The intended recipient for all items that do
- * not already have a designated recipient and are
- * not used as part of a provided fulfillment.
+ * @param advancedOrders The orders to check and perform executions for.
+ * @param executions An array of elements indicating the sequence of
+ * transfers to perform when fulfilling the given
+ * orders.
+ * @param orderHashes An array of order hashes for each order.
+ * @param recipient The intended recipient for all items that do not
+ * already have a designated recipient and are not
+ * used as part of a provided fulfillment.
+ * @param containsNonOpen A boolean indicating whether any restricted or
+ * contract orders are present within the provided
+ * array of advanced orders.
*
- * @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 availableOrders An array of booleans indicating if each order
+ * with an index corresponding to the index of the
+ * returned boolean was fulfillable or not.
*/
function _performFinalChecksAndExecuteOrders(
AdvancedOrder[] memory advancedOrders,
Execution[] memory executions,
bytes32[] memory orderHashes,
- address recipient
+ address recipient,
+ bool containsNonOpen
) internal returns (bool[] memory /* availableOrders */) {
- // Declare a variable for the available native token balance.
- uint256 nativeTokenBalance;
-
// Retrieve the length of the advanced orders array and place on stack.
uint256 totalOrders = advancedOrders.length;
@@ -710,56 +724,58 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
// accessed and modified, however.
bytes memory accumulator = new bytes(AccumulatorDisarmed);
- // Retrieve the length of the executions array and place on stack.
- uint256 totalExecutions = executions.length;
+ {
+ // Declare a variable for the available native token balance.
+ uint256 nativeTokenBalance;
- // Iterate over each execution.
- for (uint256 i = 0; i < totalExecutions; ) {
- // Retrieve the execution and the associated received item.
- Execution memory execution = executions[i];
- ReceivedItem memory item = execution.item;
+ // Retrieve the length of the executions array and place on stack.
+ uint256 totalExecutions = executions.length;
- // If execution transfers native tokens, reduce value available.
- if (item.itemType == ItemType.NATIVE) {
- // Get the current available balance of native tokens.
- assembly {
- nativeTokenBalance := selfbalance()
- }
+ // Iterate over each execution.
+ for (uint256 i = 0; i < totalExecutions; ) {
+ // Retrieve the execution and the associated received item.
+ Execution memory execution = executions[i];
+ ReceivedItem memory item = execution.item;
- // Ensure that sufficient native tokens are still available.
- if (item.amount > nativeTokenBalance) {
- _revertInsufficientNativeTokensSupplied();
+ // If execution transfers native tokens, reduce value available.
+ if (item.itemType == ItemType.NATIVE) {
+ // Get the current available balance of native tokens.
+ assembly {
+ nativeTokenBalance := selfbalance()
+ }
+
+ // Ensure that sufficient native tokens are still available.
+ if (item.amount > nativeTokenBalance) {
+ _revertInsufficientNativeTokensSupplied();
+ }
}
- }
- // Transfer the item specified by the execution.
- _transfer(
- item,
- execution.offerer,
- execution.conduitKey,
- accumulator
- );
+ // Transfer the item specified by the execution.
+ _transfer(
+ item,
+ execution.offerer,
+ execution.conduitKey,
+ accumulator
+ );
- // Skip overflow check as for loop is indexed starting at zero.
- unchecked {
- ++i;
+ // Skip overflow check as for loop is indexed starting at zero.
+ unchecked {
+ ++i;
+ }
}
}
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
- // duplicate recipient address to stack to avoid stack-too-deep
- address _recipient = recipient;
-
- // Iterate over orders to ensure all consideration items are met.
+ // Iterate over each order.
for (uint256 i = 0; i < totalOrders; ++i) {
// Retrieve the order in question.
AdvancedOrder memory advancedOrder = advancedOrders[i];
- // Skip consideration item checks for order if not fulfilled.
+ // Skip the order in question if not being not fulfilled.
if (advancedOrder.numerator == 0) {
- // This is required because the current memory region, which
- // was previously used by the accumulator, might be dirty.
+ // Explicitly set availableOrders at the given index to
+ // guard against the possibility of dirtied memory.
availableOrders[i] = false;
continue;
}
@@ -779,20 +795,17 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
// Iterate over each offer item to restore it.
for (uint256 j = 0; j < totalOfferItems; ++j) {
+ // Retrieve the offer item in question.
OfferItem memory offerItem = offer[j];
- // Retrieve original amount on the offer item.
- uint256 originalAmount = offerItem.endAmount;
- // Retrieve remaining amount on the offer item.
- uint256 unspentAmount = offerItem.startAmount;
// Transfer to recipient if unspent amount is not zero.
// Note that the transfer will not be reflected in the
// executions array.
- if (unspentAmount != 0) {
+ if (offerItem.startAmount != 0) {
_transfer(
- _convertOfferItemToReceivedItemWithRecipient(
+ _fromOfferItemToReceivedItemWithRecipient(
offerItem,
- _recipient
+ recipient
),
parameters.offerer,
parameters.conduitKey,
@@ -801,26 +814,26 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
}
// Restore original amount on the offer item.
- offerItem.startAmount = originalAmount;
+ offerItem.startAmount = offerItem.endAmount;
}
}
{
- // Retrieve consideration items & ensure they are fulfilled.
+ // Read consideration items & ensure they are fulfilled.
ConsiderationItem[] memory consideration = (
parameters.consideration
);
- // Read length of consideration array & place on the stack.
+ // Read length of consideration array & place on stack.
uint256 totalConsiderationItems = consideration.length;
- // Iterate over each consideration item to ensure it is met.
+ // Iterate over each consideration item.
for (uint256 j = 0; j < totalConsiderationItems; ++j) {
ConsiderationItem memory considerationItem = (
consideration[j]
);
- // Retrieve remaining amount on the consideration item.
+ // Retrieve remaining amount on consideration item.
uint256 unmetAmount = considerationItem.startAmount;
// Revert if the remaining amount is not zero.
@@ -846,27 +859,44 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
}
}
}
-
- // Check restricted orders and contract orders.
- _assertRestrictedAdvancedOrderValidity(
- advancedOrder,
- orderHashes,
- orderHashes[i]
- );
}
}
- // Trigger any remaining accumulated transfers via call to the conduit.
+ // Trigger any accumulated transfers via call to the conduit.
_triggerIfArmed(accumulator);
// Determine whether any native token balance remains.
+ uint256 remainingNativeTokenBalance;
assembly {
- nativeTokenBalance := selfbalance()
+ remainingNativeTokenBalance := selfbalance()
}
// Return any remaining native token balance to the caller.
- if (nativeTokenBalance != 0) {
- _transferNativeTokens(payable(msg.sender), nativeTokenBalance);
+ if (remainingNativeTokenBalance != 0) {
+ _transferNativeTokens(
+ payable(msg.sender),
+ remainingNativeTokenBalance
+ );
+ }
+
+ // If any restricted or contract orders are present in the group of
+ // orders being fulfilled, perform any validateOrder or ratifyOrder
+ // calls after all executions and related transfers are complete.
+ if (containsNonOpen) {
+ // Iterate over each order a second time.
+ for (uint256 i = 0; i < totalOrders; ) {
+ // Check restricted orders and contract orders.
+ _assertRestrictedAdvancedOrderValidity(
+ advancedOrders[i],
+ orderHashes,
+ orderHashes[i]
+ );
+
+ // Skip overflow checks as for loop is indexed starting at zero.
+ unchecked {
+ ++i;
+ }
+ }
}
// Clear the reentrancy guard.
@@ -955,13 +985,16 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
address recipient
) internal returns (Execution[] memory /* executions */) {
// Validate orders, update order status, and determine item amounts.
- bytes32[] memory orderHashes = _validateOrdersAndPrepareToFulfill(
- advancedOrders,
- criteriaResolvers,
- true, // Signifies that invalid orders should revert.
- advancedOrders.length,
- recipient
- );
+ (
+ bytes32[] memory orderHashes,
+ bool containsNonOpen
+ ) = _validateOrdersAndPrepareToFulfill(
+ advancedOrders,
+ criteriaResolvers,
+ true, // Signifies that invalid orders should revert.
+ advancedOrders.length,
+ recipient
+ );
// Emit OrdersMatched event, providing an array of matched order hashes.
_emitOrdersMatched(orderHashes);
@@ -972,7 +1005,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
advancedOrders,
fulfillments,
orderHashes,
- recipient
+ recipient,
+ containsNonOpen
);
}
@@ -981,27 +1015,30 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
* full or partial, after validating, adjusting amounts, and applying
* criteria resolvers.
*
- * @param advancedOrders The orders to match, including a fraction to
- * attempt to fill for each order.
- * @param fulfillments An array of elements allocating offer
- * components to consideration components. Note
- * that the final amount of each consideration
- * component must be zero for a match operation to
- * be considered valid.
- * @param orderHashes An array of order hashes for each order.
- * @param recipient The intended recipient for all items that do
- * not already have a designated recipient and are
- * not used as part of a provided fulfillment.
+ * @param advancedOrders The orders to match, including a fraction to
+ * attempt to fill for each order.
+ * @param fulfillments An array of elements allocating offer components
+ * to consideration components. Note that the final
+ * amount of each consideration component must be
+ * zero for a match operation to be considered valid.
+ * @param orderHashes An array of order hashes for each order.
+ * @param recipient The intended recipient for all items that do not
+ * already have a designated recipient and are not
+ * used as part of a provided fulfillment.
+ * @param containsNonOpen A boolean indicating whether any restricted or
+ * contract orders are present within the provided
+ * array of advanced orders.
*
- * @return executions An array of elements indicating the sequence of
- * transfers performed as part of matching the
- * given orders.
+ * @return executions An array of elements indicating the sequence of
+ * transfers performed as part of matching the given
+ * orders.
*/
function _fulfillAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
Fulfillment[] memory fulfillments,
bytes32[] memory orderHashes,
- address recipient
+ address recipient,
+ bool containsNonOpen
) internal returns (Execution[] memory executions) {
// Retrieve fulfillments array length and place on the stack.
uint256 totalFulfillments = fulfillments.length;
@@ -1027,13 +1064,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
i
);
- // If offerer and recipient on the execution are the same...
- if (
- _unmaskedAddressComparison(
- execution.item.recipient,
- execution.offerer
- )
- ) {
+ // If the execution is filterable...
+ if (_isFilterableExecution(execution)) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
@@ -1059,10 +1091,46 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
advancedOrders,
executions,
orderHashes,
- recipient
+ recipient,
+ containsNonOpen
);
// Return the executions array.
return executions;
}
+
+ /**
+ * @dev Internal pure function to determine whether a given execution is
+ * filterable and may be removed from the executions array. The offerer
+ * and the recipient must be the same address and the item type cannot
+ * indicate a native token transfer.
+ *
+ * @param execution The execution to check for filterability.
+ *
+ * @return filterable A boolean indicating whether the execution in question
+ * can be filtered from the executions array.
+ */
+ function _isFilterableExecution(
+ Execution memory execution
+ ) internal pure returns (bool filterable) {
+ // Utilize assembly to efficiently determine if execution is filterable.
+ assembly {
+ // Retrieve the received item referenced by the execution.
+ let item := mload(execution)
+
+ // Determine whether the execution is filterable.
+ filterable := and(
+ // Determine if offerer and recipient are the same address.
+ eq(
+ // Retrieve the recipient's address from the received item.
+ mload(add(item, ReceivedItem_recipient_offset)),
+ // Retrieve the offerer's address from the execution.
+ mload(add(execution, Execution_offerer_offset))
+ ),
+ // Determine if received item's item type is non-zero, thereby
+ // indicating that the execution does not involve native tokens.
+ iszero(iszero(mload(item)))
+ )
+ }
+ }
}
diff --git a/contracts/lib/Verifiers.sol b/contracts/lib/Verifiers.sol
index 65fd80f0e..46587429f 100644
--- a/contracts/lib/Verifiers.sol
+++ b/contracts/lib/Verifiers.sol
@@ -92,11 +92,18 @@ contract Verifiers is Assertions, SignatureVerification {
bytes32 orderHash,
bytes memory signature
) internal view {
+ // Determine whether the offerer is the caller.
+ bool offererIsCaller;
+ assembly {
+ offererIsCaller := eq(offerer, caller())
+ }
+
// Skip signature verification if the offerer is the caller.
- if (_unmaskedAddressComparison(offerer, msg.sender)) {
+ if (offererIsCaller) {
return;
}
+ // Derive the EIP-712 domain separator.
bytes32 domainSeparator = _domainSeparator();
// Derive original EIP-712 digest using domain separator and order hash.
@@ -105,14 +112,17 @@ contract Verifiers is Assertions, SignatureVerification {
orderHash
);
+ // Read the length of the signature from memory and place on the stack.
uint256 originalSignatureLength = signature.length;
+ // Determine effective digest if signature has a valid bulk order size.
bytes32 digest;
if (_isValidBulkOrderSize(originalSignatureLength)) {
// Rederive order hash and digest using bulk order proof.
(orderHash) = _computeBulkOrderProof(signature, orderHash);
digest = _deriveEIP712Digest(domainSeparator, orderHash);
} else {
+ // Supply the original digest as the effective digest.
digest = originalDigest;
}
@@ -171,7 +181,7 @@ contract Verifiers is Assertions, SignatureVerification {
function _computeBulkOrderProof(
bytes memory proofAndSignature,
bytes32 leaf
- ) internal view returns (bytes32 bulkOrderHash) {
+ ) internal pure returns (bytes32 bulkOrderHash) {
// Declare arguments for the root hash and the height of the proof.
bytes32 root;
uint256 height;
@@ -222,7 +232,7 @@ contract Verifiers is Assertions, SignatureVerification {
root := keccak256(0, TwoWords)
}
- // Retrieve appropriate typehash from runtime storage based on height.
+ // Retrieve appropriate typehash constant based on height.
bytes32 rootTypeHash = _lookupBulkOrderTypehash(height);
// Use the typehash and the root hash to derive final bulk order hash.
diff --git a/contracts/test/Reenterer.sol b/contracts/test/Reenterer.sol
index 03eb2239d..625c6cc8b 100644
--- a/contracts/test/Reenterer.sol
+++ b/contracts/test/Reenterer.sol
@@ -37,4 +37,14 @@ contract Reenterer {
isPrepared = false;
}
}
+
+ function execute(address to, uint256 value, bytes memory data) external {
+ (bool success, ) = payable(to).call{ value: value }(data);
+ if (!success) {
+ assembly {
+ returndatacopy(0, 0, returndatasize())
+ revert(0, returndatasize())
+ }
+ }
+ }
}
diff --git a/contracts/test/TestTransferValidationZoneOfferer.sol b/contracts/test/TestTransferValidationZoneOfferer.sol
new file mode 100644
index 000000000..25f45460a
--- /dev/null
+++ b/contracts/test/TestTransferValidationZoneOfferer.sol
@@ -0,0 +1,300 @@
+//SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {
+ ERC20Interface,
+ ERC721Interface,
+ ERC1155Interface
+} from "../interfaces/AbridgedTokenInterfaces.sol";
+
+import {
+ ReceivedItem,
+ Schema,
+ SpentItem,
+ ZoneParameters
+} from "../lib/ConsiderationStructs.sol";
+
+import { ItemType } from "../lib/ConsiderationEnums.sol";
+
+import {
+ ContractOffererInterface
+} from "../interfaces/ContractOffererInterface.sol";
+
+import { ZoneInterface } from "../interfaces/ZoneInterface.sol";
+
+contract TestTransferValidationZoneOfferer is
+ ContractOffererInterface,
+ ZoneInterface
+{
+ error InvalidBalance();
+ error InvalidOwner();
+
+ constructor() {}
+
+ /**
+ * @dev Validates that the parties have received the correct items.
+ *
+ * @param zoneParameters The zone parameters, including the SpentItem and
+ * ReceivedItem arrays.
+ *
+ * @return validOrderMagicValue The magic value to indicate things are OK.
+ */
+
+ function validateOrder(
+ ZoneParameters calldata zoneParameters
+ ) external view override returns (bytes4 validOrderMagicValue) {
+ // Validate the order.
+ // Currently assumes that the balances of all tokens of addresses are
+ // zero at the start of the transaction.
+
+ // Check if all consideration items have been received.
+ _assertValidReceivedItems(zoneParameters.consideration);
+
+ // Check if all offer items have been spent.
+ _assertValidSpentItems(zoneParameters.fulfiller, zoneParameters.offer);
+
+ // Return the selector of validateOrder as the magic value.
+ validOrderMagicValue = ZoneInterface.validateOrder.selector;
+ }
+
+ /**
+ * @dev Generates an order with the specified minimum and maximum spent
+ * items.
+ */
+ function generateOrder(
+ address,
+ SpentItem[] calldata a,
+ SpentItem[] calldata b,
+ bytes calldata c
+ )
+ external
+ virtual
+ override
+ returns (SpentItem[] memory offer, ReceivedItem[] memory consideration)
+ {
+ return previewOrder(address(this), address(this), a, b, c);
+ }
+
+ /**
+ * @dev View function to preview an order generated in response to a minimum
+ * set of received items, maximum set of spent items, and context
+ * (supplied as extraData).
+ */
+ function previewOrder(
+ address,
+ address,
+ SpentItem[] calldata a,
+ SpentItem[] calldata b,
+ bytes calldata
+ )
+ public
+ view
+ override
+ returns (SpentItem[] memory offer, ReceivedItem[] memory consideration)
+ {
+ return (a, _convertSpentToReceived(b));
+ }
+
+ /**
+ * @dev Ratifies that the parties have received the correct items.
+ *
+ * @param minimumReceived The minimum items that the caller was willing to
+ * receive.
+ * @param maximumSpent The maximum items that the caller was willing to
+ * spend.
+ * @param context The context of the order.
+ * @ param orderHashes The order hashes, unused here.
+ * @ param contractNonce The contract nonce, unused here.
+ *
+ * @return ratifyOrderMagicValue The magic value to indicate things are OK.
+ */
+ function ratifyOrder(
+ SpentItem[] calldata minimumReceived /* offer */,
+ ReceivedItem[] calldata maximumSpent /* consideration */,
+ bytes calldata context /* context */,
+ bytes32[] calldata /* orderHashes */,
+ uint256 /* contractNonce */
+ ) external view override returns (bytes4 /* ratifyOrderMagicValue */) {
+ // Ratify the order.
+
+ // Ensure that the offerer or recipient has received all consideration
+ // items.
+ _assertValidReceivedItems(maximumSpent);
+
+ // Get the fulfiller address from the context.
+ address fulfiller = address(bytes20(context[0:20]));
+
+ // Ensure that the fulfiller has received all offer items.
+ _assertValidSpentItems(fulfiller, minimumReceived);
+
+ return this.ratifyOrder.selector;
+ }
+
+ function getSeaportMetadata()
+ external
+ pure
+ override(ContractOffererInterface, ZoneInterface)
+ returns (string memory name, Schema[] memory schemas)
+ {
+ // Return the metadata.
+ name = "TestTransferValidationZoneOfferer";
+ schemas = new Schema[](1);
+ schemas[0].id = 1337;
+ schemas[0].metadata = new bytes(0);
+ }
+
+ function _convertSpentToReceived(
+ SpentItem[] calldata spentItems
+ ) internal view returns (ReceivedItem[] memory) {
+ ReceivedItem[] memory receivedItems = new ReceivedItem[](
+ spentItems.length
+ );
+ for (uint256 i = 0; i < spentItems.length; ++i) {
+ receivedItems[i] = _convertSpentToReceived(spentItems[i]);
+ }
+ return receivedItems;
+ }
+
+ function _convertSpentToReceived(
+ SpentItem calldata spentItem
+ ) internal view returns (ReceivedItem memory) {
+ return
+ ReceivedItem({
+ itemType: spentItem.itemType,
+ token: spentItem.token,
+ identifier: spentItem.identifier,
+ amount: spentItem.amount,
+ recipient: payable(address(this))
+ });
+ }
+
+ function _assertValidReceivedItems(
+ ReceivedItem[] calldata receivedItems
+ ) internal view {
+ address recipient;
+ ItemType itemType;
+ ReceivedItem memory receivedItem;
+ // Check if all consideration items have been received.
+ for (uint256 i = 0; i < receivedItems.length; i++) {
+ // Check if the consideration item has been received.
+ receivedItem = receivedItems[i];
+ // Get the recipient of the consideration item.
+ recipient = receivedItem.recipient;
+
+ // Get item type.
+ itemType = receivedItem.itemType;
+
+ // Check balance/ownerOf depending on item type.
+ if (itemType == ItemType.NATIVE) {
+ // NATIVE Token
+ _assertNativeTokenTransfer(receivedItem.amount, recipient);
+ } else if (itemType == ItemType.ERC20) {
+ // ERC20 Token
+ _assertERC20Transfer(
+ receivedItem.amount,
+ receivedItem.token,
+ recipient
+ );
+ } else if (itemType == ItemType.ERC721) {
+ // ERC721 Token
+ _assertERC721Transfer(
+ receivedItem.identifier,
+ receivedItem.token,
+ recipient
+ );
+ } else if (itemType == ItemType.ERC1155) {
+ // ERC1155 Token
+ _assertERC1155Transfer(
+ receivedItem.amount,
+ receivedItem.identifier,
+ receivedItem.token,
+ recipient
+ );
+ }
+ }
+ }
+
+ function _assertValidSpentItems(
+ address fulfiller,
+ SpentItem[] calldata spentItems
+ ) internal view {
+ SpentItem memory spentItem;
+ ItemType itemType;
+
+ // Check if all offer items have been spent.
+ for (uint256 i = 0; i < spentItems.length; i++) {
+ // Check if the offer item has been spent.
+ spentItem = spentItems[i];
+ // Get item type.
+ itemType = spentItem.itemType;
+
+ // Check balance/ownerOf depending on item type.
+ if (itemType == ItemType.NATIVE) {
+ // NATIVE Token
+ _assertNativeTokenTransfer(spentItem.amount, fulfiller);
+ } else if (itemType == ItemType.ERC20) {
+ // ERC20 Token
+ _assertERC20Transfer(
+ spentItem.amount,
+ spentItem.token,
+ fulfiller
+ );
+ } else if (itemType == ItemType.ERC721) {
+ // ERC721 Token
+ _assertERC721Transfer(
+ spentItem.identifier,
+ spentItem.token,
+ fulfiller
+ );
+ } else if (itemType == ItemType.ERC1155) {
+ // ERC1155 Token
+ _assertERC1155Transfer(
+ spentItem.amount,
+ spentItem.identifier,
+ spentItem.token,
+ fulfiller
+ );
+ }
+ }
+ }
+
+ function _assertNativeTokenTransfer(
+ uint256 amount,
+ address recipient
+ ) internal view {
+ if (amount > address(recipient).balance) {
+ revert InvalidBalance();
+ }
+ }
+
+ function _assertERC20Transfer(
+ uint256 amount,
+ address token,
+ address recipient
+ ) internal view {
+ if (amount > ERC20Interface(token).balanceOf(recipient)) {
+ revert InvalidBalance();
+ }
+ }
+
+ function _assertERC721Transfer(
+ uint256 identifier,
+ address token,
+ address recipient
+ ) internal view {
+ if (recipient != ERC721Interface(token).ownerOf(identifier)) {
+ revert InvalidOwner();
+ }
+ }
+
+ function _assertERC1155Transfer(
+ uint256 amount,
+ uint256 identifier,
+ address token,
+ address recipient
+ ) internal view {
+ if (amount > ERC1155Interface(token).balanceOf(recipient, identifier)) {
+ revert InvalidBalance();
+ }
+ }
+}
diff --git a/contracts/lib/TypehashDirectory.sol b/contracts/test/TypehashDirectory.sol
similarity index 100%
rename from contracts/lib/TypehashDirectory.sol
rename to contracts/test/TypehashDirectory.sol
diff --git a/foundry.toml b/foundry.toml
index 9da1ed98c..a86f15a71 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -5,11 +5,12 @@ out = 'out'
libs = ["node_modules", "lib"]
test = 'test/foundry'
remappings = [
+ '@rari-capital/solmate/=lib/solmate/',
'ds-test/=lib/ds-test/src/',
'forge-std/=lib/forge-std/src/',
- '@rari-capital/solmate/=lib/solmate/',
'murky/=lib/murky/src/',
- 'openzeppelin-contracts/=lib/openzeppelin-contracts/'
+ 'openzeppelin-contracts/=lib/openzeppelin-contracts/',
+ 'seaport-sol/=contracts/helpers/sol/'
]
optimizer_runs = 4_294_967_295
fs_permissions = [
diff --git a/lib/forge-std b/lib/forge-std
index d666309ed..a2edd39db 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1 +1 @@
-Subproject commit d666309ed272e7fa16fa35f28d63ee6442df45fc
+Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
diff --git a/lib/murky b/lib/murky
index 5f962edf9..1d9566b90 160000
--- a/lib/murky
+++ b/lib/murky
@@ -1 +1 @@
-Subproject commit 5f962edf98f2aeaf2706f7bfd07fac4532b42cc6
+Subproject commit 1d9566b908b9702c45d354a1caabe8ef5a69938d
diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts
index 24d1bb668..5a00628ed 160000
--- a/lib/openzeppelin-contracts
+++ b/lib/openzeppelin-contracts
@@ -1 +1 @@
-Subproject commit 24d1bb668a1152528a6e6d71c2e285d227ed19d9
+Subproject commit 5a00628ed3d6ce3154cee4d2cc93fad920e8ea30
diff --git a/lib/solmate b/lib/solmate
index 3a752b8c8..1b3adf677 160000
--- a/lib/solmate
+++ b/lib/solmate
@@ -1 +1 @@
-Subproject commit 3a752b8c83427ed1ea1df23f092ea7a810205b6c
+Subproject commit 1b3adf677e7e383cc684b5d5bd441da86bf4bf1c
diff --git a/package.json b/package.json
index 141c8079e..afba7954c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "seaport",
- "version": "1.2.0",
+ "version": "1.3.0",
"description": "Seaport is a marketplace protocol for safely and efficiently buying and selling NFTs. Each listing contains an arbitrary number of items that the offerer is willing to give (the \"offer\") along with an arbitrary number of items that must be received along with their respective receivers (the \"consideration\").",
"main": "contracts/Seaport.sol",
"author": "0age",
diff --git a/reference/ReferenceConsideration.sol b/reference/ReferenceConsideration.sol
index 91501bfed..94b50cc6a 100644
--- a/reference/ReferenceConsideration.sol
+++ b/reference/ReferenceConsideration.sol
@@ -28,7 +28,7 @@ import { OrderToExecute } from "./lib/ReferenceConsiderationStructs.sol";
* @author 0age
* @custom:coauthor d1ll0n
* @custom:coauthor transmissions11
- * @custom:version 1.2-reference
+ * @custom:version 1.3-reference
* @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
diff --git a/reference/lib/ReferenceConsiderationBase.sol b/reference/lib/ReferenceConsiderationBase.sol
index 4357df44b..09cff6686 100644
--- a/reference/lib/ReferenceConsiderationBase.sol
+++ b/reference/lib/ReferenceConsiderationBase.sol
@@ -25,7 +25,7 @@ contract ReferenceConsiderationBase is
{
// Declare constants for name, version, and reentrancy sentinel values.
string internal constant _NAME = "Consideration";
- string internal constant _VERSION = "1.2-reference";
+ string internal constant _VERSION = "1.3-reference";
uint256 internal constant _NOT_ENTERED = 1;
uint256 internal constant _ENTERED = 2;
@@ -195,15 +195,16 @@ contract ReferenceConsiderationBase is
bytes32 _nameHash,
bytes32 _versionHash
) internal view virtual returns (bytes32) {
- return keccak256(
- abi.encode(
- _eip712DomainTypeHash,
- _nameHash,
- _versionHash,
- block.chainid,
- address(this)
- )
- );
+ return
+ keccak256(
+ abi.encode(
+ _eip712DomainTypeHash,
+ _nameHash,
+ _versionHash,
+ block.chainid,
+ address(this)
+ )
+ );
}
/**
@@ -245,40 +246,40 @@ contract ReferenceConsiderationBase is
// Construct the OfferItem type string.
bytes memory offerItemTypeString = abi.encodePacked(
"OfferItem(",
- "uint8 itemType,",
- "address token,",
- "uint256 identifierOrCriteria,",
- "uint256 startAmount,",
- "uint256 endAmount",
+ "uint8 itemType,",
+ "address token,",
+ "uint256 identifierOrCriteria,",
+ "uint256 startAmount,",
+ "uint256 endAmount",
")"
);
// Construct the ConsiderationItem type string.
bytes memory considerationItemTypeString = abi.encodePacked(
"ConsiderationItem(",
- "uint8 itemType,",
- "address token,",
- "uint256 identifierOrCriteria,",
- "uint256 startAmount,",
- "uint256 endAmount,",
- "address recipient",
+ "uint8 itemType,",
+ "address token,",
+ "uint256 identifierOrCriteria,",
+ "uint256 startAmount,",
+ "uint256 endAmount,",
+ "address recipient",
")"
);
// Construct the OrderComponents type string, not including the above.
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",
+ "address offerer,",
+ "address zone,",
+ "OfferItem[] offer,",
+ "ConsiderationItem[] consideration,",
+ "uint8 orderType,",
+ "uint256 startTime,",
+ "uint256 endTime,",
+ "bytes32 zoneHash,",
+ "uint256 salt,",
+ "bytes32 conduitKey,",
+ "uint256 counter",
")"
);
@@ -286,10 +287,10 @@ contract ReferenceConsiderationBase is
eip712DomainTypehash = keccak256(
abi.encodePacked(
"EIP712Domain(",
- "string name,",
- "string version,",
- "uint256 chainId,",
- "address verifyingContract",
+ "string name,",
+ "string version,",
+ "uint256 chainId,",
+ "address verifyingContract",
")"
)
);
diff --git a/reference/lib/ReferenceFulfillmentApplier.sol b/reference/lib/ReferenceFulfillmentApplier.sol
index 9cd14006a..665f3d7d7 100644
--- a/reference/lib/ReferenceFulfillmentApplier.sol
+++ b/reference/lib/ReferenceFulfillmentApplier.sol
@@ -79,10 +79,12 @@ contract ReferenceFulfillmentApplier is
// Skip aggregating offer items if no consideration items are available.
if (considerationItem.amount == 0) {
- // Set the offerer and recipient to null address if execution
- // amount is zero. This will cause the execution item to be skipped.
+ // Set the offerer and recipient to null address and item type to a
+ // non-native item type if execution amount is zero. This will cause
+ // the execution item to be skipped.
execution.offerer = address(0);
execution.item.recipient = payable(0);
+ execution.item.itemType = ItemType.ERC20;
return execution;
}
@@ -213,7 +215,7 @@ contract ReferenceFulfillmentApplier is
return
Execution(
ReceivedItem(
- ItemType.NATIVE,
+ ItemType.ERC20,
address(0),
0,
0,
diff --git a/reference/lib/ReferenceOrderCombiner.sol b/reference/lib/ReferenceOrderCombiner.sol
index 90f8d9f96..833a34ea7 100644
--- a/reference/lib/ReferenceOrderCombiner.sol
+++ b/reference/lib/ReferenceOrderCombiner.sol
@@ -503,8 +503,12 @@ contract ReferenceOrderCombiner is
recipient
);
- // If offerer and recipient on the execution are the same...
- if (execution.item.recipient == execution.offerer) {
+ // If offerer and recipient on the execution are the same and the
+ // execution item has a non-native item type...
+ if (
+ execution.item.recipient == execution.offerer &&
+ execution.item.itemType != ItemType.NATIVE
+ ) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
@@ -529,8 +533,12 @@ contract ReferenceOrderCombiner is
recipient // unused
);
- // If offerer and recipient on the execution are the same...
- if (execution.item.recipient == execution.offerer) {
+ // If offerer and recipient on the execution are the same and the
+ // execution item has a non-native item type...
+ if (
+ execution.item.recipient == execution.offerer &&
+ execution.item.itemType != ItemType.NATIVE
+ ) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
@@ -926,8 +934,12 @@ contract ReferenceOrderCombiner is
i
);
- // If offerer and recipient on the execution are the same...
- if (execution.item.recipient == execution.offerer) {
+ // If offerer and recipient on the execution are the same and the
+ // execution item has a non-native item type...
+ if (
+ execution.item.recipient == execution.offerer &&
+ execution.item.itemType != ItemType.NATIVE
+ ) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol
index 9abdd9933..f26b4b4af 100644
--- a/reference/shim/Shim.sol
+++ b/reference/shim/Shim.sol
@@ -46,3 +46,7 @@ import { ConduitMock } from "../../contracts/test/ConduitMock.sol";
import {
ImmutableCreate2FactoryInterface
} from "../../contracts/interfaces/ImmutableCreate2FactoryInterface.sol";
+
+import {
+ TestTransferValidationZoneOfferer
+} from "../../contracts/test/TestTransferValidationZoneOfferer.sol";
diff --git a/remappings.txt b/remappings.txt
deleted file mode 100644
index d56027ba4..000000000
--- a/remappings.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-@rari-capital/solmate=lib/solmate/
-ds-test/=lib/ds-test/src/
-forge-std/=lib/forge-std/src/
-murky/=lib/murky/src/
-openzeppelin-contracts/=lib/openzeppelin-contracts/
\ No newline at end of file
diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts
index dcc7c15b6..805129fe0 100644
--- a/test/advanced.spec.ts
+++ b/test/advanced.spec.ts
@@ -5631,7 +5631,7 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () {
];
const considerationOne = [
- getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getTestItem20(parseEther("10"), parseEther("10"), seller.address),
];
const { order: orderOne, orderHash: orderHashOne } = await createOrder(
@@ -5642,7 +5642,7 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () {
0 // FULL_OPEN
);
- const offerTwo = [getItemETH(parseEther("10"), parseEther("10"))];
+ const offerTwo = [getTestItem20(parseEther("10"), parseEther("10"))];
const considerationTwo = [
getTestItem721(
@@ -5746,6 +5746,105 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () {
);
return receipt;
});
+ it("Does not filter native tokens", async () => {
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+ const secondNFTId = await mintAndApprove721(
+ buyer,
+ marketplaceContract.address
+ );
+
+ const offerOne = [
+ getTestItem721(nftId, toBN(1), toBN(1), undefined, testERC721.address),
+ ];
+
+ const considerationOne = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ ];
+
+ const { order: orderOne } = await createOrder(
+ seller,
+ zone,
+ offerOne,
+ considerationOne,
+ 0 // FULL_OPEN
+ );
+
+ const offerTwo = [getItemETH(parseEther("10"), parseEther("10"))];
+
+ const considerationTwo = [
+ getTestItem721(
+ secondNFTId,
+ toBN(1),
+ toBN(1),
+ seller.address,
+ testERC721.address
+ ),
+ ];
+
+ const { order: orderTwo } = await createOrder(
+ seller,
+ zone,
+ offerTwo,
+ considerationTwo,
+ 0 // FULL_OPEN
+ );
+
+ const offerThree = [
+ getTestItem721(
+ secondNFTId,
+ toBN(1),
+ toBN(1),
+ undefined,
+ testERC721.address
+ ),
+ ];
+
+ const considerationThree = [
+ getTestItem721(
+ nftId,
+ toBN(1),
+ toBN(1),
+ buyer.address,
+ testERC721.address
+ ),
+ ];
+
+ const { order: orderThree } = await createOrder(
+ buyer,
+ zone,
+ offerThree,
+ considerationThree,
+ 0 // FULL_OPEN
+ );
+
+ const fulfillments = [
+ [[[1, 0]], [[0, 0]]],
+ [[[0, 0]], [[2, 0]]],
+ [[[2, 0]], [[1, 0]]],
+ ].map(([offerArr, considerationArr]) =>
+ toFulfillment(offerArr, considerationArr)
+ );
+
+ await expect(
+ marketplaceContract
+ .connect(owner)
+ .matchAdvancedOrders(
+ [orderOne, orderTwo, orderThree],
+ [],
+ fulfillments,
+ ethers.constants.AddressZero,
+ {
+ value: 0,
+ }
+ )
+ ).to.be.revertedWithCustomError(
+ marketplaceContract,
+ "InsufficientNativeTokensSupplied"
+ );
+ });
});
describe("Order groups", async () => {
diff --git a/test/foundry/BulkSignature.t.sol b/test/foundry/BulkSignature.t.sol
index 84459d669..10dae8d99 100644
--- a/test/foundry/BulkSignature.t.sol
+++ b/test/foundry/BulkSignature.t.sol
@@ -100,7 +100,7 @@ contract BulkSignatureTest is BaseOrderTest {
configureOrderComponents(context.seaport);
OrderComponents[] memory orderComponents = new OrderComponents[](3);
orderComponents[0] = baseOrderComponents;
- // other order components can remain empty
+ // The other order components can remain empty.
EIP712MerkleTree merkleTree = new EIP712MerkleTree();
bytes memory bulkSignature = merkleTree.signBulkOrder(
@@ -177,7 +177,7 @@ contract BulkSignatureTest is BaseOrderTest {
configureOrderComponents(context.seaport);
OrderComponents[] memory orderComponents = new OrderComponents[](3);
orderComponents[0] = baseOrderComponents;
- // other order components can remain empty
+ // The other order components can remain empty.
EIP712MerkleTree merkleTree = new EIP712MerkleTree();
bytes memory bulkSignature = merkleTree.signSparseBulkOrder(
@@ -195,7 +195,7 @@ contract BulkSignatureTest is BaseOrderTest {
context.seaport.fulfillOrder{ value: 1 }(order, bytes32(0));
}
- function testSparseBulkSignatureFuzz(SparseArgs memory sparseArgs) public {
+ function testBulkSignatureSparseFuzz(SparseArgs memory sparseArgs) public {
sparseArgs.height = uint8(bound(sparseArgs.height, 1, 24));
sparseArgs.orderIndex = uint24(
bound(sparseArgs.orderIndex, 0, 2 ** uint256(sparseArgs.height) - 1)
@@ -235,6 +235,7 @@ contract BulkSignatureTest is BaseOrderTest {
);
}
+ // This tests the "targeting" of orders in the out-of-range index case.
function execBulkSignatureIndexOutOfBounds(
Context memory context
) external stateless {
@@ -269,38 +270,37 @@ contract BulkSignatureTest is BaseOrderTest {
EIP712MerkleTree merkleTree = new EIP712MerkleTree();
- // Get the signature for the second order.
+ // Get the signature for the order at index 0.
bytes memory bulkSignature = merkleTree.signBulkOrder(
context.seaport,
key,
orderComponents,
- 1,
+ 0,
context.useCompact2098
);
Order memory order = Order({
- parameters: secondOrderParameters,
+ parameters: baseOrderParameters,
signature: bulkSignature
});
- // Fulfill the second order.
+ // Fulfill the order at index 0.
context.seaport.fulfillOrder{ value: 1 }(order, bytes32(0));
- assertEq(test721_1.ownerOf(2), address(this));
+ assertEq(test721_1.ownerOf(1), address(this));
- // Get the signature for the first order.
+ // Get the signature for the order at index 1.
bulkSignature = merkleTree.signBulkOrder(
context.seaport,
key,
orderComponents,
- 0,
+ 1,
context.useCompact2098
);
uint256 signatureLength = context.useCompact2098 ? 64 : 65;
- // Swap in a fake index. Here, we use 4 instead of 0.
+ // Swap in a fake index. Here, we use 5 instead of 1.
assembly {
- // mload(bulkSignature) := signatureLength
let indexAndProofDataPointer := add(
signatureLength,
add(bulkSignature, 0x20)
@@ -312,25 +312,27 @@ contract BulkSignatureTest is BaseOrderTest {
)
let fakeIndexAndProofData := or(
maskedProofData,
- 0x0000040000000000000000000000000000000000000000000000000000000000
+ 0x0000050000000000000000000000000000000000000000000000000000000000
)
mstore(indexAndProofDataPointer, fakeIndexAndProofData)
}
order = Order({
- parameters: baseOrderParameters,
+ parameters: secondOrderParameters,
signature: bulkSignature
});
- // Fulfill the first order using th bulk signature with the out of
- // bounds index..
+ // Fulfill the order order at index 1 using the bulk signature with the
+ // out of bounds index (5).
context.seaport.fulfillOrder{ value: 1 }(order, bytes32(0));
- // Should wrap around and fulfill the first order, which is for token ID
- // 1.
- assertEq(test721_1.ownerOf(1), address(this));
+ // Should wrap around and fulfill the order at index 1, which is for
+ // token ID 2.
+ assertEq(test721_1.ownerOf(2), address(this));
}
+ // Pulled out bc of stacc2dank but can be moved or cleaned up in some better
+ // way.
function setUpSecondOrder(
Context memory context,
address addr
@@ -410,4 +412,285 @@ contract BulkSignatureTest is BaseOrderTest {
})
);
}
+
+ // This tests the overflow behavior for trees of different heights.
+ function execBulkSignatureSparseIndexOutOfBounds(
+ Context memory context
+ ) external stateless {
+ // Set up the boilerplate for the offerer.
+ string memory offerer = "offerer";
+ (address addr, uint256 key) = makeAddrAndKey(offerer);
+ addErc721OfferItem(1);
+ test721_1.mint(address(addr), 1);
+ vm.prank(addr);
+ test721_1.setApprovalForAll(address(context.seaport), true);
+ // Set up the order.
+ addConsiderationItem(
+ ConsiderationItem({
+ itemType: ItemType.NATIVE,
+ token: address(0),
+ identifierOrCriteria: 0,
+ startAmount: 1,
+ endAmount: 1,
+ recipient: payable(addr)
+ })
+ );
+ configureOrderParameters(addr);
+ configureOrderComponents(context.seaport);
+ OrderComponents[] memory orderComponents = new OrderComponents[](3);
+ orderComponents[0] = baseOrderComponents;
+ // The other order components can remain empty because
+ // `signSparseBulkOrder` will fill them in with empty orders and we only
+ // care about one order.
+
+ EIP712MerkleTree merkleTree = new EIP712MerkleTree();
+
+ // Get the real signature.
+ bytes memory bulkSignature = merkleTree.signSparseBulkOrder(
+ context.seaport,
+ key,
+ baseOrderComponents,
+ context.args.height,
+ context.args.orderIndex,
+ context.useCompact2098
+ );
+
+ // The memory region that needs to be modified depends on the signature
+ // length.
+ uint256 signatureLength = context.useCompact2098 ? 64 : 65;
+ // Set up an index equal to orderIndex + 2 ** tree height.
+ uint256 index = context.args.orderIndex + (2 ** context.args.height);
+ uint24 convertedIndex = uint24(index);
+
+ // Use assembly to swap in a fake index.
+ assembly {
+ // Get the pointer to the index and proof data.
+ let indexAndProofDataPointer := add(
+ signatureLength,
+ add(bulkSignature, 0x20)
+ )
+ // Load the index and proof data into memory.
+ let indexAndProofData := mload(indexAndProofDataPointer)
+ // Mask for the index.
+ let maskedProofData := and(
+ indexAndProofData,
+ 0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ )
+ // 256 - 24 = 232
+ // Create a new value the same as the old value, except that it uses
+ // the fake index.
+ let fakeIndexAndProofData := or(
+ maskedProofData,
+ // Shift the fake index left by 232 bits to produce a value to
+ // `or` with the `maskedProofData`. For example:
+ // `0x000005000...`
+ shl(232, convertedIndex)
+ )
+ // Store the fake index and proof data at the
+ // indexAndProofDataPointer location.
+ mstore(indexAndProofDataPointer, fakeIndexAndProofData)
+ }
+
+ // Create an order using the `bulkSignature` that was modified in the
+ // assembly block above.
+ Order memory order = Order({
+ parameters: baseOrderParameters,
+ signature: bulkSignature
+ });
+
+ // This should wrap around and fulfill the order at the index equal to
+ // (the fake order index - (height ** 2)).
+ context.seaport.fulfillOrder{ value: 1 }(order, bytes32(0));
+ }
+
+ function testBulkSignatureSparseIndexOutOfBoundsFuzz(
+ SparseArgs memory sparseArgs
+ ) public {
+ sparseArgs.height = uint8(bound(sparseArgs.height, 1, 24));
+ sparseArgs.orderIndex = uint24(
+ bound(sparseArgs.orderIndex, 0, 2 ** uint256(sparseArgs.height) - 1)
+ );
+
+ test(
+ this.execBulkSignatureSparseIndexOutOfBounds,
+ Context({
+ seaport: consideration,
+ args: sparseArgs,
+ useCompact2098: false
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBounds,
+ Context({
+ seaport: referenceConsideration,
+ args: sparseArgs,
+ useCompact2098: false
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBounds,
+ Context({
+ seaport: consideration,
+ args: sparseArgs,
+ useCompact2098: true
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBounds,
+ Context({
+ seaport: referenceConsideration,
+ args: sparseArgs,
+ useCompact2098: true
+ })
+ );
+ }
+
+ // This tests that indexes other than the predicted overflow indexes do not
+ // work.
+ function execBulkSignatureSparseIndexOutOfBoundsNonHits(
+ Context memory context
+ ) external stateless {
+ // Set up the boilerplate for the offerer.
+ string memory offerer = "offerer";
+ (address addr, uint256 key) = makeAddrAndKey(offerer);
+ addErc721OfferItem(1);
+ test721_1.mint(address(addr), 1);
+ vm.prank(addr);
+ test721_1.setApprovalForAll(address(context.seaport), true);
+ // Set up the order.
+ addConsiderationItem(
+ ConsiderationItem({
+ itemType: ItemType.NATIVE,
+ token: address(0),
+ identifierOrCriteria: 0,
+ startAmount: 1,
+ endAmount: 1,
+ recipient: payable(addr)
+ })
+ );
+ configureOrderParameters(addr);
+ configureOrderComponents(context.seaport);
+ OrderComponents[] memory orderComponents = new OrderComponents[](3);
+ orderComponents[0] = baseOrderComponents;
+ // The other order components can remain empty because
+ // `signSparseBulkOrder` will fill them in with empty orders and we only
+ // care about one order.
+
+ EIP712MerkleTree merkleTree = new EIP712MerkleTree();
+
+ uint256 treeHeight = 8;
+
+ // Get the real signature.
+ bytes memory bulkSignature = merkleTree.signSparseBulkOrder(
+ context.seaport,
+ key,
+ baseOrderComponents,
+ treeHeight,
+ uint24(0),
+ context.useCompact2098
+ );
+
+ // The memory region that needs to be modified depends on the signature
+ // length.
+ uint256 signatureLength = context.useCompact2098 ? 64 : 65;
+ uint256 firstExpectedOverflowIndex = 2 ** treeHeight;
+ uint256 secondExpectedOverflowIndex = (2 ** treeHeight) * 2;
+
+ uint24 convertedIndex;
+ Order memory order;
+
+ // Iterate over all indexes from the firstExpectedOverflowIndex + 1 to
+ // the secondExpectedOverflowIndex, inclusive, and make sure that none
+ // of them work except the expected indexes.
+ for (
+ uint256 i = firstExpectedOverflowIndex + 1;
+ i <= secondExpectedOverflowIndex;
+ ++i
+ ) {
+ convertedIndex = uint24(i);
+
+ // Use assembly to swap a fake index into the bulkSignature.
+ assembly {
+ // Get the pointer to the index and proof data.
+ let indexAndProofDataPointer := add(
+ signatureLength,
+ add(bulkSignature, 0x20)
+ )
+ // Load the index and proof data into memory.
+ let indexAndProofData := mload(indexAndProofDataPointer)
+ // Mask for the index.
+ let maskedProofData := and(
+ indexAndProofData,
+ 0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ )
+ // 256 - 24 = 232
+ // Create a new value the same as the old value, except that it
+ // uses the fake index.
+ let fakeIndexAndProofData := or(
+ maskedProofData,
+ // Shift the fake index left by 232 bits to produce a value
+ // to `or` with the `maskedProofData`. For example:
+ // `0x000005000...`
+ shl(232, convertedIndex)
+ )
+ // Store the fake index and proof data at the
+ // indexAndProofDataPointer location.
+ mstore(indexAndProofDataPointer, fakeIndexAndProofData)
+ }
+
+ // Create an order using the `bulkSignature` that was modified in
+ // the assembly block above.
+ order = Order({
+ parameters: baseOrderParameters,
+ signature: bulkSignature
+ });
+
+ // Expect a revert for all indexes except the
+ // secondExpectedOverflowIndex. The starting range should revert
+ // with `InvalidSigner`, and the secondExpectedOverflowIndex should
+ // fulfill the order.
+ if (i != secondExpectedOverflowIndex) {
+ // Expect InvalidSigner because we're trying to recover the
+ // signer using a signature and proof for one of the dummy
+ // orders that signSparseBulkOrder filled in.
+ vm.expectRevert(abi.encodeWithSignature("InvalidSigner()"));
+ }
+ context.seaport.fulfillOrder{ value: 1 }(order, bytes32(0));
+ }
+ }
+
+ function testBulkSignatureSparseIndexOutOfBoundsNonHits() public {
+ test(
+ this.execBulkSignatureSparseIndexOutOfBoundsNonHits,
+ Context({
+ seaport: consideration,
+ args: _defaultArgs,
+ useCompact2098: false
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBoundsNonHits,
+ Context({
+ seaport: referenceConsideration,
+ args: _defaultArgs,
+ useCompact2098: false
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBoundsNonHits,
+ Context({
+ seaport: consideration,
+ args: _defaultArgs,
+ useCompact2098: true
+ })
+ );
+ test(
+ this.execBulkSignatureSparseIndexOutOfBoundsNonHits,
+ Context({
+ seaport: referenceConsideration,
+ args: _defaultArgs,
+ useCompact2098: true
+ })
+ );
+ }
}
diff --git a/test/foundry/GetterTests.t.sol b/test/foundry/GetterTests.t.sol
index 7a1effc00..c95addf57 100644
--- a/test/foundry/GetterTests.t.sol
+++ b/test/foundry/GetterTests.t.sol
@@ -41,7 +41,7 @@ contract TestGetters is BaseConsiderationTest {
function testGetsCorrectVersion() public {
(string memory version, , ) = consideration.information();
- assertEq(version, "1.2");
+ assertEq(version, "1.3");
}
function testGetCorrectDomainSeparator() public {
diff --git a/test/foundry/helpers/sol/BaseTest.sol b/test/foundry/helpers/sol/BaseTest.sol
new file mode 100644
index 000000000..77c0d32b8
--- /dev/null
+++ b/test/foundry/helpers/sol/BaseTest.sol
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { Test } from "forge-std/Test.sol";
+import {
+ OrderParameters,
+ OfferItem,
+ ConsiderationItem,
+ SpentItem,
+ ReceivedItem,
+ Execution,
+ AdditionalRecipient,
+ CriteriaResolver,
+ Fulfillment,
+ FulfillmentComponent,
+ OrderComponents,
+ OrderParameters,
+ Order
+} from "../../../../contracts/lib/ConsiderationStructs.sol";
+import {
+ ItemType,
+ OrderType
+} from "../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderComponentsLib
+} from "../../../../contracts/helpers/sol/lib/OrderComponentsLib.sol";
+import {
+ OrderParametersLib
+} from "../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+import { OrderLib } from "../../../../contracts/helpers/sol/lib/OrderLib.sol";
+
+contract BaseTest is Test {
+ using OrderComponentsLib for OrderComponents;
+ using OrderParametersLib for OrderParameters;
+ using OrderLib for Order;
+
+ struct OrderParametersBlob {
+ address offerer; // 0x00
+ address zone; // 0x20
+ OfferItemBlob[] offer; // 0x40
+ ConsiderationItemBlob[] consideration; // 0x60
+ uint8 _orderType; // 0x80
+ uint256 startTime; // 0xa0
+ uint256 endTime; // 0xc0
+ bytes32 zoneHash; // 0xe0
+ uint256 salt; // 0x100
+ bytes32 conduitKey; // 0x120
+ uint256 totalOriginalConsiderationItems; // 0x140
+ }
+
+ struct OrderBlob {
+ OrderParametersBlob parameters;
+ bytes signature;
+ }
+
+ function _fromBlob(
+ OrderBlob memory orderBlob
+ ) internal view returns (Order memory order) {
+ order = OrderLib.empty();
+ order.parameters = _fromBlob(orderBlob.parameters);
+ order.signature = orderBlob.signature;
+ }
+
+ function assertEq(Order memory a, Order memory b) internal {
+ assertEq(a.parameters, b.parameters);
+ assertEq(a.signature, b.signature, "signature");
+ }
+
+ function assertEq(Order[] memory a, Order[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function setUp() public virtual {}
+
+ function assertEq(OfferItem memory a, OfferItem memory b) internal {
+ assertEq(uint8(a.itemType), uint8(b.itemType), "itemType");
+ assertEq(a.token, b.token, "token");
+ assertEq(
+ a.identifierOrCriteria,
+ b.identifierOrCriteria,
+ "identifierOrCriteria"
+ );
+ assertEq(a.startAmount, b.startAmount, "startAmount");
+ assertEq(a.endAmount, b.endAmount, "endAmount");
+ }
+
+ function assertEq(
+ ConsiderationItem memory a,
+ ConsiderationItem memory b
+ ) internal {
+ assertEq(uint8(a.itemType), uint8(b.itemType), "itemType");
+ assertEq(a.token, b.token, "token");
+ assertEq(
+ a.identifierOrCriteria,
+ b.identifierOrCriteria,
+ "identifierOrCriteria"
+ );
+ assertEq(a.startAmount, b.startAmount, "startAmount");
+ assertEq(a.endAmount, b.endAmount, "endAmount");
+ assertEq(a.recipient, b.recipient, "recipient");
+ }
+
+ function assertEq(OfferItem[] memory a, OfferItem[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(
+ ConsiderationItem[] memory a,
+ ConsiderationItem[] memory b
+ ) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(
+ OrderParameters memory a,
+ OrderParameters memory b
+ ) internal {
+ assertEq(a.offerer, b.offerer, "offerer");
+ assertEq(a.zone, b.zone, "zone");
+ assertEq(a.offer, b.offer);
+ assertEq(a.consideration, b.consideration);
+ assertEq(uint8(a.orderType), uint8(b.orderType), "orderType");
+ assertEq(a.startTime, b.startTime, "startTime");
+ assertEq(a.endTime, b.endTime, "endTime");
+ assertEq(a.zoneHash, b.zoneHash, "zoneHash");
+ assertEq(a.salt, b.salt, "salt");
+ assertEq(a.conduitKey, b.conduitKey, "conduitKey");
+ assertEq(
+ a.totalOriginalConsiderationItems,
+ b.totalOriginalConsiderationItems,
+ "totalOriginalConsiderationItems"
+ );
+ }
+
+ function assertEq(SpentItem memory a, SpentItem memory b) internal {
+ assertEq(uint8(a.itemType), uint8(b.itemType), "itemType");
+ assertEq(a.token, b.token, "token");
+ assertEq(a.identifier, b.identifier, "identifier");
+ assertEq(a.amount, b.amount, "amount");
+ }
+
+ function assertEq(SpentItem[] memory a, SpentItem[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(
+ AdditionalRecipient memory a,
+ AdditionalRecipient memory b
+ ) internal {
+ assertEq(a.amount, b.amount, "amount");
+ assertEq(a.recipient, b.recipient, "recipient");
+ }
+
+ function assertEq(
+ AdditionalRecipient[] memory a,
+ AdditionalRecipient[] memory b
+ ) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(ReceivedItem memory a, ReceivedItem memory b) internal {
+ assertEq(uint8(a.itemType), uint8(b.itemType), "itemType");
+ assertEq(a.token, b.token, "token");
+ assertEq(a.identifier, b.identifier, "identifier");
+ assertEq(a.amount, b.amount, "amount");
+ assertEq(a.recipient, b.recipient, "recipient");
+ }
+
+ function assertEq(
+ ReceivedItem[] memory a,
+ ReceivedItem[] memory b
+ ) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(
+ CriteriaResolver memory a,
+ CriteriaResolver memory b
+ ) internal {
+ assertEq(a.orderIndex, b.orderIndex, "orderIndex");
+ assertEq(uint8(a.side), uint8(b.side), "side");
+ assertEq(a.index, b.index, "index");
+ assertEq(a.identifier, b.identifier, "identifier");
+ assertEq(a.criteriaProof, b.criteriaProof);
+ }
+
+ function assertEq(
+ CriteriaResolver[] memory a,
+ CriteriaResolver[] memory b
+ ) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(bytes32[] memory a, bytes32[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(Execution memory a, Execution memory b) internal {
+ assertEq(a.item, b.item);
+ assertEq(a.offerer, b.offerer, "offerer");
+ assertEq(a.conduitKey, b.conduitKey, "conduitKey");
+ }
+
+ function assertEq(Execution[] memory a, Execution[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function toItemType(uint8 _itemType) internal view returns (ItemType) {
+ return ItemType(bound(_itemType, 0, 5));
+ }
+
+ function assertEq(
+ FulfillmentComponent memory a,
+ FulfillmentComponent memory b
+ ) internal {
+ assertEq(a.orderIndex, b.orderIndex, "orderIndex");
+ assertEq(a.itemIndex, b.itemIndex, "itemIndex");
+ }
+
+ function assertEq(
+ FulfillmentComponent[] memory a,
+ FulfillmentComponent[] memory b
+ ) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function assertEq(Fulfillment memory a, Fulfillment memory b) internal {
+ assertEq(a.offerComponents, b.offerComponents);
+ assertEq(a.considerationComponents, b.considerationComponents);
+ }
+
+ function assertEq(Fulfillment[] memory a, Fulfillment[] memory b) internal {
+ assertEq(a.length, b.length, "length");
+ for (uint256 i = 0; i < a.length; i++) {
+ assertEq(a[i], b[i]);
+ }
+ }
+
+ function toOrderType(uint8 _orderType) internal view returns (OrderType) {
+ return OrderType(bound(_orderType, 0, 3));
+ }
+
+ struct OfferItemBlob {
+ uint8 _itemType;
+ address token;
+ uint256 identifierOrCriteria;
+ uint256 startAmount;
+ uint256 endAmount;
+ }
+
+ struct ConsiderationItemBlob {
+ uint8 _itemType;
+ address token;
+ uint256 identifierOrCriteria;
+ uint256 startAmount;
+ uint256 endAmount;
+ address payable recipient;
+ }
+
+ struct OrderComponentsBlob {
+ address offerer;
+ address zone;
+ OfferItemBlob[] offer;
+ ConsiderationItemBlob[] consideration;
+ uint8 _orderType;
+ uint256 startTime;
+ uint256 endTime;
+ bytes32 zoneHash;
+ uint256 salt;
+ bytes32 conduitKey;
+ uint256 counter;
+ }
+
+ function _fromBlob(
+ OfferItemBlob memory blob
+ ) internal view returns (OfferItem memory) {
+ return
+ OfferItem(
+ toItemType(blob._itemType),
+ blob.token,
+ blob.identifierOrCriteria,
+ blob.startAmount,
+ blob.endAmount
+ );
+ }
+
+ function _fromBlob(
+ ConsiderationItemBlob memory blob
+ ) internal view returns (ConsiderationItem memory) {
+ return
+ ConsiderationItem(
+ toItemType(blob._itemType),
+ blob.token,
+ blob.identifierOrCriteria,
+ blob.startAmount,
+ blob.endAmount,
+ blob.recipient
+ );
+ }
+
+ function _fromBlobs(
+ OfferItemBlob[] memory blob
+ ) internal view returns (OfferItem[] memory) {
+ OfferItem[] memory items = new OfferItem[](blob.length);
+ for (uint256 i = 0; i < blob.length; i++) {
+ items[i] = _fromBlob(blob[i]);
+ }
+ return items;
+ }
+
+ function _fromBlobs(
+ ConsiderationItemBlob[] memory blob
+ ) internal view returns (ConsiderationItem[] memory) {
+ ConsiderationItem[] memory items = new ConsiderationItem[](blob.length);
+ for (uint256 i = 0; i < blob.length; i++) {
+ items[i] = _fromBlob(blob[i]);
+ }
+ return items;
+ }
+
+ function _fromBlob(
+ OrderComponentsBlob memory blob
+ ) internal view returns (OrderComponents memory) {
+ OrderComponents memory components = OrderComponentsLib.empty();
+ components = components.withOfferer(blob.offerer);
+ components = components.withZone(blob.zone);
+ components = components.withOffer(_fromBlobs(blob.offer));
+ components = components.withConsideration(
+ _fromBlobs(blob.consideration)
+ );
+ components = components.withOrderType(toOrderType(blob._orderType));
+ components = components.withStartTime(blob.startTime);
+ components = components.withEndTime(blob.endTime);
+ components = components.withZoneHash(blob.zoneHash);
+ components = components.withSalt(blob.salt);
+ components = components.withConduitKey(blob.conduitKey);
+ components = components.withCounter(blob.counter);
+ return components;
+ }
+
+ function _fromBlob(
+ OrderParametersBlob memory blob
+ ) internal view returns (OrderParameters memory) {
+ OrderParameters memory parameters = OrderParametersLib.empty();
+ parameters = parameters.withOfferer(blob.offerer);
+ parameters = parameters.withZone(blob.zone);
+ parameters = parameters.withOffer(_fromBlobs(blob.offer));
+ parameters = parameters.withConsideration(
+ _fromBlobs(blob.consideration)
+ );
+ parameters = parameters.withOrderType(toOrderType(blob._orderType));
+ parameters = parameters.withStartTime(blob.startTime);
+ parameters = parameters.withEndTime(blob.endTime);
+ parameters = parameters.withZoneHash(blob.zoneHash);
+ parameters = parameters.withSalt(blob.salt);
+ parameters = parameters.withConduitKey(blob.conduitKey);
+ parameters = parameters.withTotalOriginalConsiderationItems(
+ blob.totalOriginalConsiderationItems
+ );
+ return parameters;
+ }
+
+ function assertEq(
+ OrderComponents memory a,
+ OrderComponents memory b
+ ) internal {
+ assertEq(a.offerer, b.offerer, "offerer");
+ assertEq(a.zone, b.zone, "zone");
+ assertEq(a.offer, b.offer);
+ assertEq(a.consideration, b.consideration);
+ assertEq(uint8(a.orderType), uint8(b.orderType), "orderType");
+ assertEq(a.startTime, b.startTime, "startTime");
+ assertEq(a.endTime, b.endTime, "endTime");
+ assertEq(a.zoneHash, b.zoneHash, "zoneHash");
+ assertEq(a.salt, b.salt, "salt");
+ assertEq(a.conduitKey, b.conduitKey, "conduitKey");
+ assertEq(a.counter, b.counter, "counter");
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/AdditionalRecipientLib.t.sol b/test/foundry/helpers/sol/lib/AdditionalRecipientLib.t.sol
new file mode 100644
index 000000000..4910503e6
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/AdditionalRecipientLib.t.sol
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ AdditionalRecipientLib
+} from "../../../../../contracts/helpers/sol/lib/AdditionalRecipientLib.sol";
+import {
+ AdditionalRecipient
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract AdditionalRecipientLibTest is BaseTest {
+ using AdditionalRecipientLib for AdditionalRecipient;
+
+ function testRetrieveDefault(
+ uint256 amount,
+ address payable recipient
+ ) public {
+ AdditionalRecipient memory additionalRecipient = AdditionalRecipient({
+ amount: amount,
+ recipient: recipient
+ });
+ AdditionalRecipientLib.saveDefault(additionalRecipient, "default");
+ AdditionalRecipient
+ memory defaultAdditionalRecipient = AdditionalRecipientLib
+ .fromDefault("default");
+ assertEq(additionalRecipient, defaultAdditionalRecipient);
+ }
+
+ function testComposeEmpty(
+ uint256 amount,
+ address payable recipient
+ ) public {
+ AdditionalRecipient memory additionalRecipient = AdditionalRecipientLib
+ .empty()
+ .withAmount(amount)
+ .withRecipient(recipient);
+ assertEq(
+ additionalRecipient,
+ AdditionalRecipient({ amount: amount, recipient: recipient })
+ );
+ }
+
+ function testCopy() public {
+ AdditionalRecipient memory additionalRecipient = AdditionalRecipient({
+ amount: 1,
+ recipient: payable(address(1))
+ });
+ AdditionalRecipient memory copy = additionalRecipient.copy();
+ assertEq(additionalRecipient, copy);
+ additionalRecipient.amount = 2;
+ assertEq(copy.amount, 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint256[3] memory amount,
+ address payable[3] memory recipient
+ ) public {
+ AdditionalRecipient[]
+ memory additionalRecipients = new AdditionalRecipient[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ additionalRecipients[i] = AdditionalRecipient({
+ amount: amount[i],
+ recipient: recipient[i]
+ });
+ }
+ AdditionalRecipientLib.saveDefaultMany(additionalRecipients, "default");
+ AdditionalRecipient[]
+ memory defaultAdditionalRecipients = AdditionalRecipientLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(additionalRecipients[i], defaultAdditionalRecipients[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/AdvancedOrderLib.t.sol b/test/foundry/helpers/sol/lib/AdvancedOrderLib.t.sol
new file mode 100644
index 000000000..9a2096a9a
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/AdvancedOrderLib.t.sol
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ AdvancedOrderLib
+} from "../../../../../contracts/helpers/sol/lib/AdvancedOrderLib.sol";
+import {
+ AdvancedOrder,
+ OrderParameters
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+
+contract AdvancedOrderLibTest is BaseTest {
+ using AdvancedOrderLib for AdvancedOrder;
+ using OrderParametersLib for OrderParameters;
+
+ function testRetrieveDefault(
+ uint120 numerator,
+ uint120 denominator,
+ bytes memory signature,
+ bytes memory extraData
+ ) public {
+ OrderParameters memory orderParameters = OrderParametersLib
+ .empty()
+ .withOfferer(address(1234));
+ AdvancedOrder memory advancedOrder = AdvancedOrder({
+ parameters: orderParameters,
+ numerator: numerator,
+ denominator: denominator,
+ signature: signature,
+ extraData: extraData
+ });
+ AdvancedOrderLib.saveDefault(advancedOrder, "default");
+ AdvancedOrder memory defaultAdvancedOrder = AdvancedOrderLib
+ .fromDefault("default");
+ assertEq(advancedOrder, defaultAdvancedOrder);
+ }
+
+ function testComposeEmpty(
+ uint120 numerator,
+ uint120 denominator,
+ bytes memory signature,
+ bytes memory extraData
+ ) public {
+ AdvancedOrder memory advancedOrder = AdvancedOrderLib
+ .empty()
+ .withParameters(OrderParametersLib.empty())
+ .withNumerator(numerator)
+ .withDenominator(denominator)
+ .withSignature(signature)
+ .withExtraData(extraData);
+ assertEq(
+ advancedOrder,
+ AdvancedOrder({
+ parameters: OrderParametersLib.empty(),
+ numerator: numerator,
+ denominator: denominator,
+ signature: signature,
+ extraData: extraData
+ })
+ );
+ }
+
+ function testCopy() public {
+ AdvancedOrder memory advancedOrder = AdvancedOrder({
+ parameters: OrderParametersLib.empty(),
+ numerator: 1,
+ denominator: 1,
+ signature: "signature",
+ extraData: "extraData"
+ });
+ AdvancedOrder memory copy = advancedOrder.copy();
+ assertEq(advancedOrder, copy);
+ advancedOrder.numerator = 2;
+ assertEq(copy.numerator, 1);
+
+ advancedOrder.parameters = OrderParametersLib.empty().withOfferer(
+ address(1234)
+ );
+ assertEq(copy.parameters.offerer, address(0));
+ }
+
+ function testRetrieveDefaultMany(
+ uint120[3] memory numerator,
+ uint120[3] memory denominator,
+ bytes[3] memory signature,
+ bytes[3] memory extraData
+ ) public {
+ AdvancedOrder[] memory advancedOrders = new AdvancedOrder[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ advancedOrders[i] = AdvancedOrder({
+ parameters: OrderParametersLib.empty().withOfferer(
+ address(1234)
+ ),
+ numerator: numerator[i],
+ denominator: denominator[i],
+ signature: signature[i],
+ extraData: extraData[i]
+ });
+ }
+ AdvancedOrderLib.saveDefaultMany(advancedOrders, "default");
+ AdvancedOrder[] memory defaultAdvancedOrders = AdvancedOrderLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(advancedOrders[i], defaultAdvancedOrders[i]);
+ }
+ }
+
+ function assertEq(AdvancedOrder memory a, AdvancedOrder memory b) internal {
+ assertEq(a.parameters, b.parameters);
+ assertEq(a.numerator, b.numerator, "numerator");
+ assertEq(a.denominator, b.denominator, "denominator");
+ assertEq(a.signature, b.signature, "signature");
+ assertEq(a.extraData, b.extraData, "extraData");
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol b/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol
new file mode 100644
index 000000000..1383ddb5e
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ BasicOrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/BasicOrderParametersLib.sol";
+import {
+ AdditionalRecipientLib
+} from "../../../../../contracts/helpers/sol/lib/AdditionalRecipientLib.sol";
+import {
+ BasicOrderParameters,
+ OrderParameters,
+ AdditionalRecipient
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import {
+ ItemType,
+ BasicOrderType
+} from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+import {
+ SeaportArrays
+} from "../../../../../contracts/helpers/sol/lib/SeaportArrays.sol";
+
+contract BasicOrderParametersLibTest is BaseTest {
+ using BasicOrderParametersLib for BasicOrderParameters;
+ using OrderParametersLib for OrderParameters;
+
+ struct Blob {
+ 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
+ uint8 basicOrderType;
+ 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;
+ }
+
+ function testRetrieveDefault(Blob memory blob) public {
+ // assign everything from blob
+ BasicOrderParameters
+ memory basicOrderParameters = BasicOrderParametersLib.empty();
+ basicOrderParameters = basicOrderParameters.withConsiderationToken(
+ blob.considerationToken
+ );
+ basicOrderParameters = basicOrderParameters.withConsiderationIdentifier(
+ blob.considerationIdentifier
+ );
+ basicOrderParameters = basicOrderParameters.withConsiderationAmount(
+ blob.considerationAmount
+ );
+ basicOrderParameters = basicOrderParameters.withOfferer(blob.offerer);
+ basicOrderParameters = basicOrderParameters.withZone(blob.zone);
+ basicOrderParameters = basicOrderParameters.withOfferToken(
+ blob.offerToken
+ );
+ basicOrderParameters = basicOrderParameters.withOfferIdentifier(
+ blob.offerIdentifier
+ );
+ basicOrderParameters = basicOrderParameters.withOfferAmount(
+ blob.offerAmount
+ );
+ basicOrderParameters = basicOrderParameters.withBasicOrderType(
+ BasicOrderType(bound(blob.basicOrderType, 0, 23))
+ );
+ basicOrderParameters = basicOrderParameters.withStartTime(
+ blob.startTime
+ );
+ basicOrderParameters = basicOrderParameters.withEndTime(blob.endTime);
+ basicOrderParameters = basicOrderParameters.withZoneHash(blob.zoneHash);
+ basicOrderParameters = basicOrderParameters.withSalt(blob.salt);
+ basicOrderParameters = basicOrderParameters.withOffererConduitKey(
+ blob.offererConduitKey
+ );
+ basicOrderParameters = basicOrderParameters.withFulfillerConduitKey(
+ blob.fulfillerConduitKey
+ );
+ basicOrderParameters = basicOrderParameters
+ .withTotalOriginalAdditionalRecipients(
+ blob.totalOriginalAdditionalRecipients
+ );
+ basicOrderParameters = basicOrderParameters.withAdditionalRecipients(
+ blob.additionalRecipients
+ );
+ basicOrderParameters = basicOrderParameters.withSignature(
+ blob.signature
+ );
+
+ BasicOrderParametersLib.saveDefault(basicOrderParameters, "default");
+ BasicOrderParameters
+ memory defaultBasicOrderParameters = BasicOrderParametersLib
+ .fromDefault("default");
+ assertEq(basicOrderParameters, defaultBasicOrderParameters);
+ }
+
+ function testCopy() public {
+ AdditionalRecipient[] memory additionalRecipients = SeaportArrays
+ .AdditionalRecipients(
+ AdditionalRecipient({
+ amount: 1,
+ recipient: payable(address(1234))
+ })
+ );
+ BasicOrderParameters
+ memory basicOrderParameters = BasicOrderParametersLib
+ .empty()
+ .withConsiderationToken(address(1))
+ .withConsiderationIdentifier(2)
+ .withConsiderationAmount(3)
+ .withOfferer(address(4))
+ .withZone(address(5))
+ .withOfferToken(address(6))
+ .withOfferIdentifier(7)
+ .withOfferAmount(8)
+ .withBasicOrderType(BasicOrderType(9))
+ .withStartTime(10)
+ .withEndTime(11)
+ .withZoneHash(bytes32(uint256(12)))
+ .withSalt(13)
+ .withOffererConduitKey(bytes32(uint256(14)))
+ .withFulfillerConduitKey(bytes32(uint256(15)))
+ .withTotalOriginalAdditionalRecipients(16)
+ .withAdditionalRecipients(additionalRecipients)
+ .withSignature(new bytes(0));
+ BasicOrderParameters memory copy = basicOrderParameters.copy();
+ assertEq(basicOrderParameters, copy);
+ basicOrderParameters.considerationIdentifier = 123;
+ assertEq(copy.considerationIdentifier, 2, "copy changed");
+
+ additionalRecipients[0].recipient = payable(address(456));
+
+ assertEq(
+ copy.additionalRecipients[0].recipient,
+ address(1234),
+ "copy recipient changed"
+ );
+ }
+
+ function testRetrieveDefaultMany(
+ uint256[3] memory considerationidentifier,
+ uint256[3] memory considerationAmount,
+ address payable[3] memory recipient
+ ) public {
+ BasicOrderParameters[]
+ memory basicOrderParameterss = new BasicOrderParameters[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ AdditionalRecipient[] memory additionalRecipients = SeaportArrays
+ .AdditionalRecipients(
+ AdditionalRecipient({
+ amount: 1,
+ recipient: payable(address(recipient[i]))
+ })
+ );
+
+ basicOrderParameterss[i] = BasicOrderParametersLib
+ .empty()
+ .withConsiderationIdentifier(considerationidentifier[i])
+ .withConsiderationAmount(considerationAmount[i])
+ .withAdditionalRecipients(additionalRecipients);
+ }
+ BasicOrderParametersLib.saveDefaultMany(
+ basicOrderParameterss,
+ "default"
+ );
+ BasicOrderParameters[]
+ memory defaultBasicOrderParameterss = BasicOrderParametersLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(basicOrderParameterss[i], defaultBasicOrderParameterss[i]);
+ }
+ }
+
+ function assertEq(
+ BasicOrderParameters memory a,
+ BasicOrderParameters memory b
+ ) internal {
+ /**
+ * do all these
+ * // 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
+ */
+ assertEq(
+ a.considerationToken,
+ b.considerationToken,
+ "considerationToken"
+ );
+ assertEq(
+ a.considerationIdentifier,
+ b.considerationIdentifier,
+ "considerationIdentifier"
+ );
+ assertEq(
+ a.considerationAmount,
+ b.considerationAmount,
+ "considerationAmount"
+ );
+ assertEq(a.offerer, b.offerer, "offerer");
+ assertEq(a.zone, b.zone, "zone");
+ assertEq(a.offerToken, b.offerToken, "offerToken");
+ assertEq(a.offerIdentifier, b.offerIdentifier, "offerIdentifier");
+ assertEq(a.offerAmount, b.offerAmount, "offerAmount");
+ assertEq(
+ uint8(a.basicOrderType),
+ uint8(b.basicOrderType),
+ "basicOrderType"
+ );
+ assertEq(a.startTime, b.startTime, "startTime");
+ assertEq(a.endTime, b.endTime, "endTime");
+ assertEq(a.zoneHash, b.zoneHash, "zoneHash");
+ assertEq(a.salt, b.salt, "salt");
+ assertEq(a.offererConduitKey, b.offererConduitKey, "offererConduitKey");
+ assertEq(
+ a.fulfillerConduitKey,
+ b.fulfillerConduitKey,
+ "fulfillerConduitKey"
+ );
+ assertEq(
+ a.totalOriginalAdditionalRecipients,
+ b.totalOriginalAdditionalRecipients,
+ "totalOriginalAdditionalRecipients"
+ );
+ assertEq(a.additionalRecipients, b.additionalRecipients);
+ assertEq(a.signature, b.signature, "signature");
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/ConsiderationItemLib.t.sol b/test/foundry/helpers/sol/lib/ConsiderationItemLib.t.sol
new file mode 100644
index 000000000..85e13900d
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/ConsiderationItemLib.t.sol
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ ConsiderationItemLib
+} from "../../../../../contracts/helpers/sol/lib/ConsiderationItemLib.sol";
+import {
+ ConsiderationItem
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract ConsiderationItemLibTest is BaseTest {
+ using ConsiderationItemLib for ConsiderationItem;
+
+ function testRetrieveDefault(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 startAmount,
+ uint256 endAmount,
+ address payable recipient
+ ) public {
+ itemType = uint8(bound(itemType, 0, 5));
+ ConsiderationItem memory considerationItem = ConsiderationItem({
+ itemType: ItemType(itemType),
+ token: token,
+ identifierOrCriteria: identifier,
+ startAmount: startAmount,
+ endAmount: endAmount,
+ recipient: recipient
+ });
+ ConsiderationItemLib.saveDefault(considerationItem, "default");
+ ConsiderationItem memory defaultConsiderationItem = ConsiderationItemLib
+ .fromDefault("default");
+ assertEq(considerationItem, defaultConsiderationItem);
+ }
+
+ function testComposeEmpty(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 startAmount,
+ uint256 endAmount,
+ address payable recipient
+ ) public {
+ ItemType _itemType = ItemType(bound(itemType, 0, 5));
+ ConsiderationItem memory considerationItem = ConsiderationItemLib
+ .empty()
+ .withRecipient(recipient)
+ .withEndAmount(endAmount)
+ .withStartAmount(startAmount)
+ .withIdentifierOrCriteria(identifier)
+ .withToken(token)
+ .withItemType(_itemType);
+ assertEq(
+ considerationItem,
+ ConsiderationItem({
+ itemType: _itemType,
+ token: token,
+ identifierOrCriteria: identifier,
+ startAmount: startAmount,
+ endAmount: endAmount,
+ recipient: recipient
+ })
+ );
+ }
+
+ function testCopy() public {
+ ConsiderationItem memory considerationItem = ConsiderationItem({
+ itemType: ItemType(1),
+ token: address(1),
+ identifierOrCriteria: 1,
+ startAmount: 1,
+ endAmount: 1,
+ recipient: payable(address(1234))
+ });
+ ConsiderationItem memory copy = considerationItem.copy();
+ assertEq(considerationItem, copy);
+ considerationItem.itemType = ItemType(2);
+ assertEq(uint8(copy.itemType), 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint8[3] memory itemType,
+ address[3] memory token,
+ uint256[3] memory identifier,
+ uint256[3] memory startAmount,
+ uint256[3] memory endAmount,
+ address payable[3] memory recipient
+ ) public {
+ ConsiderationItem[] memory considerationItems = new ConsiderationItem[](
+ 3
+ );
+ for (uint256 i = 0; i < 3; i++) {
+ itemType[i] = uint8(bound(itemType[i], 0, 5));
+ considerationItems[i] = ConsiderationItem({
+ itemType: ItemType(itemType[i]),
+ token: token[i],
+ identifierOrCriteria: identifier[i],
+ startAmount: startAmount[i],
+ endAmount: endAmount[i],
+ recipient: recipient[i]
+ });
+ }
+ ConsiderationItemLib.saveDefaultMany(considerationItems, "default");
+ ConsiderationItem[]
+ memory defaultConsiderationItems = ConsiderationItemLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(considerationItems[i], defaultConsiderationItems[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/CriteriaResolverLib.t.sol b/test/foundry/helpers/sol/lib/CriteriaResolverLib.t.sol
new file mode 100644
index 000000000..9c801adf9
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/CriteriaResolverLib.t.sol
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ CriteriaResolverLib
+} from "../../../../../contracts/helpers/sol/lib/CriteriaResolverLib.sol";
+import {
+ CriteriaResolver
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { Side } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract CriteriaResolverLibTest is BaseTest {
+ using CriteriaResolverLib for CriteriaResolver;
+
+ function testRetrieveDefault(
+ uint256 orderIndex,
+ bool side,
+ uint256 index,
+ uint256 identifier,
+ bytes32[] memory criteriaProof
+ ) public {
+ CriteriaResolver memory criteriaResolver = CriteriaResolver({
+ orderIndex: orderIndex,
+ side: Side(side ? 1 : 0),
+ index: index,
+ identifier: identifier,
+ criteriaProof: criteriaProof
+ });
+ CriteriaResolverLib.saveDefault(criteriaResolver, "default");
+ CriteriaResolver memory defaultCriteriaResolver = CriteriaResolverLib
+ .fromDefault("default");
+ assertEq(criteriaResolver, defaultCriteriaResolver);
+ }
+
+ function testComposeEmpty(
+ uint256 orderIndex,
+ bool side,
+ uint256 index,
+ uint256 identifier,
+ bytes32[] memory criteriaProof
+ ) public {
+ CriteriaResolver memory criteriaResolver = CriteriaResolverLib
+ .empty()
+ .withOrderIndex(orderIndex)
+ .withSide(Side(side ? 1 : 0))
+ .withIndex(index)
+ .withIdentifier(identifier)
+ .withCriteriaProof(criteriaProof);
+ assertEq(
+ criteriaResolver,
+ CriteriaResolver({
+ orderIndex: orderIndex,
+ side: Side(side ? 1 : 0),
+ index: index,
+ identifier: identifier,
+ criteriaProof: criteriaProof
+ })
+ );
+ }
+
+ function testCopy() public {
+ CriteriaResolver memory criteriaResolver = CriteriaResolver({
+ orderIndex: 1,
+ side: Side(1),
+ index: 1,
+ identifier: 1,
+ criteriaProof: new bytes32[](0)
+ });
+ CriteriaResolver memory copy = criteriaResolver.copy();
+ assertEq(criteriaResolver, copy);
+ criteriaResolver.index = 2;
+ assertEq(copy.index, 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint256[3] memory orderIndex,
+ bool[3] memory side,
+ uint256[3] memory index,
+ uint256[3] memory identifier,
+ bytes32[][3] memory criteriaProof
+ ) public {
+ CriteriaResolver[] memory criteriaResolvers = new CriteriaResolver[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ criteriaResolvers[i] = CriteriaResolver({
+ orderIndex: orderIndex[i],
+ side: Side(side[i] ? 1 : 0),
+ index: index[i],
+ identifier: identifier[i],
+ criteriaProof: criteriaProof[i]
+ });
+ }
+ CriteriaResolverLib.saveDefaultMany(criteriaResolvers, "default");
+ CriteriaResolver[] memory defaultCriteriaResolvers = CriteriaResolverLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(criteriaResolvers[i], defaultCriteriaResolvers[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/ExecutionsLib.t.sol b/test/foundry/helpers/sol/lib/ExecutionsLib.t.sol
new file mode 100644
index 000000000..609b33a38
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/ExecutionsLib.t.sol
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ ExecutionLib
+} from "../../../../../contracts/helpers/sol/lib/ExecutionLib.sol";
+import {
+ Execution,
+ ReceivedItem
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ ReceivedItemLib
+} from "../../../../../contracts/helpers/sol/lib/ReceivedItemLib.sol";
+
+contract ExecutionLibTest is BaseTest {
+ using ExecutionLib for Execution;
+
+ struct ReceivedItemBlob {
+ uint8 itemType;
+ address token;
+ uint256 identifier;
+ uint256 amount;
+ address payable recipient;
+ }
+
+ function testRetrieveDefault(
+ ReceivedItemBlob memory blob,
+ address offerer,
+ bytes32 conduitKey
+ ) public {
+ ReceivedItem memory receivedItem = ReceivedItem({
+ itemType: toItemType(blob.itemType),
+ token: blob.token,
+ identifier: blob.identifier,
+ amount: blob.amount,
+ recipient: blob.recipient
+ });
+ Execution memory execution = Execution({
+ item: receivedItem,
+ offerer: offerer,
+ conduitKey: conduitKey
+ });
+ ExecutionLib.saveDefault(execution, "default");
+ Execution memory defaultExecution = ExecutionLib.fromDefault("default");
+ assertEq(execution, defaultExecution);
+ }
+
+ function _fromBlob(
+ ReceivedItemBlob memory blob
+ ) internal view returns (ReceivedItem memory) {
+ return
+ ReceivedItem({
+ itemType: toItemType(blob.itemType),
+ token: blob.token,
+ identifier: blob.identifier,
+ amount: blob.amount,
+ recipient: blob.recipient
+ });
+ }
+
+ function testComposeEmpty(
+ ReceivedItemBlob memory blob,
+ address offerer,
+ bytes32 conduitKey
+ ) public {
+ ReceivedItem memory receivedItem = _fromBlob(blob);
+ Execution memory execution = ExecutionLib
+ .empty()
+ .withItem(receivedItem)
+ .withOfferer(offerer)
+ .withConduitKey(conduitKey);
+ assertEq(
+ execution,
+ Execution({
+ item: receivedItem,
+ offerer: offerer,
+ conduitKey: conduitKey
+ })
+ );
+ }
+
+ function testCopy() public {
+ Execution memory execution = Execution({
+ item: ReceivedItem({
+ itemType: ItemType(1),
+ token: address(1),
+ identifier: 1,
+ amount: 1,
+ recipient: payable(address(1234))
+ }),
+ offerer: address(1),
+ conduitKey: bytes32(uint256(1))
+ });
+ Execution memory copy = execution.copy();
+ assertEq(execution, copy);
+ execution.offerer = address(2);
+ assertEq(copy.offerer, address(1));
+ }
+
+ function testRetrieveDefaultMany(
+ ReceivedItemBlob[3] memory blob,
+ address[3] memory offerer,
+ bytes32[3] memory conduitKey
+ ) public {
+ Execution[] memory executions = new Execution[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ ReceivedItem memory item = _fromBlob(blob[i]);
+ executions[i] = Execution({
+ item: item,
+ offerer: offerer[i],
+ conduitKey: conduitKey[i]
+ });
+ }
+ ExecutionLib.saveDefaultMany(executions, "default");
+ Execution[] memory defaultExecutions = ExecutionLib.fromDefaultMany(
+ "default"
+ );
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(executions[i], defaultExecutions[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/FulfillmentComponentLib.t.sol b/test/foundry/helpers/sol/lib/FulfillmentComponentLib.t.sol
new file mode 100644
index 000000000..5d0aa4b7c
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/FulfillmentComponentLib.t.sol
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ FulfillmentComponentLib
+} from "../../../../../contracts/helpers/sol/lib/FulfillmentComponentLib.sol";
+import {
+ FulfillmentComponent
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract FulfillmentComponentLibTest is BaseTest {
+ using FulfillmentComponentLib for FulfillmentComponent;
+
+ function testRetrieveDefault(uint256 orderIndex, uint256 itemIndex) public {
+ FulfillmentComponent
+ memory fulfillmentComponent = FulfillmentComponent({
+ orderIndex: orderIndex,
+ itemIndex: itemIndex
+ });
+ FulfillmentComponentLib.saveDefault(fulfillmentComponent, "default");
+ FulfillmentComponent
+ memory defaultFulfillmentComponent = FulfillmentComponentLib
+ .fromDefault("default");
+ assertEq(fulfillmentComponent, defaultFulfillmentComponent);
+ }
+
+ function testComposeEmpty(uint256 orderIndex, uint256 itemIndex) public {
+ FulfillmentComponent
+ memory fulfillmentComponent = FulfillmentComponentLib
+ .empty()
+ .withOrderIndex(orderIndex)
+ .withItemIndex(itemIndex);
+ assertEq(
+ fulfillmentComponent,
+ FulfillmentComponent({
+ orderIndex: orderIndex,
+ itemIndex: itemIndex
+ })
+ );
+ }
+
+ function testCopy() public {
+ FulfillmentComponent
+ memory fulfillmentComponent = FulfillmentComponent({
+ orderIndex: 1,
+ itemIndex: 2
+ });
+ FulfillmentComponent memory copy = fulfillmentComponent.copy();
+ assertEq(fulfillmentComponent, copy);
+ fulfillmentComponent.orderIndex = 2;
+ assertEq(copy.orderIndex, 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint256[3] memory orderIndex,
+ uint256[3] memory itemIndex
+ ) public {
+ FulfillmentComponent[]
+ memory fulfillmentComponents = new FulfillmentComponent[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ fulfillmentComponents[i] = FulfillmentComponent({
+ orderIndex: orderIndex[i],
+ itemIndex: itemIndex[i]
+ });
+ }
+ FulfillmentComponentLib.saveDefaultMany(
+ fulfillmentComponents,
+ "default"
+ );
+ FulfillmentComponent[]
+ memory defaultFulfillmentComponents = FulfillmentComponentLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(fulfillmentComponents[i], defaultFulfillmentComponents[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/FulfillmentLib.t.sol b/test/foundry/helpers/sol/lib/FulfillmentLib.t.sol
new file mode 100644
index 000000000..a4cf7da9c
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/FulfillmentLib.t.sol
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ FulfillmentLib,
+ FulfillmentComponentLib
+} from "../../../../../contracts/helpers/sol/lib/FulfillmentLib.sol";
+import {
+ Fulfillment,
+ FulfillmentComponent
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ SeaportArrays
+} from "../../../../../contracts/helpers/sol/lib/SeaportArrays.sol";
+
+contract FulfillmentLibTest is BaseTest {
+ using FulfillmentLib for Fulfillment;
+ using FulfillmentComponentLib for FulfillmentComponent;
+
+ function testRetrieveDefault(
+ FulfillmentComponent[] memory offerComponents,
+ FulfillmentComponent[] memory considerationComponents
+ ) public {
+ Fulfillment memory fulfillment = Fulfillment({
+ offerComponents: offerComponents,
+ considerationComponents: considerationComponents
+ });
+ FulfillmentLib.saveDefault(fulfillment, "default");
+ Fulfillment memory defaultFulfillment = FulfillmentLib.fromDefault(
+ "default"
+ );
+ assertEq(fulfillment, defaultFulfillment);
+ }
+
+ function testComposeEmpty(
+ FulfillmentComponent[] memory offerComponents,
+ FulfillmentComponent[] memory considerationComponents
+ ) public {
+ Fulfillment memory fulfillment = FulfillmentLib
+ .empty()
+ .withOfferComponents(offerComponents)
+ .withConsiderationComponents(considerationComponents);
+ assertEq(
+ fulfillment,
+ Fulfillment({
+ offerComponents: offerComponents,
+ considerationComponents: considerationComponents
+ })
+ );
+ }
+
+ function testCopy() public {
+ FulfillmentComponent[] memory offerComponents = SeaportArrays
+ .FulfillmentComponents(
+ FulfillmentComponent({ orderIndex: 1, itemIndex: 1 })
+ );
+ FulfillmentComponent[] memory considerationComponents = SeaportArrays
+ .FulfillmentComponents(
+ FulfillmentComponent({ orderIndex: 2, itemIndex: 2 })
+ );
+ Fulfillment memory fulfillment = Fulfillment({
+ offerComponents: offerComponents,
+ considerationComponents: considerationComponents
+ });
+ Fulfillment memory copy = fulfillment.copy();
+ assertEq(fulfillment, copy);
+ fulfillment.considerationComponents = offerComponents;
+ assertEq(copy.considerationComponents, considerationComponents);
+ }
+
+ function testRetrieveDefaultMany(
+ FulfillmentComponent[][3] memory offerComponents,
+ FulfillmentComponent[][3] memory considerationComponents
+ ) public {
+ Fulfillment[] memory fulfillments = new Fulfillment[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ fulfillments[i] = Fulfillment({
+ offerComponents: offerComponents[i],
+ considerationComponents: considerationComponents[i]
+ });
+ }
+ FulfillmentLib.saveDefaultMany(fulfillments, "default");
+ Fulfillment[] memory defaultFulfillments = FulfillmentLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(fulfillments[i], defaultFulfillments[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/OfferItemLib.t.sol b/test/foundry/helpers/sol/lib/OfferItemLib.t.sol
new file mode 100644
index 000000000..e9a7e0a90
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/OfferItemLib.t.sol
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ OfferItemLib
+} from "../../../../../contracts/helpers/sol/lib/OfferItemLib.sol";
+import {
+ OfferItem
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract OfferItemLibTest is BaseTest {
+ using OfferItemLib for OfferItem;
+
+ function testRetrieveDefault(
+ uint8 _itemType,
+ address token,
+ uint256 identifier,
+ uint256 startAmount,
+ uint256 endAmount
+ ) public {
+ ItemType itemType = toItemType(_itemType);
+ OfferItem memory offerItem = OfferItem({
+ itemType: ItemType(itemType),
+ token: token,
+ identifierOrCriteria: identifier,
+ startAmount: startAmount,
+ endAmount: endAmount
+ });
+ OfferItemLib.saveDefault(offerItem, "default");
+ OfferItem memory defaultOfferItem = OfferItemLib.fromDefault("default");
+ assertEq(offerItem, defaultOfferItem);
+ }
+
+ function testComposeEmpty(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 startAmount,
+ uint256 endAmount
+ ) public {
+ ItemType _itemType = ItemType(bound(itemType, 0, 5));
+ OfferItem memory offerItem = OfferItemLib
+ .empty()
+ .withEndAmount(endAmount)
+ .withStartAmount(startAmount)
+ .withIdentifierOrCriteria(identifier)
+ .withToken(token)
+ .withItemType(_itemType);
+ assertEq(
+ offerItem,
+ OfferItem({
+ itemType: _itemType,
+ token: token,
+ identifierOrCriteria: identifier,
+ startAmount: startAmount,
+ endAmount: endAmount
+ })
+ );
+ }
+
+ function testCopy() public {
+ OfferItem memory offerItem = OfferItem({
+ itemType: ItemType(1),
+ token: address(1),
+ identifierOrCriteria: 1,
+ startAmount: 1,
+ endAmount: 1
+ });
+ OfferItem memory copy = offerItem.copy();
+ assertEq(offerItem, copy);
+ offerItem.itemType = ItemType(2);
+ assertEq(uint8(copy.itemType), 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint8[3] memory itemType,
+ address[3] memory token,
+ uint256[3] memory identifier,
+ uint256[3] memory startAmount,
+ uint256[3] memory endAmount
+ ) public {
+ OfferItem[] memory offerItems = new OfferItem[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ itemType[i] = uint8(bound(itemType[i], 0, 5));
+ offerItems[i] = OfferItem({
+ itemType: ItemType(itemType[i]),
+ token: token[i],
+ identifierOrCriteria: identifier[i],
+ startAmount: startAmount[i],
+ endAmount: endAmount[i]
+ });
+ }
+ OfferItemLib.saveDefaultMany(offerItems, "default");
+ OfferItem[] memory defaultOfferItems = OfferItemLib.fromDefaultMany(
+ "default"
+ );
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(offerItems[i], defaultOfferItems[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/OrderComponentsLib.t.sol b/test/foundry/helpers/sol/lib/OrderComponentsLib.t.sol
new file mode 100644
index 000000000..46caa9f70
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/OrderComponentsLib.t.sol
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ OrderComponentsLib
+} from "../../../../../contracts/helpers/sol/lib/OrderComponentsLib.sol";
+import {
+ AdditionalRecipientLib
+} from "../../../../../contracts/helpers/sol/lib/AdditionalRecipientLib.sol";
+import {
+ OrderComponents,
+ OrderParameters,
+ OfferItem,
+ ConsiderationItem,
+ AdditionalRecipient
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import {
+ ItemType,
+ BasicOrderType,
+ OrderType
+} from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+import {
+ SeaportArrays
+} from "../../../../../contracts/helpers/sol/lib/SeaportStructLib.sol";
+
+contract OrderComponentsLibTest is BaseTest {
+ using OrderComponentsLib for OrderComponents;
+ using OrderParametersLib for OrderParameters;
+
+ function testRetrieveDefault(OrderComponentsBlob memory blob) public {
+ OrderComponents memory orderComponents = _fromBlob(blob);
+ OrderComponents memory dup = OrderComponentsLib.empty();
+ dup.offerer = blob.offerer;
+ dup.zone = blob.zone;
+ dup.offer = _fromBlobs(blob.offer);
+ dup.consideration = _fromBlobs(blob.consideration);
+ dup.orderType = toOrderType(blob._orderType);
+ dup.startTime = blob.startTime;
+ dup.endTime = blob.endTime;
+ dup.zoneHash = blob.zoneHash;
+ dup.salt = blob.salt;
+ dup.conduitKey = blob.conduitKey;
+ dup.counter = blob.counter;
+ assertEq(orderComponents, dup);
+
+ OrderComponentsLib.saveDefault(orderComponents, "default");
+ OrderComponents memory defaultOrderComponents = OrderComponentsLib
+ .fromDefault("default");
+ assertEq(orderComponents, defaultOrderComponents);
+ }
+
+ function testCopy() public {
+ OrderComponents memory orderComponents = OrderComponentsLib.empty();
+ orderComponents = orderComponents.withOfferer(address(1));
+ orderComponents = orderComponents.withZone(address(2));
+ orderComponents = orderComponents.withOrderType(OrderType(3));
+ orderComponents = orderComponents.withStartTime(4);
+ orderComponents = orderComponents.withEndTime(5);
+ orderComponents = orderComponents.withZoneHash(bytes32(uint256(6)));
+ orderComponents = orderComponents.withSalt(7);
+ orderComponents = orderComponents.withConduitKey(bytes32(uint256(8)));
+ orderComponents = orderComponents.withCounter(9);
+ OfferItem[] memory offer;
+
+ {
+ offer = SeaportArrays.OfferItems(
+ OfferItem({
+ itemType: ItemType(1),
+ token: address(2),
+ identifierOrCriteria: 3,
+ startAmount: 4,
+ endAmount: 5
+ })
+ );
+ ConsiderationItem[] memory consideration = SeaportArrays
+ .ConsiderationItems(
+ ConsiderationItem({
+ itemType: ItemType(2),
+ token: address(7),
+ identifierOrCriteria: 8,
+ startAmount: 9,
+ endAmount: 10,
+ recipient: payable(address(11))
+ })
+ );
+
+ orderComponents = orderComponents.withOffer(offer);
+ orderComponents = orderComponents.withConsideration(consideration);
+ }
+
+ OrderComponents memory copy = orderComponents.copy();
+ assertEq(orderComponents, copy);
+ orderComponents.offerer = address(5678);
+ assertEq(copy.offerer, address(1), "copy changed");
+
+ offer[0].identifierOrCriteria = 123;
+ assertEq(
+ copy.offer[0].identifierOrCriteria,
+ 3,
+ "copy offer identifier changed"
+ );
+ }
+
+ function testRetrieveDefaultMany(
+ OrderComponentsBlob[3] memory blobs
+ ) public {
+ OrderComponents[] memory orderComponents = new OrderComponents[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ orderComponents[i] = _fromBlob(blobs[i]);
+ }
+ OrderComponentsLib.saveDefaultMany(orderComponents, "default");
+ OrderComponents[] memory defaultOrderComponentss = OrderComponentsLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(orderComponents[i], defaultOrderComponentss[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/OrderLib.t.sol b/test/foundry/helpers/sol/lib/OrderLib.t.sol
new file mode 100644
index 000000000..ad0df9c3e
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/OrderLib.t.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ OrderLib
+} from "../../../../../contracts/helpers/sol/lib/OrderLib.sol";
+import {
+ Order,
+ OrderParameters
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+
+contract OrderLibTest is BaseTest {
+ using OrderParametersLib for OrderParameters;
+ using OrderLib for Order;
+
+ function testRetrieveDefault(OrderBlob memory orderBlob) public {
+ Order memory order = _fromBlob(orderBlob);
+ Order memory dup = Order({
+ parameters: _fromBlob(orderBlob.parameters),
+ signature: orderBlob.signature
+ });
+ assertEq(order, dup);
+ OrderLib.saveDefault(order, "default");
+ Order memory defaultOrder = OrderLib.fromDefault("default");
+ assertEq(order, defaultOrder);
+ }
+
+ function testCopy() public {
+ OrderParameters memory parameters = OrderParametersLib
+ .empty()
+ .withOfferer(address(123));
+ Order memory order = Order({
+ parameters: parameters,
+ signature: "abc"
+ });
+ Order memory copy = order.copy();
+ assertEq(order, copy);
+ order.signature = "abcd";
+ assertEq(copy.signature, "abc");
+ order.parameters.offerer = address(5678);
+ assertEq(copy.parameters.offerer, address(123));
+ }
+
+ function testRetrieveDefaultMany(OrderBlob[3] memory blob) public {
+ Order[] memory orders = new Order[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ orders[i] = _fromBlob(blob[i]);
+ }
+ OrderLib.saveDefaultMany(orders, "default");
+ Order[] memory defaultOrders = OrderLib.fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(orders[i], defaultOrders[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/OrderParametersLib.t.sol b/test/foundry/helpers/sol/lib/OrderParametersLib.t.sol
new file mode 100644
index 000000000..01e6be5c7
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/OrderParametersLib.t.sol
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+import {
+ AdditionalRecipientLib
+} from "../../../../../contracts/helpers/sol/lib/AdditionalRecipientLib.sol";
+import {
+ OrderParameters,
+ OrderParameters,
+ OfferItem,
+ ConsiderationItem,
+ AdditionalRecipient
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import {
+ ItemType,
+ BasicOrderType,
+ OrderType
+} from "../../../../../contracts/lib/ConsiderationEnums.sol";
+import {
+ OrderParametersLib
+} from "../../../../../contracts/helpers/sol/lib/OrderParametersLib.sol";
+import {
+ SeaportArrays
+} from "../../../../../contracts/helpers/sol/lib/SeaportArrays.sol";
+
+contract OrderParametersLibTest is BaseTest {
+ using OrderParametersLib for OrderParameters;
+ using OrderParametersLib for OrderParameters;
+
+ function testRetrieveDefault(OrderParametersBlob memory blob) public {
+ OrderParameters memory orderParameters = _fromBlob(blob);
+ OrderParameters memory dup = OrderParametersLib.empty();
+ dup.offerer = blob.offerer;
+ dup.zone = blob.zone;
+ dup.offer = _fromBlobs(blob.offer);
+ dup.consideration = _fromBlobs(blob.consideration);
+ dup.orderType = toOrderType(blob._orderType);
+ dup.startTime = blob.startTime;
+ dup.endTime = blob.endTime;
+ dup.zoneHash = blob.zoneHash;
+ dup.salt = blob.salt;
+ dup.conduitKey = blob.conduitKey;
+ dup.totalOriginalConsiderationItems = blob
+ .totalOriginalConsiderationItems;
+ assertEq(orderParameters, dup);
+
+ OrderParametersLib.saveDefault(orderParameters, "default");
+ OrderParameters memory defaultOrderParameters = OrderParametersLib
+ .fromDefault("default");
+ assertEq(orderParameters, defaultOrderParameters);
+ }
+
+ function testCopy() public {
+ OrderParameters memory orderParameters = OrderParametersLib.empty();
+ orderParameters = orderParameters.withOfferer(address(1));
+ orderParameters = orderParameters.withZone(address(2));
+ orderParameters = orderParameters.withOrderType(OrderType(3));
+ orderParameters = orderParameters.withStartTime(4);
+ orderParameters = orderParameters.withEndTime(5);
+ orderParameters = orderParameters.withZoneHash(bytes32(uint256(6)));
+ orderParameters = orderParameters.withSalt(7);
+ orderParameters = orderParameters.withConduitKey(bytes32(uint256(8)));
+ OfferItem[] memory offer;
+
+ {
+ offer = SeaportArrays.OfferItems(
+ OfferItem({
+ itemType: ItemType(1),
+ token: address(2),
+ identifierOrCriteria: 3,
+ startAmount: 4,
+ endAmount: 5
+ })
+ );
+ ConsiderationItem[] memory consideration = SeaportArrays
+ .ConsiderationItems(
+ ConsiderationItem({
+ itemType: ItemType(2),
+ token: address(7),
+ identifierOrCriteria: 8,
+ startAmount: 9,
+ endAmount: 10,
+ recipient: payable(address(11))
+ })
+ );
+
+ orderParameters = orderParameters.withOffer(offer);
+ orderParameters = orderParameters.withConsideration(consideration);
+ }
+
+ OrderParameters memory copy = orderParameters.copy();
+ assertEq(orderParameters, copy);
+ orderParameters.offerer = address(5678);
+ assertEq(copy.offerer, address(1), "copy changed");
+
+ offer[0].identifierOrCriteria = 123;
+ assertEq(
+ copy.offer[0].identifierOrCriteria,
+ 3,
+ "copy offer identifier changed"
+ );
+ }
+
+ function testRetrieveDefaultMany(
+ OrderParametersBlob[3] memory blobs
+ ) public {
+ OrderParameters[] memory orderParameters = new OrderParameters[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ orderParameters[i] = _fromBlob(blobs[i]);
+ }
+ OrderParametersLib.saveDefaultMany(orderParameters, "default");
+ OrderParameters[] memory defaultOrderParameterss = OrderParametersLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(orderParameters[i], defaultOrderParameterss[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/ReceivedItemLib.t.sol b/test/foundry/helpers/sol/lib/ReceivedItemLib.t.sol
new file mode 100644
index 000000000..eeea25735
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/ReceivedItemLib.t.sol
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ ReceivedItemLib
+} from "../../../../../contracts/helpers/sol/lib/ReceivedItemLib.sol";
+import {
+ ReceivedItem
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract ReceivedItemLibTest is BaseTest {
+ using ReceivedItemLib for ReceivedItem;
+
+ function testRetrieveDefault(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 amount,
+ address payable recipient
+ ) public {
+ itemType = uint8(bound(itemType, 0, 5));
+ ReceivedItem memory receivedItem = ReceivedItem(
+ ItemType(itemType),
+ token,
+ identifier,
+ amount,
+ recipient
+ );
+ ReceivedItemLib.saveDefault(receivedItem, "default");
+ ReceivedItem memory defaultReceivedItem = ReceivedItemLib.fromDefault(
+ "default"
+ );
+ assertEq(receivedItem, defaultReceivedItem);
+ }
+
+ function testComposeEmpty(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 amount,
+ address payable recipient
+ ) public {
+ itemType = uint8(bound(itemType, 0, 5));
+ ReceivedItem memory receivedItem = ReceivedItemLib
+ .empty()
+ .withItemType(ItemType(itemType))
+ .withToken(token)
+ .withIdentifier(identifier)
+ .withAmount(amount)
+ .withRecipient(recipient);
+ assertEq(
+ receivedItem,
+ ReceivedItem({
+ itemType: ItemType(itemType),
+ token: token,
+ identifier: identifier,
+ amount: amount,
+ recipient: recipient
+ })
+ );
+ }
+
+ function testCopy() public {
+ ReceivedItem memory receivedItem = ReceivedItem(
+ ItemType(1),
+ address(1),
+ 1,
+ 1,
+ payable(address(1234))
+ );
+ ReceivedItem memory copy = receivedItem.copy();
+ assertEq(receivedItem, copy);
+ receivedItem.itemType = ItemType(2);
+ assertEq(uint8(copy.itemType), 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint8[3] memory itemType,
+ address[3] memory token,
+ uint256[3] memory identifier,
+ uint256[3] memory amount,
+ address payable[3] memory recipient
+ ) public {
+ ReceivedItem[] memory receivedItems = new ReceivedItem[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ itemType[i] = uint8(bound(itemType[i], 0, 5));
+ receivedItems[i] = ReceivedItem(
+ ItemType(itemType[i]),
+ token[i],
+ identifier[i],
+ amount[i],
+ recipient[i]
+ );
+ }
+ ReceivedItemLib.saveDefaultMany(receivedItems, "default");
+ ReceivedItem[] memory defaultReceivedItems = ReceivedItemLib
+ .fromDefaultMany("default");
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(receivedItems[i], defaultReceivedItems[i]);
+ }
+ }
+}
diff --git a/test/foundry/helpers/sol/lib/SpentItemLib.t.sol b/test/foundry/helpers/sol/lib/SpentItemLib.t.sol
new file mode 100644
index 000000000..e2d56c24b
--- /dev/null
+++ b/test/foundry/helpers/sol/lib/SpentItemLib.t.sol
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseTest } from "../BaseTest.sol";
+import {
+ SpentItemLib
+} from "../../../../../contracts/helpers/sol/lib/SpentItemLib.sol";
+import {
+ SpentItem
+} from "../../../../../contracts/lib/ConsiderationStructs.sol";
+import { ItemType } from "../../../../../contracts/lib/ConsiderationEnums.sol";
+
+contract SpentItemLibTest is BaseTest {
+ using SpentItemLib for SpentItem;
+
+ function testRetrieveDefault(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 amount
+ ) public {
+ itemType = uint8(bound(itemType, 0, 5));
+ SpentItem memory spentItem = SpentItem(
+ ItemType(itemType),
+ token,
+ identifier,
+ amount
+ );
+ SpentItemLib.saveDefault(spentItem, "default");
+ SpentItem memory defaultSpentItem = SpentItemLib.fromDefault("default");
+ assertEq(spentItem, defaultSpentItem);
+ }
+
+ function testComposeEmpty(
+ uint8 itemType,
+ address token,
+ uint256 identifier,
+ uint256 amount
+ ) public {
+ itemType = uint8(bound(itemType, 0, 5));
+ SpentItem memory spentItem = SpentItemLib
+ .empty()
+ .withItemType(ItemType(itemType))
+ .withToken(token)
+ .withIdentifier(identifier)
+ .withAmount(amount);
+ assertEq(
+ spentItem,
+ SpentItem({
+ itemType: ItemType(itemType),
+ token: token,
+ identifier: identifier,
+ amount: amount
+ })
+ );
+ }
+
+ function testCopy() public {
+ SpentItem memory spentItem = SpentItem(ItemType(1), address(1), 1, 1);
+ SpentItem memory copy = spentItem.copy();
+ assertEq(spentItem, copy);
+ spentItem.itemType = ItemType(2);
+ assertEq(uint8(copy.itemType), 1);
+ }
+
+ function testRetrieveDefaultMany(
+ uint8[3] memory itemType,
+ address[3] memory token,
+ uint256[3] memory identifier,
+ uint256[3] memory amount
+ ) public {
+ SpentItem[] memory spentItems = new SpentItem[](3);
+ for (uint256 i = 0; i < 3; i++) {
+ itemType[i] = uint8(bound(itemType[i], 0, 5));
+ spentItems[i] = SpentItem(
+ ItemType(itemType[i]),
+ token[i],
+ identifier[i],
+ amount[i]
+ );
+ }
+ SpentItemLib.saveDefaultMany(spentItems, "default");
+ SpentItem[] memory defaultSpentItems = SpentItemLib.fromDefaultMany(
+ "default"
+ );
+ for (uint256 i = 0; i < 3; i++) {
+ assertEq(spentItems[i], defaultSpentItems[i]);
+ }
+ }
+}
diff --git a/test/foundry/utils/BaseOrderTest.sol b/test/foundry/utils/BaseOrderTest.sol
index 21eeeb547..b061f05b2 100644
--- a/test/foundry/utils/BaseOrderTest.sol
+++ b/test/foundry/utils/BaseOrderTest.sol
@@ -35,6 +35,12 @@ contract BaseOrderTest is OrderBuilder, AmountDeriver {
using ArithmeticUtil for uint128;
using ArithmeticUtil for uint120;
+ ///@dev used to store address and key outputs from makeAddrAndKey(name)
+ struct Account {
+ address addr;
+ uint256 key;
+ }
+
FulfillmentComponent firstOrderFirstItem;
FulfillmentComponent firstOrderSecondItem;
FulfillmentComponent secondOrderFirstItem;
@@ -50,6 +56,8 @@ contract BaseOrderTest is OrderBuilder, AmountDeriver {
AdditionalRecipient[] additionalRecipients;
+ Account offerer1;
+
event Transfer(address indexed from, address indexed to, uint256 value);
event TransferSingle(
@@ -73,6 +81,22 @@ contract BaseOrderTest is OrderBuilder, AmountDeriver {
_;
}
+ /// @dev convenience wrapper for makeAddrAndKey
+ function makeAccount(string memory name) internal returns (Account memory) {
+ (address addr, uint256 key) = makeAddrAndKey(name);
+ return Account(addr, key);
+ }
+
+ /// @dev convenience wrapper for makeAddrAndKey that also allocates tokens,
+ /// ether, and approvals
+ function makeAndAllocateAccount(
+ string memory name
+ ) internal returns (Account memory) {
+ Account memory account = makeAccount(name);
+ allocateTokensAndApprovals(account.addr, uint128(MAX_INT));
+ return account;
+ }
+
function setUp() public virtual override {
super.setUp();
@@ -91,6 +115,9 @@ contract BaseOrderTest is OrderBuilder, AmountDeriver {
allocateTokensAndApprovals(alice, uint128(MAX_INT));
allocateTokensAndApprovals(bob, uint128(MAX_INT));
allocateTokensAndApprovals(cal, uint128(MAX_INT));
+ allocateTokensAndApprovals(offerer1.addr, uint128(MAX_INT));
+
+ offerer1 = makeAndAllocateAccount("offerer1");
}
function resetOfferComponents() internal {
diff --git a/test/foundry/utils/EIP712MerkleTree.sol b/test/foundry/utils/EIP712MerkleTree.sol
index 40f7dcbab..b20a7d96b 100644
--- a/test/foundry/utils/EIP712MerkleTree.sol
+++ b/test/foundry/utils/EIP712MerkleTree.sol
@@ -4,7 +4,7 @@ pragma solidity ^0.8.17;
import { MurkyBase } from "murky/common/MurkyBase.sol";
import {
TypehashDirectory
-} from "../../../contracts/lib/TypehashDirectory.sol";
+} from "../../../contracts/test/TypehashDirectory.sol";
import { Test } from "forge-std/Test.sol";
import {
ConsiderationInterface
@@ -40,18 +40,10 @@ contract EIP712MerkleTree is Test {
merkle = new MerkleUnsorted();
}
- /// @dev same lookup seaport optimized does
- function _lookupBulkOrderTypehash(
- uint256 treeHeight
- ) internal view returns (bytes32 typeHash) {
- TypehashDirectory directory = _typehashDirectory;
- assembly {
- let typeHashOffset := add(1, shl(0x5, sub(treeHeight, 1)))
- extcodecopy(directory, 0, typeHashOffset, 0x20)
- typeHash := mload(0)
- }
- }
-
+ /// @dev Creates a single bulk signature: a base signature + a three byte
+ /// index + a series of 32 byte proofs. The height of the tree is
+ /// determined by the length of the orderComponents array and only
+ /// fills empty orders into the tree to make the length a power of 2.
function signBulkOrder(
ConsiderationInterface consideration,
uint256 privateKey,
@@ -107,55 +99,10 @@ contract EIP712MerkleTree is Test {
);
}
- function _getSignature(
- ConsiderationInterface consideration,
- uint256 privateKey,
- bytes32 bulkOrderTypehash,
- bytes32 root,
- bytes32[] memory proof,
- uint24 orderIndex,
- bool useCompact2098
- ) internal view returns (bytes memory) {
- // bulkOrder hash is keccak256 of the specific bulk order typehash and
- // the merkle root of the order hashes
- bytes32 bulkOrderHash = keccak256(abi.encode(bulkOrderTypehash, root));
-
- // get domain separator from the particular seaport instance
- (, bytes32 domainSeparator, ) = consideration.information();
-
- // declare out here to avoid stack too deep errors
- bytes memory signature;
- // avoid stacc 2 thicc
- {
- (uint8 v, bytes32 r, bytes32 s) = vm.sign(
- privateKey,
- keccak256(
- abi.encodePacked(
- bytes2(0x1901),
- domainSeparator,
- bulkOrderHash
- )
- )
- );
- // if useCompact2098 is true, encode yParity (v) into s
- if (useCompact2098) {
- uint256 yParity = (v == 27) ? 0 : 1;
- bytes32 yAndS = bytes32(uint256(s) | (yParity << 255));
- signature = abi.encodePacked(r, yAndS);
- } else {
- signature = abi.encodePacked(r, s, v);
- }
- }
-
- // return the packed signature, order index, and proof
- // encodePacked will pack everything tightly without lengths
- // ie, long-style rsv signatures will have 1 byte for v
- // orderIndex will be the next 3 bytes
- // then proof will be each element one after another; its offset and
- // length will not be encoded
- return abi.encodePacked(signature, orderIndex, proof);
- }
-
+ /// @dev Creates a single bulk signature: a base signature + a three byte
+ /// index + a series of 32 byte proofs. The height of the tree is
+ /// determined by the height parameter and this function will fill
+ /// empty orders into the tree until the specified height is reached.
function signSparseBulkOrder(
ConsiderationInterface consideration,
uint256 privateKey,
@@ -231,4 +178,65 @@ contract EIP712MerkleTree is Test {
useCompact2098
);
}
+
+ /// @dev same lookup seaport optimized does
+ function _lookupBulkOrderTypehash(
+ uint256 treeHeight
+ ) internal view returns (bytes32 typeHash) {
+ TypehashDirectory directory = _typehashDirectory;
+ assembly {
+ let typeHashOffset := add(1, shl(0x5, sub(treeHeight, 1)))
+ extcodecopy(directory, 0, typeHashOffset, 0x20)
+ typeHash := mload(0)
+ }
+ }
+
+ function _getSignature(
+ ConsiderationInterface consideration,
+ uint256 privateKey,
+ bytes32 bulkOrderTypehash,
+ bytes32 root,
+ bytes32[] memory proof,
+ uint24 orderIndex,
+ bool useCompact2098
+ ) internal view returns (bytes memory) {
+ // bulkOrder hash is keccak256 of the specific bulk order typehash and
+ // the merkle root of the order hashes
+ bytes32 bulkOrderHash = keccak256(abi.encode(bulkOrderTypehash, root));
+
+ // get domain separator from the particular seaport instance
+ (, bytes32 domainSeparator, ) = consideration.information();
+
+ // declare out here to avoid stack too deep errors
+ bytes memory signature;
+ // avoid stacc 2 thicc
+ {
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(
+ privateKey,
+ keccak256(
+ abi.encodePacked(
+ bytes2(0x1901),
+ domainSeparator,
+ bulkOrderHash
+ )
+ )
+ );
+ // if useCompact2098 is true, encode yParity (v) into s
+ if (useCompact2098) {
+ uint256 yParity = (v == 27) ? 0 : 1;
+ bytes32 yAndS = bytes32(uint256(s) | (yParity << 255));
+ signature = abi.encodePacked(r, yAndS);
+ } else {
+ signature = abi.encodePacked(r, s, v);
+ }
+ }
+
+ // return the packed signature, order index, and proof
+ // encodePacked will pack everything tightly without lengths
+ // ie, long-style rsv signatures will have 1 byte for v
+ // orderIndex will be the next 3 bytes
+ // then proof will be each element one after another; its offset and
+ // length will not be encoded
+ return abi.encodePacked(signature, orderIndex, proof);
+ }
}
diff --git a/test/foundry/zone/PostFulfillmentCheck.t.sol b/test/foundry/zone/PostFulfillmentCheck.t.sol
index 6426f5d5a..3b43e8717 100644
--- a/test/foundry/zone/PostFulfillmentCheck.t.sol
+++ b/test/foundry/zone/PostFulfillmentCheck.t.sol
@@ -3,8 +3,14 @@ pragma solidity ^0.8.17;
import { BaseOrderTest } from "../utils/BaseOrderTest.sol";
+import { BaseConduitTest } from "../conduit/BaseConduitTest.sol";
+
import { TestZone } from "./impl/TestZone.sol";
+import {
+ TestTransferValidationZoneOfferer
+} from "../../../contracts/test/TestTransferValidationZoneOfferer.sol";
+
import {
PostFulfillmentStatefulTestZone
} from "./impl/PostFullfillmentStatefulTestZone.sol";
@@ -58,6 +64,12 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
function setUp() public override {
super.setUp();
+ conduitController.updateChannel(address(conduit), address(this), true);
+ referenceConduitController.updateChannel(
+ address(referenceConduit),
+ address(this),
+ true
+ );
vm.label(address(zone), "TestZone");
}
@@ -340,6 +352,80 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
});
}
+ function testExectBasicStatefulWithConduit() public {
+ test(
+ this.execBasicStatefulWithConduit,
+ Context({
+ consideration: consideration,
+ numOriginalAdditional: 0,
+ numTips: 0
+ })
+ );
+ test(
+ this.execBasicStatefulWithConduit,
+ Context({
+ consideration: referenceConsideration,
+ numOriginalAdditional: 0,
+ numTips: 0
+ })
+ );
+ }
+
+ function execBasicStatefulWithConduit(
+ Context memory context
+ ) public stateless {
+ addErc20OfferItem(50);
+ addErc721ConsiderationItem(alice, 42);
+ addErc20ConsiderationItem(bob, 1);
+ addErc20ConsiderationItem(cal, 1);
+
+ test721_1.mint(address(this), 42);
+
+ _configureOrderParameters({
+ offerer: alice,
+ zone: address(statefulZone),
+ zoneHash: bytes32(0),
+ salt: 0,
+ useConduit: true
+ });
+ baseOrderParameters.startTime = 1;
+ baseOrderParameters.endTime = 101;
+ baseOrderParameters.orderType = OrderType.FULL_RESTRICTED;
+
+ configureOrderComponents(0);
+ bytes32 orderHash = context.consideration.getOrderHash(
+ baseOrderComponents
+ );
+ bytes memory signature = signOrder(
+ context.consideration,
+ alicePk,
+ orderHash
+ );
+
+ BasicOrderParameters
+ memory basicOrderParameters = toBasicOrderParameters(
+ baseOrderComponents,
+ BasicOrderType.ERC721_TO_ERC20_FULL_RESTRICTED,
+ signature
+ );
+ basicOrderParameters.additionalRecipients = new AdditionalRecipient[](
+ 2
+ );
+ basicOrderParameters.additionalRecipients[0] = AdditionalRecipient({
+ recipient: bob,
+ amount: 1
+ });
+ basicOrderParameters.additionalRecipients[1] = AdditionalRecipient({
+ recipient: cal,
+ amount: 1
+ });
+ basicOrderParameters.totalOriginalAdditionalRecipients = 2;
+ vm.warp(50);
+ context.consideration.fulfillBasicOrder({
+ parameters: basicOrderParameters
+ });
+ }
+
function testBasicStateful(
uint8 numOriginalAdditional,
uint8 numTips
@@ -542,106 +628,95 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
assertTrue(statefulZone.called());
}
- // function testMatchAdvancedOrders() external {
- // test(
- // this.execMatchAdvancedOrders,
- // Context({
- // consideration: consideration,
- // numOriginalAdditional: 0,
- // numTips: 0
- // })
- // );
- // test(
- // this.execMatchAdvancedOrders,
- // Context({
- // consideration: referenceConsideration,
- // numOriginalAdditional: 0,
- // numTips: 0
- // })
- // );
- // }
-
- // function execMatchAdvancedOrders(Context memory context) external {
- // addErc20OfferItem(1);
- // addErc721ConsiderationItem(payable(address(offerer)), 42);
- // addErc721ConsiderationItem(payable(address(offerer)), 43);
- // addErc721ConsiderationItem(payable(address(offerer)), 44);
-
- // _configureOrderParameters({
- // offerer: address(this),
- // zone: address(0),
- // zoneHash: bytes32(0),
- // salt: 0,
- // useConduit: false
- // });
- // baseOrderParameters.orderType = OrderType.CONTRACT;
-
- // configureOrderComponents(0);
-
- // AdvancedOrder memory order = AdvancedOrder({
- // parameters: baseOrderParameters,
- // numerator: 1,
- // denominator: 1,
- // signature: "",
- // extraData: "context"
- // });
-
- // AdvancedOrder memory mirror = createMirrorContractOffererOrder(
- // context,
- // "mirroroooor",
- // order
- // );
-
- // CriteriaResolver[] memory criteriaResolvers = new CriteriaResolver[](0);
- // AdvancedOrder[] memory orders = new AdvancedOrder[](2);
- // orders[0] = order;
- // orders[1] = mirror;
-
- // //match first order offer to second order consideration
- // createFulfillmentFromComponentsAndAddToFulfillments({
- // _offer: FulfillmentComponent({ orderIndex: 0, itemIndex: 0 }),
- // _consideration: FulfillmentComponent({
- // orderIndex: 1,
- // itemIndex: 0
- // })
- // });
- // // match second order first offer to first order first consideration
- // createFulfillmentFromComponentsAndAddToFulfillments({
- // _offer: FulfillmentComponent({ orderIndex: 1, itemIndex: 0 }),
- // _consideration: FulfillmentComponent({
- // orderIndex: 0,
- // itemIndex: 0
- // })
- // });
- // // match second order second offer to first order second consideration
- // createFulfillmentFromComponentsAndAddToFulfillments({
- // _offer: FulfillmentComponent({ orderIndex: 1, itemIndex: 1 }),
- // _consideration: FulfillmentComponent({
- // orderIndex: 0,
- // itemIndex: 1
- // })
- // });
- // // match second order third offer to first order third consideration
- // createFulfillmentFromComponentsAndAddToFulfillments({
- // _offer: FulfillmentComponent({ orderIndex: 1, itemIndex: 2 }),
- // _consideration: FulfillmentComponent({
- // orderIndex: 0,
- // itemIndex: 2
- // })
- // });
-
- // context.consideration.matchAdvancedOrders({
- // orders: orders,
- // criteriaResolvers: criteriaResolvers,
- // fulfillments: fulfillments
- // });
- // assertTrue(zone.called());
- // }
+ function testExecMatchAdvancedOrdersWithConduit() public {
+ test(
+ this.execMatchAdvancedOrdersWithConduit,
+ Context({
+ consideration: consideration,
+ numOriginalAdditional: 0,
+ numTips: 0
+ })
+ );
+ }
+
+ function execMatchAdvancedOrdersWithConduit(
+ Context memory context
+ ) external stateless {
+ TestTransferValidationZoneOfferer transferValidationZone = new TestTransferValidationZoneOfferer();
+
+ addErc20OfferItem(50);
+ addErc721ConsiderationItem(alice, 42);
+
+ _configureOrderParameters({
+ offerer: alice,
+ zone: address(transferValidationZone),
+ zoneHash: bytes32(0),
+ salt: 0,
+ useConduit: true
+ });
+ baseOrderParameters.orderType = OrderType.FULL_RESTRICTED;
+
+ configureOrderComponents(0);
+ bytes32 orderHash = context.consideration.getOrderHash(
+ baseOrderComponents
+ );
+
+ bytes memory signature = signOrder(
+ context.consideration,
+ alicePk,
+ orderHash
+ );
+
+ AdvancedOrder memory order = AdvancedOrder({
+ parameters: baseOrderParameters,
+ numerator: 1,
+ denominator: 1,
+ signature: signature,
+ extraData: "context"
+ });
+
+ AdvancedOrder memory mirror = createMirrorOrder(
+ context,
+ "mirroroooor",
+ order,
+ true
+ );
+
+ CriteriaResolver[] memory criteriaResolvers = new CriteriaResolver[](0);
+ AdvancedOrder[] memory orders = new AdvancedOrder[](2);
+ orders[0] = order;
+ orders[1] = mirror;
+
+ //match first order offer to second order consideration
+ createFulfillmentFromComponentsAndAddToFulfillments({
+ _offer: FulfillmentComponent({ orderIndex: 0, itemIndex: 0 }),
+ _consideration: FulfillmentComponent({
+ orderIndex: 1,
+ itemIndex: 0
+ })
+ });
+ // match second order first offer to first order first consideration
+ createFulfillmentFromComponentsAndAddToFulfillments({
+ _offer: FulfillmentComponent({ orderIndex: 1, itemIndex: 0 }),
+ _consideration: FulfillmentComponent({
+ orderIndex: 0,
+ itemIndex: 0
+ })
+ });
+
+ context.consideration.matchAdvancedOrders({
+ orders: orders,
+ criteriaResolvers: criteriaResolvers,
+ fulfillments: fulfillments,
+ recipient: alice
+ });
+ }
function createMirrorOrder(
Context memory context,
string memory _offerer,
- AdvancedOrder memory advancedOrder
+ AdvancedOrder memory advancedOrder,
+ bool _useConduit
) internal returns (AdvancedOrder memory) {
delete offerItems;
delete considerationItems;
@@ -649,8 +724,11 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
(address _offererAddr, uint256 pkey) = makeAddrAndKey(_offerer);
test721_1.mint(address(_offererAddr), 42);
- vm.prank(_offererAddr);
+ vm.startPrank(_offererAddr);
+ test721_1.setApprovalForAll(address(conduit), true);
+ test721_1.setApprovalForAll(address(referenceConduit), true);
test721_1.setApprovalForAll(address(context.consideration), true);
+ vm.stopPrank();
for (uint256 i; i < advancedOrder.parameters.offer.length; i++) {
OfferItem memory _offerItem = advancedOrder.parameters.offer[i];
@@ -688,7 +766,7 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
zone: advancedOrder.parameters.zone,
zoneHash: advancedOrder.parameters.zoneHash,
salt: advancedOrder.parameters.salt,
- useConduit: false
+ useConduit: _useConduit
});
configureOrderComponents(0);
@@ -717,4 +795,84 @@ contract PostFulfillmentCheckTest is BaseOrderTest {
sum += considerationItems[i].startAmount;
}
}
+
+ function createMirrorContractOffererOrder(
+ Context memory context,
+ string memory _offerer,
+ AdvancedOrder memory advancedOrder,
+ bool _useConduit
+ ) internal returns (AdvancedOrder memory) {
+ delete offerItems;
+ delete considerationItems;
+
+ (address _offererAddr, uint256 pkey) = makeAddrAndKey(_offerer);
+ test721_1.mint(address(_offererAddr), 42);
+ test721_1.mint(address(_offererAddr), 43);
+ test721_1.mint(address(_offererAddr), 44);
+
+ vm.startPrank(_offererAddr);
+ test721_1.setApprovalForAll(address(conduit), true);
+ test721_1.setApprovalForAll(address(referenceConduit), true);
+ test721_1.setApprovalForAll(address(context.consideration), true);
+ vm.stopPrank();
+
+ for (uint256 i; i < advancedOrder.parameters.offer.length; i++) {
+ OfferItem memory _offerItem = advancedOrder.parameters.offer[i];
+
+ addConsiderationItem({
+ itemType: _offerItem.itemType,
+ token: _offerItem.token,
+ identifier: _offerItem.identifierOrCriteria,
+ startAmount: _offerItem.startAmount,
+ endAmount: _offerItem.endAmount,
+ recipient: payable(_offererAddr)
+ });
+ }
+ // do the same for considerationItem -> offerItem
+ for (
+ uint256 i;
+ i < advancedOrder.parameters.consideration.length;
+ i++
+ ) {
+ ConsiderationItem memory _considerationItem = advancedOrder
+ .parameters
+ .consideration[i];
+
+ addOfferItem({
+ itemType: _considerationItem.itemType,
+ token: _considerationItem.token,
+ identifier: _considerationItem.identifierOrCriteria,
+ startAmount: _considerationItem.startAmount,
+ endAmount: _considerationItem.endAmount
+ });
+ }
+
+ _configureOrderParameters({
+ offerer: _offererAddr,
+ zone: advancedOrder.parameters.zone,
+ zoneHash: advancedOrder.parameters.zoneHash,
+ salt: advancedOrder.parameters.salt,
+ useConduit: _useConduit
+ });
+
+ configureOrderComponents(0);
+ bytes32 orderHash = context.consideration.getOrderHash(
+ baseOrderComponents
+ );
+ bytes memory signature = signOrder(
+ context.consideration,
+ pkey,
+ orderHash
+ );
+
+ AdvancedOrder memory order = AdvancedOrder({
+ parameters: baseOrderParameters,
+ numerator: advancedOrder.denominator,
+ denominator: advancedOrder.numerator,
+ signature: signature,
+ extraData: ""
+ });
+
+ return order;
+ }
}
diff --git a/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol
new file mode 100644
index 000000000..32a4eb268
--- /dev/null
+++ b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import { BaseOrderTest } from "../utils/BaseOrderTest.sol";
+import {
+ ConsiderationItem,
+ OfferItem,
+ ItemType,
+ OrderType,
+ AdvancedOrder,
+ Order,
+ CriteriaResolver,
+ BasicOrderParameters,
+ AdditionalRecipient,
+ FulfillmentComponent,
+ Fulfillment,
+ OrderComponents,
+ OrderParameters
+} from "../../../contracts/lib/ConsiderationStructs.sol";
+import {
+ ConsiderationInterface
+} from "../../../contracts/interfaces/ConsiderationInterface.sol";
+import {
+ FulfillmentLib,
+ FulfillmentComponentLib,
+ OrderParametersLib,
+ OrderComponentsLib,
+ OrderLib,
+ OfferItemLib,
+ ConsiderationItemLib,
+ SeaportArrays
+} from "../../../contracts/helpers/sol/lib/SeaportStructLib.sol";
+import {
+ TestTransferValidationZoneOfferer
+} from "../../../contracts/test/TestTransferValidationZoneOfferer.sol";
+
+contract TestTransferValidationZoneOffererTest is BaseOrderTest {
+ using FulfillmentLib for Fulfillment;
+ using FulfillmentComponentLib for FulfillmentComponent;
+ using FulfillmentComponentLib for FulfillmentComponent[];
+ using OfferItemLib for OfferItem;
+ using OfferItemLib for OfferItem[];
+ using ConsiderationItemLib for ConsiderationItem;
+ using ConsiderationItemLib for ConsiderationItem[];
+ using OrderComponentsLib for OrderComponents;
+ using OrderParametersLib for OrderParameters;
+ using OrderLib for Order;
+ using OrderLib for Order[];
+
+ TestTransferValidationZoneOfferer zone;
+
+ // constant strings for recalling struct lib "defaults"
+ // ideally these live in a base test class
+ string constant ONE_ETH = "one eth";
+ string constant SINGLE_721 = "single 721";
+ string constant VALIDATION_ZONE = "validation zone";
+ string constant FIRST_FIRST = "first first";
+ string constant SECOND_FIRST = "second first";
+ string constant FIRST_SECOND__FIRST = "first&second first";
+
+ function setUp() public virtual override {
+ super.setUp();
+ zone = new TestTransferValidationZoneOfferer();
+
+ // create a default considerationItem for one ether;
+ // note that it does not have recipient set
+ ConsiderationItemLib
+ .empty()
+ .withItemType(ItemType.NATIVE)
+ .withToken(address(0)) // not strictly necessary
+ .withStartAmount(1 ether)
+ .withEndAmount(1 ether)
+ .withIdentifierOrCriteria(0)
+ .saveDefault(ONE_ETH); // not strictly necessary
+
+ // create a default offerItem for a single 721;
+ // note that it does not have token or identifier set
+ OfferItemLib
+ .empty()
+ .withItemType(ItemType.ERC721)
+ .withStartAmount(1)
+ .withEndAmount(1)
+ .saveDefault(SINGLE_721);
+
+ OrderComponentsLib
+ .empty()
+ .withOfferer(offerer1.addr)
+ .withZone(address(zone))
+ // fill in offer later
+ // fill in consideration later
+ .withOrderType(OrderType.FULL_RESTRICTED)
+ .withStartTime(block.timestamp)
+ .withEndTime(block.timestamp + 1)
+ .withZoneHash(bytes32(0)) // not strictly necessary
+ .withSalt(0)
+ .withConduitKey(conduitKeyOne)
+ .saveDefault(VALIDATION_ZONE); // not strictly necessary
+ // fill in counter later
+
+ // create a default fulfillmentComponent for first_first
+ // corresponds to first offer or consideration item in the first order
+ FulfillmentComponent memory firstFirst = FulfillmentComponentLib
+ .empty()
+ .withOrderIndex(0)
+ .withItemIndex(0)
+ .saveDefault(FIRST_FIRST);
+ // create a default fulfillmentComponent for second_first
+ // corresponds to first offer or consideration item in the second order
+ FulfillmentComponent memory secondFirst = FulfillmentComponentLib
+ .empty()
+ .withOrderIndex(1)
+ .withItemIndex(0)
+ .saveDefault(SECOND_FIRST);
+
+ // create a one-element array comtaining first_first
+ SeaportArrays.FulfillmentComponents(firstFirst).saveDefaultMany(
+ FIRST_FIRST
+ );
+ // create a one-element array comtaining second_first
+ SeaportArrays.FulfillmentComponents(secondFirst).saveDefaultMany(
+ SECOND_FIRST
+ );
+
+ // create a two-element array comtaining first_first and second_first
+ SeaportArrays
+ .FulfillmentComponents(firstFirst, secondFirst)
+ .saveDefaultMany(FIRST_SECOND__FIRST);
+ }
+
+ struct Context {
+ ConsiderationInterface seaport;
+ }
+
+ function test(
+ function(Context memory) external fn,
+ Context memory context
+ ) internal {
+ try fn(context) {
+ fail("Expected revert");
+ } catch (bytes memory reason) {
+ assertPass(reason);
+ }
+ }
+
+ function testAggregate() public {
+ prepareAggregate();
+
+ test(this.execAggregate, Context({ seaport: consideration }));
+ test(this.execAggregate, Context({ seaport: referenceConsideration }));
+ }
+
+ ///@dev prepare aggregate test by minting tokens to offerer1
+ function prepareAggregate() internal {
+ test721_1.mint(offerer1.addr, 1);
+ test721_2.mint(offerer1.addr, 1);
+ }
+
+ function execAggregate(Context memory context) external stateless {
+ (
+ Order[] memory orders,
+ FulfillmentComponent[][] memory offerFulfillments,
+ FulfillmentComponent[][] memory considerationFulfillments,
+ bytes32 conduitKey,
+ uint256 numOrders
+ ) = _buildFulfillmentData(context);
+
+ context.seaport.fulfillAvailableOrders{ value: 2 ether }({
+ orders: orders,
+ offerFulfillments: offerFulfillments,
+ considerationFulfillments: considerationFulfillments,
+ fulfillerConduitKey: conduitKey,
+ maximumFulfilled: numOrders
+ });
+ }
+
+ ///@dev build multiple orders from the same offerer
+ function _buildOrders(
+ Context memory context,
+ OrderComponents[] memory orderComponents,
+ uint256 key
+ ) internal view returns (Order[] memory) {
+ Order[] memory orders = new Order[](orderComponents.length);
+ for (uint256 i = 0; i < orderComponents.length; i++) {
+ orders[i] = toOrder(context.seaport, orderComponents[i], key);
+ }
+ return orders;
+ }
+
+ function _buildFulfillmentData(
+ Context memory context
+ )
+ internal
+ view
+ returns (
+ Order[] memory,
+ FulfillmentComponent[][] memory,
+ FulfillmentComponent[][] memory,
+ bytes32,
+ uint256
+ )
+ {
+ ConsiderationItem[] memory considerationArray = SeaportArrays
+ .ConsiderationItems(
+ ConsiderationItemLib.fromDefault(ONE_ETH).withRecipient(
+ offerer1.addr
+ )
+ );
+ OfferItem[] memory offerArray = SeaportArrays.OfferItems(
+ OfferItemLib
+ .fromDefault(SINGLE_721)
+ .withToken(address(test721_1))
+ .withIdentifierOrCriteria(1)
+ );
+ // build first order components
+ OrderComponents memory orderComponents = OrderComponentsLib
+ .fromDefault(VALIDATION_ZONE)
+ .withOffer(offerArray)
+ .withConsideration(considerationArray)
+ .withCounter(context.seaport.getCounter(offerer1.addr));
+
+ // second order components only differs by what is offered
+ offerArray = SeaportArrays.OfferItems(
+ OfferItemLib
+ .fromDefault(SINGLE_721)
+ .withToken(address(test721_2))
+ .withIdentifierOrCriteria(1)
+ );
+ // technically we do not need to copy() since first order components is
+ // not used again, but to encourage good practices, make a copy and
+ // edit that
+ OrderComponents memory orderComponents2 = orderComponents
+ .copy()
+ .withOffer(offerArray);
+
+ Order[] memory orders = _buildOrders(
+ context,
+ SeaportArrays.OrderComponentsArray(
+ orderComponents,
+ orderComponents2
+ ),
+ offerer1.key
+ );
+
+ // create fulfillments
+ // offer fulfillments cannot be aggregated (cannot batch transfer 721s) so there will be one array per order
+ FulfillmentComponent[][] memory offerFulfillments = SeaportArrays
+ .FulfillmentComponentArrays(
+ // first FulfillmentComponents[] is single FulfillmentComponent for test721_1 id 1
+ FulfillmentComponentLib.fromDefaultMany(FIRST_FIRST),
+ // second FulfillmentComponents[] is single FulfillmentComponent for test721_2 id 1
+ FulfillmentComponentLib.fromDefaultMany(SECOND_FIRST)
+ );
+ // consideration fulfillments can be aggregated (can batch transfer eth) so there will be one array for both orders
+ FulfillmentComponent[][]
+ memory considerationFulfillments = SeaportArrays
+ .FulfillmentComponentArrays(
+ // two-element fulfillmentcomponents array, one for each order
+ FulfillmentComponentLib.fromDefaultMany(FIRST_SECOND__FIRST)
+ );
+
+ return (
+ orders,
+ offerFulfillments,
+ considerationFulfillments,
+ conduitKeyOne,
+ 2
+ );
+ }
+
+ function toOrder(
+ ConsiderationInterface seaport,
+ OrderComponents memory orderComponents,
+ uint256 pkey
+ ) internal view returns (Order memory order) {
+ bytes32 orderHash = seaport.getOrderHash(orderComponents);
+ bytes memory signature = signOrder(seaport, pkey, orderHash);
+ order = OrderLib
+ .empty()
+ .withParameters(orderComponents.toOrderParameters())
+ .withSignature(signature);
+ }
+}
diff --git a/test/router.spec.ts b/test/router.spec.ts
new file mode 100644
index 000000000..0a3ad77b9
--- /dev/null
+++ b/test/router.spec.ts
@@ -0,0 +1,679 @@
+import { expect } from "chai";
+import { parseEther } from "ethers/lib/utils";
+import { ethers, network } from "hardhat";
+
+import { deployContract } from "./utils/contracts";
+import { getItemETH, randomHex, toKey } from "./utils/encoding";
+import { faucet } from "./utils/faucet";
+import { seaportFixture } from "./utils/fixtures";
+import { VERSION } from "./utils/helpers";
+
+import type {
+ ConduitControllerInterface,
+ ConduitInterface,
+ ConsiderationInterface,
+ Reenterer,
+ SeaportRouter,
+ TestERC721,
+} from "../typechain-types";
+import type { SeaportFixtures } from "./utils/fixtures";
+import type { Wallet } from "ethers";
+
+describe(`SeaportRouter tests (Seaport v${VERSION})`, function () {
+ if (process.env.REFERENCE) return;
+
+ const { provider } = ethers;
+ const owner = new ethers.Wallet(randomHex(32), provider);
+
+ let conduitController: ConduitControllerInterface;
+ let conduitKeyOne: string;
+ let conduitOne: ConduitInterface;
+ let marketplaceContract: ConsiderationInterface;
+ let marketplaceContract2: ConsiderationInterface;
+ let reenterer: Reenterer;
+
+ let createOrder: SeaportFixtures["createOrder"];
+ let getTestItem721: SeaportFixtures["getTestItem721"];
+ let mintAndApprove721: SeaportFixtures["mintAndApprove721"];
+ let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"];
+ let testERC721: TestERC721;
+
+ after(async () => {
+ await network.provider.request({
+ method: "hardhat_reset",
+ });
+ });
+
+ before(async () => {
+ await faucet(owner.address, provider);
+
+ ({
+ conduitController,
+ conduitKeyOne,
+ conduitOne,
+ createOrder,
+ getTestItem721,
+ marketplaceContract,
+ mintAndApprove721,
+ reenterer,
+ set721ApprovalForAll,
+ testERC721,
+ } = await seaportFixture(owner));
+
+ marketplaceContract2 = await deployContract(
+ "Seaport",
+ owner,
+ conduitController.address
+ );
+ });
+
+ let buyer: Wallet;
+ let seller: Wallet;
+ let zone: Wallet;
+
+ let router: SeaportRouter;
+
+ beforeEach(async () => {
+ // Setup basic buyer/seller wallets with ETH
+ buyer = new ethers.Wallet(randomHex(32), provider);
+ seller = new ethers.Wallet(randomHex(32), provider);
+ zone = new ethers.Wallet(randomHex(32), provider);
+
+ for (const wallet of [buyer, seller, zone, reenterer]) {
+ await faucet(wallet.address, provider);
+ }
+
+ router = await deployContract(
+ "SeaportRouter",
+ owner,
+ marketplaceContract.address,
+ marketplaceContract2.address
+ );
+ });
+
+ describe("fulfillAvailableAdvancedOrders", async () => {
+ it("Should return the allowed Seaport contracts usable through the router", async () => {
+ expect(marketplaceContract.address).to.not.equal(
+ marketplaceContract2.address
+ );
+ expect(await router.getAllowedSeaportContracts()).to.deep.equal([
+ marketplaceContract.address,
+ marketplaceContract2.address,
+ ]);
+ });
+ it("Should be able to fulfill orders through a single Seaport contract", async () => {
+ // Seller mints nft
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [marketplaceContract.address],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: buyer.address,
+ maximumFulfilled: 100,
+ };
+
+ // Expect trying to fulfill through a non-allowed contract to fail
+ await expect(
+ router.connect(buyer).fulfillAvailableAdvancedOrders(
+ { ...params, seaportContracts: [testERC721.address] },
+ {
+ value,
+ }
+ )
+ )
+ .to.be.revertedWithCustomError(router, "SeaportNotAllowed")
+ .withArgs(testERC721.address);
+
+ // Execute order
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(params, {
+ value,
+ });
+
+ // Ensure buyer now owns the nft
+ expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address);
+ });
+ it("Should be able to fulfill orders through multiple Seaport contracts", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+ const nftId2 = await mintAndApprove721(
+ seller,
+ marketplaceContract2.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+ const offer2 = [getTestItem721(nftId2)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+ const { order: order2 } = await createOrder(
+ seller,
+ zone,
+ offer2,
+ consideration,
+ 0, // FULL_OPEN
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ marketplaceContract2
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [
+ marketplaceContract.address,
+ marketplaceContract2.address,
+ ],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ {
+ advancedOrders: [order2],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: owner.address,
+ maximumFulfilled: 100,
+ };
+
+ // Execute orders
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(params, {
+ value: value.mul(2),
+ });
+
+ // Ensure the recipient (owner) now owns both nfts
+ expect(await testERC721.ownerOf(nftId)).to.equal(owner.address);
+ expect(await testERC721.ownerOf(nftId2)).to.equal(owner.address);
+ });
+ it("Should respect maximumFulfilled across multiple Seaport contracts", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+ const nftId2 = await mintAndApprove721(
+ seller,
+ marketplaceContract2.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+ const offer2 = [getTestItem721(nftId2)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+ const { order: order2 } = await createOrder(
+ seller,
+ zone,
+ offer2,
+ consideration,
+ 0, // FULL_OPEN
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ marketplaceContract2
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [
+ marketplaceContract.address,
+ marketplaceContract2.address,
+ ],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ {
+ advancedOrders: [order2],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: owner.address,
+ maximumFulfilled: 1,
+ };
+
+ // Execute orders
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(params, {
+ value: value.mul(2),
+ });
+
+ // Ensure the recipient (owner) now owns only the first NFT (maximumFulfilled=1)
+ expect(await testERC721.ownerOf(nftId)).to.equal(owner.address);
+ expect(await testERC721.ownerOf(nftId2)).to.equal(seller.address);
+ });
+ it("Should be able to fulfill orders through multiple Seaport contracts using conduit", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+ const nftId2 = await mintAndApprove721(
+ seller,
+ marketplaceContract2.address
+ );
+ // Seller approves conduit contract to transfer NFTs
+ await set721ApprovalForAll(seller, conduitOne.address, true);
+
+ const offer = [getTestItem721(nftId)];
+ const offer2 = [getTestItem721(nftId2)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+ const { order: order2 } = await createOrder(
+ seller,
+ zone,
+ offer2,
+ consideration,
+ 0, // FULL_OPEN
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ marketplaceContract2
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [
+ marketplaceContract.address,
+ marketplaceContract2.address,
+ ],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ {
+ advancedOrders: [order2],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: conduitKeyOne,
+ recipient: owner.address,
+ maximumFulfilled: 100,
+ };
+
+ // Execute orders
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(params, {
+ value: value.mul(2),
+ });
+
+ // Ensure the recipient (owner) now owns both nfts
+ expect(await testERC721.ownerOf(nftId)).to.equal(owner.address);
+ expect(await testERC721.ownerOf(nftId2)).to.equal(owner.address);
+ });
+ it("Should process valid orders while skipping invalid orders", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+ const nftId2 = await mintAndApprove721(
+ seller,
+ marketplaceContract2.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+ const offer2 = [getTestItem721(nftId2)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+ const { order: order2 } = await createOrder(
+ seller,
+ zone,
+ offer2,
+ consideration,
+ 0, // FULL_OPEN
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ marketplaceContract2
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [
+ marketplaceContract.address,
+ marketplaceContract2.address,
+ ],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ {
+ advancedOrders: [order2],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: buyer.address,
+ maximumFulfilled: 100,
+ };
+
+ const buyerEthBalanceBefore = await provider.getBalance(buyer.address);
+
+ // Execute the first order so it is fulfilled thus invalid for the next call
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(
+ {
+ ...params,
+ seaportContracts: params.seaportContracts.slice(0, 1),
+ advancedOrderParams: params.advancedOrderParams.slice(0, 1),
+ },
+ {
+ value,
+ }
+ );
+
+ // Execute orders
+ await router.connect(buyer).fulfillAvailableAdvancedOrders(params, {
+ value: value.mul(2),
+ });
+
+ // Ensure the recipient (buyer) owns both nfts
+ expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address);
+ expect(await testERC721.ownerOf(nftId2)).to.equal(buyer.address);
+
+ // Ensure the excess eth was returned
+ const buyerEthBalanceAfter = await provider.getBalance(buyer.address);
+ expect(buyerEthBalanceBefore).to.be.gt(
+ buyerEthBalanceAfter.sub(value.mul(3))
+ );
+ });
+ it("Should revert if cannot return excess ether value", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [marketplaceContract.address],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: buyer.address,
+ maximumFulfilled: 100,
+ };
+
+ // prepare the reentrant call on the reenterer
+ const tx = await reenterer.prepare(testERC721.address, 0, []);
+ await tx.wait();
+
+ // Execute orders
+ const callData = router.interface.encodeFunctionData(
+ "fulfillAvailableAdvancedOrders",
+ [params]
+ );
+ await expect(reenterer.execute(router.address, value.mul(2), callData))
+ .to.be.revertedWithCustomError(router, "EtherReturnTransferFailed")
+ .withArgs(reenterer.address, value, "0x");
+ });
+ it("Should not be able to reenter through receive()", async () => {
+ // Seller mints nfts
+ const nftId = await mintAndApprove721(
+ seller,
+ marketplaceContract.address
+ );
+
+ const offer = [getTestItem721(nftId)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), zone.address),
+ ];
+
+ const { order, value } = await createOrder(
+ seller,
+ zone,
+ offer,
+ consideration,
+ 0 // FULL_OPEN
+ );
+
+ const offerComponents = [[{ orderIndex: 0, itemIndex: 0 }]];
+ const considerationComponents = [
+ [{ orderIndex: 0, itemIndex: 0 }],
+ [{ orderIndex: 0, itemIndex: 1 }],
+ ];
+
+ const params = {
+ seaportContracts: [marketplaceContract.address],
+ advancedOrderParams: [
+ {
+ advancedOrders: [order],
+ criteriaResolvers: [],
+ offerFulfillments: offerComponents,
+ considerationFulfillments: considerationComponents,
+ etherValue: value,
+ },
+ ],
+ fulfillerConduitKey: toKey(0),
+ recipient: buyer.address,
+ maximumFulfilled: 100,
+ };
+
+ // prepare the reentrant call on the reenterer
+ const callData = router.interface.encodeFunctionData(
+ "fulfillAvailableAdvancedOrders",
+ [params]
+ );
+ const tx = await reenterer.prepare(router.address, 0, callData);
+ await tx.wait();
+
+ // Execute orders
+ await expect(reenterer.execute(router.address, value.mul(2), callData))
+ .to.be.revertedWithCustomError(router, "EtherReturnTransferFailed")
+ .withArgs(
+ reenterer.address,
+ value,
+ marketplaceContract.interface.getSighash("NoReentrantCalls")
+ );
+ });
+ it("Should not be able to receive ether from a non-Seaport address", async () => {
+ // Test receive(), which is triggered when sent eth with no data
+ const txTriggerReceive = await owner.signTransaction({
+ to: router.address,
+ value: 1,
+ nonce: await owner.getTransactionCount(),
+ gasPrice: await provider.getGasPrice(),
+ gasLimit: 50_000,
+ });
+ await expect(provider.sendTransaction(txTriggerReceive))
+ .to.be.revertedWithCustomError(router, "SeaportNotAllowed")
+ .withArgs(owner.address);
+
+ // Test receive() impersonating as Seaport
+ await network.provider.request({
+ method: "hardhat_impersonateAccount",
+ params: [marketplaceContract.address],
+ });
+
+ const seaportSigner = await ethers.getSigner(marketplaceContract.address);
+ await faucet(marketplaceContract.address, provider);
+
+ await seaportSigner.sendTransaction({ to: router.address, value: 1 });
+ expect((await provider.getBalance(router.address)).toNumber()).to.eq(1);
+
+ await network.provider.request({
+ method: "hardhat_stopImpersonatingAccount",
+ params: [marketplaceContract.address],
+ });
+ });
+ });
+});
diff --git a/test/utils/fixtures/index.ts b/test/utils/fixtures/index.ts
index 8c0691303..21b3bec5b 100644
--- a/test/utils/fixtures/index.ts
+++ b/test/utils/fixtures/index.ts
@@ -683,7 +683,10 @@ export const seaportFixture = async (owner: Wallet) => {
(x) => x.address === offerItem.token
);
- if (offer.itemType === 1) {
+ if (
+ offer.itemType === 1 &&
+ standardExecutions.map((x) => x.item.itemType).includes(1)
+ ) {
// ERC20
// search for transfer
const transferLogs = (tokenEvents ?? [])
@@ -702,7 +705,10 @@ export const seaportFixture = async (owner: Wallet) => {
// TODO: check each transferred amount
// for (const transferLog of transferLogs) {
// }
- } else if (offer.itemType === 2) {
+ } else if (
+ offer.itemType === 2 &&
+ standardExecutions.map((x) => x.item.itemType).includes(2)
+ ) {
// ERC721
// search for transfer
const transferLogs = (tokenEvents ?? [])
@@ -722,7 +728,10 @@ export const seaportFixture = async (owner: Wallet) => {
expect(transferLog.args.id.toString()).to.equal(
offer.identifier.toString()
);
- } else if (offer.itemType === 3) {
+ } else if (
+ offer.itemType === 3 &&
+ standardExecutions.map((x) => x.item.itemType).includes(3)
+ ) {
// search for transfer
const transferLogs = (tokenEvents ?? [])
.map((x) => testERC1155.interface.parseLog(x))
@@ -780,7 +789,10 @@ export const seaportFixture = async (owner: Wallet) => {
(x) => x.address === considerationItem.token
);
- if (consideration.itemType === 1) {
+ if (
+ consideration.itemType === 1 &&
+ standardExecutions.map((x) => x.item.itemType).includes(1)
+ ) {
// ERC20
// search for transfer
const transferLogs = (tokenEvents ?? [])
@@ -795,7 +807,10 @@ export const seaportFixture = async (owner: Wallet) => {
// TODO: check each transferred amount
// for (const transferLog of transferLogs) {
// }
- } else if (consideration.itemType === 2) {
+ } else if (
+ consideration.itemType === 2 &&
+ standardExecutions.map((x) => x.item.itemType).includes(2)
+ ) {
// ERC721
// search for transfer
const transferLogs = (tokenEvents ?? [])
@@ -811,7 +826,10 @@ export const seaportFixture = async (owner: Wallet) => {
expect(transferLog.args.id.toString()).to.equal(
consideration.identifier.toString()
);
- } else if (consideration.itemType === 3) {
+ } else if (
+ consideration.itemType === 3 &&
+ standardExecutions.map((x) => x.item.itemType).includes(3)
+ ) {
// search for transfer
const transferLogs = (tokenEvents ?? [])
.map((x) => testERC1155.interface.parseLog(x))
diff --git a/test/utils/fixtures/marketplace.ts b/test/utils/fixtures/marketplace.ts
index bea064ab9..fe137c93d 100644
--- a/test/utils/fixtures/marketplace.ts
+++ b/test/utils/fixtures/marketplace.ts
@@ -124,17 +124,18 @@ export const marketplaceFixture = async (
// Returns signature
const signOrder = async (
orderComponents: OrderComponents,
- signer: Wallet | Contract
+ signer: Wallet | Contract,
+ marketplace = marketplaceContract
) => {
const signature = await signer._signTypedData(
- domainData,
+ { ...domainData, verifyingContract: marketplace.address },
orderType,
orderComponents
);
const orderHash = await getAndVerifyOrderHash(orderComponents);
- const { domainSeparator } = await marketplaceContract.information();
+ const { domainSeparator } = await marketplace.information();
const digest = keccak256(
`0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}`
);
@@ -215,9 +216,10 @@ export const marketplaceFixture = async (
extraCheap = false,
useBulkSignature = false,
bulkSignatureIndex?: number,
- bulkSignatureHeight?: number
+ bulkSignatureHeight?: number,
+ marketplace = marketplaceContract
) => {
- const counter = await marketplaceContract.getCounter(offerer.address);
+ const counter = await marketplace.getCounter(offerer.address);
const salt = !extraCheap ? randomHex() : constants.HashZero;
const startTime =
@@ -249,7 +251,7 @@ export const marketplaceFixture = async (
const orderHash = await getAndVerifyOrderHash(orderComponents);
const { isValidated, isCancelled, totalFilled, totalSize } =
- await marketplaceContract.getOrderStatus(orderHash);
+ await marketplace.getOrderStatus(orderHash);
expect(isCancelled).to.equal(false);
@@ -260,7 +262,11 @@ export const marketplaceFixture = async (
totalSize,
};
- const flatSig = await signOrder(orderComponents, signer ?? offerer);
+ const flatSig = await signOrder(
+ orderComponents,
+ signer ?? offerer,
+ marketplace
+ );
const order = {
parameters: orderParameters,
diff --git a/test/utils/helpers.ts b/test/utils/helpers.ts
index 8cd0ac946..ba393ada2 100644
--- a/test/utils/helpers.ts
+++ b/test/utils/helpers.ts
@@ -9,7 +9,7 @@ import type {
Order,
} from "./types";
-export const VERSION = `1.2${process.env.REFERENCE ? "-reference" : ""}`;
+export const VERSION = `1.3${process.env.REFERENCE ? "-reference" : ""}`;
export const minRandom = (min: ethers.BigNumberish) => randomBN(10).add(min);
diff --git a/test/zone.spec.ts b/test/zone.spec.ts
index dddd1b648..c58fc2c63 100644
--- a/test/zone.spec.ts
+++ b/test/zone.spec.ts
@@ -10,6 +10,7 @@ import {
toAddress,
toBN,
toFulfillment,
+ toFulfillmentComponents,
toKey,
} from "./utils/encoding";
import { decodeEvents } from "./utils/events";
@@ -18,6 +19,7 @@ import { seaportFixture } from "./utils/fixtures";
import { VERSION } from "./utils/helpers";
import type {
+ ConduitInterface,
ConsiderationInterface,
TestERC721,
TestZone,
@@ -1002,3 +1004,386 @@ describe(`Zone - PausableZone (Seaport v${VERSION})`, function () {
expect(await pausableZoneController.owner()).to.equal(buyer.address);
});
});
+
+describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () {
+ const { provider } = ethers;
+ const owner = new ethers.Wallet(randomHex(32), provider);
+
+ let marketplaceContract: ConsiderationInterface;
+ let conduitKeyOne: string;
+ let conduitOne: ConduitInterface;
+
+ let checkExpectedEvents: SeaportFixtures["checkExpectedEvents"];
+ let createOrder: SeaportFixtures["createOrder"];
+ let getTestItem721: SeaportFixtures["getTestItem721"];
+ let getTestItem1155: SeaportFixtures["getTestItem1155"];
+ let getTestItem721WithCriteria: SeaportFixtures["getTestItem721WithCriteria"];
+ let mintAndApprove721: SeaportFixtures["mintAndApprove721"];
+ let mintAndApprove1155: SeaportFixtures["mintAndApprove1155"];
+ let withBalanceChecks: SeaportFixtures["withBalanceChecks"];
+
+ after(async () => {
+ await network.provider.request({
+ method: "hardhat_reset",
+ });
+ });
+
+ before(async () => {
+ await faucet(owner.address, provider);
+
+ ({
+ checkExpectedEvents,
+ conduitKeyOne,
+ conduitOne,
+ createOrder,
+ getTestItem721,
+ getTestItem1155,
+ getTestItem721WithCriteria,
+ marketplaceContract,
+ mintAndApprove721,
+ mintAndApprove1155,
+ withBalanceChecks,
+ } = await seaportFixture(owner));
+ });
+
+ let buyer: Wallet;
+ let seller: Wallet;
+
+ async function setupFixture() {
+ // Setup basic buyer/seller wallets with ETH
+ const seller = new ethers.Wallet(randomHex(32), provider);
+ const buyer = new ethers.Wallet(randomHex(32), provider);
+
+ for (const wallet of [seller, buyer]) {
+ await faucet(wallet.address, provider);
+ }
+
+ return { seller, buyer };
+ }
+
+ beforeEach(async () => {
+ ({ seller, buyer } = await loadFixture(setupFixture));
+ });
+
+ it("Fulfills an order with a transfer validation zone", async () => {
+ // execute basic 721 <=> ETH order
+ const nftId = await mintAndApprove721(seller, marketplaceContract.address);
+
+ const offer = [getTestItem721(nftId)];
+
+ const TransferValidationZoneOffererFactory =
+ await ethers.getContractFactory(
+ "TestTransferValidationZoneOfferer",
+ owner
+ );
+
+ const zoneAddr = await TransferValidationZoneOffererFactory.deploy();
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), owner.address),
+ ];
+
+ const { order, orderHash, value } = await createOrder(
+ seller,
+ zoneAddr,
+ offer,
+ consideration,
+ 2 // FULL_RESTRICTED
+ );
+
+ await withBalanceChecks([order], 0, undefined, async () => {
+ const tx = await marketplaceContract
+ .connect(buyer)
+ .fulfillOrder(order, toKey(0), {
+ value,
+ });
+
+ const receipt = await tx.wait();
+ await checkExpectedEvents(tx, receipt, [
+ {
+ order,
+ orderHash,
+ fulfiller: buyer.address,
+ fulfillerConduitKey: toKey(0),
+ },
+ ]);
+ return receipt;
+ });
+ });
+
+ it("Fulfills an advanced order with criteria with the transfer validation zone", async () => {
+ // execute basic 721 <=> ETH order
+ const nftId = await mintAndApprove721(seller, marketplaceContract.address);
+
+ const { root, proofs } = merkleTree([nftId]);
+
+ const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), owner.address),
+ ];
+
+ const criteriaResolvers = [
+ buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]),
+ ];
+
+ const TransferValidationZoneOffererFactory =
+ await ethers.getContractFactory(
+ "TestTransferValidationZoneOfferer",
+ owner
+ );
+
+ const zoneAddr = await TransferValidationZoneOffererFactory.deploy();
+
+ const { order, orderHash, value } = await createOrder(
+ seller,
+ zoneAddr,
+ offer,
+ consideration,
+ 2, // FULL_RESTRICTED
+ criteriaResolvers
+ );
+
+ await withBalanceChecks([order], 0, criteriaResolvers, async () => {
+ const tx = await marketplaceContract
+ .connect(buyer)
+ .fulfillAdvancedOrder(
+ order,
+ criteriaResolvers,
+ toKey(0),
+ ethers.constants.AddressZero,
+ {
+ value,
+ }
+ );
+
+ const receipt = await tx.wait();
+ await checkExpectedEvents(
+ tx,
+ receipt,
+ [
+ {
+ order,
+ orderHash,
+ fulfiller: buyer.address,
+ fulfillerConduitKey: toKey(0),
+ },
+ ],
+ undefined,
+ criteriaResolvers
+ );
+ return receipt;
+ });
+ });
+
+ it("Fulfills a PARTIAL_RESTRICTED order with the caller being the offerer through the transfer validation zone", async () => {
+ // execute basic 721 <=> ETH order
+ const nftId = await mintAndApprove721(seller, marketplaceContract.address);
+
+ const offer = [getTestItem721(nftId)];
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), owner.address),
+ ];
+
+ const TransferValidationZoneOffererFactory =
+ await ethers.getContractFactory(
+ "TestTransferValidationZoneOfferer",
+ owner
+ );
+
+ const zoneAddr = await TransferValidationZoneOffererFactory.deploy();
+
+ const { order, orderHash, value } = await createOrder(
+ seller,
+ zoneAddr,
+ offer,
+ consideration,
+ 3 // PARTIAL_RESTRICTED
+ );
+
+ await withBalanceChecks([order], 0, undefined, async () => {
+ const tx = await marketplaceContract
+ .connect(buyer)
+ .fulfillAdvancedOrder(
+ order,
+ [],
+ toKey(0),
+ ethers.constants.AddressZero,
+ {
+ value,
+ }
+ );
+
+ const receipt = await tx.wait();
+ await checkExpectedEvents(tx, receipt, [
+ {
+ order,
+ orderHash,
+ fulfiller: buyer.address,
+ fulfillerConduitKey: toKey(0),
+ },
+ ]);
+ return receipt;
+ });
+ });
+
+ it("Reverts on fulfill and aggregate multiple orders (ERC-1155) via fulfillAvailableAdvancedOrders (via conduit) with balance checking on validation zone (1.2 Issue - resolved in 1.3)", async () => {
+ // Seller mints nft
+ const { nftId, amount } = await mintAndApprove1155(
+ seller,
+ conduitOne.address,
+ 1,
+ 1,
+ 10000
+ );
+
+ const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))];
+
+ const noZoneAddr = new ethers.Wallet(randomHex(32), provider);
+
+ const consideration = [
+ getItemETH(parseEther("10"), parseEther("10"), seller.address),
+ getItemETH(parseEther("1"), parseEther("1"), noZoneAddr.address),
+ getItemETH(parseEther("1"), parseEther("1"), owner.address),
+ ];
+
+ const TransferValidationZoneOffererFactory =
+ await ethers.getContractFactory(
+ "TestTransferValidationZoneOfferer",
+ owner
+ );
+
+ const transferValidationZone =
+ await TransferValidationZoneOffererFactory.deploy();
+
+ const {
+ order: orderOne,
+ orderHash: orderHashOne,
+ value,
+ } = await createOrder(
+ seller,
+ transferValidationZone,
+ offer,
+ consideration,
+ 2, // FULL_RESTRICTED
+ [],
+ null,
+ seller,
+ undefined,
+ conduitKeyOne
+ );
+
+ const { order: orderTwo, orderHash: orderHashTwo } = await createOrder(
+ seller,
+ noZoneAddr,
+ offer,
+ consideration,
+ 0, // FULL_OPEN
+ [],
+ null,
+ seller,
+ undefined,
+ conduitKeyOne
+ );
+
+ // test orderHashes
+ orderOne.extraData = ethers.utils.defaultAbiCoder.encode(
+ ["bytes32[]"],
+ [[orderHashOne, orderHashTwo]]
+ );
+
+ expect((orderOne.extraData.length - 2) / 64).to.equal(4);
+
+ const offerComponents = [
+ toFulfillmentComponents([
+ [0, 0],
+ [1, 0],
+ ]),
+ ];
+
+ const considerationComponents = [
+ [
+ [0, 0],
+ [1, 0],
+ ],
+ [
+ [0, 1],
+ [1, 1],
+ ],
+ [
+ [0, 2],
+ [1, 2],
+ ],
+ ].map(toFulfillmentComponents);
+
+ // 1.2 Issue - resolved in 1.3
+ if (VERSION === "1.2") {
+ await expect(
+ marketplaceContract
+ .connect(buyer)
+ .fulfillAvailableAdvancedOrders(
+ [orderOne, orderTwo],
+ [],
+ offerComponents,
+ considerationComponents,
+ toKey(0),
+ ethers.constants.AddressZero,
+ 100,
+ {
+ value: value.mul(2),
+ }
+ )
+ ).to.be.revertedWithCustomError(transferValidationZone, "InvalidBalance");
+ } else {
+ // This should pass in 1.3
+ await withBalanceChecks(
+ [orderOne, orderTwo],
+ 0,
+ undefined,
+ async () => {
+ const tx = marketplaceContract
+ .connect(buyer)
+ .fulfillAvailableAdvancedOrders(
+ [orderOne, orderTwo],
+ [],
+ offerComponents,
+ considerationComponents,
+ toKey(0),
+ ethers.constants.AddressZero,
+ 100,
+ {
+ value: value.mul(2),
+ }
+ );
+ const receipt = await (await tx).wait();
+ await checkExpectedEvents(
+ tx,
+ receipt,
+ [
+ {
+ order: orderOne,
+ orderHash: orderHashOne,
+ fulfiller: buyer.address,
+ },
+ {
+ order: orderTwo,
+ orderHash: orderHashTwo,
+ fulfiller: buyer.address,
+ },
+ ],
+ [],
+ [],
+ false,
+ 2
+ );
+ return receipt;
+ },
+ 2
+ );
+ }
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index b72a4c3d1..2214e8bb2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3488,9 +3488,9 @@ http-basic@^8.1.1:
parse-cache-control "^1.0.1"
http-cache-semantics@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
- integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
http-errors@2.0.0:
version "2.0.0"
|