From 5a34035793e72f5fe982f2625f0d40e7b69a8450 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:26:45 -0800 Subject: [PATCH 01/57] Update .solcover.js --- config/.solcover.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/.solcover.js b/config/.solcover.js index 2a8e2f2fd..b4be68a67 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", From ba49fa604347ce63c481617dc8e0d45c8dda5834 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 1 Feb 2023 21:55:22 -0800 Subject: [PATCH 02/57] readme: update deployed addresses deployed 1.2 across all chains --- README.md | 59 +++++++++++++++---------------------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 039476b01..0a0ad5e91 100644 --- a/README.md +++ b/README.md @@ -79,20 +79,6 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts [0x00000000F9490004C11Cef243f5400493c00Ad63](https://etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) - - -Rinkeby - -[0x00000000006c3852cbEf3e08E8dF289169EdE581](https://rinkeby.etherscan.io/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) - - - -- - - - -[0x00000000F9490004C11Cef243f5400493c00Ad63](https://rinkeby.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) - Goerli @@ -100,25 +86,12 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://goerli.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) [0x00000000F9490004C11Cef243f5400493c00Ad63](https://goerli.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) - -Kovan - -[0x00000000006c3852cbEf3e08E8dF289169EdE581](https://kovan.etherscan/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) - - - -- - - - -[0x00000000F9490004C11Cef243f5400493c00Ad63](https://kovan.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) - Sepolia @@ -126,7 +99,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://sepolia.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -139,7 +112,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://polygonscan.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -152,7 +125,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://mumbai.polygonscan.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -165,7 +138,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://scope.klaytn.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -178,7 +151,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://baobab.scope.klaytn.com/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -191,7 +164,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://optimistic.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -204,7 +177,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://goerli-optimism.etherscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -217,7 +190,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -230,7 +203,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://goerli.arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -243,7 +216,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://nova.arbiscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -256,7 +229,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://snowtrace.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -269,7 +242,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://testnet.snowtrace.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -282,7 +255,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://gnosisscan.io/address/0x00000000000006c7676171937C444f6BDe3D6282#code) @@ -295,7 +268,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://bscscan.com/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) @@ -308,7 +281,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts -- +[0x00000000000006c7676171937C444f6BDe3D6282](https://testnet.bscscan.com/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) From ee295a3e292f3151a40844aea3adbdede08c4ca5 Mon Sep 17 00:00:00 2001 From: djviau Date: Thu, 2 Feb 2023 12:04:53 -0500 Subject: [PATCH 03/57] add a test for index overflow with trees of different heights and refactor a bit --- test/foundry/BulkSignature.t.sol | 172 +++++++++++++++++++++--- test/foundry/utils/EIP712MerkleTree.sol | 130 +++++++++--------- 2 files changed, 222 insertions(+), 80 deletions(-) diff --git a/test/foundry/BulkSignature.t.sol b/test/foundry/BulkSignature.t.sol index 84459d669..46ec692fd 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,136 @@ 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 + tree height ** 2. + uint256 index = context.args.orderIndex + (context.args.height ** 2); + 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 + }) + ); + } } diff --git a/test/foundry/utils/EIP712MerkleTree.sol b/test/foundry/utils/EIP712MerkleTree.sol index 40f7dcbab..ff1653549 100644 --- a/test/foundry/utils/EIP712MerkleTree.sol +++ b/test/foundry/utils/EIP712MerkleTree.sol @@ -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); + } } From 9e145c0d1a11f32ddaabe5e2b7343e6606bee5ed Mon Sep 17 00:00:00 2001 From: djviau Date: Thu, 2 Feb 2023 12:45:28 -0500 Subject: [PATCH 04/57] fix stupid stupid bug --- test/foundry/BulkSignature.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/BulkSignature.t.sol b/test/foundry/BulkSignature.t.sol index 46ec692fd..324919a04 100644 --- a/test/foundry/BulkSignature.t.sol +++ b/test/foundry/BulkSignature.t.sol @@ -459,7 +459,7 @@ contract BulkSignatureTest is BaseOrderTest { // length. uint256 signatureLength = context.useCompact2098 ? 64 : 65; // Set up an index equal to orderIndex + tree height ** 2. - uint256 index = context.args.orderIndex + (context.args.height ** 2); + uint256 index = context.args.orderIndex + (2 ** context.args.height); uint24 convertedIndex = uint24(index); // Use assembly to swap in a fake index. From 420687ebc8491ff469f541b302acf9552a7eaf55 Mon Sep 17 00:00:00 2001 From: djviau Date: Thu, 2 Feb 2023 13:30:53 -0500 Subject: [PATCH 05/57] add a test that overflowing non-hits fail --- test/foundry/BulkSignature.t.sol | 152 ++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/test/foundry/BulkSignature.t.sol b/test/foundry/BulkSignature.t.sol index 324919a04..c74e3bcd6 100644 --- a/test/foundry/BulkSignature.t.sol +++ b/test/foundry/BulkSignature.t.sol @@ -458,7 +458,7 @@ contract BulkSignatureTest is BaseOrderTest { // 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 + tree height ** 2. + // Set up an index equal to orderIndex + 2 ** tree height. uint256 index = context.args.orderIndex + (2 ** context.args.height); uint24 convertedIndex = uint24(index); @@ -544,4 +544,154 @@ contract BulkSignatureTest is BaseOrderTest { }) ); } + + // 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 to the + // secondExpectedOverflowIndex, inclusive, and make sure that none of + // them work except the expected indexes. + for ( + uint256 i = firstExpectedOverflowIndex; + 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 + }); + + // We expect a revert for all indexes except the + // firstExpectedOverflowIndex and the secondExpectedOverflowIndex. + if ( + i != firstExpectedOverflowIndex && + i != secondExpectedOverflowIndex + ) { + // We 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 + }) + ); + } } From c3c7227d4fa44903ff11f2dfb056af458a0742ad Mon Sep 17 00:00:00 2001 From: djviau Date: Thu, 2 Feb 2023 14:02:27 -0500 Subject: [PATCH 06/57] fix bug --- test/foundry/BulkSignature.t.sol | 33 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/test/foundry/BulkSignature.t.sol b/test/foundry/BulkSignature.t.sol index c74e3bcd6..10dae8d99 100644 --- a/test/foundry/BulkSignature.t.sol +++ b/test/foundry/BulkSignature.t.sol @@ -599,11 +599,11 @@ contract BulkSignatureTest is BaseOrderTest { uint24 convertedIndex; Order memory order; - // Iterate over all indexes from the firstExpectedOverflowIndex to the - // secondExpectedOverflowIndex, inclusive, and make sure that none of - // them work except the expected indexes. + // 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; + uint256 i = firstExpectedOverflowIndex + 1; i <= secondExpectedOverflowIndex; ++i ) { @@ -624,12 +624,12 @@ contract BulkSignatureTest is BaseOrderTest { 0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ) // 256 - 24 = 232 - // Create a new value the same as the old value, except that it uses - // the fake index. + // 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: + // Shift the fake index left by 232 bits to produce a value + // to `or` with the `maskedProofData`. For example: // `0x000005000...` shl(232, convertedIndex) ) @@ -638,20 +638,19 @@ contract BulkSignatureTest is BaseOrderTest { mstore(indexAndProofDataPointer, fakeIndexAndProofData) } - // Create an order using the `bulkSignature` that was modified in the - // assembly block above. + // Create an order using the `bulkSignature` that was modified in + // the assembly block above. order = Order({ parameters: baseOrderParameters, signature: bulkSignature }); - // We expect a revert for all indexes except the - // firstExpectedOverflowIndex and the secondExpectedOverflowIndex. - if ( - i != firstExpectedOverflowIndex && - i != secondExpectedOverflowIndex - ) { - // We expect InvalidSigner because we're trying to recover the + // 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()")); From 06ad953d62cbbadff1ca0332a270b2b7e6504867 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Feb 2023 10:29:20 +0000 Subject: [PATCH 07/57] Bump http-cache-semantics from 4.1.0 to 4.1.1 Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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" From 11ab71020213c52f45525e69d4243331862b0118 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 13 Dec 2022 14:15:54 -0800 Subject: [PATCH 08/57] add SeaportRouter and tests --- contracts/helpers/SeaportRouter.sol | 183 ++++++ contracts/helpers/SeaportRouterStructs.sol | 32 + contracts/interfaces/SeaportRouterErrors.sol | 35 + contracts/test/Reenterer.sol | 10 + hardhat.config.ts | 10 + test/router.spec.ts | 631 +++++++++++++++++++ test/utils/fixtures/marketplace.ts | 22 +- 7 files changed, 915 insertions(+), 8 deletions(-) create mode 100644 contracts/helpers/SeaportRouter.sol create mode 100644 contracts/helpers/SeaportRouterStructs.sol create mode 100644 contracts/interfaces/SeaportRouterErrors.sol create mode 100644 test/router.spec.ts diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol new file mode 100644 index 000000000..eac5f5cae --- /dev/null +++ b/contracts/helpers/SeaportRouter.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { + AdvancedOrderParams, + FulfillAvailableAdvancedOrdersParams +} from "./SeaportRouterStructs.sol"; + +import { SeaportRouterErrors } from "../interfaces/SeaportRouterErrors.sol"; + +import { Execution } from "../lib/ConsiderationStructs.sol"; + +import { SeaportInterface } from "../interfaces/SeaportInterface.sol"; + +import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol"; + +/** + * @title SeaportRouter + * @author ryanio + * @notice A utility contract for interacting with multiple Seaport versions. + */ +contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { + /** + * @dev The allowed Seaport contracts usable through this router. + */ + address private immutable _SEAPORT_V1_1; + address private immutable _SEAPORT_V1_2; + + /** + * @dev Deploy contract with the supported Seaport contracts. + */ + constructor(address SEAPORT_V1_1, address SEAPORT_V1_2) { + _SEAPORT_V1_1 = SEAPORT_V1_1; + _SEAPORT_V1_2 = SEAPORT_V1_2; + } + + /** + * @notice Returns the Seaport contracts allowed to be used through this + * router. + */ + function getAllowedSeaportContracts() + public + view + returns (address[] memory seaportContracts) + { + seaportContracts = new address[](2); + seaportContracts[0] = _SEAPORT_V1_1; + seaportContracts[1] = _SEAPORT_V1_2; + } + + /** + * @notice Fulfill available advanced orders through multiple Seaport + * versions. + * See {SeaportInterface-fulfillAvailableAdvancedOrders} + */ + function fulfillAvailableAdvancedOrders( + FulfillAvailableAdvancedOrdersParams calldata params + ) + external + payable + returns ( + bool[][] memory availableOrders, + Execution[][] memory executions + ) + { + // Ensure this function cannot be triggered during a reentrant call. + _assertNonReentrant(); + + // 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; + + // Iterate through the provided Seaport contracts. + for (uint256 i = 0; i < seaportContractsLength; ) { + address seaport = params.seaportContracts[i]; + // Ensure the provided Seaport contract is allowed. + if (seaport != _SEAPORT_V1_1 && seaport != _SEAPORT_V1_2) { + revert SeaportNotAllowed(seaport); + } + + // Put the order params on the stack. + AdvancedOrderParams calldata orderParams = params + .advancedOrderParams[i]; + + // Execute the orders, collecting the 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(seaport).fulfillAvailableAdvancedOrders{ + value: orderParams.value + }( + orderParams.advancedOrders, + orderParams.criteriaResolvers, + orderParams.offerFulfillments, + orderParams.considerationFulfillments, + params.fulfillerConduitKey, + params.recipient, + fulfillmentsLeft + ) + 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 (availableOrders[i][j]) { + unchecked { + --fulfillmentsLeft; + ++j; + } + } + } + + // Break if the maximum number of executions has been reached. + if (fulfillmentsLeft == 0) { + break; + } + } catch {} + + unchecked { + ++i; + } + } + + // Return excess ether that may not have been used. + if (address(this).balance > 0) { + _returnExcessEther(); + } + } + + /** + * @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 { + // Return excess ether in the same transaction. + _returnExcessEther(); + } + + /** + * @dev Fallback function to receive excess ether, in case total amount of + * ether sent is more than the amount required to fulfill the order. + */ + fallback() external payable { + // Return excess ether in the same transaction. + _returnExcessEther(); + } + + /** + * @dev Fallback 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 { + // Ensure this function cannot be triggered during a reentrant call. + _setReentrancyGuard(); + + // 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 + ); + } + + // Clear the reentrancy guard. + _clearReentrancyGuard(); + } +} diff --git a/contracts/helpers/SeaportRouterStructs.sol b/contracts/helpers/SeaportRouterStructs.sol new file mode 100644 index 000000000..30564fab0 --- /dev/null +++ b/contracts/helpers/SeaportRouterStructs.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { + AdvancedOrder, + CriteriaResolver, + FulfillmentComponent +} from "../lib/ConsiderationStructs.sol"; + +/** + * @dev Advanced order parameters for use through the + * FulfillAvailableAdvancedOrdersParams struct. + */ +struct AdvancedOrderParams { + AdvancedOrder[] advancedOrders; + CriteriaResolver[] criteriaResolvers; + FulfillmentComponent[][] offerFulfillments; + FulfillmentComponent[][] considerationFulfillments; + uint256 value; // The amount of 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; +} diff --git a/contracts/interfaces/SeaportRouterErrors.sol b/contracts/interfaces/SeaportRouterErrors.sol new file mode 100644 index 000000000..9fe77a2f4 --- /dev/null +++ b/contracts/interfaces/SeaportRouterErrors.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** + * @title SeaportRouterErrors + */ +interface SeaportRouterErrors { + /** + * @dev Revert with an error if a Seaport contract is not allowed + * to be used through the router. + */ + error SeaportNotAllowed(address eaport); + + /** + * @dev Revert with an error if a Seaport contract is already allowed + * in the router. + */ + error SeaportAlreadyAdded(address seaport); + + /** + * @dev Revert with an error if a Seaport contract is not present to remove + * in the router. + */ + error SeaportNotPresent(address eaport); + + /** + * @dev Revert with an error if an ether transfer back to the fulfiller + * fails. + */ + error EtherReturnTransferFailed( + address recipient, + uint256 amount, + bytes returnData + ); +} 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/hardhat.config.ts b/hardhat.config.ts index 575615292..013244953 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -119,6 +119,16 @@ const config: HardhatUserConfig = { }, }, }, + "contracts/helper/SeaportRouter.sol": { + version: "0.8.14", + settings: { + viaIR: true, + optimizer: { + enabled: true, + runs: 1000000, + }, + }, + }, }, }, networks: { diff --git a/test/router.spec.ts b/test/router.spec.ts new file mode 100644 index 000000000..ec857d003 --- /dev/null +++ b/test/router.spec.ts @@ -0,0 +1,631 @@ +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 { + 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 () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + 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); + + ({ + conduitKeyOne, + conduitOne, + createOrder, + directMarketplaceContract: marketplaceContract2, + getTestItem721, + marketplaceContract, + mintAndApprove721, + reenterer, + set721ApprovalForAll, + testERC721, + } = await seaportFixture(owner)); + }); + + 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, + 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, + 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, + value, + }, + { + advancedOrders: [order2], + criteriaResolvers: [], + offerFulfillments: offerComponents, + considerationFulfillments: considerationComponents, + 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, + 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, + value, + }, + { + advancedOrders: [order2], + criteriaResolvers: [], + offerFulfillments: offerComponents, + considerationFulfillments: considerationComponents, + 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, + 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, + value, + }, + { + advancedOrders: [order2], + criteriaResolvers: [], + offerFulfillments: offerComponents, + considerationFulfillments: considerationComponents, + 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, + 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, + value, + }, + { + advancedOrders: [order2], + criteriaResolvers: [], + offerFulfillments: offerComponents, + considerationFulfillments: considerationComponents, + 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, + 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, + 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") + ); + }); + }); +}); diff --git a/test/utils/fixtures/marketplace.ts b/test/utils/fixtures/marketplace.ts index bea064ab9..f68a4cfff 100644 --- a/test/utils/fixtures/marketplace.ts +++ b/test/utils/fixtures/marketplace.ts @@ -50,7 +50,7 @@ export const marketplaceFixture = async ( const directMarketplaceContract = await deployContract( - process.env.REFERENCE ? "ReferenceConsideration" : "Consideration", + process.env.REFERENCE ? "ReferenceConsideration" : "Seaport", owner, conduitController.address ); @@ -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, From 8a641c5fdcebb6e9ce60d8ff972ef664e3574072 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Fri, 16 Dec 2022 15:55:07 -0800 Subject: [PATCH 09/57] - only set reentrancy on main fn - send funds back once at the end of the fn instead of on every receive - optimize checking seaport address --- contracts/helpers/SeaportRouter.sol | 50 ++++++++++++++------ contracts/interfaces/SeaportRouterErrors.sol | 2 +- test/router.spec.ts | 26 ++++++++++ 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index eac5f5cae..ac5cd3ad6 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -64,7 +64,7 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { ) { // Ensure this function cannot be triggered during a reentrant call. - _assertNonReentrant(); + _setReentrancyGuard(); // Put the number of Seaport contracts on the stack. uint256 seaportContractsLength = params.seaportContracts.length; @@ -80,9 +80,7 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { for (uint256 i = 0; i < seaportContractsLength; ) { address seaport = params.seaportContracts[i]; // Ensure the provided Seaport contract is allowed. - if (seaport != _SEAPORT_V1_1 && seaport != _SEAPORT_V1_2) { - revert SeaportNotAllowed(seaport); - } + _assertSeaportAllowed(seaport); // Put the order params on the stack. AdvancedOrderParams calldata orderParams = params @@ -131,10 +129,13 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { } } - // Return excess ether that may not have been used. + // Return excess ether that may not have been used or was sent back. if (address(this).balance > 0) { _returnExcessEther(); } + + // Clear the reentrancy guard. + _clearReentrancyGuard(); } /** @@ -142,8 +143,8 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { * ether sent is more than the amount required to fulfill the order. */ receive() external payable { - // Return excess ether in the same transaction. - _returnExcessEther(); + // Ensure we only receive ether from Seaport. + _assertSeaportAllowed(msg.sender); } /** @@ -151,18 +152,15 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { * ether sent is more than the amount required to fulfill the order. */ fallback() external payable { - // Return excess ether in the same transaction. - _returnExcessEther(); + // Ensure we only receive ether from Seaport. + _assertSeaportAllowed(msg.sender); } /** - * @dev Fallback function to return excess ether, in case total amount of + * @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 { - // Ensure this function cannot be triggered during a reentrant call. - _setReentrancyGuard(); - // Send received funds back to msg.sender. (bool success, bytes memory data) = payable(msg.sender).call{ value: address(this).balance @@ -176,8 +174,30 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { data ); } + } - // Clear the reentrancy guard. - _clearReentrancyGuard(); + /** + * @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 Internal pure function to cast a `bool` value to a `uint256` value. + * + * @param b The `bool` value to cast. + * + * @return u The `uint256` value. + */ + function _cast(bool b) internal pure returns (uint256 u) { + assembly { + u := b + } } } diff --git a/contracts/interfaces/SeaportRouterErrors.sol b/contracts/interfaces/SeaportRouterErrors.sol index 9fe77a2f4..0f2b45ccc 100644 --- a/contracts/interfaces/SeaportRouterErrors.sol +++ b/contracts/interfaces/SeaportRouterErrors.sol @@ -9,7 +9,7 @@ interface SeaportRouterErrors { * @dev Revert with an error if a Seaport contract is not allowed * to be used through the router. */ - error SeaportNotAllowed(address eaport); + error SeaportNotAllowed(address seaport); /** * @dev Revert with an error if a Seaport contract is already allowed diff --git a/test/router.spec.ts b/test/router.spec.ts index ec857d003..b7ecab734 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -627,5 +627,31 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { marketplaceContract.interface.getSighash("NoReentrantCalls") ); }); + it("Should not be able to receive ether from a non-Seaport address", async () => { + // Test receive(), which is triggered when set eth with no data + const txReceive = await owner.signTransaction({ + to: router.address, + value: 1, + nonce: await owner.getTransactionCount(), + gasPrice: await provider.getGasPrice(), + gasLimit: 50_000, + }); + await expect(provider.sendTransaction(txReceive)) + .to.be.revertedWithCustomError(router, "SeaportNotAllowed") + .withArgs(owner.address); + + // Test fallback(), which is triggered when set eth with data + const txFallback = await owner.signTransaction({ + to: router.address, + value: 1, + data: "0x12", + nonce: await owner.getTransactionCount(), + gasPrice: await provider.getGasPrice(), + gasLimit: 50_000, + }); + await expect(provider.sendTransaction(txFallback)) + .to.be.revertedWithCustomError(router, "SeaportNotAllowed") + .withArgs(owner.address); + }); }); }); From 418a6a174fa99779aa92fe75df7548ea88cda0ba Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Fri, 16 Dec 2022 16:08:30 -0800 Subject: [PATCH 10/57] test that seaport address can send ether to router contract --- test/router.spec.ts | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/test/router.spec.ts b/test/router.spec.ts index b7ecab734..766d81a83 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -628,20 +628,20 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { ); }); it("Should not be able to receive ether from a non-Seaport address", async () => { - // Test receive(), which is triggered when set eth with no data - const txReceive = await owner.signTransaction({ + // 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(txReceive)) + await expect(provider.sendTransaction(txTriggerReceive)) .to.be.revertedWithCustomError(router, "SeaportNotAllowed") .withArgs(owner.address); - // Test fallback(), which is triggered when set eth with data - const txFallback = await owner.signTransaction({ + // Test fallback(), which is triggered when sent eth with data + const txTriggerFallback = await owner.signTransaction({ to: router.address, value: 1, data: "0x12", @@ -649,9 +649,33 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { gasPrice: await provider.getGasPrice(), gasLimit: 50_000, }); - await expect(provider.sendTransaction(txFallback)) + await expect(provider.sendTransaction(txTriggerFallback)) .to.be.revertedWithCustomError(router, "SeaportNotAllowed") .withArgs(owner.address); + + // Test receive() and fallback() 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 seaportSigner.sendTransaction({ + to: router.address, + value: 1, + data: "0x12", + }); + expect((await provider.getBalance(router.address)).toNumber()).to.eq(2); + + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [marketplaceContract.address], + }); }); }); }); From 9d87b2dc81de96eb4971ddd8c8d968cf7ebf9100 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Fri, 16 Dec 2022 23:28:08 -0800 Subject: [PATCH 11/57] remove fallback --- contracts/helpers/SeaportRouter.sol | 9 --------- test/router.spec.ts | 22 +--------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index ac5cd3ad6..6942b5572 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -147,15 +147,6 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { _assertSeaportAllowed(msg.sender); } - /** - * @dev Fallback function to receive excess ether, in case total amount of - * ether sent is more than the amount required to fulfill the order. - */ - fallback() external payable { - // Ensure we only receive ether from Seaport. - _assertSeaportAllowed(msg.sender); - } - /** * @dev Function to return excess ether, in case total amount of * ether sent is more than the amount required to fulfill the order. diff --git a/test/router.spec.ts b/test/router.spec.ts index 766d81a83..18123cdb2 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -640,20 +640,7 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { .to.be.revertedWithCustomError(router, "SeaportNotAllowed") .withArgs(owner.address); - // Test fallback(), which is triggered when sent eth with data - const txTriggerFallback = await owner.signTransaction({ - to: router.address, - value: 1, - data: "0x12", - nonce: await owner.getTransactionCount(), - gasPrice: await provider.getGasPrice(), - gasLimit: 50_000, - }); - await expect(provider.sendTransaction(txTriggerFallback)) - .to.be.revertedWithCustomError(router, "SeaportNotAllowed") - .withArgs(owner.address); - - // Test receive() and fallback() impersonating as Seaport + // Test receive() impersonating as Seaport await network.provider.request({ method: "hardhat_impersonateAccount", params: [marketplaceContract.address], @@ -665,13 +652,6 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { await seaportSigner.sendTransaction({ to: router.address, value: 1 }); expect((await provider.getBalance(router.address)).toNumber()).to.eq(1); - await seaportSigner.sendTransaction({ - to: router.address, - value: 1, - data: "0x12", - }); - expect((await provider.getBalance(router.address)).toNumber()).to.eq(2); - await network.provider.request({ method: "hardhat_stopImpersonatingAccount", params: [marketplaceContract.address], From 25924338ea7f5018c2b1771aac461af9cc767c86 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 3 Jan 2023 17:01:07 -0800 Subject: [PATCH 12/57] fix lint warnings --- contracts/helpers/SeaportRouter.sol | 105 ++++++++++-------- .../interfaces/SeaportRouterInterface.sol | 42 +++++++ test/router.spec.ts | 6 + 3 files changed, 108 insertions(+), 45 deletions(-) create mode 100644 contracts/interfaces/SeaportRouterInterface.sol diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index 6942b5572..15beb8393 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -6,6 +6,10 @@ import { FulfillAvailableAdvancedOrdersParams } from "./SeaportRouterStructs.sol"; +import { + SeaportRouterInterface +} from "../interfaces/SeaportRouterInterface.sol"; + import { SeaportRouterErrors } from "../interfaces/SeaportRouterErrors.sol"; import { Execution } from "../lib/ConsiderationStructs.sol"; @@ -19,45 +23,49 @@ import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol"; * @author ryanio * @notice A utility contract for interacting with multiple Seaport versions. */ -contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { - /** - * @dev The allowed Seaport contracts usable through this router. - */ +contract SeaportRouter is + SeaportRouterInterface, + SeaportRouterErrors, + ReentrancyGuard +{ + /// @dev The allowed Seaport v1.1 contract usable through this router. address private immutable _SEAPORT_V1_1; + /// @dev The allowed Seaport 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 SEAPORT_V1_1, address SEAPORT_V1_2) { - _SEAPORT_V1_1 = SEAPORT_V1_1; - _SEAPORT_V1_2 = SEAPORT_V1_2; + constructor(address seaportV1point1, address seaportV1point2) { + _SEAPORT_V1_1 = seaportV1point1; + _SEAPORT_V1_2 = seaportV1point2; } /** - * @notice Returns the Seaport contracts allowed to be used through this - * router. + * @dev Fallback function to receive excess ether, in case total amount of + * ether sent is more than the amount required to fulfill the order. */ - function getAllowedSeaportContracts() - public - view - returns (address[] memory seaportContracts) - { - seaportContracts = new address[](2); - seaportContracts[0] = _SEAPORT_V1_1; - seaportContracts[1] = _SEAPORT_V1_2; + 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 @@ -86,9 +94,10 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { AdvancedOrderParams calldata orderParams = params .advancedOrderParams[i]; - // Execute the orders, collecting the 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(). + // 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(seaport).fulfillAvailableAdvancedOrders{ value: orderParams.value @@ -139,32 +148,18 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { } /** - * @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 { - // Ensure we only receive ether from Seaport. - _assertSeaportAllowed(msg.sender); - } - - /** - * @dev Function to return excess ether, in case total amount of - * ether sent is more than the amount required to fulfill the order. + * @notice Returns the Seaport contracts allowed to be used through this + * router. */ - 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 - ); - } + function getAllowedSeaportContracts() + external + view + override + returns (address[] memory seaportContracts) + { + seaportContracts = new address[](2); + seaportContracts[0] = _SEAPORT_V1_1; + seaportContracts[1] = _SEAPORT_V1_2; } /** @@ -191,4 +186,24 @@ contract SeaportRouter is SeaportRouterErrors, ReentrancyGuard { u := b } } + + /** + * @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/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol new file mode 100644 index 000000000..b19b58847 --- /dev/null +++ b/contracts/interfaces/SeaportRouterInterface.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { + FulfillAvailableAdvancedOrdersParams +} from "../helpers/SeaportRouterStructs.sol"; + +import { Execution } from "../lib/ConsiderationStructs.sol"; + +interface SeaportRouterInterface { + /** + * @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/test/router.spec.ts b/test/router.spec.ts index 18123cdb2..b52da8ae9 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -194,6 +194,8 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { undefined, undefined, undefined, + undefined, + undefined, marketplaceContract2 ); @@ -362,6 +364,8 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { undefined, undefined, undefined, + undefined, + undefined, marketplaceContract2 ); @@ -445,6 +449,8 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { undefined, undefined, undefined, + undefined, + undefined, marketplaceContract2 ); From 7f5e6a25177e8651726ddddb13871412d24d042f Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 30 Jan 2023 13:12:47 -0800 Subject: [PATCH 13/57] optimization: use newAvailableOrders in place of availableOrders[i] --- contracts/helpers/SeaportRouter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index 15beb8393..f223b821b 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -119,7 +119,7 @@ contract SeaportRouter is // Subtract the number of orders fulfilled. uint256 newAvailableOrdersLength = newAvailableOrders.length; for (uint256 j = 0; j < newAvailableOrdersLength; ) { - if (availableOrders[i][j]) { + if (newAvailableOrders[j]) { unchecked { --fulfillmentsLeft; ++j; From fbf805a1a8055f10691295c09d66d3fdd20c05c9 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 5 Feb 2023 14:28:41 -0800 Subject: [PATCH 14/57] consolidate files into interface --- contracts/helpers/SeaportRouter.sol | 34 ++----------- contracts/helpers/SeaportRouterStructs.sol | 32 ------------ contracts/interfaces/SeaportRouterErrors.sol | 35 ------------- .../interfaces/SeaportRouterInterface.sol | 51 ++++++++++++++++++- hardhat.config.ts | 10 ---- test/router.spec.ts | 12 ++++- test/utils/fixtures/marketplace.ts | 2 +- 7 files changed, 66 insertions(+), 110 deletions(-) delete mode 100644 contracts/helpers/SeaportRouterStructs.sol delete mode 100644 contracts/interfaces/SeaportRouterErrors.sol diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index f223b821b..d1b4b6b3c 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -1,17 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { - AdvancedOrderParams, - FulfillAvailableAdvancedOrdersParams -} from "./SeaportRouterStructs.sol"; - import { SeaportRouterInterface } from "../interfaces/SeaportRouterInterface.sol"; -import { SeaportRouterErrors } from "../interfaces/SeaportRouterErrors.sol"; - import { Execution } from "../lib/ConsiderationStructs.sol"; import { SeaportInterface } from "../interfaces/SeaportInterface.sol"; @@ -21,16 +14,12 @@ import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol"; /** * @title SeaportRouter * @author ryanio - * @notice A utility contract for interacting with multiple Seaport versions. + * @notice A utility contract for fulfilling orders with multiple Seaport versions. */ -contract SeaportRouter is - SeaportRouterInterface, - SeaportRouterErrors, - ReentrancyGuard -{ - /// @dev The allowed Seaport v1.1 contract usable through this router. +contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { + /// @dev The allowed v1.1 contract usable through this router. address private immutable _SEAPORT_V1_1; - /// @dev The allowed Seaport v1.2 contract usable through this router. + /// @dev The allowed v1.2 contract usable through this router. address private immutable _SEAPORT_V1_2; /** @@ -72,7 +61,7 @@ contract SeaportRouter is ) { // Ensure this function cannot be triggered during a reentrant call. - _setReentrancyGuard(); + _setReentrancyGuard(true); // Put the number of Seaport contracts on the stack. uint256 seaportContractsLength = params.seaportContracts.length; @@ -174,19 +163,6 @@ contract SeaportRouter is } } - /** - * @dev Internal pure function to cast a `bool` value to a `uint256` value. - * - * @param b The `bool` value to cast. - * - * @return u The `uint256` value. - */ - function _cast(bool b) internal pure returns (uint256 u) { - assembly { - u := b - } - } - /** * @dev Function to return excess ether, in case total amount of * ether sent is more than the amount required to fulfill the order. diff --git a/contracts/helpers/SeaportRouterStructs.sol b/contracts/helpers/SeaportRouterStructs.sol deleted file mode 100644 index 30564fab0..000000000 --- a/contracts/helpers/SeaportRouterStructs.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import { - AdvancedOrder, - CriteriaResolver, - FulfillmentComponent -} from "../lib/ConsiderationStructs.sol"; - -/** - * @dev Advanced order parameters for use through the - * FulfillAvailableAdvancedOrdersParams struct. - */ -struct AdvancedOrderParams { - AdvancedOrder[] advancedOrders; - CriteriaResolver[] criteriaResolvers; - FulfillmentComponent[][] offerFulfillments; - FulfillmentComponent[][] considerationFulfillments; - uint256 value; // The amount of 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; -} diff --git a/contracts/interfaces/SeaportRouterErrors.sol b/contracts/interfaces/SeaportRouterErrors.sol deleted file mode 100644 index 0f2b45ccc..000000000 --- a/contracts/interfaces/SeaportRouterErrors.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -/** - * @title SeaportRouterErrors - */ -interface SeaportRouterErrors { - /** - * @dev Revert with an error if a Seaport contract is not allowed - * to be used through the router. - */ - error SeaportNotAllowed(address seaport); - - /** - * @dev Revert with an error if a Seaport contract is already allowed - * in the router. - */ - error SeaportAlreadyAdded(address seaport); - - /** - * @dev Revert with an error if a Seaport contract is not present to remove - * in the router. - */ - error SeaportNotPresent(address eaport); - - /** - * @dev Revert with an error if an ether transfer back to the fulfiller - * fails. - */ - error EtherReturnTransferFailed( - address recipient, - uint256 amount, - bytes returnData - ); -} diff --git a/contracts/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol index b19b58847..1170d86b0 100644 --- a/contracts/interfaces/SeaportRouterInterface.sol +++ b/contracts/interfaces/SeaportRouterInterface.sol @@ -2,12 +2,59 @@ pragma solidity ^0.8.13; import { - FulfillAvailableAdvancedOrdersParams -} from "../helpers/SeaportRouterStructs.sol"; + AdvancedOrder, + CriteriaResolver, + FulfillmentComponent +} from "../lib/ConsiderationStructs.sol"; import { Execution } from "../lib/ConsiderationStructs.sol"; +/** + * @title SeaportRouterInterface + * @author ryanio + * @notice A utility contract for fulfilling orders with multiple Seaport versions. + */ interface SeaportRouterInterface { + /** + * @dev Advanced order parameters for use through the + * FulfillAvailableAdvancedOrdersParams struct. + */ + struct AdvancedOrderParams { + AdvancedOrder[] advancedOrders; + CriteriaResolver[] criteriaResolvers; + FulfillmentComponent[][] offerFulfillments; + FulfillmentComponent[][] considerationFulfillments; + uint256 value; // The amount of 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 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. diff --git a/hardhat.config.ts b/hardhat.config.ts index 013244953..575615292 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -119,16 +119,6 @@ const config: HardhatUserConfig = { }, }, }, - "contracts/helper/SeaportRouter.sol": { - version: "0.8.14", - settings: { - viaIR: true, - optimizer: { - enabled: true, - runs: 1000000, - }, - }, - }, }, }, networks: { diff --git a/test/router.spec.ts b/test/router.spec.ts index b52da8ae9..569b29da0 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -9,6 +9,7 @@ import { seaportFixture } from "./utils/fixtures"; import { VERSION } from "./utils/helpers"; import type { + ConduitControllerInterface, ConduitInterface, ConsiderationInterface, Reenterer, @@ -22,6 +23,7 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { const { provider } = ethers; const owner = new ethers.Wallet(randomHex(32), provider); + let conduitController: ConduitControllerInterface; let conduitKeyOne: string; let conduitOne: ConduitInterface; let marketplaceContract: ConsiderationInterface; @@ -44,10 +46,10 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { await faucet(owner.address, provider); ({ + conduitController, conduitKeyOne, conduitOne, createOrder, - directMarketplaceContract: marketplaceContract2, getTestItem721, marketplaceContract, mintAndApprove721, @@ -55,6 +57,12 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { set721ApprovalForAll, testERC721, } = await seaportFixture(owner)); + + marketplaceContract2 = await deployContract( + "Seaport", + owner, + conduitController.address + ); }); let buyer: Wallet; @@ -279,6 +287,8 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { undefined, undefined, undefined, + undefined, + undefined, marketplaceContract2 ); diff --git a/test/utils/fixtures/marketplace.ts b/test/utils/fixtures/marketplace.ts index f68a4cfff..fe137c93d 100644 --- a/test/utils/fixtures/marketplace.ts +++ b/test/utils/fixtures/marketplace.ts @@ -50,7 +50,7 @@ export const marketplaceFixture = async ( const directMarketplaceContract = await deployContract( - process.env.REFERENCE ? "ReferenceConsideration" : "Seaport", + process.env.REFERENCE ? "ReferenceConsideration" : "Consideration", owner, conduitController.address ); From 206fd35b0949b83f3de442c5d92ace9d79e545da Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 17:54:29 -0500 Subject: [PATCH 15/57] avoid stack too deep errors --- contracts/helpers/SeaportRouter.sol | 65 ++++++++++++++----- .../interfaces/SeaportRouterInterface.sol | 20 +++++- test/router.spec.ts | 22 +++---- 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index d1b4b6b3c..c6ce88a70 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -5,16 +5,22 @@ import { SeaportRouterInterface } from "../interfaces/SeaportRouterInterface.sol"; -import { Execution } from "../lib/ConsiderationStructs.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 ryanio - * @notice A utility contract for fulfilling orders with multiple Seaport versions. + * @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth) + * @notice A utility contract for fulfilling orders with multiple + * Seaport versions. */ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { /// @dev The allowed v1.1 contract usable through this router. @@ -22,6 +28,10 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { /// @dev The allowed v1.2 contract usable through this router. address private immutable _SEAPORT_V1_2; + /// @dev We overwrite etherValue in AdvancedOrderParams to avoid stack too deep + /// when formatting the fulfillAvailableAdvancedOrders call. + uint256 private constant ENCODED_ADVANCED_ORDER_PARAMS_VALUE_OFFSET = 0xc4; + /** * @dev Deploy contract with the supported Seaport contracts. * @@ -73,31 +83,50 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { // 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. + CalldataParams memory calldataParams = CalldataParams({ + advancedOrders: new AdvancedOrder[](0), + criteriaResolvers: new CriteriaResolver[](0), + offerFulfillments: new FulfillmentComponent[][](0), + considerationFulfillments: new FulfillmentComponent[][](0), + fulfillerConduitKey: params.fulfillerConduitKey, + recipient: params.recipient, + maximumFulfilled: fulfillmentsLeft + }); + // Iterate through the provided Seaport contracts. - for (uint256 i = 0; i < seaportContractsLength; ) { - address seaport = params.seaportContracts[i]; + for (uint256 i = 0; i < params.seaportContracts.length; ) { // Ensure the provided Seaport contract is allowed. - _assertSeaportAllowed(seaport); + _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(seaport).fulfillAvailableAdvancedOrders{ - value: orderParams.value + SeaportInterface(params.seaportContracts[i]) + .fulfillAvailableAdvancedOrders{ + value: orderParams.etherValue }( - orderParams.advancedOrders, - orderParams.criteriaResolvers, - orderParams.offerFulfillments, - orderParams.considerationFulfillments, - params.fulfillerConduitKey, - params.recipient, - fulfillmentsLeft + calldataParams.advancedOrders, + calldataParams.criteriaResolvers, + calldataParams.offerFulfillments, + calldataParams.considerationFulfillments, + calldataParams.fulfillerConduitKey, + calldataParams.recipient, + calldataParams.maximumFulfilled ) returns ( bool[] memory newAvailableOrders, @@ -105,6 +134,7 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { ) { availableOrders[i] = newAvailableOrders; executions[i] = newExecutions; + // Subtract the number of orders fulfilled. uint256 newAvailableOrdersLength = newAvailableOrders.length; for (uint256 j = 0; j < newAvailableOrdersLength; ) { @@ -122,6 +152,9 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { } } catch {} + // Update fulfillments left. + calldataParams.maximumFulfilled = fulfillmentsLeft; + unchecked { ++i; } diff --git a/contracts/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol index 1170d86b0..cb1a99255 100644 --- a/contracts/interfaces/SeaportRouterInterface.sol +++ b/contracts/interfaces/SeaportRouterInterface.sol @@ -11,8 +11,9 @@ import { Execution } from "../lib/ConsiderationStructs.sol"; /** * @title SeaportRouterInterface - * @author ryanio - * @notice A utility contract for fulfilling orders with multiple Seaport versions. + * @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth) + * @notice A utility contract for fulfilling orders with multiple + * Seaport versions. */ interface SeaportRouterInterface { /** @@ -24,7 +25,7 @@ interface SeaportRouterInterface { CriteriaResolver[] criteriaResolvers; FulfillmentComponent[][] offerFulfillments; FulfillmentComponent[][] considerationFulfillments; - uint256 value; // The amount of ether value to send with the set of orders. + uint256 etherValue; // The amount of ether value to send with the set of orders. } /** @@ -39,6 +40,19 @@ interface SeaportRouterInterface { 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. diff --git a/test/router.spec.ts b/test/router.spec.ts index 569b29da0..d603c9ab1 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -135,7 +135,7 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), @@ -224,14 +224,14 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, { advancedOrders: [order2], criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), @@ -309,14 +309,14 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, { advancedOrders: [order2], criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), @@ -396,14 +396,14 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, { advancedOrders: [order2], criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: conduitKeyOne, @@ -481,14 +481,14 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, { advancedOrders: [order2], criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), @@ -561,7 +561,7 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), @@ -618,7 +618,7 @@ describe(`SeaportRouter tests (Seaport v${VERSION})`, function () { criteriaResolvers: [], offerFulfillments: offerComponents, considerationFulfillments: considerationComponents, - value, + etherValue: value, }, ], fulfillerConduitKey: toKey(0), From 20eefebe8051afe4bb4adf2732b601254c185f98 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 17:55:18 -0500 Subject: [PATCH 16/57] remove unused constant --- contracts/helpers/SeaportRouter.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index c6ce88a70..8a588bc5f 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -28,10 +28,6 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { /// @dev The allowed v1.2 contract usable through this router. address private immutable _SEAPORT_V1_2; - /// @dev We overwrite etherValue in AdvancedOrderParams to avoid stack too deep - /// when formatting the fulfillAvailableAdvancedOrders call. - uint256 private constant ENCODED_ADVANCED_ORDER_PARAMS_VALUE_OFFSET = 0xc4; - /** * @dev Deploy contract with the supported Seaport contracts. * From 4ae2e23db0e26e3c963b47261241ec73900bc3e6 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 18:07:36 -0500 Subject: [PATCH 17/57] skip router in ref tests --- test/router.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/router.spec.ts b/test/router.spec.ts index d603c9ab1..0a3ad77b9 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -20,6 +20,8 @@ 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); From a3161e97d5048dbbd5c0ba7fa8cdda58ec07d461 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 18:13:49 -0500 Subject: [PATCH 18/57] define empty memory vars instead of allocating arrays --- contracts/helpers/SeaportRouter.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index 8a588bc5f..8d4de38dd 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -81,11 +81,14 @@ contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { // 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: new AdvancedOrder[](0), - criteriaResolvers: new CriteriaResolver[](0), - offerFulfillments: new FulfillmentComponent[][](0), - considerationFulfillments: new FulfillmentComponent[][](0), + advancedOrders: emptyAdvancedOrders, + criteriaResolvers: emptyCriteriaResolvers, + offerFulfillments: emptyFulfillmentComponents, + considerationFulfillments: emptyFulfillmentComponents, fulfillerConduitKey: params.fulfillerConduitKey, recipient: params.recipient, maximumFulfilled: fulfillmentsLeft From 0aadb03431dabc764165b15997ef2bf0140ac3f3 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 18:21:24 -0500 Subject: [PATCH 19/57] add disclaimer that router only works for native consideration items --- contracts/helpers/SeaportRouter.sol | 3 ++- contracts/interfaces/SeaportRouterInterface.sol | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/helpers/SeaportRouter.sol b/contracts/helpers/SeaportRouter.sol index 8d4de38dd..43b57c393 100644 --- a/contracts/helpers/SeaportRouter.sol +++ b/contracts/helpers/SeaportRouter.sol @@ -20,7 +20,8 @@ import { * @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. + * 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. diff --git a/contracts/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol index cb1a99255..a01debe98 100644 --- a/contracts/interfaces/SeaportRouterInterface.sol +++ b/contracts/interfaces/SeaportRouterInterface.sol @@ -13,7 +13,8 @@ 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. + * Seaport versions. DISCLAIMER: This contract only works when + * all consideration items across all listings are native tokens. */ interface SeaportRouterInterface { /** From c873f2a1e0aa4319e01be910eec5ab76898877d0 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 7 Feb 2023 19:11:27 -0500 Subject: [PATCH 20/57] shorten comment to satisfy line length lint --- contracts/interfaces/SeaportRouterInterface.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/SeaportRouterInterface.sol b/contracts/interfaces/SeaportRouterInterface.sol index a01debe98..108761496 100644 --- a/contracts/interfaces/SeaportRouterInterface.sol +++ b/contracts/interfaces/SeaportRouterInterface.sol @@ -26,7 +26,7 @@ interface SeaportRouterInterface { CriteriaResolver[] criteriaResolvers; FulfillmentComponent[][] offerFulfillments; FulfillmentComponent[][] considerationFulfillments; - uint256 etherValue; // The amount of ether value to send with the set of orders. + uint256 etherValue; /// The ether value to send with the set of orders. } /** From 8a02c35fc01eb336904aaa56ea4059001cc3bb08 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 14:41:51 -0800 Subject: [PATCH 21/57] loop through orders again if restricted/contract present --- contracts/lib/ConsiderationDecoder.sol | 2 +- contracts/lib/OrderCombiner.sol | 372 +++++++++++++++++-------- 2 files changed, 260 insertions(+), 114 deletions(-) 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/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index fc47d4b07..9156a69af 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -136,13 +136,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 +155,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { considerationFulfillments, fulfillerConduitKey, recipient, - orderHashes + orderHashes, + containsNonOpen ); } @@ -183,6 +187,9 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { * already used as part of a provided fulfillment. * * @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 +197,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 +306,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 +561,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 +579,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) @@ -664,7 +685,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { advancedOrders, executions, orderHashes, - recipient + recipient, + containsNonOpen ); return (availableOrders, executions); @@ -683,6 +705,9 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { * @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 @@ -692,7 +717,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { 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; @@ -748,117 +774,228 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { // 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. - 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. - if (advancedOrder.numerator == 0) { - // This is required because the current memory region, which - // was previously used by the accumulator, might be dirty. - availableOrders[i] = false; - continue; - } + // If any restricted or contract orders are present in the group of + // orders being fulfilled, ensure that all transfers have completed + // before making any validateOrder or ratifyOrder calls. + if (containsNonOpen) { + // Iterate over each order. + for (uint256 i = 0; i < totalOrders; ++i) { + // Retrieve the order in question. + AdvancedOrder memory advancedOrder = advancedOrders[i]; + + // Skip the order in question if not being not fulfilled. + if (advancedOrder.numerator == 0) { + // Explicitly set availableOrders at the given index to + // guard against the possibility of dirtied memory. + availableOrders[i] = false; + continue; + } + + // Mark the order as available. + availableOrders[i] = true; - // Mark the order as available. - availableOrders[i] = true; + // Retrieve the order parameters. + OrderParameters memory parameters = ( + advancedOrder.parameters + ); + + { + // Retrieve offer items. + OfferItem[] memory offer = parameters.offer; + + // Read length of offer array & place on the stack. + uint256 totalOfferItems = offer.length; + + // Iterate over each offer item to restore it. + for (uint256 j = 0; j < totalOfferItems; ++j) { + 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) { + _transfer( + _fromOfferItemToReceivedItemWithRecipient( + offerItem, + recipient + ), + parameters.offerer, + parameters.conduitKey, + accumulator + ); + } + + // Restore original amount on the offer item. + offerItem.startAmount = originalAmount; + } + } - // Retrieve the order parameters. - OrderParameters memory parameters = advancedOrder.parameters; + { + // Read consideration items & ensure they are fulfilled. + ConsiderationItem[] memory consideration = ( + parameters.consideration + ); - { - // Retrieve offer items. - OfferItem[] memory offer = parameters.offer; - - // Read length of offer array & place on the stack. - uint256 totalOfferItems = offer.length; - - // Iterate over each offer item to restore it. - for (uint256 j = 0; j < totalOfferItems; ++j) { - 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) { - _transfer( - _convertOfferItemToReceivedItemWithRecipient( - offerItem, - _recipient - ), - parameters.offerer, - parameters.conduitKey, - accumulator + // Read length of consideration array & place on stack. + uint256 totalConsiderationItems = consideration.length; + + // Iterate over each consideration item. + for (uint256 j = 0; j < totalConsiderationItems; ++j) { + ConsiderationItem memory considerationItem = ( + consideration[j] ); - } - // Restore original amount on the offer item. - offerItem.startAmount = originalAmount; + // Retrieve remaining amount on consideration item. + uint256 unmetAmount = considerationItem.startAmount; + + // Revert if the remaining amount is not zero. + if (unmetAmount != 0) { + _revertConsiderationNotMet(i, j, unmetAmount); + } + + // Utilize assembly to restore the original value. + assembly { + // Write recipient to startAmount. + mstore( + add( + considerationItem, + ReceivedItem_amount_offset + ), + mload( + add( + considerationItem, + ConsiderationItem_recipient_offset + ) + ) + ) + } + } } } - { - // Retrieve consideration items & ensure they are fulfilled. - ConsiderationItem[] memory consideration = ( - parameters.consideration + // Trigger any accumulated transfers via call to the conduit. + _triggerIfArmed(accumulator); + + // Iterate over each order a second time. + for (uint256 i = 0; i < totalOrders; ++i) { + // Check restricted orders and contract orders. + _assertRestrictedAdvancedOrderValidity( + advancedOrders[i], + orderHashes, + orderHashes[i] ); + } + } else { + // Iterate over each order. + for (uint256 i = 0; i < totalOrders; ++i) { + // Retrieve the order in question. + AdvancedOrder memory advancedOrder = advancedOrders[i]; + + // Skip the order in question if not being not fulfilled. + if (advancedOrder.numerator == 0) { + // Explicitly set availableOrders at the given index to + // guard against the possibility of dirtied memory. + availableOrders[i] = false; + continue; + } - // Read length of consideration array & place on the stack. - uint256 totalConsiderationItems = consideration.length; + // Mark the order as available. + availableOrders[i] = true; + + // Retrieve the order parameters. + OrderParameters memory parameters = ( + advancedOrder.parameters + ); - // Iterate over each consideration item to ensure it is met. - for (uint256 j = 0; j < totalConsiderationItems; ++j) { - ConsiderationItem memory considerationItem = ( - consideration[j] + { + // Retrieve offer items. + OfferItem[] memory offer = parameters.offer; + + // Read length of offer array & place on the stack. + uint256 totalOfferItems = offer.length; + + // Iterate over each offer item to restore it. + for (uint256 j = 0; j < totalOfferItems; ++j) { + 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) { + _transfer( + _fromOfferItemToReceivedItemWithRecipient( + offerItem, + recipient + ), + parameters.offerer, + parameters.conduitKey, + accumulator + ); + } + + // Restore original amount on the offer item. + offerItem.startAmount = originalAmount; + } + } + + { + // Read consideration items & ensure they are fulfilled. + ConsiderationItem[] memory consideration = ( + parameters.consideration ); - // Retrieve remaining amount on the consideration item. - uint256 unmetAmount = considerationItem.startAmount; + // Read length of consideration array & place on stack. + uint256 totalConsiderationItems = consideration.length; - // Revert if the remaining amount is not zero. - if (unmetAmount != 0) { - _revertConsiderationNotMet(i, j, unmetAmount); - } + // Iterate over each consideration item. + for (uint256 j = 0; j < totalConsiderationItems; ++j) { + ConsiderationItem memory considerationItem = ( + consideration[j] + ); - // Utilize assembly to restore the original value. - assembly { - // Write recipient to startAmount. - mstore( - add( - considerationItem, - ReceivedItem_amount_offset - ), - mload( + // Retrieve remaining amount on consideration item. + uint256 unmetAmount = considerationItem.startAmount; + + // Revert if the remaining amount is not zero. + if (unmetAmount != 0) { + _revertConsiderationNotMet(i, j, unmetAmount); + } + + // Utilize assembly to restore the original value. + assembly { + // Write recipient to startAmount. + mstore( add( considerationItem, - ConsiderationItem_recipient_offset + ReceivedItem_amount_offset + ), + mload( + add( + considerationItem, + ConsiderationItem_recipient_offset + ) ) ) - ) + } } } } - // Check restricted orders and contract orders. - _assertRestrictedAdvancedOrderValidity( - advancedOrder, - orderHashes, - orderHashes[i] - ); + // Trigger any remaining accumulated transfers via conduit. + _triggerIfArmed(accumulator); } } - // Trigger any remaining accumulated transfers via call to the conduit. - _triggerIfArmed(accumulator); - // Determine whether any native token balance remains. assembly { nativeTokenBalance := selfbalance() @@ -955,13 +1092,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 +1112,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { advancedOrders, fulfillments, orderHashes, - recipient + recipient, + containsNonOpen ); } @@ -992,6 +1133,9 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { * @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 @@ -1001,7 +1145,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { 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; @@ -1059,7 +1204,8 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { advancedOrders, executions, orderHashes, - recipient + recipient, + containsNonOpen ); // Return the executions array. From 91aed8688e9737e4ac18b9242bfe167beeb85188 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 14:44:56 -0800 Subject: [PATCH 22/57] inline a memory read --- contracts/lib/OrderCombiner.sol | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index 9156a69af..e725b9709 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -812,13 +812,10 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { // 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( _fromOfferItemToReceivedItemWithRecipient( offerItem, @@ -925,13 +922,10 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { // 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( _fromOfferItemToReceivedItemWithRecipient( offerItem, From 2a19879df84f3f31355ff9eedfbdd27ffb0a89da Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 14:52:51 -0800 Subject: [PATCH 23/57] deal with stacc2thicc --- contracts/lib/OrderCombiner.sol | 73 +++++++++++++++++---------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index e725b9709..37987b3ae 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -720,9 +720,6 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { 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; @@ -736,39 +733,44 @@ 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; + + // 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(); + // 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; + } } } @@ -808,9 +810,8 @@ 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; // Transfer to recipient if unspent amount is not // zero. Note that the transfer will not be @@ -828,7 +829,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { } // Restore original amount on the offer item. - offerItem.startAmount = originalAmount; + offerItem.startAmount = offerItem.endAmount; } } @@ -918,9 +919,8 @@ 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; // Transfer to recipient if unspent amount is not // zero. Note that the transfer will not be @@ -938,7 +938,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { } // Restore original amount on the offer item. - offerItem.startAmount = originalAmount; + offerItem.startAmount = offerItem.endAmount; } } @@ -991,6 +991,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { } // Determine whether any native token balance remains. + uint256 nativeTokenBalance; assembly { nativeTokenBalance := selfbalance() } From cc9abe7715141e696dc038960b03be02ccc06ecd Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 15:00:35 -0800 Subject: [PATCH 24/57] include updated gas report --- ...9879df84f3f31355ff9eedfbdd27ffb0a89da.json | 516 ++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 .gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json diff --git a/.gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json b/.gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json new file mode 100644 index 000000000..19df87c49 --- /dev/null +++ b/.gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json @@ -0,0 +1,516 @@ +{ + "commitHash": "2a19879df84f3f31355ff9eedfbdd27ffb0a89da", + "contractReports": { + "Conduit": { + "name": "Conduit", + "methods": [ + { + "method": "execute", + "min": 77459, + "max": 2265578, + "avg": 465739, + "calls": 6 + }, + { + "method": "executeBatch1155", + "min": null, + "max": null, + "avg": 97197, + "calls": 1 + }, + { + "method": "executeWithBatch1155", + "min": 97717, + "max": 361430, + "avg": 228767, + "calls": 4 + }, + { + "method": "updateChannel", + "min": null, + "max": null, + "avg": 45802, + "calls": 1 + } + ], + "bytecodeSize": 3071, + "deployedBytecodeSize": 3030 + }, + "ConduitController": { + "name": "ConduitController", + "methods": [ + { + "method": "acceptOwnership", + "min": null, + "max": null, + "avg": 32944, + "calls": 1 + }, + { + "method": "cancelOwnershipTransfer", + "min": null, + "max": null, + "avg": 27966, + "calls": 1 + }, + { + "method": "createConduit", + "min": 712802, + "max": 712970, + "avg": 712929, + "calls": 52 + }, + { + "method": "transferOwnership", + "min": null, + "max": null, + "avg": 50329, + "calls": 2 + }, + { + "method": "updateChannel", + "min": 34454, + "max": 121098, + "avg": 117239, + "calls": 70 + } + ], + "bytecodeSize": 12007, + "deployedBytecodeSize": 8660 + }, + "ConduitControllerMock": { + "name": "ConduitControllerMock", + "methods": [ + { + "method": "createConduit", + "min": 226092, + "max": 231533, + "avg": 229598, + "calls": 6 + } + ], + "bytecodeSize": 10541, + "deployedBytecodeSize": 7340 + }, + "EIP1271Wallet": { + "name": "EIP1271Wallet", + "methods": [ + { + "method": "approveNFT", + "min": null, + "max": null, + "avg": 49674, + "calls": 14 + }, + { + "method": "registerDigest", + "min": 22227, + "max": 44139, + "avg": 36831, + "calls": 3 + }, + { + "method": "revertWithMessage", + "min": null, + "max": null, + "avg": 21677, + "calls": 1 + }, + { + "method": "setValid", + "min": 21699, + "max": 43611, + "avg": 32655, + "calls": 2 + } + ], + "bytecodeSize": 2834, + "deployedBytecodeSize": 2656 + }, + "ExcessReturnDataRecipient": { + "name": "ExcessReturnDataRecipient", + "methods": [ + { + "method": "setRevertDataSize", + "min": null, + "max": null, + "avg": 43441, + "calls": 2 + } + ], + "bytecodeSize": 1907, + "deployedBytecodeSize": 1879 + }, + "PausableZone": { + "name": "PausableZone", + "methods": [ + { + "method": "cancelOrders", + "min": null, + "max": null, + "avg": 65339, + "calls": 1 + } + ], + "bytecodeSize": 5556, + "deployedBytecodeSize": 5450 + }, + "PausableZoneController": { + "name": "PausableZoneController", + "methods": [ + { + "method": "acceptOwnership", + "min": null, + "max": null, + "avg": 28942, + "calls": 1 + }, + { + "method": "assignOperator", + "min": null, + "max": null, + "avg": 50892, + "calls": 1 + }, + { + "method": "assignPauser", + "min": null, + "max": null, + "avg": 47183, + "calls": 1 + }, + { + "method": "cancelOrders", + "min": null, + "max": null, + "avg": 73870, + "calls": 1 + }, + { + "method": "cancelOwnershipTransfer", + "min": null, + "max": null, + "avg": 24578, + "calls": 1 + }, + { + "method": "createZone", + "min": 1154302, + "max": 1154314, + "avg": 1154313, + "calls": 31 + }, + { + "method": "executeMatchAdvancedOrders", + "min": null, + "max": null, + "avg": 288740, + "calls": 2 + }, + { + "method": "executeMatchOrders", + "min": null, + "max": null, + "avg": 282316, + "calls": 2 + }, + { + "method": "pause", + "min": 32863, + "max": 35006, + "avg": 33577, + "calls": 3 + }, + { + "method": "transferOwnership", + "min": null, + "max": null, + "avg": 47199, + "calls": 2 + } + ], + "bytecodeSize": 17744, + "deployedBytecodeSize": 11975 + }, + "Reenterer": { + "name": "Reenterer", + "methods": [ + { + "method": "prepare", + "min": 49267, + "max": 2351690, + "avg": 1061788, + "calls": 26 + } + ], + "bytecodeSize": 2726, + "deployedBytecodeSize": 2698 + }, + "Seaport": { + "name": "Seaport", + "methods": [ + { + "method": "cancel", + "min": 41214, + "max": 58422, + "avg": 54029, + "calls": 16 + }, + { + "method": "fulfillAdvancedOrder", + "min": 96300, + "max": 225181, + "avg": 159954, + "calls": 188 + }, + { + "method": "fulfillAvailableAdvancedOrders", + "min": 149675, + "max": 350657, + "avg": 208078, + "calls": 29 + }, + { + "method": "fulfillAvailableOrders", + "min": 165032, + "max": 215786, + "avg": 201432, + "calls": 21 + }, + { + "method": "fulfillBasicOrder", + "min": 90639, + "max": 1621615, + "avg": 598701, + "calls": 187 + }, + { + "method": "fulfillBasicOrder_efficient_6GL6yc", + "min": 90261, + "max": 111468, + "avg": 100865, + "calls": 6 + }, + { + "method": "fulfillOrder", + "min": 119409, + "max": 225080, + "avg": 177753, + "calls": 105 + }, + { + "method": "incrementCounter", + "min": null, + "max": null, + "avg": 47054, + "calls": 6 + }, + { + "method": "matchAdvancedOrders", + "min": 179547, + "max": 300213, + "avg": 248859, + "calls": 77 + }, + { + "method": "matchOrders", + "min": 157522, + "max": 348225, + "avg": 264565, + "calls": 151 + }, + { + "method": "validate", + "min": 53153, + "max": 83874, + "avg": 73531, + "calls": 29 + } + ], + "bytecodeSize": 27052, + "deployedBytecodeSize": 23893 + }, + "SeaportRouter": { + "name": "SeaportRouter", + "methods": [ + { + "method": "fulfillAvailableAdvancedOrders", + "min": 183991, + "max": 311933, + "avg": 233620, + "calls": 6 + } + ], + "bytecodeSize": 6345, + "deployedBytecodeSize": 6158 + }, + "TestContractOfferer": { + "name": "TestContractOfferer", + "methods": [ + { + "method": "activate", + "min": 201543, + "max": 246674, + "avg": 205516, + "calls": 33 + }, + { + "method": "activateWithCriteria", + "min": null, + "max": null, + "avg": 201834, + "calls": 1 + }, + { + "method": "extendAvailable", + "min": null, + "max": null, + "avg": 50704, + "calls": 1 + }, + { + "method": "extendRequired", + "min": null, + "max": null, + "avg": 45780, + "calls": 1 + } + ], + "bytecodeSize": 8462, + "deployedBytecodeSize": 8265 + }, + "TestContractOffererNativeToken": { + "name": "TestContractOffererNativeToken", + "methods": [ + { + "method": "activate", + "min": null, + "max": null, + "avg": 139181, + "calls": 1 + } + ], + "bytecodeSize": 6940, + "deployedBytecodeSize": 6764 + }, + "TestERC1155": { + "name": "TestERC1155", + "methods": [ + { + "method": "mint", + "min": 47223, + "max": 49903, + "avg": 49451, + "calls": 250 + }, + { + "method": "setApprovalForAll", + "min": 26102, + "max": 46002, + "avg": 45662, + "calls": 468 + } + ], + "bytecodeSize": 4173, + "deployedBytecodeSize": 4145 + }, + "TestERC20": { + "name": "TestERC20", + "methods": [ + { + "method": "approve", + "min": 28881, + "max": 46245, + "avg": 45779, + "calls": 336 + }, + { + "method": "blockTransfer", + "min": 21978, + "max": 43890, + "avg": 32934, + "calls": 4 + }, + { + "method": "mint", + "min": 33994, + "max": 68458, + "avg": 67462, + "calls": 163 + }, + { + "method": "setNoReturnData", + "min": 21926, + "max": 43838, + "avg": 32882, + "calls": 2 + } + ], + "bytecodeSize": 5807, + "deployedBytecodeSize": 4636 + }, + "TestERC721": { + "name": "TestERC721", + "methods": [ + { + "method": "mint", + "min": 51396, + "max": 68796, + "avg": 65843, + "calls": 292 + }, + { + "method": "setApprovalForAll", + "min": 26195, + "max": 46095, + "avg": 45530, + "calls": 494 + } + ], + "bytecodeSize": 5238, + "deployedBytecodeSize": 4451 + }, + "TestInvalidContractOfferer": { + "name": "TestInvalidContractOfferer", + "methods": [ + { + "method": "activate", + "min": 201543, + "max": 201555, + "avg": 201549, + "calls": 2 + } + ], + "bytecodeSize": 7954, + "deployedBytecodeSize": 7764 + }, + "TestInvalidContractOffererRatifyOrder": { + "name": "TestInvalidContractOffererRatifyOrder", + "methods": [ + { + "method": "activate", + "min": null, + "max": null, + "avg": 201558, + "calls": 1 + } + ], + "bytecodeSize": 7957, + "deployedBytecodeSize": 7760 + }, + "TransferHelper": { + "name": "TransferHelper", + "methods": [ + { + "method": "bulkTransfer", + "min": 77935, + "max": 1433918, + "avg": 632612, + "calls": 3 + } + ], + "bytecodeSize": 4140, + "deployedBytecodeSize": 3865 + } + } +} \ No newline at end of file From c687f0d344e278b0d202fb406229dc1240040ef9 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 15:22:04 -0800 Subject: [PATCH 25/57] address compiler warning --- contracts/lib/OrderCombiner.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index 37987b3ae..b72b65534 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -991,14 +991,17 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { } // Determine whether any native token balance remains. - uint256 nativeTokenBalance; + 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 + ); } // Clear the reentrancy guard. From fb93ce07340d11e1ae8a46c3e1149f39e359e0eb Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 16:28:58 -0800 Subject: [PATCH 26/57] do not filter native token executions --- contracts/lib/FulfillmentApplier.sol | 47 ++++++---- contracts/lib/LowLevelHelpers.sol | 20 ---- contracts/lib/OrderCombiner.sol | 132 +++++++++++++++------------ contracts/lib/Verifiers.sol | 12 ++- 4 files changed, 115 insertions(+), 96 deletions(-) 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 b72b65534..801834957 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, @@ -186,10 +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 containsNonOpen A boolean indicating whether any restricted or - * contract orders are present within the provided - * array of advanced orders. + * @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, @@ -614,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 { @@ -643,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 { @@ -697,21 +688,21 @@ 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 containsNonOpen A boolean indicating whether any restricted or - * contract orders are present within the provided - * array of advanced orders. + * @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, @@ -1120,24 +1111,23 @@ 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 containsNonOpen A boolean indicating whether any restricted or - * contract orders are present within the provided - * array of advanced orders. + * @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, @@ -1170,13 +1160,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 { @@ -1209,4 +1194,39 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { // 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..2db47e2ba 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; } From 84e75005f9a8774ff86b138b53028274fea12df6 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 16:48:13 -0800 Subject: [PATCH 27/57] adjust tests accordingly --- test/advanced.spec.ts | 106 ++++++++++++++++++++++++++++++++++- test/utils/fixtures/index.ts | 30 ++++++++-- 2 files changed, 127 insertions(+), 9 deletions(-) diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts index dcc7c15b6..34516d566 100644 --- a/test/advanced.spec.ts +++ b/test/advanced.spec.ts @@ -5616,7 +5616,7 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () { executions ); }); - it("Match with fewer executions when one party has multiple orders that coincide", async () => { + it.only("Match with fewer executions when one party has multiple orders that coincide", async () => { const nftId = await mintAndApprove721( seller, marketplaceContract.address @@ -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,106 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () { ); return receipt; }); + it.only("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/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)) From 08c6618a845c41e48e8529c5dc22da3f8a573c6c Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 16:50:31 -0800 Subject: [PATCH 28/57] remove only --- test/advanced.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts index 34516d566..fb797eaaa 100644 --- a/test/advanced.spec.ts +++ b/test/advanced.spec.ts @@ -5616,7 +5616,7 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () { executions ); }); - it.only("Match with fewer executions when one party has multiple orders that coincide", async () => { + it("Match with fewer executions when one party has multiple orders that coincide", async () => { const nftId = await mintAndApprove721( seller, marketplaceContract.address @@ -5746,7 +5746,7 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () { ); return receipt; }); - it.only("Does not filter native tokens", async () => { + it("Does not filter native tokens", async () => { const nftId = await mintAndApprove721( seller, marketplaceContract.address From 68722147c98a5d477fb4e91b8a9b86a680609d51 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 16:51:52 -0800 Subject: [PATCH 29/57] fix lint issues --- test/advanced.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts index fb797eaaa..805129fe0 100644 --- a/test/advanced.spec.ts +++ b/test/advanced.spec.ts @@ -5812,14 +5812,13 @@ describe(`Advanced orders (Seaport v${VERSION})`, function () { ), ]; - const { order: orderThree } = - await createOrder( - buyer, - zone, - offerThree, - considerationThree, - 0 // FULL_OPEN - ); + const { order: orderThree } = await createOrder( + buyer, + zone, + offerThree, + considerationThree, + 0 // FULL_OPEN + ); const fulfillments = [ [[[1, 0]], [[0, 0]]], From cddd085dda2fc6f1c5148314bd3b295bf389ce98 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 16:57:43 -0800 Subject: [PATCH 30/57] update reference too --- reference/lib/ReferenceFulfillmentApplier.sol | 6 +++-- reference/lib/ReferenceOrderCombiner.sol | 24 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/reference/lib/ReferenceFulfillmentApplier.sol b/reference/lib/ReferenceFulfillmentApplier.sol index 9cd14006a..ea28ac027 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; } 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 { From cbf805993371824b08dbf46cd19e2e8cdb8c142c Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 17:08:07 -0800 Subject: [PATCH 31/57] use ERC20 in place of Native on filtered on reference --- reference/lib/ReferenceFulfillmentApplier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/lib/ReferenceFulfillmentApplier.sol b/reference/lib/ReferenceFulfillmentApplier.sol index ea28ac027..665f3d7d7 100644 --- a/reference/lib/ReferenceFulfillmentApplier.sol +++ b/reference/lib/ReferenceFulfillmentApplier.sol @@ -215,7 +215,7 @@ contract ReferenceFulfillmentApplier is return Execution( ReceivedItem( - ItemType.NATIVE, + ItemType.ERC20, address(0), 0, 0, From 73ec7340ba6d8981d749a359e7a0830d4a8462d4 Mon Sep 17 00:00:00 2001 From: Benjamin LeFevre Date: Mon, 13 Feb 2023 20:59:59 -0600 Subject: [PATCH 32/57] First commit --- config/.solcover.js | 1 + .../interfaces/AbridgedTokenInterfaces.sol | 35 +- .../test/TransferValidationZoneOfferer.sol | 367 ++++++++++++++++++ test/zone.spec.ts | 210 ++++++++++ 4 files changed, 608 insertions(+), 5 deletions(-) create mode 100644 contracts/test/TransferValidationZoneOfferer.sol diff --git a/config/.solcover.js b/config/.solcover.js index b4be68a67..5fe64f17d 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -44,6 +44,7 @@ module.exports = { "test/ConduitMockInvalidMagic.sol", "test/ConduitMockRevertBytes.sol", "test/ConduitMockRevertNoReason.sol", + "test/TransferValidationZoneOfferer.sol", "zones/PausableZone.sol", "zones/PausableZoneController.sol", "zones/interfaces/PausableZoneControllerInterface.sol", diff --git a/contracts/interfaces/AbridgedTokenInterfaces.sol b/contracts/interfaces/AbridgedTokenInterfaces.sol index 914279c7b..184559130 100644 --- a/contracts/interfaces/AbridgedTokenInterfaces.sol +++ b/contracts/interfaces/AbridgedTokenInterfaces.sol @@ -30,10 +30,18 @@ interface ERC20Interface { * * @return success True if the approval was successful. */ - function approve( - address spender, - uint256 value - ) external returns (bool success); + 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); } /** @@ -48,7 +56,11 @@ interface ERC721Interface { * @param to The address of the recipient. * @param tokenId The ID of the token to transfer. */ - function transferFrom(address from, address to, uint256 tokenId) external; + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; /** * @dev Allows an owner to approve an operator to transfer all tokens on a @@ -116,4 +128,17 @@ 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/test/TransferValidationZoneOfferer.sol b/contracts/test/TransferValidationZoneOfferer.sol new file mode 100644 index 000000000..4ab69eaf5 --- /dev/null +++ b/contracts/test/TransferValidationZoneOfferer.sol @@ -0,0 +1,367 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { + ERC20Interface, + ERC721Interface, + ERC1155Interface +} from "../interfaces/AbridgedTokenInterfaces.sol"; + +import { + ReceivedItem, + SpentItem, + Schema, + ZoneParameters +} from "../lib/ConsiderationStructs.sol"; + +import { ItemType } from "../lib/ConsiderationEnums.sol"; + +import { + ContractOffererInterface +} from "../interfaces/ContractOffererInterface.sol"; + +import { ZoneInterface } from "../interfaces/ZoneInterface.sol"; + +contract TransferValidationZoneOfferer is + ContractOffererInterface, + ZoneInterface +{ + error InvalidBalance(); + error InvalidOwner(); + + constructor() {} + + 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. + address recipient; + ItemType itemType; + ReceivedItem memory receivedItem; + + // Check if all consideration items have been received. + for (uint256 i = 0; i < zoneParameters.consideration.length; i++) { + // Check if the consideration item has been received. + receivedItem = zoneParameters.consideration[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 + ); + } + } + + // Fulfiller should receive all offer items. + address fulfiller = zoneParameters.fulfiller; + SpentItem memory spentItem; + + // Check if all offer items have been spent. + for (uint256 i = 0; i < zoneParameters.offer.length; i++) { + // Check if the offer item has been spent. + spentItem = zoneParameters.offer[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 + ); + } + } + + // 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)); + } + + 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 TransferValidationZoneOfferer.ratifyOrder.selector; + } + + function getSeaportMetadata() + external + pure + override(ContractOffererInterface, ZoneInterface) + returns (string memory name, Schema[] memory schemas) + { + // Return the metadata. + name = "TransferValidationZoneOfferer"; + 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/test/zone.spec.ts b/test/zone.spec.ts index dddd1b648..32a277ff3 100644 --- a/test/zone.spec.ts +++ b/test/zone.spec.ts @@ -1002,3 +1002,213 @@ 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 checkExpectedEvents: SeaportFixtures["checkExpectedEvents"]; + let createOrder: SeaportFixtures["createOrder"]; + let getTestItem721: SeaportFixtures["getTestItem721"]; + let getTestItem721WithCriteria: SeaportFixtures["getTestItem721WithCriteria"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let withBalanceChecks: SeaportFixtures["withBalanceChecks"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + checkExpectedEvents, + createOrder, + getTestItem721, + getTestItem721WithCriteria, + marketplaceContract, + mintAndApprove721, + 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("TransferValidationZoneOfferer", 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("TransferValidationZoneOfferer", 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("TransferValidationZoneOfferer", 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; + }); + }); +}); From a1051dce9431f22bf182dd4a7f09fa0b6aac53f4 Mon Sep 17 00:00:00 2001 From: Benjamin LeFevre Date: Mon, 13 Feb 2023 21:10:08 -0600 Subject: [PATCH 33/57] Skip reference for now --- test/zone.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/zone.spec.ts b/test/zone.spec.ts index 32a277ff3..e3e867562 100644 --- a/test/zone.spec.ts +++ b/test/zone.spec.ts @@ -1004,6 +1004,7 @@ describe(`Zone - PausableZone (Seaport v${VERSION})`, function () { }); describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { + if (process.env.REFERENCE) return; const { provider } = ethers; const owner = new ethers.Wallet(randomHex(32), provider); From 572489de1c8ab348747842001594e09c49aab889 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 21:34:11 -0800 Subject: [PATCH 34/57] use big switch instead of typehash directory --- contracts/lib/ConsiderationBase.sol | 212 ++++++++++++++++-- contracts/lib/ConsiderationConstants.sol | 73 +++++++ contracts/lib/OrderCombiner.sol | 263 +++++++---------------- contracts/lib/Verifiers.sol | 4 +- 4 files changed, 356 insertions(+), 196 deletions(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index 074fe2cf9..6df3f0674 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(); @@ -277,14 +294,185 @@ contract ConsiderationBase is orderTypehash = keccak256(orderTypeString); } + /** + * @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 view returns (bytes32 typeHash) { - TypehashDirectory directory = _BULK_ORDER_TYPEHASH_DIRECTORY; + ) internal pure returns (bytes32 typeHash) { assembly { - let typeHashOffset := add(1, shl(OneWordShift, sub(treeHeight, 1))) - extcodecopy(directory, 0, typeHashOffset, OneWord) - typeHash := mload(0) + switch lt(treeHeight, 13) + case 1 { + switch lt(treeHeight, 7) + case 1 { + switch lt(treeHeight, 4) + case 1 { + typeHash := add( + add( + mul( + eq(treeHeight, 1), + BulkOrder_Typehash_Height_One + ), + mul( + eq(treeHeight, 2), + BulkOrder_Typehash_Height_Two + ) + ), + mul( + eq(treeHeight, 3), + BulkOrder_Typehash_Height_Three + ) + ) + } + default { + typeHash := add( + add( + mul( + eq(treeHeight, 4), + BulkOrder_Typehash_Height_Four + ), + mul( + eq(treeHeight, 5), + BulkOrder_Typehash_Height_Five + ) + ), + mul( + eq(treeHeight, 6), + BulkOrder_Typehash_Height_Six + ) + ) + } + } + default { + switch lt(treeHeight, 10) + case 1 { + typeHash := add( + add( + mul( + eq(treeHeight, 7), + BulkOrder_Typehash_Height_Seven + ), + mul( + eq(treeHeight, 8), + BulkOrder_Typehash_Height_Eight + ) + ), + mul( + eq(treeHeight, 9), + BulkOrder_Typehash_Height_Nine + ) + ) + } + default { + typeHash := add( + add( + mul( + eq(treeHeight, 10), + BulkOrder_Typehash_Height_Ten + ), + mul( + eq(treeHeight, 11), + BulkOrder_Typehash_Height_Eleven + ) + ), + mul( + eq(treeHeight, 12), + BulkOrder_Typehash_Height_Twelve + ) + ) + } + } + } + default { + switch lt(treeHeight, 19) + case 1 { + switch lt(treeHeight, 16) + case 1 { + typeHash := add( + add( + mul( + eq(treeHeight, 13), + BulkOrder_Typehash_Height_Thirteen + ), + mul( + eq(treeHeight, 14), + BulkOrder_Typehash_Height_Fourteen + ) + ), + mul( + eq(treeHeight, 15), + BulkOrder_Typehash_Height_Fifteen + ) + ) + } + default { + typeHash := add( + add( + mul( + eq(treeHeight, 16), + BulkOrder_Typehash_Height_Sixteen + ), + mul( + eq(treeHeight, 17), + BulkOrder_Typehash_Height_Seventeen + ) + ), + mul( + eq(treeHeight, 18), + BulkOrder_Typehash_Height_Eighteen + ) + ) + } + } + default { + switch lt(treeHeight, 21) + case 1 { + typeHash := add( + add( + mul( + eq(treeHeight, 19), + BulkOrder_Typehash_Height_Nineteen + ), + mul( + eq(treeHeight, 20), + BulkOrder_Typehash_Height_Twenty + ) + ), + mul( + eq(treeHeight, 21), + BulkOrder_Typehash_Height_TwentyOne + ) + ) + } + default { + typeHash := add( + add( + mul( + eq(treeHeight, 22), + BulkOrder_Typehash_Height_TwentyTwo + ), + mul( + eq(treeHeight, 23), + BulkOrder_Typehash_Height_TwentyThree + ) + ), + mul( + eq(treeHeight, 24), + BulkOrder_Typehash_Height_TwentyFour + ) + ) + } + } + } } } } diff --git a/contracts/lib/ConsiderationConstants.sol b/contracts/lib/ConsiderationConstants.sol index 23848c64d..a1e75ac38 100644 --- a/contracts/lib/ConsiderationConstants.sol +++ b/contracts/lib/ConsiderationConstants.sol @@ -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/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index 801834957..10e5d3028 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -767,109 +767,107 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { // Skip overflow checks as all for loops are indexed starting at zero. unchecked { - // If any restricted or contract orders are present in the group of - // orders being fulfilled, ensure that all transfers have completed - // before making any validateOrder or ratifyOrder calls. - if (containsNonOpen) { - // Iterate over each order. - for (uint256 i = 0; i < totalOrders; ++i) { - // Retrieve the order in question. - AdvancedOrder memory advancedOrder = advancedOrders[i]; - - // Skip the order in question if not being not fulfilled. - if (advancedOrder.numerator == 0) { - // Explicitly set availableOrders at the given index to - // guard against the possibility of dirtied memory. - availableOrders[i] = false; - continue; - } + // Iterate over each order. + for (uint256 i = 0; i < totalOrders; ++i) { + // Retrieve the order in question. + AdvancedOrder memory advancedOrder = advancedOrders[i]; + + // Skip the order in question if not being not fulfilled. + if (advancedOrder.numerator == 0) { + // Explicitly set availableOrders at the given index to + // guard against the possibility of dirtied memory. + availableOrders[i] = false; + continue; + } - // Mark the order as available. - availableOrders[i] = true; + // Mark the order as available. + availableOrders[i] = true; - // Retrieve the order parameters. - OrderParameters memory parameters = ( - advancedOrder.parameters - ); + // Retrieve the order parameters. + OrderParameters memory parameters = (advancedOrder.parameters); - { - // Retrieve offer items. - OfferItem[] memory offer = parameters.offer; - - // Read length of offer array & place on the stack. - uint256 totalOfferItems = offer.length; - - // 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]; - - // Transfer to recipient if unspent amount is not - // zero. Note that the transfer will not be - // reflected in the executions array. - if (offerItem.startAmount != 0) { - _transfer( - _fromOfferItemToReceivedItemWithRecipient( - offerItem, - recipient - ), - parameters.offerer, - parameters.conduitKey, - accumulator - ); - } - - // Restore original amount on the offer item. - offerItem.startAmount = offerItem.endAmount; + { + // Retrieve offer items. + OfferItem[] memory offer = parameters.offer; + + // Read length of offer array & place on the stack. + uint256 totalOfferItems = offer.length; + + // 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]; + + // Transfer to recipient if unspent amount is not zero. + // Note that the transfer will not be reflected in the + // executions array. + if (offerItem.startAmount != 0) { + _transfer( + _fromOfferItemToReceivedItemWithRecipient( + offerItem, + recipient + ), + parameters.offerer, + parameters.conduitKey, + accumulator + ); } + + // Restore original amount on the offer item. + offerItem.startAmount = offerItem.endAmount; } + } - { - // Read consideration items & ensure they are fulfilled. - ConsiderationItem[] memory consideration = ( - parameters.consideration - ); + { + // Read consideration items & ensure they are fulfilled. + ConsiderationItem[] memory consideration = ( + parameters.consideration + ); - // Read length of consideration array & place on stack. - uint256 totalConsiderationItems = consideration.length; + // Read length of consideration array & place on stack. + uint256 totalConsiderationItems = consideration.length; - // Iterate over each consideration item. - for (uint256 j = 0; j < totalConsiderationItems; ++j) { - ConsiderationItem memory considerationItem = ( - consideration[j] - ); + // Iterate over each consideration item. + for (uint256 j = 0; j < totalConsiderationItems; ++j) { + ConsiderationItem memory considerationItem = ( + consideration[j] + ); - // Retrieve remaining amount on consideration item. - uint256 unmetAmount = considerationItem.startAmount; + // Retrieve remaining amount on consideration item. + uint256 unmetAmount = considerationItem.startAmount; - // Revert if the remaining amount is not zero. - if (unmetAmount != 0) { - _revertConsiderationNotMet(i, j, unmetAmount); - } + // Revert if the remaining amount is not zero. + if (unmetAmount != 0) { + _revertConsiderationNotMet(i, j, unmetAmount); + } - // Utilize assembly to restore the original value. - assembly { - // Write recipient to startAmount. - mstore( + // Utilize assembly to restore the original value. + assembly { + // Write recipient to startAmount. + mstore( + add( + considerationItem, + ReceivedItem_amount_offset + ), + mload( add( considerationItem, - ReceivedItem_amount_offset - ), - mload( - add( - considerationItem, - ConsiderationItem_recipient_offset - ) + ConsiderationItem_recipient_offset ) ) - } + ) } } } + } - // Trigger any accumulated transfers via call to the conduit. - _triggerIfArmed(accumulator); + // Trigger any accumulated transfers via call to the conduit. + _triggerIfArmed(accumulator); + // 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; ++i) { // Check restricted orders and contract orders. @@ -879,105 +877,6 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { orderHashes[i] ); } - } else { - // Iterate over each order. - for (uint256 i = 0; i < totalOrders; ++i) { - // Retrieve the order in question. - AdvancedOrder memory advancedOrder = advancedOrders[i]; - - // Skip the order in question if not being not fulfilled. - if (advancedOrder.numerator == 0) { - // Explicitly set availableOrders at the given index to - // guard against the possibility of dirtied memory. - availableOrders[i] = false; - continue; - } - - // Mark the order as available. - availableOrders[i] = true; - - // Retrieve the order parameters. - OrderParameters memory parameters = ( - advancedOrder.parameters - ); - - { - // Retrieve offer items. - OfferItem[] memory offer = parameters.offer; - - // Read length of offer array & place on the stack. - uint256 totalOfferItems = offer.length; - - // 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]; - - // Transfer to recipient if unspent amount is not - // zero. Note that the transfer will not be - // reflected in the executions array. - if (offerItem.startAmount != 0) { - _transfer( - _fromOfferItemToReceivedItemWithRecipient( - offerItem, - recipient - ), - parameters.offerer, - parameters.conduitKey, - accumulator - ); - } - - // Restore original amount on the offer item. - offerItem.startAmount = offerItem.endAmount; - } - } - - { - // Read consideration items & ensure they are fulfilled. - ConsiderationItem[] memory consideration = ( - parameters.consideration - ); - - // Read length of consideration array & place on stack. - uint256 totalConsiderationItems = consideration.length; - - // Iterate over each consideration item. - for (uint256 j = 0; j < totalConsiderationItems; ++j) { - ConsiderationItem memory considerationItem = ( - consideration[j] - ); - - // Retrieve remaining amount on consideration item. - uint256 unmetAmount = considerationItem.startAmount; - - // Revert if the remaining amount is not zero. - if (unmetAmount != 0) { - _revertConsiderationNotMet(i, j, unmetAmount); - } - - // Utilize assembly to restore the original value. - assembly { - // Write recipient to startAmount. - mstore( - add( - considerationItem, - ReceivedItem_amount_offset - ), - mload( - add( - considerationItem, - ConsiderationItem_recipient_offset - ) - ) - ) - } - } - } - } - - // Trigger any remaining accumulated transfers via conduit. - _triggerIfArmed(accumulator); } } diff --git a/contracts/lib/Verifiers.sol b/contracts/lib/Verifiers.sol index 2db47e2ba..46587429f 100644 --- a/contracts/lib/Verifiers.sol +++ b/contracts/lib/Verifiers.sol @@ -181,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; @@ -232,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. From 9ad538df2b5d861f57c9518a8f357b5789c500d5 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Mon, 13 Feb 2023 21:39:06 -0800 Subject: [PATCH 35/57] update gas report --- ...489de1c8ab348747842001594e09c49aab889.json | 516 ++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 .gas_reports/572489de1c8ab348747842001594e09c49aab889.json diff --git a/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json b/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json new file mode 100644 index 000000000..952678540 --- /dev/null +++ b/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json @@ -0,0 +1,516 @@ +{ + "commitHash": "572489de1c8ab348747842001594e09c49aab889", + "contractReports": { + "Conduit": { + "name": "Conduit", + "methods": [ + { + "method": "execute", + "min": 77459, + "max": 2186674, + "avg": 452589, + "calls": 6 + }, + { + "method": "executeBatch1155", + "min": null, + "max": null, + "avg": 97245, + "calls": 1 + }, + { + "method": "executeWithBatch1155", + "min": 97717, + "max": 361418, + "avg": 228761, + "calls": 4 + }, + { + "method": "updateChannel", + "min": null, + "max": null, + "avg": 45802, + "calls": 1 + } + ], + "bytecodeSize": 3071, + "deployedBytecodeSize": 3030 + }, + "ConduitController": { + "name": "ConduitController", + "methods": [ + { + "method": "acceptOwnership", + "min": null, + "max": null, + "avg": 32944, + "calls": 1 + }, + { + "method": "cancelOwnershipTransfer", + "min": null, + "max": null, + "avg": 27966, + "calls": 1 + }, + { + "method": "createConduit", + "min": 712802, + "max": 712970, + "avg": 712929, + "calls": 52 + }, + { + "method": "transferOwnership", + "min": null, + "max": null, + "avg": 50329, + "calls": 2 + }, + { + "method": "updateChannel", + "min": 34454, + "max": 121098, + "avg": 117240, + "calls": 70 + } + ], + "bytecodeSize": 12007, + "deployedBytecodeSize": 8660 + }, + "ConduitControllerMock": { + "name": "ConduitControllerMock", + "methods": [ + { + "method": "createConduit", + "min": 226092, + "max": 231533, + "avg": 229598, + "calls": 6 + } + ], + "bytecodeSize": 10541, + "deployedBytecodeSize": 7340 + }, + "EIP1271Wallet": { + "name": "EIP1271Wallet", + "methods": [ + { + "method": "approveNFT", + "min": null, + "max": null, + "avg": 49674, + "calls": 14 + }, + { + "method": "registerDigest", + "min": 22239, + "max": 44151, + "avg": 36847, + "calls": 3 + }, + { + "method": "revertWithMessage", + "min": null, + "max": null, + "avg": 21677, + "calls": 1 + }, + { + "method": "setValid", + "min": 21699, + "max": 43611, + "avg": 32655, + "calls": 2 + } + ], + "bytecodeSize": 2834, + "deployedBytecodeSize": 2656 + }, + "ExcessReturnDataRecipient": { + "name": "ExcessReturnDataRecipient", + "methods": [ + { + "method": "setRevertDataSize", + "min": null, + "max": null, + "avg": 43441, + "calls": 2 + } + ], + "bytecodeSize": 1907, + "deployedBytecodeSize": 1879 + }, + "PausableZone": { + "name": "PausableZone", + "methods": [ + { + "method": "cancelOrders", + "min": null, + "max": null, + "avg": 65315, + "calls": 1 + } + ], + "bytecodeSize": 5556, + "deployedBytecodeSize": 5450 + }, + "PausableZoneController": { + "name": "PausableZoneController", + "methods": [ + { + "method": "acceptOwnership", + "min": null, + "max": null, + "avg": 28942, + "calls": 1 + }, + { + "method": "assignOperator", + "min": null, + "max": null, + "avg": 50880, + "calls": 1 + }, + { + "method": "assignPauser", + "min": null, + "max": null, + "avg": 47183, + "calls": 1 + }, + { + "method": "cancelOrders", + "min": null, + "max": null, + "avg": 73870, + "calls": 1 + }, + { + "method": "cancelOwnershipTransfer", + "min": null, + "max": null, + "avg": 24578, + "calls": 1 + }, + { + "method": "createZone", + "min": 1154302, + "max": 1154314, + "avg": 1154313, + "calls": 31 + }, + { + "method": "executeMatchAdvancedOrders", + "min": null, + "max": null, + "avg": 288725, + "calls": 2 + }, + { + "method": "executeMatchOrders", + "min": null, + "max": null, + "avg": 282289, + "calls": 2 + }, + { + "method": "pause", + "min": 32875, + "max": 35006, + "avg": 33585, + "calls": 3 + }, + { + "method": "transferOwnership", + "min": null, + "max": null, + "avg": 47199, + "calls": 2 + } + ], + "bytecodeSize": 17744, + "deployedBytecodeSize": 11975 + }, + "Reenterer": { + "name": "Reenterer", + "methods": [ + { + "method": "prepare", + "min": 49267, + "max": 2351702, + "avg": 1061785, + "calls": 26 + } + ], + "bytecodeSize": 2726, + "deployedBytecodeSize": 2698 + }, + "Seaport": { + "name": "Seaport", + "methods": [ + { + "method": "cancel", + "min": 41250, + "max": 58422, + "avg": 54045, + "calls": 16 + }, + { + "method": "fulfillAdvancedOrder", + "min": 96300, + "max": 225169, + "avg": 159834, + "calls": 188 + }, + { + "method": "fulfillAvailableAdvancedOrders", + "min": 149638, + "max": 350725, + "avg": 208123, + "calls": 29 + }, + { + "method": "fulfillAvailableOrders", + "min": 165022, + "max": 215788, + "avg": 201418, + "calls": 21 + }, + { + "method": "fulfillBasicOrder", + "min": 90639, + "max": 1621603, + "avg": 598687, + "calls": 187 + }, + { + "method": "fulfillBasicOrder_efficient_6GL6yc", + "min": 90237, + "max": 111468, + "avg": 100853, + "calls": 6 + }, + { + "method": "fulfillOrder", + "min": 119409, + "max": 225056, + "avg": 177743, + "calls": 105 + }, + { + "method": "incrementCounter", + "min": null, + "max": null, + "avg": 47054, + "calls": 6 + }, + { + "method": "matchAdvancedOrders", + "min": 179562, + "max": 300213, + "avg": 248872, + "calls": 77 + }, + { + "method": "matchOrders", + "min": 157486, + "max": 348207, + "avg": 264525, + "calls": 151 + }, + { + "method": "validate", + "min": 53201, + "max": 83886, + "avg": 73539, + "calls": 29 + } + ], + "bytecodeSize": 26123, + "deployedBytecodeSize": 24398 + }, + "SeaportRouter": { + "name": "SeaportRouter", + "methods": [ + { + "method": "fulfillAvailableAdvancedOrders", + "min": 183954, + "max": 311835, + "avg": 233586, + "calls": 6 + } + ], + "bytecodeSize": 6345, + "deployedBytecodeSize": 6158 + }, + "TestContractOfferer": { + "name": "TestContractOfferer", + "methods": [ + { + "method": "activate", + "min": 201543, + "max": 246674, + "avg": 205516, + "calls": 33 + }, + { + "method": "activateWithCriteria", + "min": null, + "max": null, + "avg": 201834, + "calls": 1 + }, + { + "method": "extendAvailable", + "min": null, + "max": null, + "avg": 50704, + "calls": 1 + }, + { + "method": "extendRequired", + "min": null, + "max": null, + "avg": 45780, + "calls": 1 + } + ], + "bytecodeSize": 8462, + "deployedBytecodeSize": 8265 + }, + "TestContractOffererNativeToken": { + "name": "TestContractOffererNativeToken", + "methods": [ + { + "method": "activate", + "min": null, + "max": null, + "avg": 139181, + "calls": 1 + } + ], + "bytecodeSize": 6940, + "deployedBytecodeSize": 6764 + }, + "TestERC1155": { + "name": "TestERC1155", + "methods": [ + { + "method": "mint", + "min": 47223, + "max": 49903, + "avg": 49474, + "calls": 268 + }, + { + "method": "setApprovalForAll", + "min": 26102, + "max": 46002, + "avg": 45686, + "calls": 504 + } + ], + "bytecodeSize": 4173, + "deployedBytecodeSize": 4145 + }, + "TestERC20": { + "name": "TestERC20", + "methods": [ + { + "method": "approve", + "min": 28881, + "max": 46245, + "avg": 45766, + "calls": 316 + }, + { + "method": "blockTransfer", + "min": 21978, + "max": 43890, + "avg": 32934, + "calls": 4 + }, + { + "method": "mint", + "min": 33994, + "max": 68458, + "avg": 67414, + "calls": 153 + }, + { + "method": "setNoReturnData", + "min": 21926, + "max": 43838, + "avg": 32882, + "calls": 2 + } + ], + "bytecodeSize": 5807, + "deployedBytecodeSize": 4636 + }, + "TestERC721": { + "name": "TestERC721", + "methods": [ + { + "method": "mint", + "min": 51396, + "max": 68796, + "avg": 65786, + "calls": 286 + }, + { + "method": "setApprovalForAll", + "min": 26195, + "max": 46095, + "avg": 45516, + "calls": 482 + } + ], + "bytecodeSize": 5238, + "deployedBytecodeSize": 4451 + }, + "TestInvalidContractOfferer": { + "name": "TestInvalidContractOfferer", + "methods": [ + { + "method": "activate", + "min": 201543, + "max": 201555, + "avg": 201549, + "calls": 2 + } + ], + "bytecodeSize": 7954, + "deployedBytecodeSize": 7764 + }, + "TestInvalidContractOffererRatifyOrder": { + "name": "TestInvalidContractOffererRatifyOrder", + "methods": [ + { + "method": "activate", + "min": null, + "max": null, + "avg": 201558, + "calls": 1 + } + ], + "bytecodeSize": 7957, + "deployedBytecodeSize": 7760 + }, + "TransferHelper": { + "name": "TransferHelper", + "methods": [ + { + "method": "bulkTransfer", + "min": 77935, + "max": 1468298, + "avg": 641594, + "calls": 3 + } + ], + "bytecodeSize": 4140, + "deployedBytecodeSize": 3865 + } + } +} \ No newline at end of file From 61d6d2051182b4340beb07f70a9b9aa8b1a26bb6 Mon Sep 17 00:00:00 2001 From: djviau Date: Tue, 14 Feb 2023 09:45:35 -0500 Subject: [PATCH 36/57] linting, cleanup, and a tiny refactor --- .../test/TransferValidationZoneOfferer.sol | 170 ++++++------------ 1 file changed, 51 insertions(+), 119 deletions(-) diff --git a/contracts/test/TransferValidationZoneOfferer.sol b/contracts/test/TransferValidationZoneOfferer.sol index 4ab69eaf5..f9e819b23 100644 --- a/contracts/test/TransferValidationZoneOfferer.sol +++ b/contracts/test/TransferValidationZoneOfferer.sol @@ -9,8 +9,8 @@ import { import { ReceivedItem, - SpentItem, Schema, + SpentItem, ZoneParameters } from "../lib/ConsiderationStructs.sol"; @@ -31,104 +31,34 @@ contract TransferValidationZoneOfferer is constructor() {} - function validateOrder(ZoneParameters calldata zoneParameters) - external - view - override - returns (bytes4 validOrderMagicValue) - { + /** + * @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. - address recipient; - ItemType itemType; - ReceivedItem memory receivedItem; // Check if all consideration items have been received. - for (uint256 i = 0; i < zoneParameters.consideration.length; i++) { - // Check if the consideration item has been received. - receivedItem = zoneParameters.consideration[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 - ); - } - } - - // Fulfiller should receive all offer items. - address fulfiller = zoneParameters.fulfiller; - SpentItem memory spentItem; + _assertValidReceivedItems(zoneParameters.consideration); // Check if all offer items have been spent. - for (uint256 i = 0; i < zoneParameters.offer.length; i++) { - // Check if the offer item has been spent. - spentItem = zoneParameters.offer[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 - ); - } - } + _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, + * @dev Generates an order with the specified minimum and maximum spent + * items. */ function generateOrder( address, @@ -164,23 +94,30 @@ contract TransferValidationZoneOfferer is 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 */ + 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 + ) external view override returns (bytes4 /* ratifyOrderMagicValue */) { + // Ratify the order. - // Ensure that the offerer or recipient has received all consideration items. + // Ensure that the offerer or recipient has received all consideration + // items. _assertValidReceivedItems(maximumSpent); // Get the fulfiller address from the context. @@ -189,7 +126,7 @@ contract TransferValidationZoneOfferer is // Ensure that the fulfiller has received all offer items. _assertValidSpentItems(fulfiller, minimumReceived); - return TransferValidationZoneOfferer.ratifyOrder.selector; + return this.ratifyOrder.selector; } function getSeaportMetadata() @@ -205,11 +142,9 @@ contract TransferValidationZoneOfferer is schemas[0].metadata = new bytes(0); } - function _convertSpentToReceived(SpentItem[] calldata spentItems) - internal - view - returns (ReceivedItem[] memory) - { + function _convertSpentToReceived( + SpentItem[] calldata spentItems + ) internal view returns (ReceivedItem[] memory) { ReceivedItem[] memory receivedItems = new ReceivedItem[]( spentItems.length ); @@ -219,11 +154,9 @@ contract TransferValidationZoneOfferer is return receivedItems; } - function _convertSpentToReceived(SpentItem calldata spentItem) - internal - view - returns (ReceivedItem memory) - { + function _convertSpentToReceived( + SpentItem calldata spentItem + ) internal view returns (ReceivedItem memory) { return ReceivedItem({ itemType: spentItem.itemType, @@ -234,10 +167,9 @@ contract TransferValidationZoneOfferer is }); } - function _assertValidReceivedItems(ReceivedItem[] calldata receivedItems) - internal - view - { + function _assertValidReceivedItems( + ReceivedItem[] calldata receivedItems + ) internal view { address recipient; ItemType itemType; ReceivedItem memory receivedItem; @@ -325,10 +257,10 @@ contract TransferValidationZoneOfferer is } } - function _assertNativeTokenTransfer(uint256 amount, address recipient) - internal - view - { + function _assertNativeTokenTransfer( + uint256 amount, + address recipient + ) internal view { if (amount > address(recipient).balance) { revert InvalidBalance(); } From 18bd267286ec90e1cb00913154612b4aba73ad26 Mon Sep 17 00:00:00 2001 From: Benjamin LeFevre Date: Tue, 14 Feb 2023 10:14:40 -0600 Subject: [PATCH 37/57] Fix reference, rename test zone --- config/.solcover-reference.js | 1 + config/.solcover.js | 2 +- ... => TestTransferValidationZoneOfferer.sol} | 63 ++++++++++++------- reference/shim/Shim.sol | 4 ++ test/zone.spec.ts | 16 +++-- 5 files changed, 57 insertions(+), 29 deletions(-) rename contracts/test/{TransferValidationZoneOfferer.sol => TestTransferValidationZoneOfferer.sol} (88%) 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 5fe64f17d..8cac48297 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -44,7 +44,7 @@ module.exports = { "test/ConduitMockInvalidMagic.sol", "test/ConduitMockRevertBytes.sol", "test/ConduitMockRevertNoReason.sol", - "test/TransferValidationZoneOfferer.sol", + "test/TestTransferValidationZoneOfferer.sol", "zones/PausableZone.sol", "zones/PausableZoneController.sol", "zones/interfaces/PausableZoneControllerInterface.sol", diff --git a/contracts/test/TransferValidationZoneOfferer.sol b/contracts/test/TestTransferValidationZoneOfferer.sol similarity index 88% rename from contracts/test/TransferValidationZoneOfferer.sol rename to contracts/test/TestTransferValidationZoneOfferer.sol index f9e819b23..ec59be36b 100644 --- a/contracts/test/TransferValidationZoneOfferer.sol +++ b/contracts/test/TestTransferValidationZoneOfferer.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.13; import { ERC20Interface, @@ -22,7 +22,7 @@ import { import { ZoneInterface } from "../interfaces/ZoneInterface.sol"; -contract TransferValidationZoneOfferer is +contract TestTransferValidationZoneOfferer is ContractOffererInterface, ZoneInterface { @@ -39,9 +39,12 @@ contract TransferValidationZoneOfferer is * * @return validOrderMagicValue The magic value to indicate things are OK. */ - function validateOrder( - ZoneParameters calldata zoneParameters - ) external view override returns (bytes4 validOrderMagicValue) { + 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. @@ -108,12 +111,19 @@ contract TransferValidationZoneOfferer is * @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 */, + SpentItem[] calldata minimumReceived, /* offer */ + ReceivedItem[] calldata maximumSpent, /* consideration */ + bytes calldata context, /* context */ + bytes32[] calldata, /* orderHashes */ uint256 /* contractNonce */ - ) external view override returns (bytes4 /* ratifyOrderMagicValue */) { + ) + external + view + override + returns ( + bytes4 /* ratifyOrderMagicValue */ + ) + { // Ratify the order. // Ensure that the offerer or recipient has received all consideration @@ -136,15 +146,17 @@ contract TransferValidationZoneOfferer is returns (string memory name, Schema[] memory schemas) { // Return the metadata. - name = "TransferValidationZoneOfferer"; + 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) { + function _convertSpentToReceived(SpentItem[] calldata spentItems) + internal + view + returns (ReceivedItem[] memory) + { ReceivedItem[] memory receivedItems = new ReceivedItem[]( spentItems.length ); @@ -154,9 +166,11 @@ contract TransferValidationZoneOfferer is return receivedItems; } - function _convertSpentToReceived( - SpentItem calldata spentItem - ) internal view returns (ReceivedItem memory) { + function _convertSpentToReceived(SpentItem calldata spentItem) + internal + view + returns (ReceivedItem memory) + { return ReceivedItem({ itemType: spentItem.itemType, @@ -167,9 +181,10 @@ contract TransferValidationZoneOfferer is }); } - function _assertValidReceivedItems( - ReceivedItem[] calldata receivedItems - ) internal view { + function _assertValidReceivedItems(ReceivedItem[] calldata receivedItems) + internal + view + { address recipient; ItemType itemType; ReceivedItem memory receivedItem; @@ -257,10 +272,10 @@ contract TransferValidationZoneOfferer is } } - function _assertNativeTokenTransfer( - uint256 amount, - address recipient - ) internal view { + function _assertNativeTokenTransfer(uint256 amount, address recipient) + internal + view + { if (amount > address(recipient).balance) { revert InvalidBalance(); } 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/test/zone.spec.ts b/test/zone.spec.ts index e3e867562..9628032c1 100644 --- a/test/zone.spec.ts +++ b/test/zone.spec.ts @@ -1004,7 +1004,6 @@ describe(`Zone - PausableZone (Seaport v${VERSION})`, function () { }); describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { - if (process.env.REFERENCE) return; const { provider } = ethers; const owner = new ethers.Wallet(randomHex(32), provider); @@ -1063,7 +1062,10 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { const offer = [getTestItem721(nftId)]; const TransferValidationZoneOffererFactory = - await ethers.getContractFactory("TransferValidationZoneOfferer", owner); + await ethers.getContractFactory( + "TestTransferValidationZoneOfferer", + owner + ); const zoneAddr = await TransferValidationZoneOffererFactory.deploy(); @@ -1118,7 +1120,10 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { ]; const TransferValidationZoneOffererFactory = - await ethers.getContractFactory("TransferValidationZoneOfferer", owner); + await ethers.getContractFactory( + "TestTransferValidationZoneOfferer", + owner + ); const zoneAddr = await TransferValidationZoneOffererFactory.deploy(); @@ -1175,7 +1180,10 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { ]; const TransferValidationZoneOffererFactory = - await ethers.getContractFactory("TransferValidationZoneOfferer", owner); + await ethers.getContractFactory( + "TestTransferValidationZoneOfferer", + owner + ); const zoneAddr = await TransferValidationZoneOffererFactory.deploy(); From 4b37cb34cde6d2e3ac7f41e38634a0d88bd26589 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Tue, 14 Feb 2023 08:41:22 -0800 Subject: [PATCH 38/57] Update contracts/lib/ConsiderationBase.sol --- contracts/lib/ConsiderationBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index 6df3f0674..c0852248a 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -434,7 +434,7 @@ contract ConsiderationBase is } } default { - switch lt(treeHeight, 21) + switch lt(treeHeight, 22) case 1 { typeHash := add( add( From b20d35eed1157fbabeb890ad2f99cf0b9f34479b Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 09:38:02 -0800 Subject: [PATCH 39/57] update version to 1.3 --- contracts/Seaport.sol | 2 +- .../interfaces/ConsiderationInterface.sol | 2 +- contracts/interfaces/SeaportInterface.sol | 2 +- contracts/lib/Consideration.sol | 2 +- contracts/lib/ConsiderationBase.sol | 2 +- contracts/lib/ConsiderationConstants.sol | 2 +- reference/ReferenceConsideration.sol | 2 +- reference/lib/ReferenceConsiderationBase.sol | 73 ++++++++++--------- test/foundry/GetterTests.t.sol | 2 +- test/utils/helpers.ts | 2 +- 10 files changed, 46 insertions(+), 45 deletions(-) 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/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/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 c0852248a..ad380c5bf 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -224,7 +224,7 @@ 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( diff --git a/contracts/lib/ConsiderationConstants.sol b/contracts/lib/ConsiderationConstants.sol index a1e75ac38..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; 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/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/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); From 8c54f89ccc361aa3967ecbc42d733abe1776b59a Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 09:57:45 -0800 Subject: [PATCH 40/57] move excess native token transfer to before post-execution checks --- contracts/lib/OrderCombiner.sol | 43 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index 10e5d3028..648625a86 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -784,7 +784,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { availableOrders[i] = true; // Retrieve the order parameters. - OrderParameters memory parameters = (advancedOrder.parameters); + OrderParameters memory parameters = advancedOrder.parameters; { // Retrieve offer items. @@ -860,26 +860,11 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { } } } - - // Trigger any accumulated transfers via call to the conduit. - _triggerIfArmed(accumulator); - - // 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; ++i) { - // Check restricted orders and contract orders. - _assertRestrictedAdvancedOrderValidity( - advancedOrders[i], - orderHashes, - orderHashes[i] - ); - } - } } + // Trigger any accumulated transfers via call to the conduit. + _triggerIfArmed(accumulator); + // Determine whether any native token balance remains. uint256 remainingNativeTokenBalance; assembly { @@ -894,6 +879,26 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier { ); } + // 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. _clearReentrancyGuard(); From 84a6b12749d308b329fb67917d2b0e32b1c2f762 Mon Sep 17 00:00:00 2001 From: Benjamin LeFevre Date: Tue, 14 Feb 2023 12:43:19 -0600 Subject: [PATCH 41/57] Add failing test for 1.2->1.3 --- test/zone.spec.ts | 117 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/test/zone.spec.ts b/test/zone.spec.ts index 9628032c1..5d00367fd 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, @@ -1008,12 +1010,16 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { 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 () => { @@ -1027,11 +1033,15 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { ({ checkExpectedEvents, + conduitKeyOne, + conduitOne, createOrder, getTestItem721, + getTestItem1155, getTestItem721WithCriteria, marketplaceContract, mintAndApprove721, + mintAndApprove1155, withBalanceChecks, } = await seaportFixture(owner)); }); @@ -1220,4 +1230,111 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { 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); + + 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"); + }); }); From bd335ca0ab9db80af2973deecc4b695a344237a8 Mon Sep 17 00:00:00 2001 From: Benjamin LeFevre Date: Tue, 14 Feb 2023 12:58:24 -0600 Subject: [PATCH 42/57] Add switch based on version --- test/zone.spec.ts | 81 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/test/zone.spec.ts b/test/zone.spec.ts index 5d00367fd..c58fc2c63 100644 --- a/test/zone.spec.ts +++ b/test/zone.spec.ts @@ -1320,21 +1320,70 @@ describe(`Zone - Transfer Validation (Seaport v${VERSION})`, function () { ], ].map(toFulfillmentComponents); - 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"); + // 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 + ); + } }); }); From 745ea67fcbec5f11f3101913daa5e2ade5c06e9d Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 13:46:56 -0800 Subject: [PATCH 43/57] refactor typehash lookup as while loop --- contracts/lib/ConsiderationBase.sol | 355 ++++++++++++++++------------ 1 file changed, 204 insertions(+), 151 deletions(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index ad380c5bf..30198f637 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -229,40 +229,40 @@ contract ConsiderationBase is // 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" ")" ); @@ -270,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" ")" ) ); @@ -309,169 +309,222 @@ contract ConsiderationBase is function _lookupBulkOrderTypehash( uint256 treeHeight ) internal pure returns (bytes32 typeHash) { + // Utilize assembly to efficiently retrieve correct bulk order typehash. assembly { - switch lt(treeHeight, 13) - case 1 { - switch lt(treeHeight, 7) - case 1 { - switch lt(treeHeight, 4) - case 1 { - typeHash := add( - add( - mul( - eq(treeHeight, 1), - BulkOrder_Typehash_Height_One - ), + // Progress until typehash is located; break before loop completes. + for {} 1 {} { + // Handle tree heights one through eight. + if iszero(gt(treeHeight, 8)) { + // Handle tree heights one through four. + if iszero(gt(treeHeight, 4)) { + // Handle tree heights one and two. + if iszero(gt(treeHeight, 2)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_One, mul( eq(treeHeight, 2), - BulkOrder_Typehash_Height_Two + xor( + BulkOrder_Typehash_Height_One, + BulkOrder_Typehash_Height_Two + ) ) - ), + ) + + // Exit the loop once typehash has been located. + break + } + + // Handle height three and four via branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_Three, mul( - eq(treeHeight, 3), - BulkOrder_Typehash_Height_Three + eq(treeHeight, 4), + xor( + BulkOrder_Typehash_Height_Three, + BulkOrder_Typehash_Height_Four + ) ) ) + + // Exit the loop once typehash has been located. + break } - default { - typeHash := add( - add( - mul( - eq(treeHeight, 4), - BulkOrder_Typehash_Height_Four - ), - mul( - eq(treeHeight, 5), - BulkOrder_Typehash_Height_Five - ) - ), + + // Handle tree height five and six. + if iszero(gt(treeHeight, 6)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_Five, mul( eq(treeHeight, 6), - BulkOrder_Typehash_Height_Six + xor( + BulkOrder_Typehash_Height_Five, + BulkOrder_Typehash_Height_Six + ) ) ) + + // Exit the loop once typehash has been located. + break } - } - default { - switch lt(treeHeight, 10) - case 1 { - typeHash := add( - add( - mul( - eq(treeHeight, 7), - BulkOrder_Typehash_Height_Seven - ), - mul( - eq(treeHeight, 8), - BulkOrder_Typehash_Height_Eight - ) - ), - mul( - eq(treeHeight, 9), - BulkOrder_Typehash_Height_Nine + + // Handle height seven and eight via branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_Seven, + mul( + eq(treeHeight, 4), + xor( + BulkOrder_Typehash_Height_Seven, + BulkOrder_Typehash_Height_Eight ) ) - } - default { - typeHash := add( - add( + ) + + // Exit the loop once typehash has been located. + break + } + + // Handle tree height nine through sixteen. + if iszero(gt(treeHeight, 16)) { + // Handle tree height nine through twelve. + if iszero(gt(treeHeight, 12)) { + // Handle tree height nine and ten. + if iszero(gt(treeHeight, 10)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_Nine, mul( eq(treeHeight, 10), - BulkOrder_Typehash_Height_Ten - ), - mul( - eq(treeHeight, 11), - BulkOrder_Typehash_Height_Eleven + xor( + BulkOrder_Typehash_Height_Nine, + BulkOrder_Typehash_Height_Ten + ) ) - ), + ) + + // Exit the loop once typehash has been located. + break + } + + // Handle height eleven and twelve via branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_Eleven, mul( eq(treeHeight, 12), - BulkOrder_Typehash_Height_Twelve + xor( + BulkOrder_Typehash_Height_Eleven, + BulkOrder_Typehash_Height_Twelve + ) ) ) + + // Exit the loop once typehash has been located. + break } - } - } - default { - switch lt(treeHeight, 19) - case 1 { - switch lt(treeHeight, 16) - case 1 { - typeHash := add( - add( - mul( - eq(treeHeight, 13), - BulkOrder_Typehash_Height_Thirteen - ), - mul( - eq(treeHeight, 14), + + // Handle tree height thirteen and fourteen. + if iszero(gt(treeHeight, 14)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_Thirteen, + mul( + eq(treeHeight, 14), + xor( + BulkOrder_Typehash_Height_Thirteen, BulkOrder_Typehash_Height_Fourteen ) - ), - mul( - eq(treeHeight, 15), - BulkOrder_Typehash_Height_Fifteen ) ) + + // Exit the loop once typehash has been located. + break } - default { - typeHash := add( - add( - mul( - eq(treeHeight, 16), - BulkOrder_Typehash_Height_Sixteen - ), - mul( - eq(treeHeight, 17), - BulkOrder_Typehash_Height_Seventeen - ) - ), - mul( - eq(treeHeight, 18), - BulkOrder_Typehash_Height_Eighteen + + // Handle height fifteen and sixteen via branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_Fifteen, + mul( + eq(treeHeight, 16), + xor( + BulkOrder_Typehash_Height_Fifteen, + BulkOrder_Typehash_Height_Sixteen ) ) - } + ) + + // Exit the loop once typehash has been located. + break } - default { - switch lt(treeHeight, 22) - case 1 { - typeHash := add( - add( - mul( - eq(treeHeight, 19), - BulkOrder_Typehash_Height_Nineteen - ), - mul( - eq(treeHeight, 20), - BulkOrder_Typehash_Height_Twenty - ) - ), + + // Handle tree height seventeen through twenty. + if iszero(gt(treeHeight, 20)) { + // Handle tree height seventeen and eighteen. + if iszero(gt(treeHeight, 18)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_Seventeen, mul( - eq(treeHeight, 21), - BulkOrder_Typehash_Height_TwentyOne + eq(treeHeight, 18), + xor( + BulkOrder_Typehash_Height_Seventeen, + BulkOrder_Typehash_Height_Eighteen + ) ) ) + + // Exit the loop once typehash has been located. + break } - default { - typeHash := add( - add( - mul( - eq(treeHeight, 22), - BulkOrder_Typehash_Height_TwentyTwo - ), - mul( - eq(treeHeight, 23), - BulkOrder_Typehash_Height_TwentyThree - ) - ), - mul( - eq(treeHeight, 24), - BulkOrder_Typehash_Height_TwentyFour + + // Handle height nineteen and twenty via branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_Nineteen, + mul( + eq(treeHeight, 20), + xor( + BulkOrder_Typehash_Height_Nineteen, + BulkOrder_Typehash_Height_Twenty ) ) - } + ) + + // Exit the loop once typehash has been located. + break } + + // Handle tree height twenty-one and twenty-two. + if iszero(gt(treeHeight, 22)) { + // Utilize branchless logic to determine typehash. + typeHash := xor( + BulkOrder_Typehash_Height_TwentyOne, + mul( + eq(treeHeight, 22), + xor( + BulkOrder_Typehash_Height_TwentyOne, + BulkOrder_Typehash_Height_TwentyTwo + ) + ) + ) + + // Exit the loop once typehash has been located. + break + } + + // Handle height twenty-three & twenty-four w/ branchless logic. + typeHash := xor( + BulkOrder_Typehash_Height_TwentyThree, + mul( + eq(treeHeight, 24), + xor( + BulkOrder_Typehash_Height_TwentyThree, + BulkOrder_Typehash_Height_TwentyFour + ) + ) + ) + + // Exit the loop once typehash has been located. + break } } } From c7ae3942cf02742d7bd8ad027e4ee2e9bcfef3d1 Mon Sep 17 00:00:00 2001 From: Stephan Min Date: Tue, 14 Feb 2023 16:54:55 -0500 Subject: [PATCH 44/57] add test (still need zone) --- test/foundry/zone/PostFulfillmentCheck.t.sol | 354 ++++++++++++++----- 1 file changed, 256 insertions(+), 98 deletions(-) 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; + } } From 436ce7515a9cfa3e4d355d9837ec954ce26333c1 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 13:59:34 -0800 Subject: [PATCH 45/57] fix a constant --- contracts/lib/ConsiderationBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index 30198f637..a17a25ba7 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -373,7 +373,7 @@ contract ConsiderationBase is typeHash := xor( BulkOrder_Typehash_Height_Seven, mul( - eq(treeHeight, 4), + eq(treeHeight, 8), xor( BulkOrder_Typehash_Height_Seven, BulkOrder_Typehash_Height_Eight From e1370ae1874f9fde81d87d6ffb806e62c8a9a2aa Mon Sep 17 00:00:00 2001 From: Stephan Min Date: Tue, 14 Feb 2023 17:05:37 -0500 Subject: [PATCH 46/57] add zone, update abridged interfaces --- .../interfaces/AbridgedTokenInterfaces.sol | 22 ++ .../TestTransferValidationZoneOfferer.sol | 299 ++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 contracts/test/TestTransferValidationZoneOfferer.sol diff --git a/contracts/interfaces/AbridgedTokenInterfaces.sol b/contracts/interfaces/AbridgedTokenInterfaces.sol index 914279c7b..ccf419725 100644 --- a/contracts/interfaces/AbridgedTokenInterfaces.sol +++ b/contracts/interfaces/AbridgedTokenInterfaces.sol @@ -34,6 +34,15 @@ interface ERC20Interface { 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 +125,17 @@ 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/test/TestTransferValidationZoneOfferer.sol b/contracts/test/TestTransferValidationZoneOfferer.sol new file mode 100644 index 000000000..69eda15e2 --- /dev/null +++ b/contracts/test/TestTransferValidationZoneOfferer.sol @@ -0,0 +1,299 @@ +//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(); + } + } +} From 6bc735f1d3a360fcc9c6cebf4b571558e1a196c9 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Mon, 13 Feb 2023 16:56:54 -0800 Subject: [PATCH 47/57] add seaport-sol --- .gitmodules | 3 +++ lib/seaport-sol | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/seaport-sol diff --git a/.gitmodules b/.gitmodules index 4fcf68b13..c4d2064d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate +[submodule "lib/seaport-sol"] + path = lib/seaport-sol + url = https://github.com/projectopensea/seaport-sol diff --git a/lib/seaport-sol b/lib/seaport-sol new file mode 160000 index 000000000..1309e1563 --- /dev/null +++ b/lib/seaport-sol @@ -0,0 +1 @@ +Subproject commit 1309e15636dcb5eaa0f363a7c9261918a10194f8 From 68fb4784a3df4c08f948d9bfd8e58f7af1342ff8 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Tue, 14 Feb 2023 13:28:18 -0800 Subject: [PATCH 48/57] test fulfillavailableorders with post-execution validation zone, add sol helpers --- .gitmodules | 3 - contracts/helpers/sol/README.md | 6 + .../sol/lib/AdditionalRecipientLib.sol | 166 ++ .../helpers/sol/lib/AdvancedOrderLib.sol | 204 +++ contracts/helpers/sol/lib/ArrayLib.sol | 21 + .../sol/lib/BasicOrderParametersLib.sol | 361 +++++ .../helpers/sol/lib/ConsiderationItemLib.sol | 274 ++++ .../helpers/sol/lib/CriteriaResolverLib.sol | 221 +++ contracts/helpers/sol/lib/ExecutionLib.sol | 168 ++ .../sol/lib/FulfillmentComponentLib.sol | 184 +++ contracts/helpers/sol/lib/FulfillmentLib.sol | 170 ++ contracts/helpers/sol/lib/OfferItemLib.sol | 235 +++ .../helpers/sol/lib/OrderComponentsLib.sol | 296 ++++ contracts/helpers/sol/lib/OrderLib.sol | 165 ++ .../helpers/sol/lib/OrderParametersLib.sol | 299 ++++ contracts/helpers/sol/lib/ReceivedItemLib.sol | 260 +++ contracts/helpers/sol/lib/SeaportArrays.sol | 1390 +++++++++++++++++ contracts/helpers/sol/lib/SeaportEnumsLib.sol | 52 + .../helpers/sol/lib/SeaportStructLib.sol | 20 + contracts/helpers/sol/lib/SpentItemLib.sol | 224 +++ contracts/helpers/sol/lib/StructCopier.sol | 510 ++++++ foundry.toml | 5 +- lib/forge-std | 2 +- lib/murky | 2 +- lib/openzeppelin-contracts | 2 +- lib/seaport-sol | 1 - lib/solmate | 2 +- remappings.txt | 5 - test/foundry/helpers/sol/BaseTest.sol | 409 +++++ .../sol/lib/AdditionalRecipientLib.t.sol | 76 + .../helpers/sol/lib/AdvancedOrderLib.t.sol | 120 ++ .../sol/lib/BasicOrderParametersLib.t.sol | 255 +++ .../sol/lib/ConsiderationItemLib.t.sol | 114 ++ .../helpers/sol/lib/CriteriaResolverLib.t.sol | 100 ++ .../helpers/sol/lib/ExecutionsLib.t.sol | 124 ++ .../sol/lib/FulfillmentComponentLib.t.sol | 79 + .../helpers/sol/lib/FulfillmentLib.t.sol | 91 ++ .../helpers/sol/lib/OfferItemLib.t.sol | 103 ++ .../helpers/sol/lib/OrderComponentsLib.t.sol | 122 ++ test/foundry/helpers/sol/lib/OrderLib.t.sol | 60 + .../helpers/sol/lib/OrderParametersLib.t.sol | 122 ++ .../helpers/sol/lib/ReceivedItemLib.t.sol | 104 ++ .../helpers/sol/lib/SpentItemLib.t.sol | 90 ++ test/foundry/utils/BaseOrderTest.sol | 27 + .../TestTransferValidationZoneOfferer.t.sol | 279 ++++ 45 files changed, 7508 insertions(+), 15 deletions(-) create mode 100644 contracts/helpers/sol/README.md create mode 100644 contracts/helpers/sol/lib/AdditionalRecipientLib.sol create mode 100644 contracts/helpers/sol/lib/AdvancedOrderLib.sol create mode 100644 contracts/helpers/sol/lib/ArrayLib.sol create mode 100644 contracts/helpers/sol/lib/BasicOrderParametersLib.sol create mode 100644 contracts/helpers/sol/lib/ConsiderationItemLib.sol create mode 100644 contracts/helpers/sol/lib/CriteriaResolverLib.sol create mode 100644 contracts/helpers/sol/lib/ExecutionLib.sol create mode 100644 contracts/helpers/sol/lib/FulfillmentComponentLib.sol create mode 100644 contracts/helpers/sol/lib/FulfillmentLib.sol create mode 100644 contracts/helpers/sol/lib/OfferItemLib.sol create mode 100644 contracts/helpers/sol/lib/OrderComponentsLib.sol create mode 100644 contracts/helpers/sol/lib/OrderLib.sol create mode 100644 contracts/helpers/sol/lib/OrderParametersLib.sol create mode 100644 contracts/helpers/sol/lib/ReceivedItemLib.sol create mode 100644 contracts/helpers/sol/lib/SeaportArrays.sol create mode 100644 contracts/helpers/sol/lib/SeaportEnumsLib.sol create mode 100644 contracts/helpers/sol/lib/SeaportStructLib.sol create mode 100644 contracts/helpers/sol/lib/SpentItemLib.sol create mode 100644 contracts/helpers/sol/lib/StructCopier.sol delete mode 160000 lib/seaport-sol delete mode 100644 remappings.txt create mode 100644 test/foundry/helpers/sol/BaseTest.sol create mode 100644 test/foundry/helpers/sol/lib/AdditionalRecipientLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/AdvancedOrderLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/ConsiderationItemLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/CriteriaResolverLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/ExecutionsLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/FulfillmentComponentLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/FulfillmentLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/OfferItemLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/OrderComponentsLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/OrderLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/OrderParametersLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/ReceivedItemLib.t.sol create mode 100644 test/foundry/helpers/sol/lib/SpentItemLib.t.sol create mode 100644 test/foundry/zone/TestTransferValidationZoneOfferer.t.sol diff --git a/.gitmodules b/.gitmodules index c4d2064d9..4fcf68b13 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate -[submodule "lib/seaport-sol"] - path = lib/seaport-sol - url = https://github.com/projectopensea/seaport-sol 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..7ce66547c --- /dev/null +++ b/contracts/helpers/sol/lib/ArrayLib.sol @@ -0,0 +1,21 @@ +// 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/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/seaport-sol b/lib/seaport-sol deleted file mode 160000 index 1309e1563..000000000 --- a/lib/seaport-sol +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1309e15636dcb5eaa0f363a7c9261918a10194f8 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/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/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..7af86b4df --- /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/zone/TestTransferValidationZoneOfferer.t.sol b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol new file mode 100644 index 000000000..6bab4f3a7 --- /dev/null +++ b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol @@ -0,0 +1,279 @@ +// 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 differes by what is offered + offerArray = SeaportArrays.OfferItems( + OfferItemLib + .fromDefault(SINGLE_721) + .withToken(address(test721_2)) + .withIdentifierOrCriteria(1) + ); + + 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); + } +} From 512915113d0cf368760640e9a98302ed58916579 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Tue, 14 Feb 2023 13:40:00 -0800 Subject: [PATCH 49/57] tweak comments --- test/foundry/zone/TestTransferValidationZoneOfferer.t.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol index 6bab4f3a7..f1fa1ba97 100644 --- a/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol +++ b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol @@ -218,14 +218,16 @@ contract TestTransferValidationZoneOffererTest is BaseOrderTest { .withConsideration(considerationArray) .withCounter(context.seaport.getCounter(offerer1.addr)); - // second order components only differes by what is offered + // 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); From 656095842ec7d46455af3f8b1ec7cc4babbc5af8 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 14:52:27 -0800 Subject: [PATCH 50/57] lint --- contracts/helpers/sol/lib/ArrayLib.sol | 9 ++- .../interfaces/AbridgedTokenInterfaces.sol | 23 ++++---- .../TestTransferValidationZoneOfferer.sol | 57 +++++++------------ .../sol/lib/BasicOrderParametersLib.t.sol | 4 +- .../TestTransferValidationZoneOfferer.t.sol | 11 ++-- 5 files changed, 46 insertions(+), 58 deletions(-) diff --git a/contracts/helpers/sol/lib/ArrayLib.sol b/contracts/helpers/sol/lib/ArrayLib.sol index 7ce66547c..8c8c25abd 100644 --- a/contracts/helpers/sol/lib/ArrayLib.sol +++ b/contracts/helpers/sol/lib/ArrayLib.sol @@ -2,7 +2,10 @@ pragma solidity ^0.8.17; library ArrayLib { - function setBytes32s(bytes32[] storage array, bytes32[] memory values) internal { + function setBytes32s( + bytes32[] storage array, + bytes32[] memory values + ) internal { while (array.length > 0) { array.pop(); } @@ -11,7 +14,9 @@ library ArrayLib { } } - function copy(bytes32[] memory array) internal pure returns (bytes32[] memory) { + 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]; diff --git a/contracts/interfaces/AbridgedTokenInterfaces.sol b/contracts/interfaces/AbridgedTokenInterfaces.sol index f3183c381..14d1000f3 100644 --- a/contracts/interfaces/AbridgedTokenInterfaces.sol +++ b/contracts/interfaces/AbridgedTokenInterfaces.sol @@ -30,10 +30,11 @@ interface ERC20Interface { * * @return success True if the approval was successful. */ - - function approve(address spender, uint256 value) - external - returns (bool success); + + function approve( + address spender, + uint256 value + ) external returns (bool success); /** * @dev Returns the amount of tokens owned by `account`. @@ -57,11 +58,7 @@ interface ERC721Interface { * @param to The address of the recipient. * @param tokenId The ID of the token to transfer. */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; + function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Allows an owner to approve an operator to transfer all tokens on a @@ -139,8 +136,8 @@ interface ERC1155Interface { * @return balance The balance of the token. */ - function balanceOf(address account, uint256 id) - external - view - returns (uint256); + function balanceOf( + address account, + uint256 id + ) external view returns (uint256); } diff --git a/contracts/test/TestTransferValidationZoneOfferer.sol b/contracts/test/TestTransferValidationZoneOfferer.sol index cdbb5000b..25f45460a 100644 --- a/contracts/test/TestTransferValidationZoneOfferer.sol +++ b/contracts/test/TestTransferValidationZoneOfferer.sol @@ -40,12 +40,9 @@ contract TestTransferValidationZoneOfferer is * @return validOrderMagicValue The magic value to indicate things are OK. */ - function validateOrder(ZoneParameters calldata zoneParameters) - external - view - override - returns (bytes4 validOrderMagicValue) - { + 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. @@ -112,19 +109,12 @@ contract TestTransferValidationZoneOfferer is * @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 */ + SpentItem[] calldata minimumReceived /* offer */, + ReceivedItem[] calldata maximumSpent /* consideration */, + bytes calldata context /* context */, + bytes32[] calldata /* orderHashes */, uint256 /* contractNonce */ - ) - external - view - override - returns ( - bytes4 /* ratifyOrderMagicValue */ - ) - { + ) external view override returns (bytes4 /* ratifyOrderMagicValue */) { // Ratify the order. // Ensure that the offerer or recipient has received all consideration @@ -153,11 +143,9 @@ contract TestTransferValidationZoneOfferer is schemas[0].metadata = new bytes(0); } - function _convertSpentToReceived(SpentItem[] calldata spentItems) - internal - view - returns (ReceivedItem[] memory) - { + function _convertSpentToReceived( + SpentItem[] calldata spentItems + ) internal view returns (ReceivedItem[] memory) { ReceivedItem[] memory receivedItems = new ReceivedItem[]( spentItems.length ); @@ -167,11 +155,9 @@ contract TestTransferValidationZoneOfferer is return receivedItems; } - function _convertSpentToReceived(SpentItem calldata spentItem) - internal - view - returns (ReceivedItem memory) - { + function _convertSpentToReceived( + SpentItem calldata spentItem + ) internal view returns (ReceivedItem memory) { return ReceivedItem({ itemType: spentItem.itemType, @@ -182,10 +168,9 @@ contract TestTransferValidationZoneOfferer is }); } - function _assertValidReceivedItems(ReceivedItem[] calldata receivedItems) - internal - view - { + function _assertValidReceivedItems( + ReceivedItem[] calldata receivedItems + ) internal view { address recipient; ItemType itemType; ReceivedItem memory receivedItem; @@ -273,10 +258,10 @@ contract TestTransferValidationZoneOfferer is } } - function _assertNativeTokenTransfer(uint256 amount, address recipient) - internal - view - { + function _assertNativeTokenTransfer( + uint256 amount, + address recipient + ) internal view { if (amount > address(recipient).balance) { revert InvalidBalance(); } diff --git a/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol b/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol index 7af86b4df..1383ddb5e 100644 --- a/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol +++ b/test/foundry/helpers/sol/lib/BasicOrderParametersLib.t.sol @@ -57,8 +57,8 @@ contract BasicOrderParametersLibTest is BaseTest { blob.considerationToken ); basicOrderParameters = basicOrderParameters.withConsiderationIdentifier( - blob.considerationIdentifier - ); + blob.considerationIdentifier + ); basicOrderParameters = basicOrderParameters.withConsiderationAmount( blob.considerationAmount ); diff --git a/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol index f1fa1ba97..32a4eb268 100644 --- a/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol +++ b/test/foundry/zone/TestTransferValidationZoneOfferer.t.sol @@ -251,11 +251,12 @@ contract TestTransferValidationZoneOffererTest is BaseOrderTest { 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) - ); + FulfillmentComponent[][] + memory considerationFulfillments = SeaportArrays + .FulfillmentComponentArrays( + // two-element fulfillmentcomponents array, one for each order + FulfillmentComponentLib.fromDefaultMany(FIRST_SECOND__FIRST) + ); return ( orders, From ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 14:56:14 -0800 Subject: [PATCH 51/57] move TypehashDirectory to test/ --- contracts/{lib => test}/TypehashDirectory.sol | 0 test/foundry/utils/EIP712MerkleTree.sol | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/{lib => test}/TypehashDirectory.sol (100%) 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/test/foundry/utils/EIP712MerkleTree.sol b/test/foundry/utils/EIP712MerkleTree.sol index ff1653549..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 From 47e5d8417327717973409f220943109ac70312a7 Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 15:00:28 -0800 Subject: [PATCH 52/57] reset gas profile --- ...278b75e1acc08d85bfacef494b7cfe42fa2d9.json | 502 ----------------- ...489de1c8ab348747842001594e09c49aab889.json | 516 ------------------ ...2c1285e31df6b46c36aaf72dbcff2d6e58a3.json} | 146 ++--- 3 files changed, 73 insertions(+), 1091 deletions(-) delete mode 100644 .gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json delete mode 100644 .gas_reports/572489de1c8ab348747842001594e09c49aab889.json rename .gas_reports/{2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json => ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json} (83%) diff --git a/.gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json b/.gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json deleted file mode 100644 index d09e9b45c..000000000 --- a/.gas_reports/374278b75e1acc08d85bfacef494b7cfe42fa2d9.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "commitHash": "374278b75e1acc08d85bfacef494b7cfe42fa2d9", - "contractReports": { - "Conduit": { - "name": "Conduit", - "methods": [ - { - "method": "execute", - "min": 77435, - "max": 2373103, - "avg": 483652, - "calls": 6 - }, - { - "method": "executeBatch1155", - "min": null, - "max": null, - "avg": 97245, - "calls": 1 - }, - { - "method": "executeWithBatch1155", - "min": 97717, - "max": 361418, - "avg": 228764, - "calls": 4 - }, - { - "method": "updateChannel", - "min": null, - "max": null, - "avg": 45802, - "calls": 1 - } - ], - "bytecodeSize": 3071, - "deployedBytecodeSize": 3030 - }, - "ConduitController": { - "name": "ConduitController", - "methods": [ - { - "method": "acceptOwnership", - "min": null, - "max": null, - "avg": 32944, - "calls": 1 - }, - { - "method": "cancelOwnershipTransfer", - "min": null, - "max": null, - "avg": 27966, - "calls": 1 - }, - { - "method": "createConduit", - "min": 712802, - "max": 712970, - "avg": 712930, - "calls": 51 - }, - { - "method": "transferOwnership", - "min": null, - "max": null, - "avg": 50329, - "calls": 2 - }, - { - "method": "updateChannel", - "min": 34454, - "max": 121098, - "avg": 117184, - "calls": 69 - } - ], - "bytecodeSize": 12007, - "deployedBytecodeSize": 8660 - }, - "ConduitControllerMock": { - "name": "ConduitControllerMock", - "methods": [ - { - "method": "createConduit", - "min": 226092, - "max": 231533, - "avg": 229598, - "calls": 6 - } - ], - "bytecodeSize": 10541, - "deployedBytecodeSize": 7340 - }, - "EIP1271Wallet": { - "name": "EIP1271Wallet", - "methods": [ - { - "method": "approveNFT", - "min": null, - "max": null, - "avg": 49674, - "calls": 14 - }, - { - "method": "registerDigest", - "min": 22239, - "max": 44151, - "avg": 36847, - "calls": 3 - }, - { - "method": "revertWithMessage", - "min": null, - "max": null, - "avg": 21677, - "calls": 1 - }, - { - "method": "setValid", - "min": 21699, - "max": 43611, - "avg": 32655, - "calls": 2 - } - ], - "bytecodeSize": 2834, - "deployedBytecodeSize": 2656 - }, - "ExcessReturnDataRecipient": { - "name": "ExcessReturnDataRecipient", - "methods": [ - { - "method": "setRevertDataSize", - "min": null, - "max": null, - "avg": 43441, - "calls": 2 - } - ], - "bytecodeSize": 1907, - "deployedBytecodeSize": 1879 - }, - "PausableZone": { - "name": "PausableZone", - "methods": [ - { - "method": "cancelOrders", - "min": null, - "max": null, - "avg": 65315, - "calls": 1 - } - ], - "bytecodeSize": 5556, - "deployedBytecodeSize": 5450 - }, - "PausableZoneController": { - "name": "PausableZoneController", - "methods": [ - { - "method": "acceptOwnership", - "min": null, - "max": null, - "avg": 28942, - "calls": 1 - }, - { - "method": "assignOperator", - "min": null, - "max": null, - "avg": 50892, - "calls": 1 - }, - { - "method": "assignPauser", - "min": null, - "max": null, - "avg": 47183, - "calls": 1 - }, - { - "method": "cancelOrders", - "min": null, - "max": null, - "avg": 73894, - "calls": 1 - }, - { - "method": "cancelOwnershipTransfer", - "min": null, - "max": null, - "avg": 24578, - "calls": 1 - }, - { - "method": "createZone", - "min": null, - "max": null, - "avg": 1154314, - "calls": 31 - }, - { - "method": "executeMatchAdvancedOrders", - "min": null, - "max": null, - "avg": 288298, - "calls": 2 - }, - { - "method": "executeMatchOrders", - "min": null, - "max": null, - "avg": 281862, - "calls": 2 - }, - { - "method": "pause", - "min": 32851, - "max": 35006, - "avg": 33569, - "calls": 3 - }, - { - "method": "transferOwnership", - "min": null, - "max": null, - "avg": 47199, - "calls": 2 - } - ], - "bytecodeSize": 17744, - "deployedBytecodeSize": 11975 - }, - "Reenterer": { - "name": "Reenterer", - "methods": [ - { - "method": "prepare", - "min": 69404, - "max": 2350940, - "avg": 1171827, - "calls": 20 - } - ], - "bytecodeSize": 2459, - "deployedBytecodeSize": 2431 - }, - "Seaport": { - "name": "Seaport", - "methods": [ - { - "method": "cancel", - "min": 41250, - "max": 58422, - "avg": 54035, - "calls": 16 - }, - { - "method": "fulfillAdvancedOrder", - "min": 96312, - "max": 225169, - "avg": 159958, - "calls": 188 - }, - { - "method": "fulfillAvailableAdvancedOrders", - "min": 149979, - "max": 339949, - "avg": 207233, - "calls": 29 - }, - { - "method": "fulfillAvailableOrders", - "min": 165345, - "max": 216505, - "avg": 201935, - "calls": 21 - }, - { - "method": "fulfillBasicOrder", - "min": 90639, - "max": 1621627, - "avg": 598708, - "calls": 187 - }, - { - "method": "fulfillBasicOrder_efficient_6GL6yc", - "min": 90261, - "max": 111444, - "avg": 100853, - "calls": 6 - }, - { - "method": "fulfillOrder", - "min": 119409, - "max": 225080, - "avg": 177756, - "calls": 105 - }, - { - "method": "incrementCounter", - "min": null, - "max": null, - "avg": 47054, - "calls": 6 - }, - { - "method": "matchAdvancedOrders", - "min": 180326, - "max": 299883, - "avg": 249683, - "calls": 77 - }, - { - "method": "matchOrders", - "min": 158265, - "max": 349010, - "avg": 265283, - "calls": 151 - }, - { - "method": "validate", - "min": 53201, - "max": 83886, - "avg": 73544, - "calls": 29 - } - ], - "bytecodeSize": 26712, - "deployedBytecodeSize": 23553 - }, - "TestContractOfferer": { - "name": "TestContractOfferer", - "methods": [ - { - "method": "activate", - "min": 201531, - "max": 246674, - "avg": 205514, - "calls": 33 - }, - { - "method": "activateWithCriteria", - "min": null, - "max": null, - "avg": 201834, - "calls": 1 - }, - { - "method": "extendAvailable", - "min": null, - "max": null, - "avg": 50704, - "calls": 1 - }, - { - "method": "extendRequired", - "min": null, - "max": null, - "avg": 45780, - "calls": 1 - } - ], - "bytecodeSize": 8462, - "deployedBytecodeSize": 8265 - }, - "TestContractOffererNativeToken": { - "name": "TestContractOffererNativeToken", - "methods": [ - { - "method": "activate", - "min": null, - "max": null, - "avg": 139181, - "calls": 1 - } - ], - "bytecodeSize": 6940, - "deployedBytecodeSize": 6764 - }, - "TestERC1155": { - "name": "TestERC1155", - "methods": [ - { - "method": "mint", - "min": 47235, - "max": 49915, - "avg": 49443, - "calls": 245 - }, - { - "method": "setApprovalForAll", - "min": 26102, - "max": 46002, - "avg": 45654, - "calls": 458 - } - ], - "bytecodeSize": 4173, - "deployedBytecodeSize": 4145 - }, - "TestERC20": { - "name": "TestERC20", - "methods": [ - { - "method": "approve", - "min": 28881, - "max": 46245, - "avg": 45749, - "calls": 292 - }, - { - "method": "blockTransfer", - "min": 21978, - "max": 43890, - "avg": 32934, - "calls": 4 - }, - { - "method": "mint", - "min": 33994, - "max": 68458, - "avg": 67348, - "calls": 141 - }, - { - "method": "setNoReturnData", - "min": 21926, - "max": 43838, - "avg": 32882, - "calls": 2 - } - ], - "bytecodeSize": 5807, - "deployedBytecodeSize": 4636 - }, - "TestERC721": { - "name": "TestERC721", - "methods": [ - { - "method": "mint", - "min": 51492, - "max": 68784, - "avg": 66201, - "calls": 307 - }, - { - "method": "setApprovalForAll", - "min": 26195, - "max": 46095, - "avg": 45560, - "calls": 522 - } - ], - "bytecodeSize": 5238, - "deployedBytecodeSize": 4451 - }, - "TestInvalidContractOfferer": { - "name": "TestInvalidContractOfferer", - "methods": [ - { - "method": "activate", - "min": null, - "max": null, - "avg": 201567, - "calls": 2 - } - ], - "bytecodeSize": 7954, - "deployedBytecodeSize": 7764 - }, - "TestInvalidContractOffererRatifyOrder": { - "name": "TestInvalidContractOffererRatifyOrder", - "methods": [ - { - "method": "activate", - "min": null, - "max": null, - "avg": 201558, - "calls": 1 - } - ], - "bytecodeSize": 7957, - "deployedBytecodeSize": 7760 - }, - "TransferHelper": { - "name": "TransferHelper", - "methods": [ - { - "method": "bulkTransfer", - "min": 77935, - "max": 1546854, - "avg": 673107, - "calls": 3 - } - ], - "bytecodeSize": 4140, - "deployedBytecodeSize": 3865 - } - } -} \ No newline at end of file diff --git a/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json b/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json deleted file mode 100644 index 952678540..000000000 --- a/.gas_reports/572489de1c8ab348747842001594e09c49aab889.json +++ /dev/null @@ -1,516 +0,0 @@ -{ - "commitHash": "572489de1c8ab348747842001594e09c49aab889", - "contractReports": { - "Conduit": { - "name": "Conduit", - "methods": [ - { - "method": "execute", - "min": 77459, - "max": 2186674, - "avg": 452589, - "calls": 6 - }, - { - "method": "executeBatch1155", - "min": null, - "max": null, - "avg": 97245, - "calls": 1 - }, - { - "method": "executeWithBatch1155", - "min": 97717, - "max": 361418, - "avg": 228761, - "calls": 4 - }, - { - "method": "updateChannel", - "min": null, - "max": null, - "avg": 45802, - "calls": 1 - } - ], - "bytecodeSize": 3071, - "deployedBytecodeSize": 3030 - }, - "ConduitController": { - "name": "ConduitController", - "methods": [ - { - "method": "acceptOwnership", - "min": null, - "max": null, - "avg": 32944, - "calls": 1 - }, - { - "method": "cancelOwnershipTransfer", - "min": null, - "max": null, - "avg": 27966, - "calls": 1 - }, - { - "method": "createConduit", - "min": 712802, - "max": 712970, - "avg": 712929, - "calls": 52 - }, - { - "method": "transferOwnership", - "min": null, - "max": null, - "avg": 50329, - "calls": 2 - }, - { - "method": "updateChannel", - "min": 34454, - "max": 121098, - "avg": 117240, - "calls": 70 - } - ], - "bytecodeSize": 12007, - "deployedBytecodeSize": 8660 - }, - "ConduitControllerMock": { - "name": "ConduitControllerMock", - "methods": [ - { - "method": "createConduit", - "min": 226092, - "max": 231533, - "avg": 229598, - "calls": 6 - } - ], - "bytecodeSize": 10541, - "deployedBytecodeSize": 7340 - }, - "EIP1271Wallet": { - "name": "EIP1271Wallet", - "methods": [ - { - "method": "approveNFT", - "min": null, - "max": null, - "avg": 49674, - "calls": 14 - }, - { - "method": "registerDigest", - "min": 22239, - "max": 44151, - "avg": 36847, - "calls": 3 - }, - { - "method": "revertWithMessage", - "min": null, - "max": null, - "avg": 21677, - "calls": 1 - }, - { - "method": "setValid", - "min": 21699, - "max": 43611, - "avg": 32655, - "calls": 2 - } - ], - "bytecodeSize": 2834, - "deployedBytecodeSize": 2656 - }, - "ExcessReturnDataRecipient": { - "name": "ExcessReturnDataRecipient", - "methods": [ - { - "method": "setRevertDataSize", - "min": null, - "max": null, - "avg": 43441, - "calls": 2 - } - ], - "bytecodeSize": 1907, - "deployedBytecodeSize": 1879 - }, - "PausableZone": { - "name": "PausableZone", - "methods": [ - { - "method": "cancelOrders", - "min": null, - "max": null, - "avg": 65315, - "calls": 1 - } - ], - "bytecodeSize": 5556, - "deployedBytecodeSize": 5450 - }, - "PausableZoneController": { - "name": "PausableZoneController", - "methods": [ - { - "method": "acceptOwnership", - "min": null, - "max": null, - "avg": 28942, - "calls": 1 - }, - { - "method": "assignOperator", - "min": null, - "max": null, - "avg": 50880, - "calls": 1 - }, - { - "method": "assignPauser", - "min": null, - "max": null, - "avg": 47183, - "calls": 1 - }, - { - "method": "cancelOrders", - "min": null, - "max": null, - "avg": 73870, - "calls": 1 - }, - { - "method": "cancelOwnershipTransfer", - "min": null, - "max": null, - "avg": 24578, - "calls": 1 - }, - { - "method": "createZone", - "min": 1154302, - "max": 1154314, - "avg": 1154313, - "calls": 31 - }, - { - "method": "executeMatchAdvancedOrders", - "min": null, - "max": null, - "avg": 288725, - "calls": 2 - }, - { - "method": "executeMatchOrders", - "min": null, - "max": null, - "avg": 282289, - "calls": 2 - }, - { - "method": "pause", - "min": 32875, - "max": 35006, - "avg": 33585, - "calls": 3 - }, - { - "method": "transferOwnership", - "min": null, - "max": null, - "avg": 47199, - "calls": 2 - } - ], - "bytecodeSize": 17744, - "deployedBytecodeSize": 11975 - }, - "Reenterer": { - "name": "Reenterer", - "methods": [ - { - "method": "prepare", - "min": 49267, - "max": 2351702, - "avg": 1061785, - "calls": 26 - } - ], - "bytecodeSize": 2726, - "deployedBytecodeSize": 2698 - }, - "Seaport": { - "name": "Seaport", - "methods": [ - { - "method": "cancel", - "min": 41250, - "max": 58422, - "avg": 54045, - "calls": 16 - }, - { - "method": "fulfillAdvancedOrder", - "min": 96300, - "max": 225169, - "avg": 159834, - "calls": 188 - }, - { - "method": "fulfillAvailableAdvancedOrders", - "min": 149638, - "max": 350725, - "avg": 208123, - "calls": 29 - }, - { - "method": "fulfillAvailableOrders", - "min": 165022, - "max": 215788, - "avg": 201418, - "calls": 21 - }, - { - "method": "fulfillBasicOrder", - "min": 90639, - "max": 1621603, - "avg": 598687, - "calls": 187 - }, - { - "method": "fulfillBasicOrder_efficient_6GL6yc", - "min": 90237, - "max": 111468, - "avg": 100853, - "calls": 6 - }, - { - "method": "fulfillOrder", - "min": 119409, - "max": 225056, - "avg": 177743, - "calls": 105 - }, - { - "method": "incrementCounter", - "min": null, - "max": null, - "avg": 47054, - "calls": 6 - }, - { - "method": "matchAdvancedOrders", - "min": 179562, - "max": 300213, - "avg": 248872, - "calls": 77 - }, - { - "method": "matchOrders", - "min": 157486, - "max": 348207, - "avg": 264525, - "calls": 151 - }, - { - "method": "validate", - "min": 53201, - "max": 83886, - "avg": 73539, - "calls": 29 - } - ], - "bytecodeSize": 26123, - "deployedBytecodeSize": 24398 - }, - "SeaportRouter": { - "name": "SeaportRouter", - "methods": [ - { - "method": "fulfillAvailableAdvancedOrders", - "min": 183954, - "max": 311835, - "avg": 233586, - "calls": 6 - } - ], - "bytecodeSize": 6345, - "deployedBytecodeSize": 6158 - }, - "TestContractOfferer": { - "name": "TestContractOfferer", - "methods": [ - { - "method": "activate", - "min": 201543, - "max": 246674, - "avg": 205516, - "calls": 33 - }, - { - "method": "activateWithCriteria", - "min": null, - "max": null, - "avg": 201834, - "calls": 1 - }, - { - "method": "extendAvailable", - "min": null, - "max": null, - "avg": 50704, - "calls": 1 - }, - { - "method": "extendRequired", - "min": null, - "max": null, - "avg": 45780, - "calls": 1 - } - ], - "bytecodeSize": 8462, - "deployedBytecodeSize": 8265 - }, - "TestContractOffererNativeToken": { - "name": "TestContractOffererNativeToken", - "methods": [ - { - "method": "activate", - "min": null, - "max": null, - "avg": 139181, - "calls": 1 - } - ], - "bytecodeSize": 6940, - "deployedBytecodeSize": 6764 - }, - "TestERC1155": { - "name": "TestERC1155", - "methods": [ - { - "method": "mint", - "min": 47223, - "max": 49903, - "avg": 49474, - "calls": 268 - }, - { - "method": "setApprovalForAll", - "min": 26102, - "max": 46002, - "avg": 45686, - "calls": 504 - } - ], - "bytecodeSize": 4173, - "deployedBytecodeSize": 4145 - }, - "TestERC20": { - "name": "TestERC20", - "methods": [ - { - "method": "approve", - "min": 28881, - "max": 46245, - "avg": 45766, - "calls": 316 - }, - { - "method": "blockTransfer", - "min": 21978, - "max": 43890, - "avg": 32934, - "calls": 4 - }, - { - "method": "mint", - "min": 33994, - "max": 68458, - "avg": 67414, - "calls": 153 - }, - { - "method": "setNoReturnData", - "min": 21926, - "max": 43838, - "avg": 32882, - "calls": 2 - } - ], - "bytecodeSize": 5807, - "deployedBytecodeSize": 4636 - }, - "TestERC721": { - "name": "TestERC721", - "methods": [ - { - "method": "mint", - "min": 51396, - "max": 68796, - "avg": 65786, - "calls": 286 - }, - { - "method": "setApprovalForAll", - "min": 26195, - "max": 46095, - "avg": 45516, - "calls": 482 - } - ], - "bytecodeSize": 5238, - "deployedBytecodeSize": 4451 - }, - "TestInvalidContractOfferer": { - "name": "TestInvalidContractOfferer", - "methods": [ - { - "method": "activate", - "min": 201543, - "max": 201555, - "avg": 201549, - "calls": 2 - } - ], - "bytecodeSize": 7954, - "deployedBytecodeSize": 7764 - }, - "TestInvalidContractOffererRatifyOrder": { - "name": "TestInvalidContractOffererRatifyOrder", - "methods": [ - { - "method": "activate", - "min": null, - "max": null, - "avg": 201558, - "calls": 1 - } - ], - "bytecodeSize": 7957, - "deployedBytecodeSize": 7760 - }, - "TransferHelper": { - "name": "TransferHelper", - "methods": [ - { - "method": "bulkTransfer", - "min": 77935, - "max": 1468298, - "avg": 641594, - "calls": 3 - } - ], - "bytecodeSize": 4140, - "deployedBytecodeSize": 3865 - } - } -} \ No newline at end of file diff --git a/.gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json b/.gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json similarity index 83% rename from .gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json rename to .gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json index 19df87c49..72b1390e5 100644 --- a/.gas_reports/2a19879df84f3f31355ff9eedfbdd27ffb0a89da.json +++ b/.gas_reports/ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3.json @@ -1,5 +1,5 @@ { - "commitHash": "2a19879df84f3f31355ff9eedfbdd27ffb0a89da", + "commitHash": "ddea2c1285e31df6b46c36aaf72dbcff2d6e58a3", "contractReports": { "Conduit": { "name": "Conduit", @@ -7,15 +7,15 @@ { "method": "execute", "min": 77459, - "max": 2265578, - "avg": 465739, + "max": 2168532, + "avg": 449559, "calls": 6 }, { "method": "executeBatch1155", "min": null, "max": null, - "avg": 97197, + "avg": 97245, "calls": 1 }, { @@ -55,10 +55,10 @@ }, { "method": "createConduit", - "min": 712802, + "min": 712826, "max": 712970, - "avg": 712929, - "calls": 52 + "avg": 712927, + "calls": 53 }, { "method": "transferOwnership", @@ -71,8 +71,8 @@ "method": "updateChannel", "min": 34454, "max": 121098, - "avg": 117239, - "calls": 70 + "avg": 117294, + "calls": 71 } ], "bytecodeSize": 12007, @@ -85,7 +85,7 @@ "method": "createConduit", "min": 226092, "max": 231533, - "avg": 229598, + "avg": 229596, "calls": 6 } ], @@ -104,9 +104,9 @@ }, { "method": "registerDigest", - "min": 22227, - "max": 44139, - "avg": 36831, + "min": 22239, + "max": 44151, + "avg": 36847, "calls": 3 }, { @@ -183,7 +183,7 @@ "method": "cancelOrders", "min": null, "max": null, - "avg": 73870, + "avg": 73894, "calls": 1 }, { @@ -197,28 +197,28 @@ "method": "createZone", "min": 1154302, "max": 1154314, - "avg": 1154313, + "avg": 1154312, "calls": 31 }, { "method": "executeMatchAdvancedOrders", "min": null, "max": null, - "avg": 288740, + "avg": 288785, "calls": 2 }, { "method": "executeMatchOrders", "min": null, "max": null, - "avg": 282316, + "avg": 282325, "calls": 2 }, { "method": "pause", - "min": 32863, + "min": 32875, "max": 35006, - "avg": 33577, + "avg": 33585, "calls": 3 }, { @@ -238,8 +238,8 @@ { "method": "prepare", "min": 49267, - "max": 2351690, - "avg": 1061788, + "max": 2351654, + "avg": 1061779, "calls": 26 } ], @@ -251,52 +251,52 @@ "methods": [ { "method": "cancel", - "min": 41214, + "min": 41250, "max": 58422, - "avg": 54029, + "avg": 54039, "calls": 16 }, { "method": "fulfillAdvancedOrder", - "min": 96300, + "min": 96288, "max": 225181, - "avg": 159954, - "calls": 188 + "avg": 159382, + "calls": 194 }, { "method": "fulfillAvailableAdvancedOrders", - "min": 149675, - "max": 350657, - "avg": 208078, - "calls": 29 + "min": 149626, + "max": 350749, + "avg": 214389, + "calls": 33 }, { "method": "fulfillAvailableOrders", - "min": 165032, - "max": 215786, - "avg": 201432, + "min": 164998, + "max": 215740, + "avg": 201364, "calls": 21 }, { "method": "fulfillBasicOrder", "min": 90639, - "max": 1621615, - "avg": 598701, + "max": 1621627, + "avg": 598707, "calls": 187 }, { "method": "fulfillBasicOrder_efficient_6GL6yc", "min": 90261, - "max": 111468, - "avg": 100865, + "max": 111456, + "avg": 100859, "calls": 6 }, { "method": "fulfillOrder", "min": 119409, "max": 225080, - "avg": 177753, - "calls": 105 + "avg": 176772, + "calls": 108 }, { "method": "incrementCounter", @@ -307,37 +307,37 @@ }, { "method": "matchAdvancedOrders", - "min": 179547, + "min": 179574, "max": 300213, - "avg": 248859, + "avg": 248903, "calls": 77 }, { "method": "matchOrders", - "min": 157522, - "max": 348225, - "avg": 264565, + "min": 157474, + "max": 348243, + "avg": 264537, "calls": 151 }, { "method": "validate", - "min": 53153, - "max": 83874, - "avg": 73531, + "min": 53177, + "max": 83910, + "avg": 73542, "calls": 29 } ], - "bytecodeSize": 27052, - "deployedBytecodeSize": 23893 + "bytecodeSize": 26099, + "deployedBytecodeSize": 24374 }, "SeaportRouter": { "name": "SeaportRouter", "methods": [ { "method": "fulfillAvailableAdvancedOrders", - "min": 183991, - "max": 311933, - "avg": 233620, + "min": 183942, + "max": 311883, + "avg": 233578, "calls": 6 } ], @@ -349,9 +349,9 @@ "methods": [ { "method": "activate", - "min": 201543, + "min": 201519, "max": 246674, - "avg": 205516, + "avg": 205512, "calls": 33 }, { @@ -398,17 +398,17 @@ "methods": [ { "method": "mint", - "min": 47223, + "min": 47235, "max": 49903, - "avg": 49451, - "calls": 250 + "avg": 49427, + "calls": 239 }, { "method": "setApprovalForAll", "min": 26102, "max": 46002, - "avg": 45662, - "calls": 468 + "avg": 45645, + "calls": 446 } ], "bytecodeSize": 4173, @@ -421,8 +421,8 @@ "method": "approve", "min": 28881, "max": 46245, - "avg": 45779, - "calls": 336 + "avg": 45807, + "calls": 390 }, { "method": "blockTransfer", @@ -435,8 +435,8 @@ "method": "mint", "min": 33994, "max": 68458, - "avg": 67462, - "calls": 163 + "avg": 67563, + "calls": 190 }, { "method": "setNoReturnData", @@ -454,17 +454,17 @@ "methods": [ { "method": "mint", - "min": 51396, - "max": 68796, - "avg": 65843, - "calls": 292 + "min": 51492, + "max": 68784, + "avg": 65745, + "calls": 282 }, { "method": "setApprovalForAll", "min": 26195, "max": 46095, - "avg": 45530, - "calls": 494 + "avg": 45506, + "calls": 474 } ], "bytecodeSize": 5238, @@ -476,8 +476,8 @@ { "method": "activate", "min": 201543, - "max": 201555, - "avg": 201549, + "max": 201567, + "avg": 201555, "calls": 2 } ], @@ -504,8 +504,8 @@ { "method": "bulkTransfer", "min": 77935, - "max": 1433918, - "avg": 632612, + "max": 1428982, + "avg": 633808, "calls": 3 } ], From bbe5c77459cd553d2bd3a7f5ad4eb94b6a29425d Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 15:01:03 -0800 Subject: [PATCH 53/57] bump package.json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 25c94ce99fd99da6c0a993f93ac2613ad3081e3f Mon Sep 17 00:00:00 2001 From: d1ll0n Date: Tue, 14 Feb 2023 18:25:50 -0600 Subject: [PATCH 54/57] Use a ternary function for typehashes, replace for-break with function-leave Difference is pretty negligible in terms of gas/code, but it is a bit easier to follow --- contracts/lib/ConsiderationBase.sol | 224 +++++++++++----------------- 1 file changed, 85 insertions(+), 139 deletions(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index a17a25ba7..cb221da65 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -300,232 +300,178 @@ contract ConsiderationBase is * 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 + * @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 + * @return _typeHash The EIP-712 typehash for the bulk order type with the * given height. */ - function _lookupBulkOrderTypehash( - uint256 treeHeight - ) internal pure returns (bytes32 typeHash) { + function _lookupBulkOrderTypehash(uint256 _treeHeight) + internal + pure + returns (bytes32 _typeHash) + { // Utilize assembly to efficiently retrieve correct bulk order typehash. assembly { - // Progress until typehash is located; break before loop completes. - for {} 1 {} { + // 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 iszero(gt(treeHeight, 8)) { + if lt(treeHeight, 9) { // Handle tree heights one through four. - if iszero(gt(treeHeight, 4)) { + if lt(treeHeight, 5) { // Handle tree heights one and two. - if iszero(gt(treeHeight, 2)) { + if lt(treeHeight, 3) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_One, - mul( - eq(treeHeight, 2), - xor( - BulkOrder_Typehash_Height_One, - BulkOrder_Typehash_Height_Two - ) - ) + typeHash := ternary( + eq(treeHeight, 2), + BulkOrder_Typehash_Height_Two, + BulkOrder_Typehash_Height_One ) // Exit the loop once typehash has been located. - break + leave } // Handle height three and four via branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_Three, - mul( - eq(treeHeight, 4), - xor( - BulkOrder_Typehash_Height_Three, - BulkOrder_Typehash_Height_Four - ) - ) + typeHash := ternary( + eq(treeHeight, 4), + BulkOrder_Typehash_Height_Four, + BulkOrder_Typehash_Height_Three ) // Exit the loop once typehash has been located. - break + leave } // Handle tree height five and six. - if iszero(gt(treeHeight, 6)) { + if lt(treeHeight, 7) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_Five, - mul( - eq(treeHeight, 6), - xor( - BulkOrder_Typehash_Height_Five, - BulkOrder_Typehash_Height_Six - ) - ) + typeHash := ternary( + eq(treeHeight, 6), + BulkOrder_Typehash_Height_Six, + BulkOrder_Typehash_Height_Five ) // Exit the loop once typehash has been located. - break + leave } // Handle height seven and eight via branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_Seven, - mul( - eq(treeHeight, 8), - xor( - BulkOrder_Typehash_Height_Seven, - BulkOrder_Typehash_Height_Eight - ) - ) + typeHash := ternary( + eq(treeHeight, 8), + BulkOrder_Typehash_Height_Eight, + BulkOrder_Typehash_Height_Seven ) // Exit the loop once typehash has been located. - break + leave } // Handle tree height nine through sixteen. - if iszero(gt(treeHeight, 16)) { + if lt(treeHeight, 17) { // Handle tree height nine through twelve. - if iszero(gt(treeHeight, 12)) { + if lt(treeHeight, 13) { // Handle tree height nine and ten. - if iszero(gt(treeHeight, 10)) { + if lt(treeHeight, 11) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_Nine, - mul( - eq(treeHeight, 10), - xor( - BulkOrder_Typehash_Height_Nine, - BulkOrder_Typehash_Height_Ten - ) - ) + typeHash := ternary( + eq(treeHeight, 10), + BulkOrder_Typehash_Height_Ten, + BulkOrder_Typehash_Height_Nine ) // Exit the loop once typehash has been located. - break + leave } // Handle height eleven and twelve via branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_Eleven, - mul( - eq(treeHeight, 12), - xor( - BulkOrder_Typehash_Height_Eleven, - BulkOrder_Typehash_Height_Twelve - ) - ) + typeHash := ternary( + eq(treeHeight, 12), + BulkOrder_Typehash_Height_Twelve, + BulkOrder_Typehash_Height_Eleven ) // Exit the loop once typehash has been located. - break + leave } // Handle tree height thirteen and fourteen. - if iszero(gt(treeHeight, 14)) { + if lt(treeHeight, 15) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_Thirteen, - mul( - eq(treeHeight, 14), - xor( - BulkOrder_Typehash_Height_Thirteen, - BulkOrder_Typehash_Height_Fourteen - ) - ) + typeHash := ternary( + eq(treeHeight, 14), + BulkOrder_Typehash_Height_Fourteen, + BulkOrder_Typehash_Height_Thirteen ) // Exit the loop once typehash has been located. - break + leave } - // Handle height fifteen and sixteen via branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_Fifteen, - mul( - eq(treeHeight, 16), - xor( - BulkOrder_Typehash_Height_Fifteen, - BulkOrder_Typehash_Height_Sixteen - ) - ) + typeHash := ternary( + eq(treeHeight, 16), + BulkOrder_Typehash_Height_Sixteen, + BulkOrder_Typehash_Height_Fifteen ) // Exit the loop once typehash has been located. - break + leave } // Handle tree height seventeen through twenty. - if iszero(gt(treeHeight, 20)) { + if lt(treeHeight, 21) { // Handle tree height seventeen and eighteen. - if iszero(gt(treeHeight, 18)) { + if lt(treeHeight, 19) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_Seventeen, - mul( - eq(treeHeight, 18), - xor( - BulkOrder_Typehash_Height_Seventeen, - BulkOrder_Typehash_Height_Eighteen - ) - ) + typeHash := ternary( + eq(treeHeight, 18), + BulkOrder_Typehash_Height_Eighteen, + BulkOrder_Typehash_Height_Seventeen ) // Exit the loop once typehash has been located. - break + leave } // Handle height nineteen and twenty via branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_Nineteen, - mul( - eq(treeHeight, 20), - xor( - BulkOrder_Typehash_Height_Nineteen, - BulkOrder_Typehash_Height_Twenty - ) - ) + typeHash := ternary( + eq(treeHeight, 20), + BulkOrder_Typehash_Height_Twenty, + BulkOrder_Typehash_Height_Nineteen ) // Exit the loop once typehash has been located. - break + leave } // Handle tree height twenty-one and twenty-two. - if iszero(gt(treeHeight, 22)) { + if lt(treeHeight, 23) { // Utilize branchless logic to determine typehash. - typeHash := xor( - BulkOrder_Typehash_Height_TwentyOne, - mul( - eq(treeHeight, 22), - xor( - BulkOrder_Typehash_Height_TwentyOne, - BulkOrder_Typehash_Height_TwentyTwo - ) - ) + typeHash := ternary( + eq(treeHeight, 22), + BulkOrder_Typehash_Height_TwentyTwo, + BulkOrder_Typehash_Height_TwentyOne ) // Exit the loop once typehash has been located. - break + leave } // Handle height twenty-three & twenty-four w/ branchless logic. - typeHash := xor( - BulkOrder_Typehash_Height_TwentyThree, - mul( - eq(treeHeight, 24), - xor( - BulkOrder_Typehash_Height_TwentyThree, - BulkOrder_Typehash_Height_TwentyFour - ) - ) + typeHash := ternary( + eq(treeHeight, 24), + BulkOrder_Typehash_Height_TwentyFour, + BulkOrder_Typehash_Height_TwentyThree ) // Exit the loop once typehash has been located. - break + leave + } + function ternary(cond, ifTrue, ifFalse) -> c { + c := xor(ifFalse, mul(cond, xor(ifFalse, ifTrue))) } + _typeHash := lookupTypeHash(_treeHeight) } } } From ba151b7d8ca9033c3eef85082c14c58aa321c996 Mon Sep 17 00:00:00 2001 From: d1ll0n Date: Tue, 14 Feb 2023 18:30:17 -0600 Subject: [PATCH 55/57] Exit the loop -> Exit the function --- contracts/lib/ConsiderationBase.sol | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index cb221da65..4cccc13ce 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -329,7 +329,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_One ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -340,7 +340,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Three ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -353,7 +353,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Five ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -364,7 +364,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Seven ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -381,7 +381,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Nine ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -392,7 +392,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Eleven ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -405,7 +405,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Thirteen ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } // Handle height fifteen and sixteen via branchless logic. @@ -415,7 +415,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Fifteen ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -430,7 +430,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Seventeen ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -441,7 +441,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_Nineteen ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -454,7 +454,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_TwentyOne ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } @@ -465,7 +465,7 @@ contract ConsiderationBase is BulkOrder_Typehash_Height_TwentyThree ) - // Exit the loop once typehash has been located. + // Exit the function once typehash has been located. leave } function ternary(cond, ifTrue, ifFalse) -> c { From ef24393578a17cc575c39334a472a88270882e8b Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Tue, 14 Feb 2023 17:10:17 -0800 Subject: [PATCH 56/57] clean up a little more --- contracts/lib/ConsiderationBase.sol | 80 +++++++++++++++-------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index 4cccc13ce..a8e1f0609 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -301,10 +301,10 @@ contract ConsiderationBase is * enforced by _isValidBulkOrderSize. * * @param _treeHeight The height of the bulk order tree. The value must be - * between one and twenty-four. + * between one and twenty-four. * * @return _typeHash The EIP-712 typehash for the bulk order type with the - * given height. + * given height. */ function _lookupBulkOrderTypehash(uint256 _treeHeight) internal @@ -324,9 +324,9 @@ contract ConsiderationBase is if lt(treeHeight, 3) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 2), - BulkOrder_Typehash_Height_Two, - BulkOrder_Typehash_Height_One + eq(treeHeight, 1), + BulkOrder_Typehash_Height_One, + BulkOrder_Typehash_Height_Two ) // Exit the function once typehash has been located. @@ -335,9 +335,9 @@ contract ConsiderationBase is // Handle height three and four via branchless logic. typeHash := ternary( - eq(treeHeight, 4), - BulkOrder_Typehash_Height_Four, - BulkOrder_Typehash_Height_Three + eq(treeHeight, 3), + BulkOrder_Typehash_Height_Three, + BulkOrder_Typehash_Height_Four ) // Exit the function once typehash has been located. @@ -348,9 +348,9 @@ contract ConsiderationBase is if lt(treeHeight, 7) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 6), - BulkOrder_Typehash_Height_Six, - BulkOrder_Typehash_Height_Five + eq(treeHeight, 5), + BulkOrder_Typehash_Height_Five, + BulkOrder_Typehash_Height_Six ) // Exit the function once typehash has been located. @@ -359,9 +359,9 @@ contract ConsiderationBase is // Handle height seven and eight via branchless logic. typeHash := ternary( - eq(treeHeight, 8), - BulkOrder_Typehash_Height_Eight, - BulkOrder_Typehash_Height_Seven + eq(treeHeight, 7), + BulkOrder_Typehash_Height_Seven, + BulkOrder_Typehash_Height_Eight ) // Exit the function once typehash has been located. @@ -376,9 +376,9 @@ contract ConsiderationBase is if lt(treeHeight, 11) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 10), - BulkOrder_Typehash_Height_Ten, - BulkOrder_Typehash_Height_Nine + eq(treeHeight, 9), + BulkOrder_Typehash_Height_Nine, + BulkOrder_Typehash_Height_Ten ) // Exit the function once typehash has been located. @@ -387,9 +387,9 @@ contract ConsiderationBase is // Handle height eleven and twelve via branchless logic. typeHash := ternary( - eq(treeHeight, 12), - BulkOrder_Typehash_Height_Twelve, - BulkOrder_Typehash_Height_Eleven + eq(treeHeight, 11), + BulkOrder_Typehash_Height_Eleven, + BulkOrder_Typehash_Height_Twelve ) // Exit the function once typehash has been located. @@ -400,9 +400,9 @@ contract ConsiderationBase is if lt(treeHeight, 15) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 14), - BulkOrder_Typehash_Height_Fourteen, - BulkOrder_Typehash_Height_Thirteen + eq(treeHeight, 13), + BulkOrder_Typehash_Height_Thirteen, + BulkOrder_Typehash_Height_Fourteen ) // Exit the function once typehash has been located. @@ -410,9 +410,9 @@ contract ConsiderationBase is } // Handle height fifteen and sixteen via branchless logic. typeHash := ternary( - eq(treeHeight, 16), - BulkOrder_Typehash_Height_Sixteen, - BulkOrder_Typehash_Height_Fifteen + eq(treeHeight, 15), + BulkOrder_Typehash_Height_Fifteen, + BulkOrder_Typehash_Height_Sixteen ) // Exit the function once typehash has been located. @@ -425,9 +425,9 @@ contract ConsiderationBase is if lt(treeHeight, 19) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 18), - BulkOrder_Typehash_Height_Eighteen, - BulkOrder_Typehash_Height_Seventeen + eq(treeHeight, 17), + BulkOrder_Typehash_Height_Seventeen, + BulkOrder_Typehash_Height_Eighteen ) // Exit the function once typehash has been located. @@ -436,9 +436,9 @@ contract ConsiderationBase is // Handle height nineteen and twenty via branchless logic. typeHash := ternary( - eq(treeHeight, 20), - BulkOrder_Typehash_Height_Twenty, - BulkOrder_Typehash_Height_Nineteen + eq(treeHeight, 19), + BulkOrder_Typehash_Height_Nineteen, + BulkOrder_Typehash_Height_Twenty ) // Exit the function once typehash has been located. @@ -449,9 +449,9 @@ contract ConsiderationBase is if lt(treeHeight, 23) { // Utilize branchless logic to determine typehash. typeHash := ternary( - eq(treeHeight, 22), - BulkOrder_Typehash_Height_TwentyTwo, - BulkOrder_Typehash_Height_TwentyOne + eq(treeHeight, 21), + BulkOrder_Typehash_Height_TwentyOne, + BulkOrder_Typehash_Height_TwentyTwo ) // Exit the function once typehash has been located. @@ -460,17 +460,21 @@ contract ConsiderationBase is // Handle height twenty-three & twenty-four w/ branchless logic. typeHash := ternary( - eq(treeHeight, 24), - BulkOrder_Typehash_Height_TwentyFour, - BulkOrder_Typehash_Height_TwentyThree + 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) } } From 2b8103af85e678317b7f4f8fb531f05b9171e094 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:12:31 -0800 Subject: [PATCH 57/57] Add v1.3 deployment addresses --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/README.md b/README.md index 0a0ad5e91..fcf0f1d3d 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts 0x00000000000006c7676171937C444f6BDe3D6282 +Seaport 1.3 +0x0000000000000aD24e80fd803C6ac37206a45f15 + + ConduitController 0x00000000F9490004C11Cef243f5400493c00Ad63 @@ -64,6 +68,7 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts Network Seaport 1.1 Seaport 1.2 +Seaport 1.3 ConduitController @@ -77,6 +82,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -90,6 +99,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://goerli.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://goerli.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -103,6 +116,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://sepolia.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://sepolia.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -116,6 +133,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://polygonscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://polygonscan.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -129,6 +150,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://mumbai.polygonscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://mumbai.polygonscan.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -142,6 +167,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://scope.klaytn.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://scope.klaytn.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -155,6 +184,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://baobab.scope.klaytn.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://baobab.scope.klaytn.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -168,6 +201,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://optimistic.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://optimistic.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -181,6 +218,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://goerli-optimism.etherscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://goerli-optimism.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -194,6 +235,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://arbiscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -207,6 +252,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://goerli.arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://goerli.arbiscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -220,6 +269,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://nova.arbiscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://nova.arbiscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -233,6 +286,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://snowtrace.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://snowtrace.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -246,6 +303,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://testnet.snowtrace.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://testnet.snowtrace.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -259,6 +320,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://gnosisscan.io/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://gnossiscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -272,6 +337,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://bscscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://bscscan.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) @@ -285,6 +354,10 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts +[0x0000000000000aD24e80fd803C6ac37206a45f15](https://testnet.bscscan.com/address/0x0000000000000ad24e80fd803c6ac37206a45f15#code) + + + [0x00000000F9490004C11Cef243f5400493c00Ad63](https://testnet.bscscan.com/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code)