From 1431c9470c3bdfa7d44dce44e23ad5b8b71e9eea Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 10:30:07 -0700 Subject: [PATCH 001/126] check that item.amount of 721 transfers is 1 --- contracts/helpers/TransferHelper.sol | 13 ++++++++++++- contracts/interfaces/TransferHelperInterface.sol | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 593fd967b..9df4e4175 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -87,6 +87,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { if (item.itemType == ConduitItemType.NATIVE) { revert InvalidItemType(); } else if (item.itemType == ConduitItemType.ERC20) { + // Transfer ERC20 token. _performERC20Transfer( item.token, msg.sender, @@ -94,13 +95,20 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { + // Ensure that exactly one 721 item is being transferred. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } + + // Transfer ERC721 token. _performERC721Transfer( item.token, msg.sender, recipient, item.identifier ); - } else { + } else if (item.itemType == ConduitItemType.ERC1155) { + // Transfer ERC1155 token. _performERC1155Transfer( item.token, msg.sender, @@ -108,6 +116,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { item.identifier, item.amount ); + } else { + // Throw with an error. + revert InvalidItemType(); } } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index c579868fd..1b59441cb 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -10,6 +10,12 @@ interface TransferHelperInterface { */ error InvalidItemType(); + /** + * @dev Revert with an error when attempting to transfer an amount of + * 721 tokens other than 1. + */ + error InvalidERC721TransferAmount(); + /** * @notice Transfer multiple items to a single recipient. * From 500ea8fe91e9793a9cc36c6507c8cf66086dc2fd Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 10:33:49 -0700 Subject: [PATCH 002/126] import TokenTransferrerErrors --- contracts/helpers/TransferHelper.sol | 2 ++ contracts/interfaces/TransferHelperInterface.sol | 6 ------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 9df4e4175..00562c0d5 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -5,6 +5,8 @@ import "./TransferHelperStructs.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; +import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; + import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; // prettier-ignore diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 1b59441cb..c579868fd 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -10,12 +10,6 @@ interface TransferHelperInterface { */ error InvalidItemType(); - /** - * @dev Revert with an error when attempting to transfer an amount of - * 721 tokens other than 1. - */ - error InvalidERC721TransferAmount(); - /** * @notice Transfer multiple items to a single recipient. * From e6a321c5855c7add10f9da6c29fd8a9c583cef8d Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 12:35:24 -0700 Subject: [PATCH 003/126] add safeTransfer check --- contracts/helpers/TransferHelper.sol | 19 ++++++++++++++++++- .../interfaces/TransferHelperInterface.sol | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 00562c0d5..7032793e4 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.7; import "./TransferHelperStructs.sol"; +import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; + import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; @@ -97,7 +99,22 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { - // Ensure that exactly one 721 item is being transferred. + // If recipient is a contract, ensure it can receive + // 721 tokens. + if (recipient.code.length != 0) { + if ( + ERC721TokenReceiver(recipient).onERC721Received( + msg.sender, + msg.sender, + item.identifier, + "" + ) != + ERC721TokenReceiver.onERC721Received.selector + ) { + revert InvalidRecipient(); + } + } + // Ensure that the amount for 721 token transfers is 1. if (item.amount != 1) { revert InvalidERC721TransferAmount(); } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index c579868fd..35fad74d3 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -10,6 +10,12 @@ interface TransferHelperInterface { */ error InvalidItemType(); + /** + * @dev Revert with an error when attempting to execute transfers to an + invalid recipient. + */ + error InvalidRecipient(); + /** * @notice Transfer multiple items to a single recipient. * From 37bccc90acba18abd0e5a9e13111e7bb58859aaa Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 13:12:09 -0700 Subject: [PATCH 004/126] add conduit check --- contracts/helpers/TransferHelper.sol | 24 ++++++++++++++----- .../interfaces/TransferHelperInterface.sol | 16 ++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 7032793e4..b9bae4219 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -35,8 +35,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Allow for interaction with the conduit controller. ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER; - // Cache the conduit creation hash used by the conduit controller. + // Set conduit creation code and runtime code hashes as immutable arguments. bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; + bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; /** * @dev Set the supplied conduit controller and retrieve its @@ -48,12 +49,13 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { * ERC20/721/1155 tokens. */ constructor(address conduitController) { - // Get the conduit creation code hash from the supplied conduit - // controller and set it as an immutable. + // Get the conduit creation code and runtime code hashes from the + // supplied conduit controller and set them as an immutable. ConduitControllerInterface controller = ConduitControllerInterface( conduitController ); - (_CONDUIT_CREATION_CODE_HASH, ) = controller.getConduitCodeHashes(); + (_CONDUIT_CREATION_CODE_HASH, _CONDUIT_RUNTIME_CODE_HASH) = controller + .getConduitCodeHashes(); // Set the supplied conduit controller as an immutable. _CONDUIT_CONTROLLER = controller; @@ -91,6 +93,11 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { if (item.itemType == ConduitItemType.NATIVE) { revert InvalidItemType(); } else if (item.itemType == ConduitItemType.ERC20) { + // Ensure that the identifier for an ERC20 token is 0. + if (item.identifier != 0) { + revert InvalidERC20Identifier(); + } + // Transfer ERC20 token. _performERC20Transfer( item.token, @@ -111,10 +118,10 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) != ERC721TokenReceiver.onERC721Received.selector ) { - revert InvalidRecipient(); + revert InvalidERC721Recipient(); } } - // Ensure that the amount for 721 token transfers is 1. + // Ensure that the amount for an ERC721 transfer is 1. if (item.amount != 1) { revert InvalidERC721TransferAmount(); } @@ -161,6 +168,11 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) ); + // Check that a conduit exists at the derived address. + if (conduit.codehash != _CONDUIT_RUNTIME_CODE_HASH) { + revert ConduitDoesNotExist(); + } + // Declare a new array to populate with each token transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( totalTransfers diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 35fad74d3..6482bd484 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -11,10 +11,20 @@ interface TransferHelperInterface { error InvalidItemType(); /** - * @dev Revert with an error when attempting to execute transfers to an - invalid recipient. + * @dev Revert with an error when attempting to execute an ERC721 transfer + to an invalid recipient. */ - error InvalidRecipient(); + error InvalidERC721Recipient(); + + /** + * @dev Revert with an error when an ERC20 token has an invalid identifier. + */ + error InvalidERC20Identifier(); + + /** + * @dev Revert with an error when attempting to use a nonexisting conduit. + */ + error ConduitDoesNotExist(); /** * @notice Transfer multiple items to a single recipient. From 941a4357d55b27f7517a53925eaaec8df2585712 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 13:48:45 -0700 Subject: [PATCH 005/126] prettier --- contracts/helpers/TransferHelper.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index b9bae4219..16a194444 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -3,10 +3,12 @@ pragma solidity >=0.8.7; import "./TransferHelperStructs.sol"; +// prettier-ignore import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; +// prettier-ignore import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; From 18a6c53767d6390f8deedacfb28a9351d95fd5b3 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 15 Jun 2022 13:54:43 -0700 Subject: [PATCH 006/126] bump --- contracts/helpers/TransferHelper.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 16a194444..e8d046b73 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -4,12 +4,16 @@ pragma solidity >=0.8.7; import "./TransferHelperStructs.sol"; // prettier-ignore -import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; +import { + ERC721TokenReceiver +} from "@rari-capital/solmate/src/tokens/ERC721.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; // prettier-ignore -import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; +import { + TokenTransferrerErrors +} from "../interfaces/TokenTransferrerErrors.sol"; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; From c3de741461176ede78e6d20095cf6d32f86b37f9 Mon Sep 17 00:00:00 2001 From: Aspyn Palatnick Date: Wed, 15 Jun 2022 17:43:25 -0400 Subject: [PATCH 007/126] update tests to use new error messages --- test/foundry/TransferHelperTest.sol | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 051934490..82504e8ec 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -585,6 +585,29 @@ contract TransferHelperTest is BaseOrderTest { // Test reverts + function testRevertBulkTransferERC20InvalidIdentifier( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItem memory item = _getFuzzedTransferItem( + ConduitItemType.ERC20, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0] + ); + // Ensure ERC20 identifier is at least 1 + item.identifier += 1; + + _performSingleItemTransferAndCheckBalances( + item, + alice, + bob, + false, + abi.encodePacked( + TransferHelperInterface.InvalidERC20Identifier.selector + ) + ); + } + function testRevertBulkTransferETHonly(FuzzInputsCommon memory inputs) public { @@ -722,7 +745,9 @@ contract TransferHelperTest is BaseOrderTest { alice, bob, true, - REVERT_DATA_NO_MSG + abi.encodePacked( + TransferHelperInterface.ConduitDoesNotExist.selector + ) ); } } From 8bd9970794226f98238dfb636b616a692326029c Mon Sep 17 00:00:00 2001 From: Aspyn Palatnick Date: Wed, 15 Jun 2022 18:07:49 -0400 Subject: [PATCH 008/126] add test --- test/foundry/TransferHelperTest.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 82504e8ec..d3b6f21ef 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -608,6 +608,27 @@ contract TransferHelperTest is BaseOrderTest { ); } + function testRevertBulkTransferERC721InvalidRecipient( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItem memory item = _getFuzzedTransferItem( + ConduitItemType.ERC721, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0] + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + item.token, + false, + abi.encodePacked( + TransferHelperInterface.InvalidERC721Recipient.selector + ) + ); + } + function testRevertBulkTransferETHonly(FuzzInputsCommon memory inputs) public { From 879ec171f583eb2079d95aa3f380244e4c247de6 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 12:33:45 -0700 Subject: [PATCH 009/126] add try catch block --- contracts/helpers/TransferHelper.sol | 20 ++++++++++++++----- .../interfaces/TransferHelperInterface.sol | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index e8d046b73..ebcb843fa 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -113,17 +113,27 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ); } else if (item.itemType == ConduitItemType.ERC721) { // If recipient is a contract, ensure it can receive - // 721 tokens. + // ERC721 tokens. if (recipient.code.length != 0) { - if ( + // Check if recipient can receive ERC721 tokens + try ERC721TokenReceiver(recipient).onERC721Received( msg.sender, msg.sender, item.identifier, "" - ) != - ERC721TokenReceiver.onERC721Received.selector - ) { + ) + returns (bytes4 selector) { + if ( + selector != + ERC721TokenReceiver + .onERC721Received + .selector + ) { + revert InvalidERC721Recipient(); + } + // Revert if recipient cannot accept ERC721 tokens. + } catch { revert InvalidERC721Recipient(); } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 6482bd484..68745f8c6 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -22,7 +22,7 @@ interface TransferHelperInterface { error InvalidERC20Identifier(); /** - * @dev Revert with an error when attempting to use a nonexisting conduit. + * @dev Revert with an error when attempting to call a nonexistent conduit. */ error ConduitDoesNotExist(); From aff8338eb9c6d5fe06a0fc7efe428ec75f818ca5 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 13:14:34 -0700 Subject: [PATCH 010/126] add js tests --- test/index.js | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/test/index.js b/test/index.js index fabf05103..5e72e5d8e 100644 --- a/test/index.js +++ b/test/index.js @@ -10426,6 +10426,157 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ) ).to.be.revertedWith("InvalidItemType"); }); + + it("Reverts on invalid ERC20 identifier", async () => { + const erc20TransferHelperItems = [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc20TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); + + it("Reverts on invalid ERC721 transfer amount", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + + it("Reverts on invalid item type", async () => { + const invalidTransferHelperItems = [ + { + itemType: 4, + token: ethers.constants.AddressZero, + identifier: 1, + amount: 10, + }, + { + itemType: 4, + token: ethers.constants.AddressZero, + identifier: 2, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + invalidTransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts on invalid ERC721 recipient", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + tempERC721Contract.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + + it("Reverts on nonexistent conduit", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("0xabc") + ) + ).to.be.revertedWith("ConduitDoesNotExist"); + }); }); describe("Reverts", async () => { From ad797d2593a8d773cddbec49236da8ecaed9bd4c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 14:09:03 -0700 Subject: [PATCH 011/126] add invalid recipient helper contract --- contracts/helpers/TransferHelper.sol | 1 + contracts/test/InvalidERC721Recipient.sol | 13 +++++++++ test/index.js | 33 +++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 contracts/test/InvalidERC721Recipient.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index ebcb843fa..05ddab387 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -124,6 +124,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { "" ) returns (bytes4 selector) { + // Check if onERC721Received selector is valid. if ( selector != ERC721TokenReceiver diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol new file mode 100644 index 000000000..945de3c62 --- /dev/null +++ b/contracts/test/InvalidERC721Recipient.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13; + +contract InvalidERC721Recipient { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external pure returns (bytes4) { + return 0xabcd0000; + } +} diff --git a/test/index.js b/test/index.js index 5e72e5d8e..ff38fd964 100644 --- a/test/index.js +++ b/test/index.js @@ -10537,6 +10537,39 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ).to.be.revertedWith("InvalidERC721Recipient"); }); + it("Reverts on invalid function selector", async () => { + const invalidRecipientFactory = await ethers.getContractFactory( + "InvalidERC721Recipient" + ); + invalidRecipient = await invalidRecipientFactory.deploy(); + + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + invalidRecipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + it("Reverts on nonexistent conduit", async () => { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); From a090452f57226afaf1c768d9325b8abb8090d93d Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 14:24:30 -0700 Subject: [PATCH 012/126] remove check for invalid item type since transaction will revert on invalid ConduitItemType --- contracts/helpers/TransferHelper.sol | 3 --- test/index.js | 26 -------------------------- 2 files changed, 29 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 05ddab387..6954f62a6 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -159,9 +159,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { item.identifier, item.amount ); - } else { - // Throw with an error. - revert InvalidItemType(); } } } diff --git a/test/index.js b/test/index.js index ff38fd964..660400e4d 100644 --- a/test/index.js +++ b/test/index.js @@ -10482,32 +10482,6 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ).to.be.revertedWith("InvalidERC721TransferAmount"); }); - it("Reverts on invalid item type", async () => { - const invalidTransferHelperItems = [ - { - itemType: 4, - token: ethers.constants.AddressZero, - identifier: 1, - amount: 10, - }, - { - itemType: 4, - token: ethers.constants.AddressZero, - identifier: 2, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - invalidTransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidItemType"); - }); - it("Reverts on invalid ERC721 recipient", async () => { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); From f2100c7e5e950a550be97df31ac226e3fac1a8f5 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 15:19:17 -0700 Subject: [PATCH 013/126] add invaliderc721recipient to shim --- reference/shim/Shim.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index 46fd7f5c7..ad22008f2 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -13,6 +13,7 @@ import { TestERC721 } from "contracts/test/TestERC721.sol"; import { TestERC1155 } from "contracts/test/TestERC1155.sol"; import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; +import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; // prettier-ignore import { ImmutableCreate2FactoryInterface From 1edd854d8a8a6093817a0287f07ce07c4d4b9f64 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 16 Jun 2022 17:05:24 -0700 Subject: [PATCH 014/126] progress on forge tests --- test/foundry/TransferHelperTest.sol | 42 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index d3b6f21ef..260611037 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -325,7 +325,7 @@ contract TransferHelperTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + 0 ); _performSingleItemTransferAndCheckBalances( @@ -435,7 +435,7 @@ contract TransferHelperTest is BaseOrderTest { ); items[1] = _getFuzzedTransferItem( ConduitItemType.ERC721, - inputs.amounts[1], + 1, inputs.tokenIndex[1], inputs.identifiers[1] ); @@ -443,7 +443,7 @@ contract TransferHelperTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[2], inputs.tokenIndex[2], - inputs.identifiers[2] + 0 ); _performMultiItemTransferAndCheckBalances( @@ -550,25 +550,26 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testBulkTransferERC721AmountMoreThan1NotUsingConduit( + function testBulkTransferERC7211NotUsingConduit( FuzzInputsCommon memory inputs ) public { - TransferHelperItem - memory item = _getFuzzedERC721TransferItemWithAmountGreaterThan1( - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0] - ); + TransferHelperItem memory item = _getFuzzedTransferItem( + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0] + ); _performSingleItemTransferAndCheckBalances(item, alice, bob, false, ""); } - function testBulkTransferERC721AmountMoreThan1AndERC20NotUsingConduit( + function testBulkTransferERC721AndERC20NotUsingConduit( FuzzInputsCommon memory inputs ) public { TransferHelperItem[] memory items = new TransferHelperItem[](2); - items[0] = _getFuzzedERC721TransferItemWithAmountGreaterThan1( - inputs.amounts[0], + items[0] = _getFuzzedTransferItem( + ConduitItemType.ERC721, + 1, inputs.tokenIndex[0], inputs.identifiers[0] ); @@ -577,7 +578,7 @@ contract TransferHelperTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[1], inputs.tokenIndex[1], - inputs.identifiers[1] + 0 ); _performMultiItemTransferAndCheckBalances(items, alice, bob, false, ""); @@ -613,7 +614,7 @@ contract TransferHelperTest is BaseOrderTest { ) public { TransferHelperItem memory item = _getFuzzedTransferItem( ConduitItemType.ERC721, - inputs.amounts[0], + 1, inputs.tokenIndex[0], inputs.identifiers[0] ); @@ -660,7 +661,7 @@ contract TransferHelperTest is BaseOrderTest { ); items[1] = _getFuzzedTransferItem( ConduitItemType.ERC721, - inputs.amounts[1], + 1, inputs.tokenIndex[1], inputs.identifiers[1] ); @@ -675,11 +676,14 @@ contract TransferHelperTest is BaseOrderTest { } function testRevertBulkTransferERC721AmountMoreThan1UsingConduit( - FuzzInputsCommon memory inputs + FuzzInputsCommon memory inputs, + uint256 invalidAmount ) public { + vm.assume(invalidAmount > 1); + TransferHelperItem memory item = _getFuzzedERC721TransferItemWithAmountGreaterThan1( - inputs.amounts[0], + invalidAmount, inputs.tokenIndex[0], inputs.identifiers[0] ); @@ -698,6 +702,8 @@ contract TransferHelperTest is BaseOrderTest { function testRevertBulkTransferERC721AmountMoreThan1AndERC20UsingConduit( FuzzInputsCommon memory inputs ) public { + vm.assume(inputs.amounts[0] > 0); + TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedERC721TransferItemWithAmountGreaterThan1( inputs.amounts[0], From dfb5b5b3bd0abb1db62cb3cbd3cf7c6c7ddd4dbf Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 17 Jun 2022 10:45:34 -0700 Subject: [PATCH 015/126] import invaliderc721recipient --- test/foundry/TransferHelperTest.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 260611037..0092c61e3 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -17,6 +17,8 @@ import { TestERC20 } from "../../contracts/test/TestERC20.sol"; import { TestERC721 } from "../../contracts/test/TestERC721.sol"; import { TestERC1155 } from "../../contracts/test/TestERC1155.sol"; +import { InvalidERC721Recipient } from "../../contracts/test/InvalidERC721Recipient.sol"; + import { TokenTransferrerErrors } from "../../contracts/interfaces/TokenTransferrerErrors.sol"; import { TransferHelperInterface } from "../../contracts/interfaces/TransferHelperInterface.sol"; @@ -593,7 +595,7 @@ contract TransferHelperTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + 5 ); // Ensure ERC20 identifier is at least 1 item.identifier += 1; @@ -612,6 +614,8 @@ contract TransferHelperTest is BaseOrderTest { function testRevertBulkTransferERC721InvalidRecipient( FuzzInputsCommon memory inputs ) public { + InvalidERC721Recipient invalidRecipient = new InvalidERC721Recipient(); + TransferHelperItem memory item = _getFuzzedTransferItem( ConduitItemType.ERC721, 1, @@ -622,7 +626,7 @@ contract TransferHelperTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - item.token, + address(invalidRecipient), false, abi.encodePacked( TransferHelperInterface.InvalidERC721Recipient.selector From 834503e4d710fd5d369f706cfbc4f54d9b90bf75 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 17 Jun 2022 10:54:04 -0700 Subject: [PATCH 016/126] change compiler --- contracts/test/InvalidERC721Recipient.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 945de3c62..5e2aae203 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity >=0.8.7; contract InvalidERC721Recipient { function onERC721Received( From e617505fd574f1008f308641e95e3c2c17de1fb4 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 17 Jun 2022 11:11:58 -0700 Subject: [PATCH 017/126] inherit ERC721TokenReceiver --- contracts/test/InvalidERC721Recipient.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 5e2aae203..52f27f8bc 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; -contract InvalidERC721Recipient { +import { ERC721TokenReceiver } from "../../lib/solmate/src/tokens/ERC721.sol"; + +contract InvalidERC721Recipient is ERC721TokenReceiver { function onERC721Received( address, address, uint256, bytes calldata - ) external pure returns (bytes4) { + ) external pure override returns (bytes4) { return 0xabcd0000; } } From 76bd83abbd26e4c748868033101c46e70ec3b155 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 17 Jun 2022 11:15:48 -0700 Subject: [PATCH 018/126] update shim --- reference/shim/Shim.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index ad22008f2..4c8bdcf91 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -14,6 +14,8 @@ import { TestERC1155 } from "contracts/test/TestERC1155.sol"; import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; +import { ERC721TokenReceiver } from "../../lib/solmate/src/tokens/ERC721.sol"; + // prettier-ignore import { ImmutableCreate2FactoryInterface From 37fb1e0f3bf7c069df0c9df182dbf0255aa86e98 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Fri, 17 Jun 2022 11:32:10 -0700 Subject: [PATCH 019/126] update erc721tokenreceiver shim import --- reference/shim/Shim.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index 4c8bdcf91..4442eb728 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -14,7 +14,7 @@ import { TestERC1155 } from "contracts/test/TestERC1155.sol"; import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; -import { ERC721TokenReceiver } from "../../lib/solmate/src/tokens/ERC721.sol"; +import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; // prettier-ignore import { From 1023b9e2083e23782e2006784b28893b9f77c630 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 17 Jun 2022 11:44:16 -0700 Subject: [PATCH 020/126] change import --- contracts/test/InvalidERC721Recipient.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 52f27f8bc..144b041e9 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; -import { ERC721TokenReceiver } from "../../lib/solmate/src/tokens/ERC721.sol"; +import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; contract InvalidERC721Recipient is ERC721TokenReceiver { function onERC721Received( From e0772b6d4fdfa05a8f6539fa48732b970cf2d203 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 22 Jun 2022 12:55:34 -0400 Subject: [PATCH 021/126] address comments --- contracts/helpers/TransferHelper.sol | 36 ++++++++++++------- .../interfaces/TransferHelperInterface.sol | 5 +-- contracts/test/InvalidERC721Recipient.sol | 9 ++++- reference/shim/Shim.sol | 1 - test/foundry/TransferHelperTest.sol | 4 +-- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 6954f62a6..b0ab1d1b3 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -3,11 +3,6 @@ pragma solidity >=0.8.7; import "./TransferHelperStructs.sol"; -// prettier-ignore -import { - ERC721TokenReceiver -} from "@rari-capital/solmate/src/tokens/ERC721.sol"; - import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; // prettier-ignore @@ -118,7 +113,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Check if recipient can receive ERC721 tokens try ERC721TokenReceiver(recipient).onERC721Received( - msg.sender, + address(this), msg.sender, item.identifier, "" @@ -134,10 +129,11 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert InvalidERC721Recipient(); } // Revert if recipient cannot accept ERC721 tokens. - } catch { + } catch Error(string memory) { revert InvalidERC721Recipient(); } } + // Ensure that the amount for an ERC721 transfer is 1. if (item.amount != 1) { revert InvalidERC721TransferAmount(); @@ -182,11 +178,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) ); - // Check that a conduit exists at the derived address. - if (conduit.codehash != _CONDUIT_RUNTIME_CODE_HASH) { - revert ConduitDoesNotExist(); - } - // Declare a new array to populate with each token transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( totalTransfers @@ -213,10 +204,29 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } // Call the conduit and execute bulk transfers. - ConduitInterface(conduit).execute(conduitTransfers); + bytes4 conduitMagicValue = ConduitInterface(conduit).execute( + conduitTransfers + ); + + // Revert if the magic value returned by the conduit call does not + // equal the expected value. + if ( + conduitMagicValue != ConduitInterface(conduit).execute.selector + ) { + revert InvalidConduit(); + } } // Return a magic value indicating that the transfers were performed. magicValue = this.bulkTransfer.selector; } } + +interface ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 68745f8c6..75b3de6d4 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -22,9 +22,10 @@ interface TransferHelperInterface { error InvalidERC20Identifier(); /** - * @dev Revert with an error when attempting to call a nonexistent conduit. + * @dev Revert with an error when a call to a conduit returns an invalid + * magic value. */ - error ConduitDoesNotExist(); + error InvalidConduit(); /** * @notice Transfer multiple items to a single recipient. diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 144b041e9..56957714c 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,7 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; -import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; +interface ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} contract InvalidERC721Recipient is ERC721TokenReceiver { function onERC721Received( diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index 4442eb728..f7f656f52 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -14,7 +14,6 @@ import { TestERC1155 } from "contracts/test/TestERC1155.sol"; import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; -import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; // prettier-ignore import { diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 0092c61e3..d46c83333 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -776,9 +776,7 @@ contract TransferHelperTest is BaseOrderTest { alice, bob, true, - abi.encodePacked( - TransferHelperInterface.ConduitDoesNotExist.selector - ) + abi.encodePacked(TransferHelperInterface.InvalidConduit.selector) ); } } From bc7b65f949f23095aaca5eee65f162a6cfb0ecba Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 23 Jun 2022 14:25:21 -0400 Subject: [PATCH 022/126] check if recipient is contract outside loop --- contracts/helpers/TransferHelper.sol | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index b0ab1d1b3..2272555be 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -82,6 +82,14 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // If no conduitKey is given, use TokenTransferrer to perform transfers. if (conduitKey == bytes32(0)) { + // Create a boolean that reflects whether recipient is a contract. + bool recipientIsContract; + + // Check if recipient is a contract. + if (recipient.code.length != 0) { + recipientIsContract = true; + } + // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. @@ -109,8 +117,8 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } else if (item.itemType == ConduitItemType.ERC721) { // If recipient is a contract, ensure it can receive // ERC721 tokens. - if (recipient.code.length != 0) { - // Check if recipient can receive ERC721 tokens + if (recipientIsContract) { + // Check if recipient can receive ERC721 tokens. try ERC721TokenReceiver(recipient).onERC721Received( address(this), @@ -129,11 +137,18 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert InvalidERC721Recipient(); } // Revert if recipient cannot accept ERC721 tokens. - } catch Error(string memory) { - revert InvalidERC721Recipient(); + } catch (bytes memory data) { + // Bubble up recipient's revert reasons if present. + if (data.length != 0) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } else { + revert InvalidERC721Recipient(); + } } } - // Ensure that the amount for an ERC721 transfer is 1. if (item.amount != 1) { revert InvalidERC721TransferAmount(); From 5f214d5d4db81f87cb4be686cf78686613ef7499 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 24 Jun 2022 14:27:16 -0400 Subject: [PATCH 023/126] bubble up conduit revert reason --- contracts/helpers/TransferHelper.sol | 43 ++++++++++++++++------------ test/index.js | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 2272555be..cec518200 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -26,6 +26,15 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; +interface ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} + /** * @title TransferHelper * @author stuckinaboot, stephankmin @@ -138,7 +147,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } // Revert if recipient cannot accept ERC721 tokens. } catch (bytes memory data) { - // Bubble up recipient's revert reasons if present. + // Bubble up recipient's revert reason if present. if (data.length != 0) { assembly { returndatacopy(0, 0, returndatasize()) @@ -218,17 +227,24 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } - // Call the conduit and execute bulk transfers. - bytes4 conduitMagicValue = ConduitInterface(conduit).execute( - conduitTransfers - ); - // Revert if the magic value returned by the conduit call does not // equal the expected value. - if ( - conduitMagicValue != ConduitInterface(conduit).execute.selector + try ConduitInterface(conduit).execute(conduitTransfers) returns ( + bytes4 selector ) { - revert InvalidConduit(); + if (selector != ConduitInterface(conduit).execute.selector) { + revert InvalidConduit(); + } + } catch (bytes memory data) { + // Bubble up the conduit's revert reason if present. + if (data.length != 0) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } else { + revert InvalidConduit(); + } } } @@ -236,12 +252,3 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { magicValue = this.bulkTransfer.selector; } } - -interface ERC721TokenReceiver { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external returns (bytes4); -} diff --git a/test/index.js b/test/index.js index 660400e4d..332d8455e 100644 --- a/test/index.js +++ b/test/index.js @@ -10582,7 +10582,7 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function recipient.address, ethers.utils.formatBytes32String("0xabc") ) - ).to.be.revertedWith("ConduitDoesNotExist"); + ).to.be.revertedWith("InvalidConduit"); }); }); From db0fbd8e9b11f5eb7837a21bdf1b11f2a9cebcee Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 24 Jun 2022 18:54:55 -0400 Subject: [PATCH 024/126] check conduit codehash --- contracts/helpers/TransferHelper.sol | 33 +++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index cec518200..f4adbc279 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -202,6 +202,21 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) ); + // Create a variable to store the codehash of the conduit. + bytes32 codeHash; + + // Retrieve the codehash of the conduit and assign it to codeHash. + assembly { + codeHash := extcodehash(conduit) + } + + // Ensure codeHash equals the immutable conduit runtime codehash + // to ensure the conduit implements `execute` for the subsequent + // external call. + if (codeHash != _CONDUIT_RUNTIME_CODE_HASH) { + revert InvalidConduit(); + } + // Declare a new array to populate with each token transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( totalTransfers @@ -227,15 +242,11 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } - // Revert if the magic value returned by the conduit call does not - // equal the expected value. - try ConduitInterface(conduit).execute(conduitTransfers) returns ( - bytes4 selector + // If the external call fails, revert with the conduit's + // custom error. + try ConduitInterface(conduit).execute(conduitTransfers) {} catch ( + bytes memory data ) { - if (selector != ConduitInterface(conduit).execute.selector) { - revert InvalidConduit(); - } - } catch (bytes memory data) { // Bubble up the conduit's revert reason if present. if (data.length != 0) { assembly { @@ -245,6 +256,12 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } else { revert InvalidConduit(); } + // Revert if the error provides a reason string. + } catch Error(string memory reason) { + revert InvalidConduit(); + // Revert if the error was caused by a panic. + } catch Panic(uint256 errorCode) { + revert InvalidConduit(); } } From f1c488cc83f2c26fa67175764259dd4ef3de4616 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 10:34:53 -0400 Subject: [PATCH 025/126] test revert error in erc721 receiver --- contracts/test/ERC721ReceiverMock.sol | 55 +++++++++++++++++++++++ contracts/test/InvalidERC721Recipient.sol | 4 +- test/index.js | 52 ++++++++++++++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 contracts/test/ERC721ReceiverMock.sol diff --git a/contracts/test/ERC721ReceiverMock.sol b/contracts/test/ERC721ReceiverMock.sol new file mode 100644 index 000000000..9e0b7c7e5 --- /dev/null +++ b/contracts/test/ERC721ReceiverMock.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IERC721Receiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} + +contract ERC721ReceiverMock is IERC721Receiver { + enum Error { + None, + RevertWithMessage, + RevertWithoutMessage, + Panic + } + + bytes4 private immutable _retval; + Error private immutable _error; + + event Received( + address operator, + address from, + uint256 tokenId, + bytes data, + uint256 gas + ); + + constructor(bytes4 retval, Error error) { + _retval = retval; + _error = error; + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes memory data + ) public override returns (bytes4) { + if (_error == Error.RevertWithMessage) { + revert("ERC721ReceiverMock: reverting"); + } else if (_error == Error.RevertWithoutMessage) { + revert(); + } else if (_error == Error.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + emit Received(operator, from, tokenId, data, gasleft()); + return _retval; + } +} diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 56957714c..b22b252dd 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.7; -interface ERC721TokenReceiver { +interface IERC721Receiver { function onERC721Received( address, address, @@ -10,7 +10,7 @@ interface ERC721TokenReceiver { ) external returns (bytes4); } -contract InvalidERC721Recipient is ERC721TokenReceiver { +contract InvalidERC721Recipient is IERC721Receiver { function onERC721Received( address, address, diff --git a/test/index.js b/test/index.js index 332d8455e..0c05af09c 100644 --- a/test/index.js +++ b/test/index.js @@ -10545,7 +10545,7 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }); it("Reverts on nonexistent conduit", async () => { - // Deploy Contract + // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); const transferHelperItems = [ @@ -10584,6 +10584,56 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ) ).to.be.revertedWith("InvalidConduit"); }); + + it("Reverts on error in ERC721 receiver", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + 0xabcd0000, + 1 + ); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + mockERC721Receiver.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + }); }); describe("Reverts", async () => { From 3734c8efbd308e9b2527ef945be89c99b416941b Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 10:46:27 -0400 Subject: [PATCH 026/126] add receiver to shim --- reference/shim/Shim.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index f7f656f52..237b992ad 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -14,6 +14,7 @@ import { TestERC1155 } from "contracts/test/TestERC1155.sol"; import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; +import { ERC721ReceiverMock } from "contracts/test/ERC721ReceiverMock.sol"; // prettier-ignore import { From 2c7326e33061b12df90060bc9e63d6503449f363 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 12:14:30 -0400 Subject: [PATCH 027/126] add coverage test --- test/index.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/index.js b/test/index.js index 0c05af09c..2407aa787 100644 --- a/test/index.js +++ b/test/index.js @@ -10634,6 +10634,45 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ) ).to.be.revertedWith("ERC721ReceiverMock: reverting"); }); + + it("Reverts on error in conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const transferHelperItems = [ + // Invalid item type + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("InvalidItemType"); + }); }); describe("Reverts", async () => { From 8e42b77f526fb173ade1c2bdbddc81d1a5f93a4a Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 14:06:50 -0400 Subject: [PATCH 028/126] add panic js test --- contracts/helpers/TransferHelper.sol | 4 +- .../interfaces/TransferHelperInterface.sol | 12 ++++ contracts/test/TestERC20Panic.sol | 17 +++++ test/index.js | 70 ++++++++++++++++++- 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 contracts/test/TestERC20Panic.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index f4adbc279..5127a22d8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -258,10 +258,10 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } // Revert if the error provides a reason string. } catch Error(string memory reason) { - revert InvalidConduit(); + revert ConduitErrorString(reason); // Revert if the error was caused by a panic. } catch Panic(uint256 errorCode) { - revert InvalidConduit(); + revert ConduitErrorPanic(errorCode); } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 75b3de6d4..ac615e8cf 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -27,6 +27,18 @@ interface TransferHelperInterface { */ error InvalidConduit(); + /** + * @dev Revert with an error when a call to a conduit reverts with a + * reason string. + */ + error ConduitErrorString(string reason); + + /** + * @dev Revert with an error when a call to a conduit reverts with a + * panic error. + */ + error ConduitErrorPanic(uint256 errorCode); + /** * @notice Transfer multiple items to a single recipient. * diff --git a/contracts/test/TestERC20Panic.sol b/contracts/test/TestERC20Panic.sol new file mode 100644 index 000000000..a1448a8a9 --- /dev/null +++ b/contracts/test/TestERC20Panic.sol @@ -0,0 +1,17 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity >=0.8.7; + +import "@rari-capital/solmate/src/tokens/ERC20.sol"; + +contract TestERC20Panic is ERC20("TestPanic", "PANIC", 18) { + function transferFrom( + address from, + address to, + uint256 amount + ) public override returns (bool) { + uint256 a = uint256(0) / uint256(0); + a; + + return true; + } +} diff --git a/test/index.js b/test/index.js index 2407aa787..9981093e5 100644 --- a/test/index.js +++ b/test/index.js @@ -10635,7 +10635,7 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ).to.be.revertedWith("ERC721ReceiverMock: reverting"); }); - it("Reverts on error in conduit", async () => { + it("Reverts with custom error in conduit", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); @@ -10673,6 +10673,74 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) ).to.be.revertedWith("InvalidItemType"); }); + + it("Reverts with bubbled up string error from call to conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // Call will revert since ERC721 tokens have not been minted + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); + }); + + it("Reverts with bubbled up panic error from call to conduit", async () => { + // Deploy mock ERC20 + const mockERC20PanicFactory = await ethers.getContractFactory( + "TestERC20Panic" + ); + mockERC20Panic = await mockERC20PanicFactory.deploy(); + + const transferHelperItems = [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("ConduitErrorPanic(18)"); + }); }); describe("Reverts", async () => { From 275dfa944bb3a8f2f01ab769ae0016fd809098eb Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 14:27:51 -0400 Subject: [PATCH 029/126] shim --- reference/shim/Shim.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index 237b992ad..4c0b59abf 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -15,6 +15,7 @@ import { TestZone } from "contracts/test/TestZone.sol"; import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; import { ERC721ReceiverMock } from "contracts/test/ERC721ReceiverMock.sol"; +import { TestERC20Panic } from "contracts/test/TestERC20Panic.sol"; // prettier-ignore import { From 8da30d13b167c6929475cf3c70b7a60217b6bcff Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 14:39:17 -0400 Subject: [PATCH 030/126] skip coverage for mock test contracts --- config/.solcover.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/.solcover.js b/config/.solcover.js index 9cba82828..ab5fb3fa8 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -30,6 +30,8 @@ module.exports = { "test/TestERC20.sol", "test/TestERC721.sol", "test/TestZone.sol", + "test/TestERC20Panic.sol", + "test/ERC721ReceiverMock", ], configureYulOptimizer: true, solcOptimizerDetails: { From 77456db35111c75d2398c31885ede77a05cc1eee Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 19:25:54 -0400 Subject: [PATCH 031/126] rename interface --- contracts/helpers/TransferHelper.sol | 8 +- contracts/test/ERC721ReceiverMock.sol | 3 +- contracts/test/InvalidERC721Recipient.sol | 8 +- contracts/test/TestERC20Panic.sol | 2 +- test/index.js | 1194 ++++++++++----------- 5 files changed, 606 insertions(+), 609 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 5127a22d8..300dab44d 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -26,7 +26,7 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; -interface ERC721TokenReceiver { +interface IERC721Receiver { function onERC721Received( address, address, @@ -129,7 +129,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { if (recipientIsContract) { // Check if recipient can receive ERC721 tokens. try - ERC721TokenReceiver(recipient).onERC721Received( + IERC721Receiver(recipient).onERC721Received( address(this), msg.sender, item.identifier, @@ -139,9 +139,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Check if onERC721Received selector is valid. if ( selector != - ERC721TokenReceiver - .onERC721Received - .selector + IERC721Receiver.onERC721Received.selector ) { revert InvalidERC721Recipient(); } diff --git a/contracts/test/ERC721ReceiverMock.sol b/contracts/test/ERC721ReceiverMock.sol index 9e0b7c7e5..af9d94475 100644 --- a/contracts/test/ERC721ReceiverMock.sol +++ b/contracts/test/ERC721ReceiverMock.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; +pragma solidity >=0.8.7; interface IERC721Receiver { function onERC721Received( diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index b22b252dd..9d1c4d52e 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -12,10 +12,10 @@ interface IERC721Receiver { contract InvalidERC721Recipient is IERC721Receiver { function onERC721Received( - address, - address, - uint256, - bytes calldata + address operator, + address from, + uint256 tokenId, + bytes calldata data ) external pure override returns (bytes4) { return 0xabcd0000; } diff --git a/contracts/test/TestERC20Panic.sol b/contracts/test/TestERC20Panic.sol index a1448a8a9..4ffbf2ebc 100644 --- a/contracts/test/TestERC20Panic.sol +++ b/contracts/test/TestERC20Panic.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT pragma solidity >=0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; diff --git a/test/index.js b/test/index.js index 9981093e5..0d241c4ba 100644 --- a/test/index.js +++ b/test/index.js @@ -10145,603 +10145,603 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }); }); - describe("TransferHelper tests", async () => { - let sender; - let recipient; - let senderContract; - let recipientContract; - let tempTransferHelper; - let tempConduit; - let tempConduitKey; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - sender = new ethers.Wallet(randomHex(32), provider); - recipient = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - - senderContract = await EIP1271WalletFactory.deploy(sender.address); - recipientContract = await EIP1271WalletFactory.deploy(recipient.address); - - tempConduitKey = owner.address + randomHex(12).slice(2); - tempConduit = await deployNewConduit(owner, tempConduitKey); - - await Promise.all( - [sender, recipient, zone, senderContract, recipientContract].map( - (wallet) => faucet(wallet.address, provider) - ) - ); - - // Deploy a new TransferHelper with the tempConduitController address - const transferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - tempTransferHelper = await transferHelperFactory.deploy( - conduitController.address - ); - - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, tempTransferHelper.address, true); - }); - }); - - it("Executes transfers (many token types) with a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - const erc20Contracts = [numERC20s]; - const erc20Transfers = [numERC20s]; - - const erc721Contracts = [numEC721s]; - const erc721Transfers = [numEC721s]; - - const erc1155Contracts = [numERC1155s]; - const erc1155Transfers = [numERC1155s]; - - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempConduit.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempConduit.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempConduit.address - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - const contracts = erc20Contracts.concat( - erc721Contracts, - erc1155Contracts - ); - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer(transfers, recipient.address, tempConduitKey); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect(await token.balanceOf(sender.address)).to.equal(0); - expect(await token.balanceOf(recipient.address)).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect(await token.ownerOf(identifier)).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf(recipient.address, identifier) - ).to.equal(amount); - break; - } - } - }); - - it("Executes transfers (many token types) without a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - const erc20Contracts = [numERC20s]; - const erc20Transfers = [numERC20s]; - - const erc721Contracts = [numEC721s]; - const erc721Transfers = [numEC721s]; - - const erc1155Contracts = [numERC1155s]; - const erc1155Transfers = [numERC1155s]; - - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - const contracts = erc20Contracts.concat( - erc721Contracts, - erc1155Contracts - ); - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer( - transfers, - recipient.address, - ethers.utils.formatBytes32String("") - ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect(await token.balanceOf(sender.address)).to.equal(0); - expect(await token.balanceOf(recipient.address)).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect(await token.ownerOf(identifier)).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf(recipient.address, identifier) - ).to.equal(amount); - break; - } - } - }); - - it("Reverts on native token transfers", async () => { - const ethTransferHelperItems = [ - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - ethTransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidItemType"); - }); - - it("Reverts on invalid ERC20 identifier", async () => { - const erc20TransferHelperItems = [ - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 5, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 4, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc20TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC20Identifier"); - }); - - it("Reverts on invalid ERC721 transfer amount", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 10, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); - - it("Reverts on invalid ERC721 recipient", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - tempERC721Contract.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721Recipient"); - }); - - it("Reverts on invalid function selector", async () => { - const invalidRecipientFactory = await ethers.getContractFactory( - "InvalidERC721Recipient" - ); - invalidRecipient = await invalidRecipientFactory.deploy(); - - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - invalidRecipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721Recipient"); - }); - - it("Reverts on nonexistent conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("0xabc") - ) - ).to.be.revertedWith("InvalidConduit"); - }); - - it("Reverts on error in ERC721 receiver", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // Deploy mock ERC721 receiver - const mockERC721ReceiverFactory = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - mockERC721Receiver = await mockERC721ReceiverFactory.deploy( - 0xabcd0000, - 1 - ); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - mockERC721Receiver.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("ERC721ReceiverMock: reverting"); - }); - - it("Reverts with custom error in conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const transferHelperItems = [ - // Invalid item type - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("InvalidItemType"); - }); - - it("Reverts with bubbled up string error from call to conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // Call will revert since ERC721 tokens have not been minted - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); - }); - - it("Reverts with bubbled up panic error from call to conduit", async () => { - // Deploy mock ERC20 - const mockERC20PanicFactory = await ethers.getContractFactory( - "TestERC20Panic" - ); - mockERC20Panic = await mockERC20PanicFactory.deploy(); - - const transferHelperItems = [ - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("ConduitErrorPanic(18)"); - }); - }); + // describe("TransferHelper tests", async () => { + // let sender; + // let recipient; + // let senderContract; + // let recipientContract; + // let tempTransferHelper; + // let tempConduit; + // let tempConduitKey; + + // beforeEach(async () => { + // // Setup basic buyer/seller wallets with ETH + // sender = new ethers.Wallet(randomHex(32), provider); + // recipient = new ethers.Wallet(randomHex(32), provider); + // zone = new ethers.Wallet(randomHex(32), provider); + + // senderContract = await EIP1271WalletFactory.deploy(sender.address); + // recipientContract = await EIP1271WalletFactory.deploy(recipient.address); + + // tempConduitKey = owner.address + randomHex(12).slice(2); + // tempConduit = await deployNewConduit(owner, tempConduitKey); + + // await Promise.all( + // [sender, recipient, zone, senderContract, recipientContract].map( + // (wallet) => faucet(wallet.address, provider) + // ) + // ); + + // // Deploy a new TransferHelper with the tempConduitController address + // const transferHelperFactory = await ethers.getContractFactory( + // "TransferHelper" + // ); + // tempTransferHelper = await transferHelperFactory.deploy( + // conduitController.address + // ); + + // await whileImpersonating(owner.address, provider, async () => { + // await conduitController + // .connect(owner) + // .updateChannel(tempConduit.address, tempTransferHelper.address, true); + // }); + // }); + + // it("Executes transfers (many token types) with a conduit", async () => { + // // Get 3 Numbers that's value adds to Item Amount and minimum 1. + // const itemsToCreate = 10; + // const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + // const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + // const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + // const erc20Contracts = [numERC20s]; + // const erc20Transfers = [numERC20s]; + + // const erc721Contracts = [numEC721s]; + // const erc721Transfers = [numEC721s]; + + // const erc1155Contracts = [numERC1155s]; + // const erc1155Transfers = [numERC1155s]; + + // // Create numERC20s amount of ERC20 objects + // for (let i = 0; i < numERC20s; i++) { + // // Deploy Contract + // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // // Create/Approve X amount of ERC20s + // const erc20Transfer = await createTransferWithApproval( + // tempERC20Contract, + // sender, + // 1, + // tempConduit.address + // ); + // erc20Contracts[i] = tempERC20Contract; + // erc20Transfers[i] = erc20Transfer; + // } + + // // Create numEC721s amount of ERC20 objects + // for (let i = 0; i < numEC721s; i++) { + // // Deploy Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // // Create/Approve numEC721s amount of ERC721s + // const erc721Transfer = await createTransferWithApproval( + // tempERC721Contract, + // sender, + // 2, + // tempConduit.address + // ); + // erc721Contracts[i] = tempERC721Contract; + // erc721Transfers[i] = erc721Transfer; + // } + + // // Create numERC1155s amount of ERC1155 objects + // for (let i = 0; i < numERC1155s; i++) { + // // Deploy Contract + // const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + // owner + // ); + // // Create/Approve numERC1155s amount of ERC1155s + // const erc1155Transfer = await createTransferWithApproval( + // tempERC1155Contract, + // sender, + // 3, + // tempConduit.address + // ); + // erc1155Contracts[i] = tempERC1155Contract; + // erc1155Transfers[i] = erc1155Transfer; + // } + + // const transfers = erc20Transfers.concat( + // erc721Transfers, + // erc1155Transfers + // ); + // const contracts = erc20Contracts.concat( + // erc721Contracts, + // erc1155Contracts + // ); + // // Send the bulk transfers + // await tempTransferHelper + // .connect(sender) + // .bulkTransfer(transfers, recipient.address, tempConduitKey); + // // Loop through all transfer to do ownership/balance checks + // for (let i = 0; i < transfers.length; i++) { + // // Get Itemtype, token, amount, identifier + // const { itemType, amount, identifier } = transfers[i]; + // const token = contracts[i]; + + // switch (itemType) { + // case 1: // ERC20 + // // Check balance + // expect(await token.balanceOf(sender.address)).to.equal(0); + // expect(await token.balanceOf(recipient.address)).to.equal(amount); + // break; + // case 2: // ERC721 + // case 4: // ERC721_WITH_CRITERIA + // expect(await token.ownerOf(identifier)).to.equal(recipient.address); + // break; + // case 3: // ERC1155 + // case 5: // ERC1155_WITH_CRITERIA + // // Check balance + // expect(await token.balanceOf(sender.address, identifier)).to.equal( + // 0 + // ); + // expect( + // await token.balanceOf(recipient.address, identifier) + // ).to.equal(amount); + // break; + // } + // } + // }); + + // it("Executes transfers (many token types) without a conduit", async () => { + // // Get 3 Numbers that's value adds to Item Amount and minimum 1. + // const itemsToCreate = 10; + // const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + // const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + // const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + // const erc20Contracts = [numERC20s]; + // const erc20Transfers = [numERC20s]; + + // const erc721Contracts = [numEC721s]; + // const erc721Transfers = [numEC721s]; + + // const erc1155Contracts = [numERC1155s]; + // const erc1155Transfers = [numERC1155s]; + + // // Create numERC20s amount of ERC20 objects + // for (let i = 0; i < numERC20s; i++) { + // // Deploy Contract + // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // // Create/Approve X amount of ERC20s + // const erc20Transfer = await createTransferWithApproval( + // tempERC20Contract, + // sender, + // 1, + // tempTransferHelper.address + // ); + // erc20Contracts[i] = tempERC20Contract; + // erc20Transfers[i] = erc20Transfer; + // } + + // // Create numEC721s amount of ERC20 objects + // for (let i = 0; i < numEC721s; i++) { + // // Deploy Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // // Create/Approve numEC721s amount of ERC721s + // const erc721Transfer = await createTransferWithApproval( + // tempERC721Contract, + // sender, + // 2, + // tempTransferHelper.address + // ); + // erc721Contracts[i] = tempERC721Contract; + // erc721Transfers[i] = erc721Transfer; + // } + + // // Create numERC1155s amount of ERC1155 objects + // for (let i = 0; i < numERC1155s; i++) { + // // Deploy Contract + // const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + // owner + // ); + // // Create/Approve numERC1155s amount of ERC1155s + // const erc1155Transfer = await createTransferWithApproval( + // tempERC1155Contract, + // sender, + // 3, + // tempTransferHelper.address + // ); + // erc1155Contracts[i] = tempERC1155Contract; + // erc1155Transfers[i] = erc1155Transfer; + // } + + // const transfers = erc20Transfers.concat( + // erc721Transfers, + // erc1155Transfers + // ); + // const contracts = erc20Contracts.concat( + // erc721Contracts, + // erc1155Contracts + // ); + // // Send the bulk transfers + // await tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // transfers, + // recipient.address, + // ethers.utils.formatBytes32String("") + // ); + // // Loop through all transfer to do ownership/balance checks + // for (let i = 0; i < transfers.length; i++) { + // // Get Itemtype, token, amount, identifier + // const { itemType, amount, identifier } = transfers[i]; + // const token = contracts[i]; + + // switch (itemType) { + // case 1: // ERC20 + // // Check balance + // expect(await token.balanceOf(sender.address)).to.equal(0); + // expect(await token.balanceOf(recipient.address)).to.equal(amount); + // break; + // case 2: // ERC721 + // case 4: // ERC721_WITH_CRITERIA + // expect(await token.ownerOf(identifier)).to.equal(recipient.address); + // break; + // case 3: // ERC1155 + // case 5: // ERC1155_WITH_CRITERIA + // // Check balance + // expect(await token.balanceOf(sender.address, identifier)).to.equal( + // 0 + // ); + // expect( + // await token.balanceOf(recipient.address, identifier) + // ).to.equal(amount); + // break; + // } + // } + // }); + + // it("Reverts on native token transfers", async () => { + // const ethTransferHelperItems = [ + // { + // itemType: 0, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 0, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 20, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // ethTransferHelperItems, + // recipient.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("InvalidItemType"); + // }); + + // it("Reverts on invalid ERC20 identifier", async () => { + // const erc20TransferHelperItems = [ + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 5, + // amount: 10, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 4, + // amount: 20, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // erc20TransferHelperItems, + // recipient.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("InvalidERC20Identifier"); + // }); + + // it("Reverts on invalid ERC721 transfer amount", async () => { + // // Deploy Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // const erc721TransferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 10, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 20, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // erc721TransferHelperItems, + // recipient.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("InvalidERC721TransferAmount"); + // }); + + // it("Reverts on invalid ERC721 recipient", async () => { + // // Deploy Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // const erc721TransferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // erc721TransferHelperItems, + // tempERC721Contract.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("InvalidERC721Recipient"); + // }); + + // it("Reverts on invalid function selector", async () => { + // const invalidRecipientFactory = await ethers.getContractFactory( + // "InvalidERC721Recipient" + // ); + // invalidRecipient = await invalidRecipientFactory.deploy(); + + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // const erc721TransferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // erc721TransferHelperItems, + // invalidRecipient.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("InvalidERC721Recipient"); + // }); + + // it("Reverts on nonexistent conduit", async () => { + // // Deploy ERC721 Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // const transferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 20, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // transferHelperItems, + // recipient.address, + // ethers.utils.formatBytes32String("0xabc") + // ) + // ).to.be.revertedWith("InvalidConduit"); + // }); + + // it("Reverts on error in ERC721 receiver", async () => { + // // Deploy ERC721 Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // // Deploy mock ERC721 receiver + // const mockERC721ReceiverFactory = await ethers.getContractFactory( + // "ERC721ReceiverMock" + // ); + // mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + // 0xabcd0000, + // 1 + // ); + + // const transferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 20, + // }, + // ]; + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer( + // transferHelperItems, + // mockERC721Receiver.address, + // ethers.utils.formatBytes32String("") + // ) + // ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + // }); + + // it("Reverts with custom error in conduit", async () => { + // // Deploy ERC721 Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // const transferHelperItems = [ + // // Invalid item type + // { + // itemType: 0, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 20, + // }, + // ]; + + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + // ).to.be.revertedWith("InvalidItemType"); + // }); + + // it("Reverts with bubbled up string error from call to conduit", async () => { + // // Deploy ERC721 Contract + // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // // Call will revert since ERC721 tokens have not been minted + // const transferHelperItems = [ + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 1, + // amount: 1, + // }, + // { + // itemType: 2, + // token: tempERC721Contract.address, + // identifier: 2, + // amount: 1, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: ethers.constants.AddressZero, + // identifier: 0, + // amount: 20, + // }, + // ]; + + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + // ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); + // }); + + // it("Reverts with bubbled up panic error from call to conduit", async () => { + // // Deploy mock ERC20 + // const mockERC20PanicFactory = await ethers.getContractFactory( + // "TestERC20Panic" + // ); + // mockERC20Panic = await mockERC20PanicFactory.deploy(); + + // const transferHelperItems = [ + // { + // itemType: 1, + // token: mockERC20Panic.address, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: mockERC20Panic.address, + // identifier: 0, + // amount: 20, + // }, + // ]; + + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + // ).to.be.revertedWith("ConduitErrorPanic(18)"); + // }); + // }); describe("Reverts", async () => { let seller; From c514715a39d39063bc74958100eb1935d2f735a6 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 27 Jun 2022 19:29:42 -0400 Subject: [PATCH 032/126] rename interface --- test/index.js | 1194 ++++++++++++++++++++++++------------------------- 1 file changed, 597 insertions(+), 597 deletions(-) diff --git a/test/index.js b/test/index.js index 0d241c4ba..9981093e5 100644 --- a/test/index.js +++ b/test/index.js @@ -10145,603 +10145,603 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }); }); - // describe("TransferHelper tests", async () => { - // let sender; - // let recipient; - // let senderContract; - // let recipientContract; - // let tempTransferHelper; - // let tempConduit; - // let tempConduitKey; - - // beforeEach(async () => { - // // Setup basic buyer/seller wallets with ETH - // sender = new ethers.Wallet(randomHex(32), provider); - // recipient = new ethers.Wallet(randomHex(32), provider); - // zone = new ethers.Wallet(randomHex(32), provider); - - // senderContract = await EIP1271WalletFactory.deploy(sender.address); - // recipientContract = await EIP1271WalletFactory.deploy(recipient.address); - - // tempConduitKey = owner.address + randomHex(12).slice(2); - // tempConduit = await deployNewConduit(owner, tempConduitKey); - - // await Promise.all( - // [sender, recipient, zone, senderContract, recipientContract].map( - // (wallet) => faucet(wallet.address, provider) - // ) - // ); - - // // Deploy a new TransferHelper with the tempConduitController address - // const transferHelperFactory = await ethers.getContractFactory( - // "TransferHelper" - // ); - // tempTransferHelper = await transferHelperFactory.deploy( - // conduitController.address - // ); - - // await whileImpersonating(owner.address, provider, async () => { - // await conduitController - // .connect(owner) - // .updateChannel(tempConduit.address, tempTransferHelper.address, true); - // }); - // }); - - // it("Executes transfers (many token types) with a conduit", async () => { - // // Get 3 Numbers that's value adds to Item Amount and minimum 1. - // const itemsToCreate = 10; - // const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - // const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - // const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - // const erc20Contracts = [numERC20s]; - // const erc20Transfers = [numERC20s]; - - // const erc721Contracts = [numEC721s]; - // const erc721Transfers = [numEC721s]; - - // const erc1155Contracts = [numERC1155s]; - // const erc1155Transfers = [numERC1155s]; - - // // Create numERC20s amount of ERC20 objects - // for (let i = 0; i < numERC20s; i++) { - // // Deploy Contract - // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // // Create/Approve X amount of ERC20s - // const erc20Transfer = await createTransferWithApproval( - // tempERC20Contract, - // sender, - // 1, - // tempConduit.address - // ); - // erc20Contracts[i] = tempERC20Contract; - // erc20Transfers[i] = erc20Transfer; - // } - - // // Create numEC721s amount of ERC20 objects - // for (let i = 0; i < numEC721s; i++) { - // // Deploy Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // // Create/Approve numEC721s amount of ERC721s - // const erc721Transfer = await createTransferWithApproval( - // tempERC721Contract, - // sender, - // 2, - // tempConduit.address - // ); - // erc721Contracts[i] = tempERC721Contract; - // erc721Transfers[i] = erc721Transfer; - // } - - // // Create numERC1155s amount of ERC1155 objects - // for (let i = 0; i < numERC1155s; i++) { - // // Deploy Contract - // const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - // owner - // ); - // // Create/Approve numERC1155s amount of ERC1155s - // const erc1155Transfer = await createTransferWithApproval( - // tempERC1155Contract, - // sender, - // 3, - // tempConduit.address - // ); - // erc1155Contracts[i] = tempERC1155Contract; - // erc1155Transfers[i] = erc1155Transfer; - // } - - // const transfers = erc20Transfers.concat( - // erc721Transfers, - // erc1155Transfers - // ); - // const contracts = erc20Contracts.concat( - // erc721Contracts, - // erc1155Contracts - // ); - // // Send the bulk transfers - // await tempTransferHelper - // .connect(sender) - // .bulkTransfer(transfers, recipient.address, tempConduitKey); - // // Loop through all transfer to do ownership/balance checks - // for (let i = 0; i < transfers.length; i++) { - // // Get Itemtype, token, amount, identifier - // const { itemType, amount, identifier } = transfers[i]; - // const token = contracts[i]; - - // switch (itemType) { - // case 1: // ERC20 - // // Check balance - // expect(await token.balanceOf(sender.address)).to.equal(0); - // expect(await token.balanceOf(recipient.address)).to.equal(amount); - // break; - // case 2: // ERC721 - // case 4: // ERC721_WITH_CRITERIA - // expect(await token.ownerOf(identifier)).to.equal(recipient.address); - // break; - // case 3: // ERC1155 - // case 5: // ERC1155_WITH_CRITERIA - // // Check balance - // expect(await token.balanceOf(sender.address, identifier)).to.equal( - // 0 - // ); - // expect( - // await token.balanceOf(recipient.address, identifier) - // ).to.equal(amount); - // break; - // } - // } - // }); - - // it("Executes transfers (many token types) without a conduit", async () => { - // // Get 3 Numbers that's value adds to Item Amount and minimum 1. - // const itemsToCreate = 10; - // const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - // const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - // const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - // const erc20Contracts = [numERC20s]; - // const erc20Transfers = [numERC20s]; - - // const erc721Contracts = [numEC721s]; - // const erc721Transfers = [numEC721s]; - - // const erc1155Contracts = [numERC1155s]; - // const erc1155Transfers = [numERC1155s]; - - // // Create numERC20s amount of ERC20 objects - // for (let i = 0; i < numERC20s; i++) { - // // Deploy Contract - // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // // Create/Approve X amount of ERC20s - // const erc20Transfer = await createTransferWithApproval( - // tempERC20Contract, - // sender, - // 1, - // tempTransferHelper.address - // ); - // erc20Contracts[i] = tempERC20Contract; - // erc20Transfers[i] = erc20Transfer; - // } - - // // Create numEC721s amount of ERC20 objects - // for (let i = 0; i < numEC721s; i++) { - // // Deploy Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // // Create/Approve numEC721s amount of ERC721s - // const erc721Transfer = await createTransferWithApproval( - // tempERC721Contract, - // sender, - // 2, - // tempTransferHelper.address - // ); - // erc721Contracts[i] = tempERC721Contract; - // erc721Transfers[i] = erc721Transfer; - // } - - // // Create numERC1155s amount of ERC1155 objects - // for (let i = 0; i < numERC1155s; i++) { - // // Deploy Contract - // const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - // owner - // ); - // // Create/Approve numERC1155s amount of ERC1155s - // const erc1155Transfer = await createTransferWithApproval( - // tempERC1155Contract, - // sender, - // 3, - // tempTransferHelper.address - // ); - // erc1155Contracts[i] = tempERC1155Contract; - // erc1155Transfers[i] = erc1155Transfer; - // } - - // const transfers = erc20Transfers.concat( - // erc721Transfers, - // erc1155Transfers - // ); - // const contracts = erc20Contracts.concat( - // erc721Contracts, - // erc1155Contracts - // ); - // // Send the bulk transfers - // await tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // transfers, - // recipient.address, - // ethers.utils.formatBytes32String("") - // ); - // // Loop through all transfer to do ownership/balance checks - // for (let i = 0; i < transfers.length; i++) { - // // Get Itemtype, token, amount, identifier - // const { itemType, amount, identifier } = transfers[i]; - // const token = contracts[i]; - - // switch (itemType) { - // case 1: // ERC20 - // // Check balance - // expect(await token.balanceOf(sender.address)).to.equal(0); - // expect(await token.balanceOf(recipient.address)).to.equal(amount); - // break; - // case 2: // ERC721 - // case 4: // ERC721_WITH_CRITERIA - // expect(await token.ownerOf(identifier)).to.equal(recipient.address); - // break; - // case 3: // ERC1155 - // case 5: // ERC1155_WITH_CRITERIA - // // Check balance - // expect(await token.balanceOf(sender.address, identifier)).to.equal( - // 0 - // ); - // expect( - // await token.balanceOf(recipient.address, identifier) - // ).to.equal(amount); - // break; - // } - // } - // }); - - // it("Reverts on native token transfers", async () => { - // const ethTransferHelperItems = [ - // { - // itemType: 0, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 0, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 20, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // ethTransferHelperItems, - // recipient.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("InvalidItemType"); - // }); - - // it("Reverts on invalid ERC20 identifier", async () => { - // const erc20TransferHelperItems = [ - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 5, - // amount: 10, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 4, - // amount: 20, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // erc20TransferHelperItems, - // recipient.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("InvalidERC20Identifier"); - // }); - - // it("Reverts on invalid ERC721 transfer amount", async () => { - // // Deploy Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // const erc721TransferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 10, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 20, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // erc721TransferHelperItems, - // recipient.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("InvalidERC721TransferAmount"); - // }); - - // it("Reverts on invalid ERC721 recipient", async () => { - // // Deploy Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // const erc721TransferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // erc721TransferHelperItems, - // tempERC721Contract.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("InvalidERC721Recipient"); - // }); - - // it("Reverts on invalid function selector", async () => { - // const invalidRecipientFactory = await ethers.getContractFactory( - // "InvalidERC721Recipient" - // ); - // invalidRecipient = await invalidRecipientFactory.deploy(); - - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // const erc721TransferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // erc721TransferHelperItems, - // invalidRecipient.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("InvalidERC721Recipient"); - // }); - - // it("Reverts on nonexistent conduit", async () => { - // // Deploy ERC721 Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // const transferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 20, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // transferHelperItems, - // recipient.address, - // ethers.utils.formatBytes32String("0xabc") - // ) - // ).to.be.revertedWith("InvalidConduit"); - // }); - - // it("Reverts on error in ERC721 receiver", async () => { - // // Deploy ERC721 Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // // Deploy mock ERC721 receiver - // const mockERC721ReceiverFactory = await ethers.getContractFactory( - // "ERC721ReceiverMock" - // ); - // mockERC721Receiver = await mockERC721ReceiverFactory.deploy( - // 0xabcd0000, - // 1 - // ); - - // const transferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 20, - // }, - // ]; - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer( - // transferHelperItems, - // mockERC721Receiver.address, - // ethers.utils.formatBytes32String("") - // ) - // ).to.be.revertedWith("ERC721ReceiverMock: reverting"); - // }); - - // it("Reverts with custom error in conduit", async () => { - // // Deploy ERC721 Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // const transferHelperItems = [ - // // Invalid item type - // { - // itemType: 0, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 20, - // }, - // ]; - - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - // ).to.be.revertedWith("InvalidItemType"); - // }); - - // it("Reverts with bubbled up string error from call to conduit", async () => { - // // Deploy ERC721 Contract - // const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - // // Call will revert since ERC721 tokens have not been minted - // const transferHelperItems = [ - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 1, - // amount: 1, - // }, - // { - // itemType: 2, - // token: tempERC721Contract.address, - // identifier: 2, - // amount: 1, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: ethers.constants.AddressZero, - // identifier: 0, - // amount: 20, - // }, - // ]; - - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - // ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); - // }); - - // it("Reverts with bubbled up panic error from call to conduit", async () => { - // // Deploy mock ERC20 - // const mockERC20PanicFactory = await ethers.getContractFactory( - // "TestERC20Panic" - // ); - // mockERC20Panic = await mockERC20PanicFactory.deploy(); - - // const transferHelperItems = [ - // { - // itemType: 1, - // token: mockERC20Panic.address, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: mockERC20Panic.address, - // identifier: 0, - // amount: 20, - // }, - // ]; - - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - // ).to.be.revertedWith("ConduitErrorPanic(18)"); - // }); - // }); + describe("TransferHelper tests", async () => { + let sender; + let recipient; + let senderContract; + let recipientContract; + let tempTransferHelper; + let tempConduit; + let tempConduitKey; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + sender = new ethers.Wallet(randomHex(32), provider); + recipient = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + senderContract = await EIP1271WalletFactory.deploy(sender.address); + recipientContract = await EIP1271WalletFactory.deploy(recipient.address); + + tempConduitKey = owner.address + randomHex(12).slice(2); + tempConduit = await deployNewConduit(owner, tempConduitKey); + + await Promise.all( + [sender, recipient, zone, senderContract, recipientContract].map( + (wallet) => faucet(wallet.address, provider) + ) + ); + + // Deploy a new TransferHelper with the tempConduitController address + const transferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + tempTransferHelper = await transferHelperFactory.deploy( + conduitController.address + ); + + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, tempTransferHelper.address, true); + }); + }); + + it("Executes transfers (many token types) with a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = [numERC20s]; + const erc20Transfers = [numERC20s]; + + const erc721Contracts = [numEC721s]; + const erc721Transfers = [numEC721s]; + + const erc1155Contracts = [numERC1155s]; + const erc1155Transfers = [numERC1155s]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = erc20Transfers.concat( + erc721Transfers, + erc1155Transfers + ); + const contracts = erc20Contracts.concat( + erc721Contracts, + erc1155Contracts + ); + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer(transfers, recipient.address, tempConduitKey); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect(await token.balanceOf(sender.address)).to.equal(0); + expect(await token.balanceOf(recipient.address)).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect(await token.ownerOf(identifier)).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf(recipient.address, identifier) + ).to.equal(amount); + break; + } + } + }); + + it("Executes transfers (many token types) without a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = [numERC20s]; + const erc20Transfers = [numERC20s]; + + const erc721Contracts = [numEC721s]; + const erc721Transfers = [numEC721s]; + + const erc1155Contracts = [numERC1155s]; + const erc1155Transfers = [numERC1155s]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = erc20Transfers.concat( + erc721Transfers, + erc1155Transfers + ); + const contracts = erc20Contracts.concat( + erc721Contracts, + erc1155Contracts + ); + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer( + transfers, + recipient.address, + ethers.utils.formatBytes32String("") + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect(await token.balanceOf(sender.address)).to.equal(0); + expect(await token.balanceOf(recipient.address)).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect(await token.ownerOf(identifier)).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf(recipient.address, identifier) + ).to.equal(amount); + break; + } + } + }); + + it("Reverts on native token transfers", async () => { + const ethTransferHelperItems = [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + ethTransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts on invalid ERC20 identifier", async () => { + const erc20TransferHelperItems = [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc20TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); + + it("Reverts on invalid ERC721 transfer amount", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + + it("Reverts on invalid ERC721 recipient", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + tempERC721Contract.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + + it("Reverts on invalid function selector", async () => { + const invalidRecipientFactory = await ethers.getContractFactory( + "InvalidERC721Recipient" + ); + invalidRecipient = await invalidRecipientFactory.deploy(); + + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + invalidRecipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + + it("Reverts on nonexistent conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("0xabc") + ) + ).to.be.revertedWith("InvalidConduit"); + }); + + it("Reverts on error in ERC721 receiver", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + 0xabcd0000, + 1 + ); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + mockERC721Receiver.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + }); + + it("Reverts with custom error in conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const transferHelperItems = [ + // Invalid item type + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts with bubbled up string error from call to conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + // Call will revert since ERC721 tokens have not been minted + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); + }); + + it("Reverts with bubbled up panic error from call to conduit", async () => { + // Deploy mock ERC20 + const mockERC20PanicFactory = await ethers.getContractFactory( + "TestERC20Panic" + ); + mockERC20Panic = await mockERC20PanicFactory.deploy(); + + const transferHelperItems = [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("ConduitErrorPanic(18)"); + }); + }); describe("Reverts", async () => { let seller; From 06b82622cc8d1cac38d656297db6686cd57888dd Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 10:26:12 -0400 Subject: [PATCH 033/126] remove panic error catch --- contracts/helpers/TransferHelper.sol | 3 --- test/index.js | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 300dab44d..4643f075d 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -257,9 +257,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Revert if the error provides a reason string. } catch Error(string memory reason) { revert ConduitErrorString(reason); - // Revert if the error was caused by a panic. - } catch Panic(uint256 errorCode) { - revert ConduitErrorPanic(errorCode); } } diff --git a/test/index.js b/test/index.js index 9981093e5..0348075d7 100644 --- a/test/index.js +++ b/test/index.js @@ -10739,7 +10739,9 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("ConduitErrorPanic(18)"); + ).to.be.revertedWith( + "panic code 0x12 (Division or modulo division by zero)" + ); }); }); From 4d5a068dd8841c21d8bf651acfc663a9846133be Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 11:51:28 -0400 Subject: [PATCH 034/126] add revert test --- test/index.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/index.js b/test/index.js index 0348075d7..c9950f3a1 100644 --- a/test/index.js +++ b/test/index.js @@ -10713,6 +10713,81 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); }); + it("Reverts with error without message from call to conduit", async () => { + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + 0xabcd0000, + 2 + ); + + const erc20Transfers = [3]; + const erc721Transfers = [3]; + const erc1155Transfers = [3]; + + // Create 3 ERC20 objects + for (let i = 0; i < 3; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve ERC20 + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address + ); + erc20Transfers[i] = erc20Transfer; + } + + // Create 3 ERC721 objects + for (let i = 0; i < 3; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve ERC721 + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address + ); + erc721Transfers[i] = erc721Transfer; + } + + // Create 3 ERC1155 objects + for (let i = 0; i < 3; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve ERC1155 + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address + ); + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = erc20Transfers.concat( + erc721Transfers, + erc1155Transfers + ); + + console.log("Transfers length:", transfers.length); + + console.log("Transfers:", transfers); + + // Send the bulk transfers + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transfers, recipient.address, tempConduitKey) + ).to.be.revertedWith("InvalidConduit"); + }); + it("Reverts with bubbled up panic error from call to conduit", async () => { // Deploy mock ERC20 const mockERC20PanicFactory = await ethers.getContractFactory( From 3c26b8088071fca049bb4c0d16affab697d7c292 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 13:55:05 -0400 Subject: [PATCH 035/126] remove check when data length is 0 --- contracts/helpers/TransferHelper.sol | 4 +- contracts/test/TestERC20Revert.sol | 19 +++++ test/index.js | 103 +++++---------------------- 3 files changed, 38 insertions(+), 88 deletions(-) create mode 100644 contracts/test/TestERC20Revert.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 4643f075d..536e4a6d1 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -251,12 +251,12 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } - } else { - revert InvalidConduit(); } // Revert if the error provides a reason string. } catch Error(string memory reason) { revert ConduitErrorString(reason); + } catch Panic(uint256 errorCode) { + revert ConduitErrorPanic(errorCode); } } diff --git a/contracts/test/TestERC20Revert.sol b/contracts/test/TestERC20Revert.sol new file mode 100644 index 000000000..e581300fa --- /dev/null +++ b/contracts/test/TestERC20Revert.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import "@rari-capital/solmate/src/tokens/ERC20.sol"; + +contract TestERC20Revert is ERC20("TestPanic", "PANIC", 18) { + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public override returns (bool) { + revert(); + return true; + } +} diff --git a/test/index.js b/test/index.js index c9950f3a1..6340d18c1 100644 --- a/test/index.js +++ b/test/index.js @@ -10547,6 +10547,8 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function it("Reverts on nonexistent conduit", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); const transferHelperItems = [ { @@ -10563,13 +10565,13 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 10, }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 20, }, @@ -10588,6 +10590,8 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function it("Reverts on error in ERC721 receiver", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); // Deploy mock ERC721 receiver const mockERC721ReceiverFactory = await ethers.getContractFactory( @@ -10613,13 +10617,13 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 10, }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 20, }, @@ -10638,6 +10642,8 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function it("Reverts with custom error in conduit", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); const transferHelperItems = [ // Invalid item type @@ -10655,13 +10661,13 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 10, }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 20, }, @@ -10677,6 +10683,8 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function it("Reverts with bubbled up string error from call to conduit", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); // Call will revert since ERC721 tokens have not been minted const transferHelperItems = [ @@ -10694,13 +10702,13 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 10, }, { itemType: 1, - token: ethers.constants.AddressZero, + token: tempERC20Contract.address, identifier: 0, amount: 20, }, @@ -10713,81 +10721,6 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); }); - it("Reverts with error without message from call to conduit", async () => { - // Deploy mock ERC721 receiver - const mockERC721ReceiverFactory = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - mockERC721Receiver = await mockERC721ReceiverFactory.deploy( - 0xabcd0000, - 2 - ); - - const erc20Transfers = [3]; - const erc721Transfers = [3]; - const erc1155Transfers = [3]; - - // Create 3 ERC20 objects - for (let i = 0; i < 3; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve ERC20 - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address - ); - erc20Transfers[i] = erc20Transfer; - } - - // Create 3 ERC721 objects - for (let i = 0; i < 3; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve ERC721 - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address - ); - erc721Transfers[i] = erc721Transfer; - } - - // Create 3 ERC1155 objects - for (let i = 0; i < 3; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve ERC1155 - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address - ); - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - - console.log("Transfers length:", transfers.length); - - console.log("Transfers:", transfers); - - // Send the bulk transfers - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transfers, recipient.address, tempConduitKey) - ).to.be.revertedWith("InvalidConduit"); - }); - it("Reverts with bubbled up panic error from call to conduit", async () => { // Deploy mock ERC20 const mockERC20PanicFactory = await ethers.getContractFactory( @@ -10814,9 +10747,7 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith( - "panic code 0x12 (Division or modulo division by zero)" - ); + ).to.be.revertedWith("ConduitErrorPanic(18)"); }); }); From d07a06bd6030c2f34fafb1b286dfb75286c31452 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 14:01:09 -0400 Subject: [PATCH 036/126] skip coverage for test file --- config/.solcover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/.solcover.js b/config/.solcover.js index ab5fb3fa8..ad0d997ee 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -31,7 +31,8 @@ module.exports = { "test/TestERC721.sol", "test/TestZone.sol", "test/TestERC20Panic.sol", - "test/ERC721ReceiverMock", + "test/ERC721ReceiverMock.sol", + "test/TestERC20Revert.sol", ], configureYulOptimizer: true, solcOptimizerDetails: { From bcc2e8ab9b07658e6cedb9d8ba48799408cb922d Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 15:28:58 -0400 Subject: [PATCH 037/126] modify test to account for different revert in reference --- test/index.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/test/index.js b/test/index.js index 6340d18c1..574752a2a 100644 --- a/test/index.js +++ b/test/index.js @@ -10743,11 +10743,27 @@ describe(`Consideration (version: ${VERSION}) — initial test suite`, function }, ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("ConduitErrorPanic(18)"); + if (!process.env.REFERENCE) { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + tempConduitKey + ) + ).to.be.revertedWith("ConduitErrorPanic(18)"); + } else { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + tempConduitKey + ) + ).to.be.reverted; + } }); }); From 33b0f655ce623a752dd357a8826291d91005ba2b Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 28 Jun 2022 17:15:49 -0400 Subject: [PATCH 038/126] add forge tests --- contracts/helpers/TransferHelperStructs.sol | 7 ++ test/foundry/TransferHelperTest.sol | 80 +++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/contracts/helpers/TransferHelperStructs.sol b/contracts/helpers/TransferHelperStructs.sol index 35aeec140..d58537928 100644 --- a/contracts/helpers/TransferHelperStructs.sol +++ b/contracts/helpers/TransferHelperStructs.sol @@ -9,3 +9,10 @@ struct TransferHelperItem { uint256 identifier; uint256 amount; } + +enum Error { + None, + RevertWithMessage, + RevertWithoutMessage, + Panic +} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index d46c83333..88e7683de 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -23,6 +23,8 @@ import { TokenTransferrerErrors } from "../../contracts/interfaces/TokenTransfer import { TransferHelperInterface } from "../../contracts/interfaces/TransferHelperInterface.sol"; +import { ERC721ReceiverMock } from "../../contracts/test/ERC721ReceiverMock.sol"; + contract TransferHelperTest is BaseOrderTest { TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. @@ -779,4 +781,82 @@ contract TransferHelperTest is BaseOrderTest { abi.encodePacked(TransferHelperInterface.InvalidConduit.selector) ); } + + function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) + public + { + // Deploy invalid mock ERC721 receiver + ERC721ReceiverMock mockReceiver = new ERC721ReceiverMock( + 0xabcd0000, + ERC721ReceiverMock.Error.RevertWithMessage + ); + + TransferHelperItem memory item = _getFuzzedTransferItem( + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0] + ); + _performSingleItemTransferAndCheckBalances( + item, + alice, + address(mockReceiver), + false, + abi.encodePacked("ERC721ReceiverMock: reverting") + ); + } + + function testRevertInvalidItemWithConduit( + FuzzInputsCommon memory inputs, + bytes32 fuzzConduitKey + ) public { + // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". + vm.assume( + fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne + ); + TransferHelperItem memory invalidItem = _getFuzzedTransferItem( + ConduitItemType.NATIVE, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0] + ); + _performSingleItemTransferAndCheckBalances( + invalidItem, + alice, + bob, + true, + abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) + ); + } + + function testRevertStringErrorWithConduit( + FuzzInputsCommon memory inputs, + bytes32 fuzzConduitKey + ) public { + // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". + vm.assume( + fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne + ); + + // Deploy invalid mock ERC721 receiver + ERC721ReceiverMock mockReceiver = new ERC721ReceiverMock( + 0xabcd0000, + ERC721ReceiverMock.Error.RevertWithMessage + ); + + TransferHelperItem memory item = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 + ); + + _performSingleItemTransferAndCheckBalances( + item, + bob, + address(mockReceiver), + true, + abi.encodePacked('ConduitErrorString("WRONG_FROM")') + ); + } } From 2aa35d9ee55739fd676872121d8f172b56f64208 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 28 Jun 2022 14:21:49 -0700 Subject: [PATCH 039/126] Merge branch 'main' into transfer-helper-cleanup --- .eslintignore | 7 + .eslintrc.js | 57 + .github/workflows/test.yml | 6 + .prettierignore | 3 +- .prettierrc.js | 12 + CONTRIBUTORS.md | 1 + README.md | 68 +- config/.solcover-reference.js | 4 + config/.solcover.js | 3 +- contracts/Seaport.sol | 2 +- contracts/conduit/Conduit.sol | 2 +- contracts/conduit/ConduitController.sol | 2 +- contracts/conduit/lib/ConduitConstants.sol | 2 +- contracts/conduit/lib/ConduitEnums.sol | 2 +- contracts/conduit/lib/ConduitStructs.sol | 2 +- contracts/helpers/TransferHelper.sol | 8 +- contracts/helpers/TransferHelperStructs.sol | 2 +- .../interfaces/AbridgedTokenInterfaces.sol | 2 +- .../interfaces/AmountDerivationErrors.sol | 2 +- .../interfaces/ConduitControllerInterface.sol | 2 +- contracts/interfaces/ConduitInterface.sol | 2 +- .../ConsiderationEventsAndErrors.sol | 2 +- .../interfaces/ConsiderationInterface.sol | 2 +- .../interfaces/CriteriaResolutionErrors.sol | 2 +- contracts/interfaces/EIP1271Interface.sol | 2 +- .../FulfillmentApplicationErrors.sol | 2 +- .../ImmutableCreate2FactoryInterface.sol | 2 +- contracts/interfaces/ReentrancyErrors.sol | 2 +- contracts/interfaces/SeaportInterface.sol | 2 +- .../SignatureVerificationErrors.sol | 2 +- .../interfaces/TokenTransferrerErrors.sol | 2 +- .../interfaces/TransferHelperInterface.sol | 2 +- .../interfaces/ZoneInteractionErrors.sol | 2 +- contracts/interfaces/ZoneInterface.sol | 2 +- contracts/lib/AmountDeriver.sol | 2 +- contracts/lib/Assertions.sol | 2 +- contracts/lib/BasicOrderFulfiller.sol | 2 +- contracts/lib/Consideration.sol | 2 +- contracts/lib/ConsiderationBase.sol | 2 +- contracts/lib/ConsiderationConstants.sol | 2 +- contracts/lib/ConsiderationEnums.sol | 2 +- contracts/lib/ConsiderationStructs.sol | 2 +- contracts/lib/CounterManager.sol | 2 +- contracts/lib/CriteriaResolution.sol | 2 +- contracts/lib/Executor.sol | 2 +- contracts/lib/FulfillmentApplier.sol | 2 +- contracts/lib/GettersAndDerivers.sol | 2 +- contracts/lib/LowLevelHelpers.sol | 2 +- contracts/lib/OrderCombiner.sol | 2 +- contracts/lib/OrderFulfiller.sol | 2 +- contracts/lib/OrderValidator.sol | 2 +- contracts/lib/ReentrancyGuard.sol | 2 +- contracts/lib/SignatureVerification.sol | 2 +- contracts/lib/TokenTransferrer.sol | 2 +- contracts/lib/TokenTransferrerConstants.sol | 2 +- contracts/lib/Verifiers.sol | 6 +- contracts/lib/ZoneInteraction.sol | 2 +- contracts/test/EIP1271Wallet.sol | 2 +- contracts/test/ERC1155BatchRecipient.sol | 2 +- contracts/test/ERC721ReceiverMock.sol | 2 +- contracts/test/ExcessReturnDataRecipient.sol | 4 +- contracts/test/InvalidERC721Recipient.sol | 10 +- contracts/test/Reenterer.sol | 2 +- contracts/test/TestERC1155.sol | 4 +- contracts/test/TestERC20.sol | 4 +- contracts/test/TestERC20Panic.sol | 10 +- contracts/test/TestERC20Revert.sol | 11 +- contracts/test/TestERC721.sol | 4 +- contracts/test/TestZone.sol | 2 +- docs/Deployment.md | 96 + docs/FunctionSignatures.md | 13 + hardhat-coverage.config.ts | 6 +- hardhat-reference-coverage.config.ts | 6 +- hardhat-reference.config.ts | 11 +- hardhat.config.ts | 19 +- package.json | 67 +- reference/ReferenceConsideration.sol | 2 +- reference/lib/ReferenceAssertions.sol | 16 +- .../lib/ReferenceBasicOrderFulfiller.sol | 2 +- reference/lib/ReferenceConsiderationBase.sol | 2 +- reference/lib/ReferenceGettersAndDerivers.sol | 2 +- reference/lib/ReferenceOrderCombiner.sol | 34 +- .../lib/ReferenceSignatureVerification.sol | 9 +- reference/lib/ReferenceVerifiers.sol | 4 +- test/advanced.spec.ts | 4213 ++++ test/basic.spec.ts | 3626 ++++ test/conduit.spec.ts | 1471 ++ test/counter.spec.ts | 906 + .../AdditionalRecipientsOffByOne.spec.ts | 44 +- .../CriteriaResolverUnhashedLeaves.spec.ts | 45 +- ...ulfillmentOverflowWithMissingItems.spec.ts | 39 +- .../PartialFillFractionOverflow.spec.ts | 50 +- test/foundry/CeilEquivalenceTest.t.sol | 2 +- test/foundry/FulfillAdvancedOrder.t.sol | 2 +- .../FulfillAdvancedOrderCriteria.t.sol | 2 +- .../FulfillAvailableAdvancedOrder.t.sol | 2 +- ...ulfillAvailableAdvancedOrderCriteria.t.sol | 2 +- test/foundry/FulfillBasicOrderTest.t.sol | 2 +- test/foundry/FulfillOrderTest.t.sol | 2 +- test/foundry/FullfillAvailableOrder.t.sol | 2 +- test/foundry/GetterTests.t.sol | 2 +- test/foundry/MatchAdvancedOrder.t.sol | 2 +- test/foundry/MatchOrders.t.sol | 2 +- test/foundry/NonReentrant.t.sol | 2 +- test/foundry/SignatureVerification.t.sol | 181 + test/foundry/TransferHelperTest.sol | 2 +- test/foundry/conduit/BaseConduitTest.sol | 2 +- test/foundry/conduit/ConduitExecute.t.sol | 2 +- .../conduit/ConduitExecuteBatch1155.t.sol | 2 +- .../conduit/ConduitExecuteWithBatch1155.t.sol | 2 +- .../interfaces/OwnableDelegateProxy.sol | 2 +- test/foundry/interfaces/ProxyRegistry.sol | 2 +- test/foundry/token/ERC721.sol | 2 +- test/foundry/utils/ArithmeticUtil.sol | 2 +- test/foundry/utils/BaseConsiderationTest.sol | 2 +- test/foundry/utils/BaseOrderTest.sol | 2 +- test/foundry/utils/DifferentialTest.sol | 2 +- test/foundry/utils/ERC1155Recipient.sol | 2 +- test/foundry/utils/ERC721Recipient.sol | 2 +- test/foundry/utils/ExternalCounter.sol | 2 +- .../utils/OfferConsiderationItemAdder.sol | 2 +- test/foundry/utils/PseudoRandom.sol | 2 +- test/foundry/utils/StructCopier.sol | 2 +- test/foundry/utils/TestTokenMinter.sol | 2 +- .../utils/reentrancy/ReentrantEnums.sol | 2 +- .../utils/reentrancy/ReentrantStructs.sol | 2 +- test/getter.spec.ts | 74 + test/index.js | 16673 ---------------- test/revert.spec.ts | 5936 ++++++ test/transferhelper.spec.ts | 694 + test/utils/contracts.ts | 12 +- test/utils/{criteria.js => criteria.ts} | 53 +- test/utils/encoding.ts | 33 +- test/utils/fixtures/conduit.ts | 25 +- test/utils/fixtures/create2.ts | 6 +- test/utils/fixtures/index.ts | 83 +- test/utils/fixtures/marketplace.ts | 59 +- test/utils/fixtures/tokens.ts | 32 +- test/utils/helpers.ts | 46 + test/utils/impersonate.ts | 4 +- test/utils/sign.ts | 0 test/utils/types.ts | 9 +- tsconfig.json | 5 +- yarn.lock | 122 +- 144 files changed, 17975 insertions(+), 17145 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .prettierrc.js create mode 100644 docs/Deployment.md create mode 100644 docs/FunctionSignatures.md create mode 100644 test/advanced.spec.ts create mode 100644 test/basic.spec.ts create mode 100644 test/conduit.spec.ts create mode 100644 test/counter.spec.ts create mode 100644 test/foundry/SignatureVerification.t.sol create mode 100644 test/getter.spec.ts delete mode 100644 test/index.js create mode 100644 test/revert.spec.ts create mode 100644 test/transferhelper.spec.ts rename test/utils/{criteria.js => criteria.ts} (57%) create mode 100644 test/utils/helpers.ts delete mode 100644 test/utils/sign.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..c70c84dce --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules +.eslintrc* +artifacts +cache +constants +coverage +lib/murky \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..4a461452c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,57 @@ +module.exports = { + env: { + browser: false, + es2021: true, + mocha: true, + node: true, + }, + plugins: ["@typescript-eslint", "import"], + extends: [ + "standard", + "plugin:prettier/recommended", + "eslint:recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 12, + project: "./tsconfig.json", + }, + rules: { + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/prefer-nullish-coalescing": "error", + camelcase: [ + "error", + { allow: ["Conduit__factory", "EIP1271Wallet__factory"] }, + ], + "import/order": [ + "error", + { + alphabetize: { + order: "asc", + }, + groups: [ + "object", + ["builtin", "external"], + "parent", + "sibling", + "index", + "type", + ], + "newlines-between": "always", + }, + ], + "object-shorthand": "error", + "prefer-const": "error", + "sort-imports": ["error", { ignoreDeclarationSort: true }], + }, + overrides: [ + { + files: ["test/**/*.spec.ts"], + rules: { + "no-unused-expressions": "off", + }, + }, + ], +}; diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2665adfb8..df06a2cc1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn build @@ -38,6 +39,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn lint:check @@ -55,6 +57,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn build - run: yarn test @@ -76,6 +79,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn build - run: yarn build:ref @@ -142,6 +146,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn build - run: yarn coverage @@ -167,6 +172,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: 'yarn' - run: yarn install - run: yarn build - run: yarn build:ref diff --git a/.prettierignore b/.prettierignore index d35df378a..b509e04a4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,5 @@ node_modules artifacts cache coverage* -gasReporterOutput.json \ No newline at end of file +gasReporterOutput.json +lib/murky \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..3eee3b635 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,12 @@ +module.exports = { + overrides: [ + { + files: "*.sol", + options: { + tabWidth: 4, + printWidth: 80, + bracketSpacing: true, + }, + }, + ], +}; diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e80ee82e0..08a5c1368 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -69,3 +69,4 @@ rfart(rfa) | shuklaayush | `shuklaayush.eth` Riley Holterhus | big-tech-sux | +naps62 | `naps62.eth` diff --git a/README.md b/README.md index 611eca339..1e1387055 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,17 @@ Seaport is a new marketplace protocol for safely and efficiently buying and sell ## Table of Contents -- [Background](#background) -- [Deployments](#deployments) -- [Diagram](#diagram) -- [Install](#install) -- [Usage](#usage) -- [Audits](#audits) -- [Contributing](#contributing) -- [License](#license) +- [Seaport](#seaport) + - [Table of Contents](#table-of-contents) + - [Background](#background) + - [Deployments](#deployments) + - [Diagram](#diagram) + - [Install](#install) + - [Usage](#usage) + - [Foundry Tests](#foundry-tests) + - [Audits](#audits) + - [Contributing](#contributing) + - [License](#license) ## Background @@ -23,19 +26,36 @@ See the [documentation](docs/SeaportDocumentation.md), the [interface](contracts ## Deployments -Seaport 1.1 deployment addresses: + + + + + + -| Network | Address | -| ---------------- | ------------------------------------------ | -| Ethereum Mainnet | [0x00000000006c3852cbEf3e08E8dF289169EdE581](https://etherscan.io/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) | -| Rinkeby | [0x00000000006c3852cbEf3e08E8dF289169EdE581](https://rinkeby.etherscan.io/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) | + + + + + + + + + + + + + + +
NetworkSeaport 1.1ConduitController
Ethereum -Conduit Controller deployment addresses: +[0x00000000006c3852cbEf3e08E8dF289169EdE581](https://etherscan.io/address/0x00000000006c3852cbEf3e08E8dF289169EdE581#code) -| Network | Address | -| ---------------- | ------------------------------------------ | -| Ethereum Mainnet | [0x00000000F9490004C11Cef243f5400493c00Ad63](https://etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) | -| Rinkeby | [0x00000000F9490004C11Cef243f5400493c00Ad63](https://rinkeby.etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) | + + +[0x00000000F9490004C11Cef243f5400493c00Ad63](https://etherscan.io/address/0x00000000F9490004C11Cef243f5400493c00Ad63#code) + +
Rinkeby
Goerli
Kovan
Polygon
Mumbai
Optimism
Optimistic Kovan
Arbitrum
Arbitrum Rinkeby
Gnosis Chain
Klaytn
Baobab
## Diagram @@ -152,7 +172,6 @@ You may wish to include a `.env` file that `export`s a specific profile when dev **Note** the `local-ffi` profile uses Forge's `ffi` flag. `ffi` can potentially be unsafe, as it allows Forge to execute arbitrary code. Use with caution, and always ensure you trust the code in this repository, especially when working on third-party forks. - The following modifiers are also available: - Level 2 (-vv): Logs emitted during tests are also displayed. @@ -192,16 +211,15 @@ When making a pull request, ensure that: - All tests pass. - Code coverage remains at 100% (coverage tests must currently be written in hardhat). - All new code adheres to the style guide: - - All lint checks pass. - - Code is thoroughly commented with natspec where relevant. + - All lint checks pass. + - Code is thoroughly commented with natspec where relevant. - If making a change to the contracts: - - Gas snapshots are provided and demonstrate an improvement (or an acceptable deficit given other improvements). - - Reference contracts are modified correspondingly if relevant. - - New tests (ideally via foundry) are included for all new features or code paths. + - Gas snapshots are provided and demonstrate an improvement (or an acceptable deficit given other improvements). + - Reference contracts are modified correspondingly if relevant. + - New tests (ideally via foundry) are included for all new features or code paths. - If making a modification to third-party dependencies, `yarn audit` passes. - A descriptive summary of the PR has been provided. ## License [MIT](LICENSE) Copyright 2022 Ozone Networks, Inc. - diff --git a/config/.solcover-reference.js b/config/.solcover-reference.js index 71ef3285d..80b256384 100644 --- a/config/.solcover-reference.js +++ b/config/.solcover-reference.js @@ -28,5 +28,9 @@ module.exports = { "test/TestERC20.sol", "test/TestERC721.sol", "test/TestZone.sol", + "test/TestERC20Panic.sol", + "test/TestERC20Revert.sol", + "test/InvalidERC721Recipient.sol", + "test/ERC721ReceiverMock.sol", ], }; diff --git a/config/.solcover.js b/config/.solcover.js index ad0d997ee..55a647a22 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -31,8 +31,9 @@ module.exports = { "test/TestERC721.sol", "test/TestZone.sol", "test/TestERC20Panic.sol", - "test/ERC721ReceiverMock.sol", "test/TestERC20Revert.sol", + "test/InvalidERC721Recipient.sol", + "test/ERC721ReceiverMock.sol", ], configureYulOptimizer: true, solcOptimizerDetails: { diff --git a/contracts/Seaport.sol b/contracts/Seaport.sol index ad036f657..4265cd50e 100644 --- a/contracts/Seaport.sol +++ b/contracts/Seaport.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { Consideration } from "./lib/Consideration.sol"; diff --git a/contracts/conduit/Conduit.sol b/contracts/conduit/Conduit.sol index 411f77bc4..2e5a8c71c 100644 --- a/contracts/conduit/Conduit.sol +++ b/contracts/conduit/Conduit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; diff --git a/contracts/conduit/ConduitController.sol b/contracts/conduit/ConduitController.sol index d3e0b711a..304589ef8 100644 --- a/contracts/conduit/ConduitController.sol +++ b/contracts/conduit/ConduitController.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/conduit/lib/ConduitConstants.sol b/contracts/conduit/lib/ConduitConstants.sol index 2979289c4..516607318 100644 --- a/contracts/conduit/lib/ConduitConstants.sol +++ b/contracts/conduit/lib/ConduitConstants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // error ChannelClosed(address channel) uint256 constant ChannelClosed_error_signature = ( diff --git a/contracts/conduit/lib/ConduitEnums.sol b/contracts/conduit/lib/ConduitEnums.sol index 3d0243cd4..2154bf7bd 100644 --- a/contracts/conduit/lib/ConduitEnums.sol +++ b/contracts/conduit/lib/ConduitEnums.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; enum ConduitItemType { NATIVE, // unused diff --git a/contracts/conduit/lib/ConduitStructs.sol b/contracts/conduit/lib/ConduitStructs.sol index d1b19f0c3..03c54f4ec 100644 --- a/contracts/conduit/lib/ConduitStructs.sol +++ b/contracts/conduit/lib/ConduitStructs.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { ConduitItemType } from "./ConduitEnums.sol"; diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 536e4a6d1..6be5a4296 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import "./TransferHelperStructs.sol"; @@ -141,11 +141,13 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { selector != IERC721Receiver.onERC721Received.selector ) { + // Revert if recipient cannot accept + // ERC721 tokens. revert InvalidERC721Recipient(); } - // Revert if recipient cannot accept ERC721 tokens. } catch (bytes memory data) { - // Bubble up recipient's revert reason if present. + // Bubble up recipient's revert reason + // if present. if (data.length != 0) { assembly { returndatacopy(0, 0, returndatasize()) diff --git a/contracts/helpers/TransferHelperStructs.sol b/contracts/helpers/TransferHelperStructs.sol index d58537928..aa4dda0e2 100644 --- a/contracts/helpers/TransferHelperStructs.sol +++ b/contracts/helpers/TransferHelperStructs.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol"; diff --git a/contracts/interfaces/AbridgedTokenInterfaces.sol b/contracts/interfaces/AbridgedTokenInterfaces.sol index fc39bdae0..d961faf29 100644 --- a/contracts/interfaces/AbridgedTokenInterfaces.sol +++ b/contracts/interfaces/AbridgedTokenInterfaces.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; interface ERC20Interface { function transferFrom( diff --git a/contracts/interfaces/AmountDerivationErrors.sol b/contracts/interfaces/AmountDerivationErrors.sol index 718809956..58deccc40 100644 --- a/contracts/interfaces/AmountDerivationErrors.sol +++ b/contracts/interfaces/AmountDerivationErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title AmountDerivationErrors diff --git a/contracts/interfaces/ConduitControllerInterface.sol b/contracts/interfaces/ConduitControllerInterface.sol index 83561ae54..bf3ec0c8a 100644 --- a/contracts/interfaces/ConduitControllerInterface.sol +++ b/contracts/interfaces/ConduitControllerInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title ConduitControllerInterface diff --git a/contracts/interfaces/ConduitInterface.sol b/contracts/interfaces/ConduitInterface.sol index d988fcc83..c4955ac7b 100644 --- a/contracts/interfaces/ConduitInterface.sol +++ b/contracts/interfaces/ConduitInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/interfaces/ConsiderationEventsAndErrors.sol b/contracts/interfaces/ConsiderationEventsAndErrors.sol index 13682ecda..6d84b04d1 100644 --- a/contracts/interfaces/ConsiderationEventsAndErrors.sol +++ b/contracts/interfaces/ConsiderationEventsAndErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { SpentItem, ReceivedItem } from "../lib/ConsiderationStructs.sol"; diff --git a/contracts/interfaces/ConsiderationInterface.sol b/contracts/interfaces/ConsiderationInterface.sol index 1c6d5c9fc..8b5f2465b 100644 --- a/contracts/interfaces/ConsiderationInterface.sol +++ b/contracts/interfaces/ConsiderationInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/interfaces/CriteriaResolutionErrors.sol b/contracts/interfaces/CriteriaResolutionErrors.sol index dfbe88c26..9d7af48ae 100644 --- a/contracts/interfaces/CriteriaResolutionErrors.sol +++ b/contracts/interfaces/CriteriaResolutionErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title CriteriaResolutionErrors diff --git a/contracts/interfaces/EIP1271Interface.sol b/contracts/interfaces/EIP1271Interface.sol index 21c9cf1c6..50b671196 100644 --- a/contracts/interfaces/EIP1271Interface.sol +++ b/contracts/interfaces/EIP1271Interface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; interface EIP1271Interface { function isValidSignature(bytes32 digest, bytes calldata signature) diff --git a/contracts/interfaces/FulfillmentApplicationErrors.sol b/contracts/interfaces/FulfillmentApplicationErrors.sol index 776a0f6be..e72d4a40d 100644 --- a/contracts/interfaces/FulfillmentApplicationErrors.sol +++ b/contracts/interfaces/FulfillmentApplicationErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { Side } from "../lib/ConsiderationEnums.sol"; diff --git a/contracts/interfaces/ImmutableCreate2FactoryInterface.sol b/contracts/interfaces/ImmutableCreate2FactoryInterface.sol index a8a34a590..e9756fb95 100644 --- a/contracts/interfaces/ImmutableCreate2FactoryInterface.sol +++ b/contracts/interfaces/ImmutableCreate2FactoryInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title ImmutableCreate2FactoryInterface diff --git a/contracts/interfaces/ReentrancyErrors.sol b/contracts/interfaces/ReentrancyErrors.sol index 042654f5c..94f523873 100644 --- a/contracts/interfaces/ReentrancyErrors.sol +++ b/contracts/interfaces/ReentrancyErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title ReentrancyErrors diff --git a/contracts/interfaces/SeaportInterface.sol b/contracts/interfaces/SeaportInterface.sol index 6593f8658..37c6199a6 100644 --- a/contracts/interfaces/SeaportInterface.sol +++ b/contracts/interfaces/SeaportInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/interfaces/SignatureVerificationErrors.sol b/contracts/interfaces/SignatureVerificationErrors.sol index 90a2a4c97..b20ea0033 100644 --- a/contracts/interfaces/SignatureVerificationErrors.sol +++ b/contracts/interfaces/SignatureVerificationErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title SignatureVerificationErrors diff --git a/contracts/interfaces/TokenTransferrerErrors.sol b/contracts/interfaces/TokenTransferrerErrors.sol index 21887650b..6734c9fba 100644 --- a/contracts/interfaces/TokenTransferrerErrors.sol +++ b/contracts/interfaces/TokenTransferrerErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title TokenTransferrerErrors diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index ac615e8cf..7f4a16c80 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { TransferHelperItem } from "../helpers/TransferHelperStructs.sol"; diff --git a/contracts/interfaces/ZoneInteractionErrors.sol b/contracts/interfaces/ZoneInteractionErrors.sol index f7b271c4c..fc0ef9773 100644 --- a/contracts/interfaces/ZoneInteractionErrors.sol +++ b/contracts/interfaces/ZoneInteractionErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /** * @title ZoneInteractionErrors diff --git a/contracts/interfaces/ZoneInterface.sol b/contracts/interfaces/ZoneInterface.sol index 94dd037f2..90f2f4158 100644 --- a/contracts/interfaces/ZoneInterface.sol +++ b/contracts/interfaces/ZoneInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/lib/AmountDeriver.sol b/contracts/lib/AmountDeriver.sol index f35b2cb73..a1fc0ddf6 100644 --- a/contracts/lib/AmountDeriver.sol +++ b/contracts/lib/AmountDeriver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; // prettier-ignore import { diff --git a/contracts/lib/Assertions.sol b/contracts/lib/Assertions.sol index ec10a11fe..4bff5c683 100644 --- a/contracts/lib/Assertions.sol +++ b/contracts/lib/Assertions.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderParameters } from "./ConsiderationStructs.sol"; diff --git a/contracts/lib/BasicOrderFulfiller.sol b/contracts/lib/BasicOrderFulfiller.sol index 62dcf1816..54d5d75ee 100644 --- a/contracts/lib/BasicOrderFulfiller.sol +++ b/contracts/lib/BasicOrderFulfiller.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; diff --git a/contracts/lib/Consideration.sol b/contracts/lib/Consideration.sol index 765f6b38b..a10fa3333 100644 --- a/contracts/lib/Consideration.sol +++ b/contracts/lib/Consideration.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; // prettier-ignore import { diff --git a/contracts/lib/ConsiderationBase.sol b/contracts/lib/ConsiderationBase.sol index f7e46be2a..db45f1d4f 100644 --- a/contracts/lib/ConsiderationBase.sol +++ b/contracts/lib/ConsiderationBase.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; // prettier-ignore import { diff --git a/contracts/lib/ConsiderationConstants.sol b/contracts/lib/ConsiderationConstants.sol index 12f4b0963..4aa08de85 100644 --- a/contracts/lib/ConsiderationConstants.sol +++ b/contracts/lib/ConsiderationConstants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /* * -------------------------- Disambiguation & Other Notes --------------------- diff --git a/contracts/lib/ConsiderationEnums.sol b/contracts/lib/ConsiderationEnums.sol index c8797f204..6bcfd701c 100644 --- a/contracts/lib/ConsiderationEnums.sol +++ b/contracts/lib/ConsiderationEnums.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore enum OrderType { diff --git a/contracts/lib/ConsiderationStructs.sol b/contracts/lib/ConsiderationStructs.sol index 064d01d9a..c85893058 100644 --- a/contracts/lib/ConsiderationStructs.sol +++ b/contracts/lib/ConsiderationStructs.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; // prettier-ignore import { diff --git a/contracts/lib/CounterManager.sol b/contracts/lib/CounterManager.sol index 69532b391..d262449cc 100644 --- a/contracts/lib/CounterManager.sol +++ b/contracts/lib/CounterManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; // prettier-ignore import { diff --git a/contracts/lib/CriteriaResolution.sol b/contracts/lib/CriteriaResolution.sol index 6c814958a..6b60e2614 100644 --- a/contracts/lib/CriteriaResolution.sol +++ b/contracts/lib/CriteriaResolution.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ItemType, Side } from "./ConsiderationEnums.sol"; diff --git a/contracts/lib/Executor.sol b/contracts/lib/Executor.sol index 6e030b29b..4e5516f57 100644 --- a/contracts/lib/Executor.sol +++ b/contracts/lib/Executor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; diff --git a/contracts/lib/FulfillmentApplier.sol b/contracts/lib/FulfillmentApplier.sol index 02c0957f5..809518fac 100644 --- a/contracts/lib/FulfillmentApplier.sol +++ b/contracts/lib/FulfillmentApplier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ItemType, Side } from "./ConsiderationEnums.sol"; diff --git a/contracts/lib/GettersAndDerivers.sol b/contracts/lib/GettersAndDerivers.sol index 4b0d16357..bef8b6db6 100644 --- a/contracts/lib/GettersAndDerivers.sol +++ b/contracts/lib/GettersAndDerivers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderParameters } from "./ConsiderationStructs.sol"; diff --git a/contracts/lib/LowLevelHelpers.sol b/contracts/lib/LowLevelHelpers.sol index c3bba2398..0b45d0ced 100644 --- a/contracts/lib/LowLevelHelpers.sol +++ b/contracts/lib/LowLevelHelpers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import "./ConsiderationConstants.sol"; diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol index 7de31ccef..5ba99d8c6 100644 --- a/contracts/lib/OrderCombiner.sol +++ b/contracts/lib/OrderCombiner.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { Side, ItemType } from "./ConsiderationEnums.sol"; diff --git a/contracts/lib/OrderFulfiller.sol b/contracts/lib/OrderFulfiller.sol index 4a7fe6a21..437b0c5a5 100644 --- a/contracts/lib/OrderFulfiller.sol +++ b/contracts/lib/OrderFulfiller.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ItemType } from "./ConsiderationEnums.sol"; diff --git a/contracts/lib/OrderValidator.sol b/contracts/lib/OrderValidator.sol index 70e05f049..b3dbc842e 100644 --- a/contracts/lib/OrderValidator.sol +++ b/contracts/lib/OrderValidator.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType } from "./ConsiderationEnums.sol"; diff --git a/contracts/lib/ReentrancyGuard.sol b/contracts/lib/ReentrancyGuard.sol index 69169fe0a..ece4802e9 100644 --- a/contracts/lib/ReentrancyGuard.sol +++ b/contracts/lib/ReentrancyGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ReentrancyErrors } from "../interfaces/ReentrancyErrors.sol"; diff --git a/contracts/lib/SignatureVerification.sol b/contracts/lib/SignatureVerification.sol index 023d3c25f..ea4e5128b 100644 --- a/contracts/lib/SignatureVerification.sol +++ b/contracts/lib/SignatureVerification.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { EIP1271Interface } from "../interfaces/EIP1271Interface.sol"; diff --git a/contracts/lib/TokenTransferrer.sol b/contracts/lib/TokenTransferrer.sol index 197a28355..b32cbaf70 100644 --- a/contracts/lib/TokenTransferrer.sol +++ b/contracts/lib/TokenTransferrer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import "./TokenTransferrerConstants.sol"; diff --git a/contracts/lib/TokenTransferrerConstants.sol b/contracts/lib/TokenTransferrerConstants.sol index adb01c58d..54293bcb4 100644 --- a/contracts/lib/TokenTransferrerConstants.sol +++ b/contracts/lib/TokenTransferrerConstants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; /* * -------------------------- Disambiguation & Other Notes --------------------- diff --git a/contracts/lib/Verifiers.sol b/contracts/lib/Verifiers.sol index baba8da90..4d495a1e4 100644 --- a/contracts/lib/Verifiers.sol +++ b/contracts/lib/Verifiers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderStatus } from "./ConsiderationStructs.sol"; @@ -57,8 +57,8 @@ contract Verifiers is Assertions, SignatureVerification { /** * @dev Internal view function to verify the signature of an order. An * ERC-1271 fallback will be attempted if either the signature length - * is not 32 or 33 bytes or if the recovered signer does not match the - * supplied offerer. Note that in cases where a 32 or 33 byte signature + * is not 64 or 65 bytes or if the recovered signer does not match the + * supplied offerer. Note that in cases where a 64 or 65 byte signature * is supplied, only standard ECDSA signatures that recover to a * non-zero address are supported. * diff --git a/contracts/lib/ZoneInteraction.sol b/contracts/lib/ZoneInteraction.sol index fbe88a514..326ccc4fd 100644 --- a/contracts/lib/ZoneInteraction.sol +++ b/contracts/lib/ZoneInteraction.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ZoneInterface } from "../interfaces/ZoneInterface.sol"; diff --git a/contracts/test/EIP1271Wallet.sol b/contracts/test/EIP1271Wallet.sol index b0e5b50a1..75bb206c5 100644 --- a/contracts/test/EIP1271Wallet.sol +++ b/contracts/test/EIP1271Wallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; interface ERC20ApprovalInterface { function approve(address, uint256) external returns (bool); diff --git a/contracts/test/ERC1155BatchRecipient.sol b/contracts/test/ERC1155BatchRecipient.sol index 2704c169d..c5a6a2d4a 100644 --- a/contracts/test/ERC1155BatchRecipient.sol +++ b/contracts/test/ERC1155BatchRecipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; contract ERC1155BatchRecipient { error UnexpectedBatchData(); diff --git a/contracts/test/ERC721ReceiverMock.sol b/contracts/test/ERC721ReceiverMock.sol index af9d94475..e69b02b1c 100644 --- a/contracts/test/ERC721ReceiverMock.sol +++ b/contracts/test/ERC721ReceiverMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; interface IERC721Receiver { function onERC721Received( diff --git a/contracts/test/ExcessReturnDataRecipient.sol b/contracts/test/ExcessReturnDataRecipient.sol index 7196920ab..908874fb2 100644 --- a/contracts/test/ExcessReturnDataRecipient.sol +++ b/contracts/test/ExcessReturnDataRecipient.sol @@ -1,5 +1,5 @@ -//SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.7; +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.7; contract ExcessReturnDataRecipient { uint256 private revertDataSize; diff --git a/contracts/test/InvalidERC721Recipient.sol b/contracts/test/InvalidERC721Recipient.sol index 9d1c4d52e..25db2bb8d 100644 --- a/contracts/test/InvalidERC721Recipient.sol +++ b/contracts/test/InvalidERC721Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; interface IERC721Receiver { function onERC721Received( @@ -12,10 +12,10 @@ interface IERC721Receiver { contract InvalidERC721Recipient is IERC721Receiver { function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data + address, /* operator */ + address, /* from */ + uint256, /* tokenId */ + bytes calldata /* data */ ) external pure override returns (bytes4) { return 0xabcd0000; } diff --git a/contracts/test/Reenterer.sol b/contracts/test/Reenterer.sol index 18d7eda0d..d932daf67 100644 --- a/contracts/test/Reenterer.sol +++ b/contracts/test/Reenterer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; contract Reenterer { address public target; diff --git a/contracts/test/TestERC1155.sol b/contracts/test/TestERC1155.sol index 959dad790..6e53b4974 100644 --- a/contracts/test/TestERC1155.sol +++ b/contracts/test/TestERC1155.sol @@ -1,5 +1,5 @@ -//SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.7; +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC1155.sol"; diff --git a/contracts/test/TestERC20.sol b/contracts/test/TestERC20.sol index 9113ecf68..f9a6fcf16 100644 --- a/contracts/test/TestERC20.sol +++ b/contracts/test/TestERC20.sol @@ -1,5 +1,5 @@ -//SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.7; +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; diff --git a/contracts/test/TestERC20Panic.sol b/contracts/test/TestERC20Panic.sol index 4ffbf2ebc..84abb3b3d 100644 --- a/contracts/test/TestERC20Panic.sol +++ b/contracts/test/TestERC20Panic.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; contract TestERC20Panic is ERC20("TestPanic", "PANIC", 18) { function transferFrom( - address from, - address to, - uint256 amount - ) public override returns (bool) { + address, /* from */ + address, /* to */ + uint256 /* amount */ + ) public pure override returns (bool) { uint256 a = uint256(0) / uint256(0); a; diff --git a/contracts/test/TestERC20Revert.sol b/contracts/test/TestERC20Revert.sol index e581300fa..b7dddfa1d 100644 --- a/contracts/test/TestERC20Revert.sol +++ b/contracts/test/TestERC20Revert.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; @@ -9,11 +9,10 @@ contract TestERC20Revert is ERC20("TestPanic", "PANIC", 18) { } function transferFrom( - address from, - address to, - uint256 amount - ) public override returns (bool) { + address, /* from */ + address, /* to */ + uint256 /* amount */ + ) public pure override returns (bool) { revert(); - return true; } } diff --git a/contracts/test/TestERC721.sol b/contracts/test/TestERC721.sol index 5e9e143ee..c599f34ff 100644 --- a/contracts/test/TestERC721.sol +++ b/contracts/test/TestERC721.sol @@ -1,5 +1,5 @@ -//SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.7; +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC721.sol"; diff --git a/contracts/test/TestZone.sol b/contracts/test/TestZone.sol index 45f520be8..881529aa3 100644 --- a/contracts/test/TestZone.sol +++ b/contracts/test/TestZone.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { ZoneInterface } from "../interfaces/ZoneInterface.sol"; diff --git a/docs/Deployment.md b/docs/Deployment.md new file mode 100644 index 000000000..7fd1334b5 --- /dev/null +++ b/docs/Deployment.md @@ -0,0 +1,96 @@ +# Deloying Seaport + +Seaport and the ConduitController can be deployed to the same address on all EVM chains using the CREATE2 Factory. + +## Relevant Addresses + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameAddress
KEYLESS_CREATE2_DEPLOYER_ADDRESS0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1
KEYLESS_CREATE2_ADDRESS0x7A0D94F55792C434d74a40883C6ed8545E406D12
INEFFICIENT_IMMUTABLE_CREATE2_FACTORY_ADDRESS0xcfA3A7637547094fF06246817a35B8333C315196
IMMUTABLE_CREATE2_FACTORY_ADDRESS0x0000000000ffe8b47b3e2130213b802212439497
ConduitController0x00000000F9490004C11Cef243f5400493c00Ad63
Seaport 1.10x00000000006c3852cbEf3e08E8dF289169EdE581
+ +--- + +## Setting up Factory on a New Chain + +If there is no `IMMUTABLE_CREATE2_FACTORY_ADDRESS` on the chain, deploy this first. + +1. Send 0.01 Ether to the `KEYLESS_CREATE2_DEPLOYER_ADDRESS` +2. Create the `KEYLESS_CREATE2_ADDRESS` by submitting this pre-signed transaction: + +``` +0xf87e8085174876e800830186a08080ad601f80600e600039806000f350fe60003681823780368234f58015156014578182fd5b80825250506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222 +``` + +3. Create the `INEFFICIENT_IMMUTABLE_CREATE2_FACTORY_ADDRESS` by submitting: + +``` +seth send 0x7a0d94f55792c434d74a40883c6ed8545e406d12 0x608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032 +``` + +4. Create the `IMMUTABLE_CREATE2_FACTORY_ADDRESS` contract at `0x0000000000ffe8b47b3e2130213b802212439497` by submitting: + +``` +seth send 0xcfa3a7637547094ff06246817a35b8333c315196 0x64e030870000000000000000000000000000000000000000f4b0218f13a6440a6f02000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000853608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a003200000000000000000000000000 +``` + +## Deploying Seaport and ConduitController + +Once the `IMMUTABLE_CREATE2_FACTORY_ADDRESS` exists, begin to deploy the contracts: + +1. Deploy the `ConduitController` contract by submitting: + +``` +seth send 0x0000000000ffe8b47b3e2130213b802212439497 0x64e030870000000000000000000000000000000000000000dc0ef3c792976604960400000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000302760c08060405234620000ea57600090610c9f906001600160401b03603f8301601f1916820181811183821017620000da575b604052828252620023889160208101908484833951902060805260405192839281840192831184841017620000ca575b8339039082f58015620000ba575b6001600160a01b03163f60a05260405161227490816200011482396080518181816102b101528181610bcc0152610d06015260a0518181816102d401528181610c620152610da90152f35b620000c462000106565b6200006f565b620000d4620000ef565b62000061565b620000e4620000ef565b62000031565b600080fd5b50634e487b7160e01b600052604160045260246000fd5b506040513d6000823e3d90fdfe60806040526004361015610013575b600080fd5b60003560e01c8063027cc7641461012b5780630a96ad391461012257806313ad9cab1461011957806314afd79e1461011057806333bc8572146101075780634e3f9580146100fe57806351710e45146100f55780636d435421146100ec5780636e9bfd9f146100e3578063794593bc146100da5780637b37e561146100d15780638b9e028b146100c8578063906c87cc146100bf576393790f44146100b757600080fd5b61000e61126e565b5061000e6111fa565b5061000e61113c565b5061000e610fc8565b5061000e610c8a565b5061000e610b3c565b5061000e6109bf565b5061000e610765565b5061000e6106f3565b5061000e61064f565b5061000e6105db565b5061000e6102fa565b5061000e61027b565b5061000e61017a565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361000e57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361000e57565b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e576101b2610134565b602435906101bf81611574565b73ffffffffffffffffffffffffffffffffffffffff80911691600083815280602052600360408220015482101561023f5790600360408361023b9661020a9552806020522001611400565b90549060031b1c166040519182918291909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b0390f35b602484604051907f6ceb340b0000000000000000000000000000000000000000000000000000000082526004820152fd5b600091031261000e57565b503461000e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57604080517f000000000000000000000000000000000000000000000000000000000000000081527f00000000000000000000000000000000000000000000000000000000000000006020820152f35b503461000e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57610332610134565b61033a610157565b90604435918215918215840361000e5761035381611505565b73ffffffffffffffffffffffffffffffffffffffff811690813b1561000e576040517fc4e8fcb500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201528515156024820152610401926000908290604490829084905af180156105ce575b6105b5575b5073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b92600484019261043183859073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b5491821590806105ae575b1561048157505050600361047d92930161045682826114ce565b54929073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b555b005b91949391816105a5575b5061049257005b6104df61047d938560037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600098019201916104ce83546113a4565b90808203610504575b505050611447565b9073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b6105766105449161053b61051b61059c9588611400565b905473ffffffffffffffffffffffffffffffffffffffff9160031b1c1690565b92839187611400565b90919082549060031b9173ffffffffffffffffffffffffffffffffffffffff9283811b93849216901b16911916179055565b859073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b553880806104d7565b9050153861048b565b508061043c565b806105c26105c892611335565b80610270565b386103da565b6105d6611397565b6103d5565b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e576020610615610134565b61061e81611574565b73ffffffffffffffffffffffffffffffffffffffff8091166000526000825260016040600020015416604051908152f35b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5760206106e861068c610134565b73ffffffffffffffffffffffffffffffffffffffff6106a9610157565b916106b381611574565b166000526000835260046040600020019073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b541515604051908152f35b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5773ffffffffffffffffffffffffffffffffffffffff610740610134565b61074981611574565b1660005260006020526020600360406000200154604051908152f35b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5761079d610134565b6107a681611574565b61080c6107f360026107d88473ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b33036109765761047f9060007f11a3cf439fb225bfe74225716b6774765670ec1060e3796802e62139d69974da81604051a2610896600261086d8373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b017fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b73ffffffffffffffffffffffffffffffffffffffff3390806108dd60016107d88673ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b169083167fc8894f26f396ce8c004245c8b7cd1b92103a6e4302fcbab883987149ac01b7ec6000604051a46001610935339273ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b019073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b6040517f88c3a11500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602490fd5b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e576109f7610134565b6109ff610157565b90610a0981611505565b73ffffffffffffffffffffffffffffffffffffffff808316908115610b095750610a5b6107f360026107d88573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b8114610ab95761093561047f93926002927f11a3cf439fb225bfe74225716b6774765670ec1060e3796802e62139d69974da6000604051a273ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b506040517fcbc080ca00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015291166024820152604490fd5b82602491604051917fa388d263000000000000000000000000000000000000000000000000000000008352166004820152fd5b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e576040517fff00000000000000000000000000000000000000000000000000000000000000602082019081523060601b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260043560358301527f0000000000000000000000000000000000000000000000000000000000000000605583015273ffffffffffffffffffffffffffffffffffffffff91610c3b81607581015b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611356565b519020604080519290911673ffffffffffffffffffffffffffffffffffffffff811683523f7f000000000000000000000000000000000000000000000000000000000000000014602083015290f35b503461000e576040807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57600435610cc6610157565b73ffffffffffffffffffffffffffffffffffffffff91828216908115610f9f57338160601c03610f7657610da46107f386516020810190610d8881610c0f7f0000000000000000000000000000000000000000000000000000000000000000883087917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000605594927fff00000000000000000000000000000000000000000000000000000000000000855260601b166001840152601583015260358201520190565b51902073ffffffffffffffffffffffffffffffffffffffff1690565b92833f7f000000000000000000000000000000000000000000000000000000000000000014610f3057947f4397af6128d529b8ae0442f99db1296d5136062597a15bbc61c1b2a6431a7d15610eca838060009961023b989796865180610c9f8082019082821067ffffffffffffffff831117610f23575b6115a0833903908df515610f16575b610e9c610e578973ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b91600183019073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b55835173ffffffffffffffffffffffffffffffffffffffff8716815260208101919091529081906040820190565b0390a15194859483167fc8894f26f396ce8c004245c8b7cd1b92103a6e4302fcbab883987149ac01b7ec8287a473ffffffffffffffffffffffffffffffffffffffff1682526020820190565b610f1e611397565b610e2a565b610f2b611305565b610e1b565b85517f6328ccb200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602490fd5b600485517fcb6e5344000000000000000000000000000000000000000000000000000000008152fd5b600485517f99faaa04000000000000000000000000000000000000000000000000000000008152fd5b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57611000610134565b61100981611505565b73ffffffffffffffffffffffffffffffffffffffff9081811660009281845283602052600260408520015416156110ba575061108e600291837f11a3cf439fb225bfe74225716b6774765670ec1060e3796802e62139d69974da81604051a273ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b017fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055604051f35b602490604051907f6b0136160000000000000000000000000000000000000000000000000000000082526004820152fd5b6020908160408183019282815285518094520193019160005b828110611112575050505090565b835173ffffffffffffffffffffffffffffffffffffffff1685529381019392810192600101611104565b503461000e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57611175610134565b9061117f82611574565b73ffffffffffffffffffffffffffffffffffffffff91826000911681528082526003604082200192604051908193808654938481520195845280842093915b8383106111e15761023b866111d5818a0382611356565b604051918291826110eb565b84548116875295810195600194850194909201916111be565b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e576020611234610134565b61123d81611574565b73ffffffffffffffffffffffffffffffffffffffff8091166000526000825260026040600020015416604051908152f35b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5773ffffffffffffffffffffffffffffffffffffffff6112bb610134565b16600052600060205260406000205480156112db57602090604051908152f35b60046040517f4ca82090000000000000000000000000000000000000000000000000000000008152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff811161134957604052565b611351611305565b604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761134957604052565b506040513d6000823e3d90fd5b600181106113d1577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80548210156114185760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b8054801561149f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061147c8282611400565b73ffffffffffffffffffffffffffffffffffffffff82549160031b1b1916905555565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b906105446114f692805490680100000000000000008210156114f8575b600182018155611400565b565b611500611305565b6114eb565b61150e81611574565b73ffffffffffffffffffffffffffffffffffffffff809116908160005260006020526001604060002001541633036115435750565b602490604051907fd4ed9a170000000000000000000000000000000000000000000000000000000082526004820152fd5b73ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002054156112db5756fe60a080604052346100235733608052610c7690816100298239608051816103c50152f35b600080fdfe60806040526004361015610013575b600080fd5b6000803560e01c9081634ce34aa21461006657508063899e104c1461005d5780638df25d92146100545763c4e8fcb51461004c57600080fd5b61000e610362565b5061000e61027f565b5061000e6101ab565b346101465760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101465760043567ffffffffffffffff8111610142576100b5903690600401610149565b9133815280602052604081205415610116575b8281106100fa576040517f4ce34aa2000000000000000000000000000000000000000000000000000000008152602090f35b8061011061010b6001938686610532565b6105c4565b016100c8565b807f93daadf2000000000000000000000000000000000000000000000000000000006024925233600452fd5b5080fd5b80fd5b9181601f8401121561000e5782359167ffffffffffffffff831161000e5760208085019460c0850201011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5767ffffffffffffffff60043581811161000e576101fc903690600401610149565b9160243590811161000e5761021590369060040161017a565b919092600033815280602052604081205415610116575b8181106102685761023d8486610acb565b6040517f899e104c000000000000000000000000000000000000000000000000000000008152602090f35b8061027961010b6001938587610532565b0161022c565b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5760043567ffffffffffffffff811161000e576102cf90369060040161017a565b33600052600060205260406000205415610316576102ec91610acb565b60206040517f8df25d92000000000000000000000000000000000000000000000000000000008152f35b7f93daadf2000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5760043561039e81610344565b6024359081151580830361000e5773ffffffffffffffffffffffffffffffffffffffff90817f00000000000000000000000000000000000000000000000000000000000000001633036105085761041f6104188473ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b5460ff1690565b1515146104b657816104a6846104767fae63067d43ac07563b7eb8db6595635fc77f1578a2a5ea06ba91b63e2afa37e29573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b60405193151584521691602090a2005b506040517f924e341e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9190911660048201529015156024820152604490fd5b60046040517f6d5769be000000000000000000000000000000000000000000000000000000008152fd5b91908110156105425760c0020190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6004111561057b57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b35600481101561000e5790565b356105c181610344565b90565b60016105cf826105aa565b6105d881610571565b0361061357806105ed602061061193016105b7565b906105fa604082016105b7565b60a0610608606084016105b7565b92013592610712565b565b600261061e826105aa565b61062781610571565b0361069657600160a08201350361066c5780610648602061061193016105b7565b90610655604082016105b7565b6080610663606084016105b7565b92013592610882565b60046040517fefcc00b1000000000000000000000000000000000000000000000000000000008152fd5b60036106a1826105aa565b6106aa81610571565b036106e857806106bf602061061193016105b7565b6106cb604083016105b7565b6106d7606084016105b7565b90608060a085013594013592610990565b60046040517f7932f1fc000000000000000000000000000000000000000000000000000000008152fd5b9092604051926000947f23b872dd00000000000000000000000000000000000000000000000000000000865280600452816024528260445260208660648180885af1803d15601f3d1160018a51141617163d151581161561077c575b505050505050604052606052565b80863b15151661076e579087959691156107bc57602486887f5f15d672000000000000000000000000000000000000000000000000000000008252600452fd5b156107f657506084947f98891923000000000000000000000000000000000000000000000000000000008552600452602452604452606452fd5b3d610835575b5060a4947ff486bc8700000000000000000000000000000000000000000000000000000000855260045260245260445281606452608452fd5b601f3d0160051c9060051c908060030291808211610869575b505060205a91011061086057856107fc565b833d81803e3d90fd5b8080600392028380020360091c9203020101868061084e565b9092813b1561096257604051926000947f23b872dd000000000000000000000000000000000000000000000000000000008652806004528160245282604452858060648180885af1156108db5750505050604052606052565b8593943d61091e575b5060a4947ff486bc870000000000000000000000000000000000000000000000000000000085526004526024526044526064526001608452fd5b601f3d0160051c9060051c908060030291808211610949575b505060205a91011061086057856108e4565b8080600392028380020360091c92030201018680610937565b507f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b929093833b15610a9d57604051936080519160a0519360c051956000987ff242432a000000000000000000000000000000000000000000000000000000008a528060045281602452826044528360645260a06084528960a452898060c48180895af115610a0d57505050505060805260a05260c052604052606052565b89949550883d610a50575b5060a4957ff486bc87000000000000000000000000000000000000000000000000000000008652600452602452604452606452608452fd5b601f3d0160051c9060051c908060030291808211610a84575b505060205a910110610a7b5786610a18565b843d81803e3d90fd5b8080600392028380020360091c92030201018780610a69565b837f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b90816020907f2eb2c2d600000000000000000000000000000000000000000000000000000000825260005b838110610b095750505050506080604052565b8435820194853590813b156109625760a09182880192833560059181831b948b60c08097608094818301868501351490606085013514169201013584141615610c165789019a890160243760061b9360e0850160a452610104850194600086526040019060c437600080858982865af115610b8a5750505050600101610af6565b869394503d610bcb575b507fafc445e20000000000000000000000000000000000000000000000000000000060005260045260645260849081510190526000fd5b84601f3d01821c911c90600381810292808311610bff575b505050835a910110610bf55784610b94565b3d6000803e3d6000fd5b8080028380020360091c9203020101858080610be3565b7feba2084c0000000000000000000000000000000000000000000000000000000060005260046000fdfea2646970667358221220c5c8d054d9d5df7c3530eab1c32506aad1fcb6772c1457f0da5443ad9e91b4a364736f6c634300080e0033a264697066735822122031e2de61a9e35e9e87d5eef6a36b045a0bab54c4031fd01a0f8138afce3cec3164736f6c634300080e003360a080604052346100235733608052610c7690816100298239608051816103c50152f35b600080fdfe60806040526004361015610013575b600080fd5b6000803560e01c9081634ce34aa21461006657508063899e104c1461005d5780638df25d92146100545763c4e8fcb51461004c57600080fd5b61000e610362565b5061000e61027f565b5061000e6101ab565b346101465760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101465760043567ffffffffffffffff8111610142576100b5903690600401610149565b9133815280602052604081205415610116575b8281106100fa576040517f4ce34aa2000000000000000000000000000000000000000000000000000000008152602090f35b8061011061010b6001938686610532565b6105c4565b016100c8565b807f93daadf2000000000000000000000000000000000000000000000000000000006024925233600452fd5b5080fd5b80fd5b9181601f8401121561000e5782359167ffffffffffffffff831161000e5760208085019460c0850201011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5767ffffffffffffffff60043581811161000e576101fc903690600401610149565b9160243590811161000e5761021590369060040161017a565b919092600033815280602052604081205415610116575b8181106102685761023d8486610acb565b6040517f899e104c000000000000000000000000000000000000000000000000000000008152602090f35b8061027961010b6001938587610532565b0161022c565b503461000e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5760043567ffffffffffffffff811161000e576102cf90369060040161017a565b33600052600060205260406000205415610316576102ec91610acb565b60206040517f8df25d92000000000000000000000000000000000000000000000000000000008152f35b7f93daadf2000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b503461000e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e5760043561039e81610344565b6024359081151580830361000e5773ffffffffffffffffffffffffffffffffffffffff90817f00000000000000000000000000000000000000000000000000000000000000001633036105085761041f6104188473ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b5460ff1690565b1515146104b657816104a6846104767fae63067d43ac07563b7eb8db6595635fc77f1578a2a5ea06ba91b63e2afa37e29573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b60405193151584521691602090a2005b506040517f924e341e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9190911660048201529015156024820152604490fd5b60046040517f6d5769be000000000000000000000000000000000000000000000000000000008152fd5b91908110156105425760c0020190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6004111561057b57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b35600481101561000e5790565b356105c181610344565b90565b60016105cf826105aa565b6105d881610571565b0361061357806105ed602061061193016105b7565b906105fa604082016105b7565b60a0610608606084016105b7565b92013592610712565b565b600261061e826105aa565b61062781610571565b0361069657600160a08201350361066c5780610648602061061193016105b7565b90610655604082016105b7565b6080610663606084016105b7565b92013592610882565b60046040517fefcc00b1000000000000000000000000000000000000000000000000000000008152fd5b60036106a1826105aa565b6106aa81610571565b036106e857806106bf602061061193016105b7565b6106cb604083016105b7565b6106d7606084016105b7565b90608060a085013594013592610990565b60046040517f7932f1fc000000000000000000000000000000000000000000000000000000008152fd5b9092604051926000947f23b872dd00000000000000000000000000000000000000000000000000000000865280600452816024528260445260208660648180885af1803d15601f3d1160018a51141617163d151581161561077c575b505050505050604052606052565b80863b15151661076e579087959691156107bc57602486887f5f15d672000000000000000000000000000000000000000000000000000000008252600452fd5b156107f657506084947f98891923000000000000000000000000000000000000000000000000000000008552600452602452604452606452fd5b3d610835575b5060a4947ff486bc8700000000000000000000000000000000000000000000000000000000855260045260245260445281606452608452fd5b601f3d0160051c9060051c908060030291808211610869575b505060205a91011061086057856107fc565b833d81803e3d90fd5b8080600392028380020360091c9203020101868061084e565b9092813b1561096257604051926000947f23b872dd000000000000000000000000000000000000000000000000000000008652806004528160245282604452858060648180885af1156108db5750505050604052606052565b8593943d61091e575b5060a4947ff486bc870000000000000000000000000000000000000000000000000000000085526004526024526044526064526001608452fd5b601f3d0160051c9060051c908060030291808211610949575b505060205a91011061086057856108e4565b8080600392028380020360091c92030201018680610937565b507f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b929093833b15610a9d57604051936080519160a0519360c051956000987ff242432a000000000000000000000000000000000000000000000000000000008a528060045281602452826044528360645260a06084528960a452898060c48180895af115610a0d57505050505060805260a05260c052604052606052565b89949550883d610a50575b5060a4957ff486bc87000000000000000000000000000000000000000000000000000000008652600452602452604452606452608452fd5b601f3d0160051c9060051c908060030291808211610a84575b505060205a910110610a7b5786610a18565b843d81803e3d90fd5b8080600392028380020360091c92030201018780610a69565b837f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b90816020907f2eb2c2d600000000000000000000000000000000000000000000000000000000825260005b838110610b095750505050506080604052565b8435820194853590813b156109625760a09182880192833560059181831b948b60c08097608094818301868501351490606085013514169201013584141615610c165789019a890160243760061b9360e0850160a452610104850194600086526040019060c437600080858982865af115610b8a5750505050600101610af6565b869394503d610bcb575b507fafc445e20000000000000000000000000000000000000000000000000000000060005260045260645260849081510190526000fd5b84601f3d01821c911c90600381810292808311610bff575b505050835a910110610bf55784610b94565b3d6000803e3d6000fd5b8080028380020360091c9203020101858080610be3565b7feba2084c0000000000000000000000000000000000000000000000000000000060005260046000fdfea2646970667358221220c5c8d054d9d5df7c3530eab1c32506aad1fcb6772c1457f0da5443ad9e91b4a364736f6c634300080e003300000000000000000000000000000000000000000000000000 +``` + +2. Deploy the `Seaport` 1.1 contract by submitting: + +``` +seth send 0x0000000000ffe8b47b3e2130213b802212439497 0x64e030870000000000000000000000000000000000000000a39d1860ddeb0e016b0900000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000670b6101c060405234620000b9576200001f6200001962000114565b62000151565b604051615f7e90816200076d82396080518161282c015260a05181612852015260c05181612809015260e051818181611758015261269701526101005181818161162401526126e60152610120518181816117f40152612734015261014051816127b7015261016051816127dd015261018051818181611003015281816122f4015261246a01526101a05181818161233201526124a80152f35b600080fd5b604081019081106001600160401b03821117620000da57604052565b634e487b7160e01b600052604160045260246000fd5b601f909101601f19168101906001600160401b03821190821017620000da57604052565b620066eb60208138039182604051938492620001318285620000f0565b833981010312620000b957516001600160a01b0381168103620000b95790565b604060049162000160620002e3565b610120526101005260e05260c05260a05260805246610140526200018362000237565b610160526001600160a01b03166101808190528151630a96ad3960e01b815292839182905afa90811562000203575b600091620001cd575b506101a052620001cb6001600055565b565b620001f3915060403d8111620001fb575b620001ea8183620000f0565b81019062000213565b5038620001bb565b503d620001de565b6200020d6200022a565b620001b2565b9190826040910312620000b9576020825192015190565b506040513d6000823e3d90fd5b60c05160805160a0516040519160208301938452604083015260608201524660808201523060a082015260a0815260c0810181811060018060401b03821117620000da5760405251902090565b604051906200029382620000be565b6003825262312e3160e81b6020830152565b90815180926000905b828210620002cb575011620002c1570190565b6000828201520190565b915080602080928401015181850152018391620002ae565b620002ed62000747565b8051602080920120916200030062000284565b8281519101209160405181810192816200032b85600a906909ecccccae492e8cada560b31b81520190565b6e1d5a5b9d0e081a5d195b551e5c194b608a1b8152600f016d1859191c995cdcc81d1bdad95b8b60921b8152600e017f75696e74323536206964656e7469666965724f7243726974657269612c0000008152601d017f75696e74323536207374617274416d6f756e742c0000000000000000000000008152601401701d5a5b9d0c8d4d88195b99105b5bdd5b9d607a1b8152601101602960f81b81526001010392601f19938481018452620003e19084620000f0565b60405171086dedce6d2c8cae4c2e8d2dedc92e8cada560731b8282019081529481601287016e1d5a5b9d0e081a5d195b551e5c194b608a1b8152600f016d1859191c995cdcc81d1bdad95b8b60921b8152600e017f75696e74323536206964656e7469666965724f7243726974657269612c0000008152601d017f75696e74323536207374617274416d6f756e742c0000000000000000000000008152601401711d5a5b9d0c8d4d88195b99105b5bdd5b9d0b60721b8152601201701859191c995cdcc81c9958da5c1a595b9d607a1b8152601101602960f81b8152600101038181018352620004d29083620000f0565b6040519283818101620004fc906010906f09ee4c8cae486dedae0dedccadce8e6560831b81520190565b6f1859191c995cdcc81bd999995c995c8b60821b81526010016c1859191c995cdcc81e9bdb994b609a1b8152600d017113d999995c925d195b56d7481bd999995c8b60721b81526012017f436f6e73696465726174696f6e4974656d5b5d20636f6e73696465726174696f8152611b8b60f21b60208201526022016f1d5a5b9d0e081bdc99195c951e5c194b60821b8152601001711d5a5b9d0c8d4d881cdd185c9d151a5b594b60721b81526012016f1d5a5b9d0c8d4d88195b99151a5b594b60821b815260100170189e5d195ccccc881e9bdb9952185cda0b607a1b81526011016c1d5a5b9d0c8d4d881cd85b1d0b609a1b8152600d017f6279746573333220636f6e647569744b65792c0000000000000000000000000081526013016e3ab4b73a191a9b1031b7bab73a32b960891b8152600f01602960f81b81526001010382810185526200064e9085620000f0565b6040516c08a92a06e626488dedac2d2dc5609b1b8282019081529080600d83016b1cdd1c9a5b99c81b985b594b60a21b8152600c016e1cdd1c9a5b99c81d995c9cda5bdb8b608a1b8152600f016f1d5a5b9d0c8d4d8818da185a5b92590b60821b81526010017f6164647265737320766572696679696e67436f6e7472616374000000000000008152601901602960f81b8152600101038481018252620006f69082620000f0565b5190209786519020968351902095604051938492830195866200071991620002a5565b6200072491620002a5565b6200072f91620002a5565b039081018252620007419082620000f0565b51902090565b604051906200075682620000be565b600782526614d9585c1bdc9d60ca1b602083015256fe60806040526004361015610013575b600080fd5b60003560e01c806306fdde031461013f57806346423aa71461013657806355944a421461012d5780635b34b9661461012457806379df72bd1461011b57806387201b41146101125780638814773214610109578063a817440414610100578063b3a34c4c146100f7578063e7acab24146100ee578063ed98a574146100e5578063f07ec373146100dc578063f47b7740146100d3578063fb0f3ee1146100ca5763fd9f1e10146100c257600080fd5b61000e61132d565b5061000e61102c565b5061000e610f8b565b5061000e610f46565b5061000e610eb5565b5061000e610e07565b5061000e610da3565b5061000e610d32565b5061000e610be3565b5061000e610b0f565b5061000e610994565b5061000e61092f565b5061000e61089e565b5061000e6101c1565b5061000e610199565b91908251928382526000905b8482106101815750601f8460209495601f199311610174575b0116010190565b600085828601015261016d565b90602090818082850101519082860101520190610154565b503461000e57600060031936011261000e57602080526707536561706f727460475260606020f35b503461000e57602060031936011261000e57600435600052600260205260806040600020546040519060ff81161515825260ff8160081c16151560208301526effffffffffffffffffffffffffffff8160101c16604083015260881c6060820152f35b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60a0810190811067ffffffffffffffff82111761027057604052565b610278610224565b604052565b60c0810190811067ffffffffffffffff82111761027057604052565b6020810190811067ffffffffffffffff82111761027057604052565b6040810190811067ffffffffffffffff82111761027057604052565b90601f601f19910116810190811067ffffffffffffffff82111761027057604052565b60405190610160820182811067ffffffffffffffff82111761027057604052565b6040519061032282610254565b565b60209067ffffffffffffffff811161033e575b60051b0190565b610346610224565b610337565b6001600160a01b0381160361000e57565b60a435906103228261034b565b35906103228261034b565b3590600682101561000e57565b92919261038d82610324565b60409461039c865192836102d1565b819584835260208093019160a080960285019481861161000e57925b8584106103c85750505050505050565b868483031261000e5784879184516103df81610254565b6103e887610374565b8152828701356103f78161034b565b83820152858701358682015260608088013590820152608080880135908201528152019301926103b8565b9080601f8301121561000e5781602061043d93359101610381565b90565b92919261044c82610324565b60409461045b865192836102d1565b819584835260208093019160c080960285019481861161000e57925b8584106104875750505050505050565b868483031261000e57848791845161049e8161027d565b6104a787610374565b8152828701356104b68161034b565b838201528587013586820152606080880135908201526080808801359082015260a080880135906104e68261034b565b820152815201930192610477565b9080601f8301121561000e5781602061043d93359101610440565b6004111561000e57565b35906103228261050f565b9190916101608184031261000e5761053a6102f4565b9261054482610369565b845261055260208301610369565b602085015267ffffffffffffffff90604083013582811161000e5781610579918501610422565b6040860152606083013591821161000e576105959183016104f4565b60608401526105a660808201610519565b608084015260a081013560a084015260c081013560c084015260e081013560e0840152610100808201359084015261012080820135908401526101408091013590830152565b35906effffffffffffffffffffffffffffff8216820361000e57565b92919267ffffffffffffffff8211610650575b604051916106336020601f19601f84011601846102d1565b82948184528183011161000e578281602093846000960137010152565b610658610224565b61061b565b9080601f8301121561000e5781602061043d93359101610608565b91909160a08184031261000e5761068d610315565b9267ffffffffffffffff823581811161000e57826106ac918501610524565b85526106ba602084016105ec565b60208601526106cb604084016105ec565b6040860152606083013581811161000e57826106e891850161065d565b6060860152608083013590811161000e57610703920161065d565b6080830152565b9080601f8301121561000e5781359061072282610324565b9261073060405194856102d1565b828452602092838086019160051b8301019280841161000e57848301915b84831061075e5750505050505090565b823567ffffffffffffffff811161000e57869161078084848094890101610678565b81520192019161074e565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600611156107f657565b6103226107bc565b608090805161080c816107ec565b8352816001600160a01b03918260208201511660208601526040810151604086015260608101516060860152015116910152565b90815180825260208080930193019160005b828110610860575050505090565b909192938260e0600192604088516108798382516107fe565b808501516001600160a01b031660a0840152015160c082015201950193929101610852565b50606060031936011261000e5767ffffffffffffffff60043581811161000e576108cc90369060040161070a565b9060243581811161000e576108e590369060040161078b565b60443592831161000e5761092b9361091161090761091795369060040161078b565b9490933691611bff565b90613e21565b604051918291602083526020830190610840565b0390f35b503461000e57600060031936011261000e57610949615017565b3360005260016020526020604060002060018154018091556040518181527f721c20121297512b72821b97f5326877ea8ecf4bb9948fea5bfcb6453074d37f833392a2604051908152f35b503461000e5760031960208136011261000e5760043567ffffffffffffffff811161000e576101608160040192823603011261000e576109d38261152d565b916109e06024830161152d565b906109ee6044840182611cfc565b6064850192916109fe8484611d50565b92909360848801610a0e90611dae565b95610a1891611d50565b969050610a236102f4565b6001600160a01b0390991689526001600160a01b031660208901523690610a4992610381565b60408701523690610a5992610440565b6060850152610a6b9060808501611db8565b60a482013560a084015260c482013560c084015260e482013560e08401526101048201356101008401526101248201356101208401526101408301526101440135610ab59161268a565b604051908152602090f35b9092916040820191604081528451809352606081019260208096019060005b818110610af95750505061043d9394818403910152610840565b8251151586529487019491870191600101610adf565b5060e060031936011261000e5767ffffffffffffffff60043581811161000e57610b3d90369060040161070a565b60243582811161000e57610b5590369060040161078b565b909160443584811161000e57610b6f90369060040161078b565b9060643595861161000e57610b8b610ba496369060040161078b565b929091610b9661035c565b9560c4359760843596611cc2565b9061092b60405192839283610ac0565b602060031982011261000e576004359067ffffffffffffffff821161000e57610bdf9160040161078b565b9091565b503461000e57610bf236610bb4565b610bfa615017565b60005b818110610c105760405160018152602090f35b80610c1e6001928486613f13565b610c2881806146ae565b610c318161152d565b91610c44610c3f3684610524565b614fa9565b91610c59836000526002602052604060002090565b610c6381856155a2565b50610c76610c72825460ff1690565b1590565b610c86575b505050505001610bfd565b7ffde361574a066b44b3b5fe98a87108b7565e327327954c4faeea56a4e6491a0a92610d2592610d01610d0793610cd6610ccf610cc86020968781019061158b565b3691610608565b898b615303565b60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b0161152d565b6040519384526001600160a01b039081169416929081906020820190565b0390a33880808080610c7b565b50604060031936011261000e5767ffffffffffffffff60043581811161000e57610d6090369060040161078b565b60249291923591821161000e5761092b92610d8d610d8561091794369060040161078b565b939092614750565b60405190610d9a82610299565b60008252613e21565b5060031960408136011261000e576004359067ffffffffffffffff821161000e57604090823603011261000e57610dfd610de16020926004016146e1565b60405190610dee82610299565b600082523391602435916141fd565b6040519015158152f35b5060031960808136011261000e576004359067ffffffffffffffff9081831161000e5760a090833603011261000e5760243590811161000e5761092b91610e55610e9692369060040161078b565b90606435610e628161034b565b6001600160a01b038116610ea85750610e90610e8433945b3690600401610678565b91604435933691611bff565b906141fd565b60405190151581529081906020820190565b610e84610e909194610e7a565b5060a060031936011261000e5767ffffffffffffffff60043581811161000e57610ee390369060040161078b565b9060243583811161000e57610efc90369060040161078b565b91909260443594851161000e57610f25610f1d610ba496369060040161078b565b929093614750565b9160405193610f3385610299565b6000855260843595339560643595612a0b565b503461000e57602060031936011261000e576020610f83600435610f698161034b565b6001600160a01b0316600052600160205260406000205490565b604051908152f35b503461000e57600060031936011261000e57610ff3610fa86127b4565b60405190610fb5826102b5565b600382527f312e3100000000000000000000000000000000000000000000000000000000006020830152604051928392606084526060840190610148565b9060208301526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660408301520390f35b5060031960208136011261000e5760043567ffffffffffffffff811161000e576102408160040192823603011261000e5761012435908160021c926001841193341585036112f85784936003821160028314916110d183600286117ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe870102018815926001820185028460011b880103998a92600360a088026024013593168a6115dc565b6110e38260051b6101c40135986107ec565b156111b5575050506111036110f78261152d565b6001600160a01b031690565b6001600160a01b0390811660248401351761118b5761115f60449461115a6111759761116b9461113560a4890161152d565b9060648901946111448661152d565b9060e48b01359360c48c01359333931691611dcf565b61152d565b91610204840190611537565b93909201356119df565b61117f6001600055565b60405160018152602090f35b60046040517f6ab37ce7000000000000000000000000000000000000000000000000000000008152fd5b9194509161121e6110f7606461122396611228996111d1611514565b8a819b996111df839b6107ec565b1561122d5750610d01916111f560a4850161152d565b61120086860161152d565b9060e48601359160c4870135916001600160a01b03339216906120c8565b611ac5565b6122c4565b611175565b611236816107ec565b6003810361127d57506112789161124f60a4850161152d565b61125a86860161152d565b9060e48601359160c4870135916001600160a01b03339216906121be565b610d01565b806112896004926107ec565b036112c3576112789161129b8861152d565b6112a686860161152d565b6044860135916001600160a01b03602488013592169033906120c8565b611278916112d08861152d565b6112db86860161152d565b6044860135916001600160a01b03602488013592169033906121be565b6040517fa61be9f0000000000000000000000000000000000000000000000000000000008152346004820152602490fd5b0390fd5b503461000e5761133c36610bb4565b611344615017565b60005b81811061135a5760405160018152602090f35b611365818385614fe2565b61136e8161152d565b60209061137c82840161152d565b6001600160a01b0391828116938433141580611508575b6114de576040956113a681880182611cfc565b6060808401926113b68486611d50565b90916080948a8689016113c890611dae565b976113d3908a611d50565b9a90506113de6102f4565b6001600160a01b03909c168c526001600160a01b03909116908b0152369061140592610381565b8c890152369061141492610440565b9086015284019061142491611db8565b60a0808201359084015260c0808201359084015260e08082013590840152610100808201359084015261012080820135908401526101409182840152013561146b9161268a565b93611480856000526002602052604060002090565b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000166101001790555193845216917f6bacc01dbe442496068f7d234edd811f1a5f833243e0aec824f86ab861f3c90d90602090a3600101611347565b60046040517f80ec7374000000000000000000000000000000000000000000000000000000008152fd5b50838316331415611393565b60405190611521826102b5565b60208083523683820137565b3561043d8161034b565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561000e570180359067ffffffffffffffff821161000e57602001918160061b3603831361000e57565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561000e570180359067ffffffffffffffff821161000e5760200191813603831361000e57565b9591906115e7615008565b6115fb610140880135610120890135615296565b50611604611927565b611622611615610200890189611537565b6101e08a013591506118f6565b7f00000000000000000000000000000000000000000000000000000000000000006080528160a0526060602460c037604060646101203760e06080908120610160526001610264359081016102a060059290921b918201526102c081019384526024906102e00137610160928460a0528560c052600060e05260005b8394610204358210156116fb5790604060a0600193602090818560061b6102840161010037838560061b6102840161012037019660e0608020885201968888528960c08201526101008360061b610284019101370193929361169e565b5090929350969590966001610204350160051b610160206060525b83610264358210156117495790604060a060019301958787528860c08201526101008360061b6102840191013701611716565b505093509490506103229391507f00000000000000000000000000000000000000000000000000000000000000006080528260a052606060c460c03760206101046101203760c0608020600052602060002060e05260016102643560051b610200015261022092836102643560051b0152606060c46102406102643560051b01376118ee610cc8608435936117f1856001600160a01b03166000526001602052604060002090565b547f00000000000000000000000000000000000000000000000000000000000000006080526040608460a03760605161010052846101205260a0610144610140376101e0526101809485608020956102643560051b0190868252336101a06102643560051b015260806101c06102643560051b01526101206101e06102643560051b01527f9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f3160a4359260a061026435026101e00190a360006060526118e56060820161115a6118bf8261152d565b966118cc6080860161152d565b906001600160a01b03809916906101608701358b61569d565b9581019061158b565b9216906147dc565b106118fd57565b60046040517f466aa616000000000000000000000000000000000000000000000000000000008152fd5b601861012435106102643560061b61026001610244351461024061022435146020600435141616161561195657565b60046040517f39f3e3fd000000000000000000000000000000000000000000000000000000008152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90156119b95790565b61043d611980565b91908110156119d2575b60061b0190565b6119da611980565b6119cb565b919234936000915b808310611a4257505050828211611a185781611a0291611e97565b808211611a0d575050565b610322910333611e97565b60046040517f1a783b8d000000000000000000000000000000000000000000000000000000008152fd5b909194611a508683856119c1565b90813590808211611a1857611a748260206001950135611a6f8161034b565b611e97565b03950191906119e7565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818110611ab9570390565b611ac1611a7e565b0390565b90939291908115611b85579333611ade60a0830161152d565b60e08301359260c08101355b61118b578460051b6101e40335946102008201611b078184611537565b93905060005b848110611b24575050505050956103229596611f2c565b8989858e611b3c85611b368989611537565b906119c1565b803592611b6a575b91611b649391611b5d6110f7602060019998960161152d565b908c611f2c565b01611b0d565b92909493919b8c611b7a91611aae565b9b9193949092611b44565b933394611b918261152d565b6040830135926020810135611aea565b81601f8201121561000e57803591611bb883610324565b92611bc660405194856102d1565b808452602092838086019260051b82010192831161000e578301905b828210611bf0575050505090565b81358152908301908301611be2565b909291611c0b84610324565b91604094611c1b865194856102d1565b839581855260208095019160051b83019380851161000e5783925b858410611c465750505050505050565b67ffffffffffffffff90843582811161000e5786019060a08285031261000e578451611c7181610254565b8235815289830135600281101561000e578a82015285830135868201526060808401359082015260808084013594851161000e57611cb3868c96879601611ba1565b90820152815201930192611c36565b90611cf090610bdf9a99989796959493986001600160a01b03811615600014611cf6575033985b3691611bff565b90612a0b565b98611ce9565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561000e570180359067ffffffffffffffff821161000e576020019160a082023603831361000e57565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561000e570180359067ffffffffffffffff821161000e576020019160c082023603831361000e57565b600411156107f657565b3561043d8161050f565b6004821015611dc45752565b611dcc6107bc565b52565b949290959391841515600014611e3b5761032296604051967f4ce34aa2000000000000000000000000000000000000000000000000000000008852602060048901526001602489015260448801526064870152608486015260a485015260c484015260e4830152612451565b9291946002919450611e4c816107ec565b03611e8b57600103611e61576103229361504d565b60046040517fefcc00b1000000000000000000000000000000000000000000000000000000008152fd5b9291906103229461515b565b90611ea181611efb565b600080808084865af115611eb3575050565b60449250611ebf612895565b6001600160a01b03604051927f470c7c1d0000000000000000000000000000000000000000000000000000000084521660048301526024820152fd5b15611f0257565b60046040517f91b3e514000000000000000000000000000000000000000000000000000000008152fd5b929193949094611f3b83611efb565b611f4581836122b1565b806120ba575050604051926000947f23b872dd00000000000000000000000000000000000000000000000000000000865280600452816024528260445260208660648180885af1803d15601f3d1160018a51141617163d1515811615611fb4575b505050505050604052606052565b80863b151516611fa657908795969115611ff457602486887f5f15d672000000000000000000000000000000000000000000000000000000008252600452fd5b1561202e57506084947f98891923000000000000000000000000000000000000000000000000000000008552600452602452604452606452fd5b3d61206d575b5060a4947ff486bc8700000000000000000000000000000000000000000000000000000000855260045260245260445281606452608452fd5b601f3d0160051c9060051c9080600302918082116120a1575b505060205a9101106120985785612034565b833d81803e3d90fd5b8080600392028380020360091c92030201018680612086565b9061032295929493916125c0565b959092949391936120d981836122b1565b806120f0575050600103611e61576103229361504d565b9060649593916000979593975060208251146000146121ab5760c0906001906040845260208401527f4ce34aa20000000000000000000000000000000000000000000000000000000060408401526020604484015280888401525b02019360027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc48601527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe48501526004840152602483015260448201520152565b5060c0868201600181510180915261214b565b9590919293946121cd86611efb565b6121d781836122b1565b806121e75750506103229461515b565b906064959694939291602082511460001461229e5760c0906001906040845260208401527f4ce34aa20000000000000000000000000000000000000000000000000000000060408401526020604484015280888401525b02019360037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc48601527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe48501526004840152602483015260448201520152565b5060c0868201600181510180915261223e565b906020820151036122bf5750565b610322905b60408082510361244d57602082015160c06064840151026044019180519260206001600160a01b036000928184927f00000000000000000000000000000000000000000000000000000000000000001674ff00000000000000000000000000000000000000001783528684527f000000000000000000000000000000000000000000000000000000000000000086526055600b201696855281805284880182885af190519015612402577fffffffff000000000000000000000000000000000000000000000000000000007f4ce34aa2000000000000000000000000000000000000000000000000000000009116036123c05750505060209052565b517f1cf99b2600000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03919091166024820152604490fd5b611329848361240f612895565b517fd13d53d40000000000000000000000000000000000000000000000000000000081526001600160a01b0390911660048201529081906024820190565b5050565b6040519160206001600160a01b036101046000938285937f00000000000000000000000000000000000000000000000000000000000000001674ff00000000000000000000000000000000000000001784528685527f00000000000000000000000000000000000000000000000000000000000000006040526055600b20169660405282805282875af190519015612574577fffffffff000000000000000000000000000000000000000000000000000000007f4ce34aa200000000000000000000000000000000000000000000000000000000911603612530575050565b6040517f1cf99b2600000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03919091166024820152604490fd5b61132983612580612895565b6040517fd13d53d40000000000000000000000000000000000000000000000000000000081526001600160a01b0390911660048201529081906024820190565b9060649492939160208251146000146126775760c0906001906040845260208401527f4ce34aa20000000000000000000000000000000000000000000000000000000060408401526020604484015280878401525b02019260017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc48501527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe484015260048301526024820152600060448201520152565b5060c08582016001815101809152612615565b91909161014081018051917f0000000000000000000000000000000000000000000000000000000000000000604051604083018051928351926020809501906000915b868684106127915750505050506040519160051b8220917f00000000000000000000000000000000000000000000000000000000000000009093606086019481865101906000915b8a831061276d575050505050601f198660051b604051209401978851907f00000000000000000000000000000000000000000000000000000000000000008a5282519383528451958552865261018089209852525252565b838082601f19600194510180519089815260e0812087525201920192019190612715565b8082601f19600194510180519088815260c08120875252019201920191906126cd565b467f0000000000000000000000000000000000000000000000000000000000000000036127ff577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f000000000000000000000000000000000000000000000000000000000000000082527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a0815261288f8161027d565b51902090565b3d61289c57565b601f3d0160051c60405160051c9080600302918082116128cf575b505060205a9101106128c557565b3d6000803e3d6000fd5b8080600392028380020360091c920302010138806128b7565b919082604091031261000e576040516040810181811067ffffffffffffffff821117612922575b6040526020808294803584520135910152565b61292a610224565b61290f565b92919261293b82610324565b60409261294a845192836102d1565b819581835260208093019160061b84019381851161000e57915b84831061297357505050505050565b83869161298084866128e8565b815201920191612964565b9291909261299884610324565b916129a660405193846102d1565b829480845260208094019060051b83019282841161000e5780915b8483106129d057505050505050565b823567ffffffffffffffff811161000e57820184601f8201121561000e578691612a00868385809535910161292f565b8152019201916129c1565b96989792612a268a612a359695612a2d95949998998b612c40565b369161298b565b93369161298b565b908251825191612a4d612a48848461314b565b61366d565b9760009586915b848310612b47575050506000935b838510612abf57505050505080612ab4575b50825115612a8a5782612a8691613b15565b9190565b60046040517fd5da9a1b000000000000000000000000000000000000000000000000000000008152fd5b835103835238612a74565b909192939488612ada84612ad38986612c1e565b518a613745565b8051608001516001600160a01b03166001600160a01b03612b086110f760208501516001600160a01b031690565b911603612b225750506001809101955b0193929190612a62565b8791612b4191612b3a85896001979c01038093612c1e565b528b612c1e565b50612b18565b9091968a612b6583612b5e8b879b98999a9b612c1e565b518c6136c9565b8051608001516001600160a01b03166001600160a01b03612b936110f760208501516001600160a01b031690565b911603612bb05750506001809101975b0191909594939295612a54565b8991612bcd91612bc6856001969d038093612c1e565b528d612c1e565b50612ba3565b90612bdd82610324565b612bea60405191826102d1565b828152601f19612bfa8294610324565b0190602036910137565b602090805115612c12570190565b612c1a611980565b0190565b6020918151811015612c33575b60051b010190565b612c3b611980565b612c2b565b93929091612c4c615008565b845192612c5884612bd3565b9160008352601d604560003560e01c061160011b9060005b868110612d30575050600314612d0657612c8a9086613266565b60005b838110612c9c57505050509050565b80612ca960019284612c1e565b5115612d0157612cfb612cbc8289612c1e565b5151612cc88386612c1e565b519086612cdc82516001600160a01b031690565b60208301516001600160a01b03169060606040850151940151946145e5565b01612c8d565b612cfb565b60046040517f12d3f5a3000000000000000000000000000000000000000000000000000000008152fd5b612d3a818a612c1e565b51918015612ebf57612d4d868685614cb3565b9290916001850189528215612eab57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff91612d89868b612c1e565b52019380519260a084015193604060c08201519101518051908560005b838110612e405750505050606080935101519485519560005b878110612dd85750505050505050506001905b01612c70565b808760a0612de860019486612c1e565b5188612e2489898d6080860197612e01895187836131fa565b918701958651908a518214600014612e30575050508085525b80885284516131a0565b90520151905201612dbf565b612e39926131fa565b8552612e1a565b612e4a8184612c1e565b519b8c5115179b86868b60808401938451612e669085896131fa565b60608192019586519881518a1460001499612e919760019b612e9b575050508187525b52845161315f565b9052018690612da6565b612ea4926131fa565b8752612e89565b509360019392506000915060200152612dd2565b91906000602060019301528181018652612dd2565b612edc615008565b805192612ee884612bd3565b92600091828552601d6045843560e01c061160011b90835b878110612f90575050600314612d0657612f1a9083613266565b838110612f275750505050565b80612f3460019285612c1e565b5115612f8b57612f85612f478285612c1e565b5151612f538387612c1e565b5190612f6681516001600160a01b031690565b60208201516001600160a01b0316906060604084015193015193614513565b01612f1a565b612f85565b612f9a8187612c1e565b51918581156130fb5750612faf888685614ee0565b929091600185018b528883156130e95750907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff91612fed868d612c1e565b52019380519260a084015191604060c0860151950151805190858c5b83811061308f5750505050606090510151938451948a5b86811061303857505050505050506001905b01612f00565b8061304560019284612c1e565b5160a0608082019189613083888b61305f87518d866131fa565b60608601948d8651908a518214600014612e305750505080855280885284516131a0565b90520151905201613020565b6130998184612c1e565b519b8c5115179b868a89608084019384516130b59085896131fa565b60608192019586519881518a14600014996130df9760019b612e9b5750505081875252845161315f565b9052018690613009565b92505093600193925060200152613032565b6020600193929401528181018852613032565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482118115151661313f570290565b613147611a7e565b0290565b81198111613157570190565b612c1a611a7e565b909283820361316e5750505090565b82939161318a613196946131909303954203918287039061310e565b9261310e565b9061314b565b9081049015150290565b90928382036131af5750505090565b926131906131cd9261318a856001969703964203918288039061310e565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830104019015150290565b9190918281146132435782818309613219576132159161310e565b0490565b7fc63cf0890000000000000000000000000000000000000000000000000000000060005260046000fd5b50905090565b600211156107f657565b5161043d816107ec565b611dcc826107ec565b815181519260005b8281106133a45750505060005b82811061328757505050565b6132918183612c1e565b516132c56132b160208301516effffffffffffffffffffffffffffff1690565b6effffffffffffffffffffffffffffff1690565b1561339b5751606081018051519060005b828110613354575050506040809101908151519160005b83811061330257505050506001905b0161327b565b61331f613319613313838551612c1e565b51613253565b60031090565b61332b576001016132ed565b600483517fa6cfc673000000000000000000000000000000000000000000000000000000008152fd5b613365613319613313838551612c1e565b613371576001016132d6565b60046040517fff75a340000000000000000000000000000000000000000000000000000000008152fd5b506001906132fc565b6133ae8183612c1e565b5180519086821015613565576020916133e56132b1846133ce848b612c1e565b5101516effffffffffffffffffffffffffffff1690565b1561355a576133f49087612c1e565b515191604092838301519183015161340b81613249565b61341481613249565b6134e55783015180518210156134bc579061342e91612c1e565b5191600383519361343e856107ec565b84906134558482019160048351981485039061325d565b606085015190525b11156134935750906001929181613478575b50505b0161326e565b61348c91608060608301519201519161358f565b388061346f565b600490517f94eb6af6000000000000000000000000000000000000000000000000000000008152fd5b600484517fbfb3f8ce000000000000000000000000000000000000000000000000000000008152fd5b929060608094015180518210156135315760039161350291612c1e565b5193845194613510866107ec565b85916135278583019260048451991486039061325d565b850151905261345d565b600483517f6088d7de000000000000000000000000000000000000000000000000000000008152fd5b505050600190613472565b60046040517f869586c4000000000000000000000000000000000000000000000000000000008152fd5b91909160009081526020808220928181019282825192600593841b0101915b8285106135eb575050505050036135c157565b60046040517f09bde339000000000000000000000000000000000000000000000000000000008152fd5b8451808711821b968752958418959095526040812094938301936135ae565b604051906060820182811067ffffffffffffffff821117613660575b8060405260408361363683610254565b6000928381528360808301528360a08301528360c08301528360e083015281528260208201520152565b613668610224565b613626565b9061367782610324565b61368460405191826102d1565b828152601f196136948294610324565b019060005b8281106136a557505050565b6020906136b061360a565b82828501015201613699565b906002821015611dc45752565b9092916136d461360a565b93805115613714576136f6926001600160a01b038693166080845101526137e9565b81516060810151156137055750565b60806000918260208601520152565b60246040517f375c24c100000000000000000000000000000000000000000000000000000000815260006004820152fd5b92919061375061360a565b9381511561378d576137639185916139aa565b60208301903382526040840152825190606082015115613781575050565b60009182608092520152565b60246040517f375c24c100000000000000000000000000000000000000000000000000000000815260016004820152fd5b507f7fda72790000000000000000000000000000000000000000000000000000000060005260046000fd5b92919260208201906020825151825181101561399d575b60051b82010151928351926020604085015181835101518151811015613990575b60051b01015160009460208697015161397a575b9061012060609260408b5193805185526020810151602086015201516040840152805160208c0152015160408a01522091805160051b01905b8181106138c1575050505060608293945101526138885750565b60011461389757610322611a7e565b7f91b3e5140000000000000000000000000000000000000000000000000000000060005260046000fd5b60209095949501906020825151855181101561396d575b60051b85010151602081015115613964575160606020604083015181865101518151811015613957575b60051b01015196818801519081158a8381011060011b17179801966000828201522084149060408a0151610120820151149060208b015190511416161561394a575b9061386e565b6139526137be565b613944565b61395f6137be565b613902565b50949394613944565b6139756137be565b6138d8565b6060820180516000909152801597509550613835565b6139986137be565b613821565b6139a56137be565b613800565b9291602080830194855151918151831015613b08575b80600593841b8301015194606093828588510151818b5101518151811015613afb575b831b010151926000968188990151613ae6575b51948451865281850151828701526040850151604087015260a0809501519a608087019b8c52878720948051851b01905b818110613a4257505050505050508394955001526138885750565b83909a999a01908c848351518551811015613ad9575b871b850101518581015115613acf578a869151015181855101518151811015613ac2575b881b0101518a81019b8d8d518091019e8f9115911060011b17179c9b60009052888b822089149251910151141615613ab5575b90613a27565b613abd6137be565b613aaf565b613aca6137be565b613a7c565b5050999899613aaf565b613ae16137be565b613a58565b848701805160009091528015995097506139f6565b613b036137be565b6139e3565b613b106137be565b6139c0565b908151613b2181612bd3565b9260005b828110613be5575050503490613b39611514565b9080519060005b828110613b7457505050613b53906122c4565b80613b64575b5061043d6001600055565b613b6e9033611e97565b38613b59565b613b7e8183612c1e565b518051908151613b8d816107ec565b613b96816107ec565b15613bca575b8560019392826040613bbb6020613bc49601516001600160a01b031690565b91015191613cae565b01613b40565b9560608293920181815111611a185751900395909190613b9c565b613bef8183612c1e565b51613c0f6132b160208301516effffffffffffffffffffffffffffff1690565b15613ca557613c27613c218388612c1e565b60019052565b606080915101519081519160005b838110613c4a57505050506001905b01613b25565b82613c558284612c1e565b51015180613c665750600101613c35565b6040517fa5f542080000000000000000000000000000000000000000000000000000000081526004810187905260248101929092526044820152606490fd5b50600190613c44565b9290918351613cbc816107ec565b613cc5816107ec565b613d1a57505050613ce36110f760208301516001600160a01b031690565b6001600160a01b03604083015191161761118b57806060613d1160806103229401516001600160a01b031690565b91015190611e97565b90919260018151613d2a816107ec565b613d33816107ec565b03613d8357604081015161118b5761032293613d5960208301516001600160a01b031690565b906001600160a01b036060613d7860808601516001600160a01b031690565b940151931691611f2c565b9260028451613d91816107ec565b613d9a816107ec565b03613de05783613db760206103229601516001600160a01b031690565b60808201516001600160a01b0316926001600160a01b03606060408501519401519416916120c8565b83613df860206103229601516001600160a01b031690565b60808201516001600160a01b0316926001600160a01b03606060408501519401519416916121be565b90613e33909493929482519083612ed4565b613e3c8261366d565b9160009485915b808310613e705750505090613e619184829495613e65575b50613b15565b5090565b825103825238613e5b565b909195613e7e878385613f13565b613ea4613e8b8280611537565b90613e9b60209485810190611537565b92909189613f6c565b906001600160a01b03613ed96110f7613ec960808651016001600160a01b0390511690565b938501516001600160a01b031690565b911603613ef057506001809101965b019190613e43565b96613f0d8298600193830390613f06828a612c1e565b5287612c1e565b50613ee8565b9190811015613f54575b60051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18136030182121561000e570190565b613f5c611980565b613f1d565b61043d9036906128e8565b92909391613f7861360a565b948115801561415e575b61413457613f8e61360a565b613fa381613f9d36888861292f565b886139aa565b5191613fba87613fb436848661292f565b886137e9565b613fc48751613253565b835190613fd0826107ec565b613fd9826107ec565b613fe2816107ec565b148015906140fc575b80156140e9575b6140bf5761043d9561406f95608095896060948588019687518784510151106000146140825750505061403161402c8593614057936119b0565b613f61565b60208361404a8d828a5191510151900396845190612c1e565b5151015191015190612c1e565b5101528651015190525b01516001600160a01b031690565b6080835101906001600160a01b03169052565b86979694506140b1935061404a856140a161402c6020956040956119b0565b9451015188518551910397612c1e565b510152519086510152614061565b60046040517f09cfb455000000000000000000000000000000000000000000000000000000008152fd5b5060408751015160408401511415613ff2565b508651602001516001600160a01b03166001600160a01b0361412b6110f760208701516001600160a01b031690565b91161415613feb565b60046040517f98e9db6e000000000000000000000000000000000000000000000000000000008152fd5b508315613f82565b6040519061417382610254565b604051608083610160830167ffffffffffffffff8111848210176141f0575b6040526000808452806020850152606093846040820152848082015281848201528160a08201528160c08201528160e08201528161010082015281610120820152816101408201528252806020830152604082015282808201520152565b6141f8610224565b614192565b909291614208615017565b600260005561421784836148c0565b9490919260405195614228876102b5565b6001875260005b6020808210156142515790602091614245614166565b90828b0101520161422f565b505061428583959761428061429e9a61428e97998351156142ba575b60208401528251156142ad575b82613266565b612c04565b515195866142c7565b81516001600160a01b0316612cdc565b6142a86001600055565b600190565b6142b5611980565b61427a565b6142c2611980565b61426d565b939192909360a093848201519360c0830151966142e2611514565b96604092838601908151519160005b8381106143d7575050505034986060809601978851519860005b8a8110614338575050505050505050505050614326906122c4565b8061432e5750565b6103229033611e97565b614343818351612c1e565b51898101805161435d87878d8c60808801958651906144a1565b8092528783015190528151614371816107ec565b61437a816107ec565b15614397575b50906143918d8c6001943390613cae565b0161430b565b90919e9d8082116143ae579d9e9d039c908a614380565b600489517f1a783b8d000000000000000000000000000000000000000000000000000000008152fd5b6143e2818351612c1e565b5180516143ee816107ec565b6143f7816107ec565b15614441579061443b8d8f93868f8d6144236001988e936060870193845195608089019687519061446a565b9052528c610120613bbb82516001600160a01b031690565b016142f1565b600488517f12d3f5a3000000000000000000000000000000000000000000000000000000008152fd5b90939084810361448057505061043d93506131fa565b938361449561043d979661449b9496866131fa565b936131fa565b9061315f565b9093908481036144b757505061043d93506131fa565b938361449561043d97966144cc9496866131fa565b906131a0565b90815180825260208080930193019160005b8281106144f2575050505090565b909192938260a08261450760019489516107fe565b019501939291016144e4565b91939290936040805193608091828601918652602090600082880152838188015285518093528160a088019601936000915b84831061459a5750505050505091614595827f9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31948380950360608501526001600160a01b038091169716956144d2565b0390a3565b90919293949684836001928a5180516145b2816107ec565b8252808401516001600160a01b031684830152858101518683015260609081015190820152019801959493019190614545565b92909493916040918251946080918287019187526001600160a01b0394856020921682890152838189015286518093528160a089019701936000915b84831061466a57505050505050828285949361459593867f9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f319896036060870152169716956144d2565b90919293949784836001928b518051614682816107ec565b8252808401518c1684830152858101518683015260609081015190820152019901959493019190614621565b9035907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea18136030182121561000e570190565b6146e9614166565b506147336147056146fa83806146ae565b92602081019061158b565b61471c6040519461471586610254565b3690610524565b845260016020850152600160408501523691610608565b606082015260405161474481610299565b60008152608082015290565b61475982610324565b9161476760405193846102d1565b808352601f1961477682610324565b0160005b8181106147c557505060005b8181106147935750505090565b806147a96147a46001938587613f13565b6146e1565b6147b38287612c1e565b526147be8186612c1e565b5001614786565b6020906147d0614166565b8282880101520161477a565b929190836000526002602052604060002091825460ff8160081c1661487b576effffffffffffffffffffffffffffff8160101c1661484a579460ff7101000000000000000000000000000001000195961615614839575b50505055565b61484292615303565b388080614833565b602486604051907fee9e0e630000000000000000000000000000000000000000000000000000000082526004820152fd5b602486604051907f1a5155740000000000000000000000000000000000000000000000000000000082526004820152fd5b90805b6148b7575090565b809106806148af565b90918151926148db610c7260a086015160c087015190615296565b614ca7576148fe6132b160208501516effffffffffffffffffffffffffffff1690565b9361491e6132b160408601516effffffffffffffffffffffffffffff1690565b948581118015614c9f575b614c755785811080614c5d575b614c335761498261494683614fa9565b9360e0840151608085015161495a81611da4565b85516001600160a01b0316918761497b60208901516001600160a01b031690565b948b615cc1565b614996836000526002602052604060002090565b916149a4610c7284866155a2565b614c23578254958460ff881615614bfc575b5050506effffffffffffffffffffffffffffff90818660101c169560881c96871515600014614b7f5760018103614b4757505085945b856149f7888361314b565b11614b3d575b86614a079161314b565b8082871183831117614ad6575b5090614a8f818493614a4e614ad19660017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b84547fffffffffffffffffffffffffffffff00000000000000000000000000000000ff16911660101b70ffffffffffffffffffffffffffffff000016178355565b815470ffffffffffffffffffffffffffffffffff1690861660881b7fffffffffffffffffffffffffffffff000000000000000000000000000000000016179055565b929190565b9690614ae987614aef92989594986148ac565b826148ac565b80150180809204970492049480861181841117614b0e57909138614a14565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80860396506149fd565b959096868103614b58575b506149ec565b614b7281614b6c89614b78959b9a9b61310e565b9861310e565b9761310e565b9438614b52565b9550955090614ad191614bb78260017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b81547fffffffffffffffffffffffffffffff00000000000000000000000000000000ff1687821660101b70ffffffffffffffffffffffffffffff000016178255614a8f565b6060614c12614c1b94516001600160a01b031690565b92015191615303565b3880846149b6565b5050509150915090600090600090565b60046040517fa11b63ff000000000000000000000000000000000000000000000000000000008152fd5b5060016080830151614c6e81611da4565b1615614936565b60046040517f5a052b32000000000000000000000000000000000000000000000000000000008152fd5b508015614929565b50600092508291508190565b919290928251614ccf610c7260a083015160c0840151906152df565b614ed057614cf26132b160208601516effffffffffffffffffffffffffffff1690565b614d116132b160408701516effffffffffffffffffffffffffffff1690565b958682118015614ec8575b614c755786821080614eb0575b614c3357614d7d90614d3a84614fa9565b9460e0850151608086015190614d4f82611da4565b87614d6188516001600160a01b031690565b93614d7660208a01516001600160a01b031690565b958c615da2565b614d91836000526002602052604060002090565b91614d9f610c728486615645565b614c23578254958460ff881615614e92575b5050506effffffffffffffffffffffffffffff90818660101c169560881c96871515600014614b7f5760018103614e6657505085945b85614df2888361314b565b11614e5c575b86614e029161314b565b8082871183821117614e48575090614a8f818493614a4e614ad19660017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b969050614aef614ae98789989594986148ac565b8086039650614df8565b959096868103614e77575b50614de7565b614b7281614b6c89614e8b959b9a9b61310e565b9438614e71565b6060614c12614ea894516001600160a01b031690565b388084614db1565b5060016080840151614ec181611da4565b1615614d29565b508115614d1c565b5050915050600090600090600090565b919290928251614efc610c7260a083015160c084015190615296565b614ed057614f1f6132b160208601516effffffffffffffffffffffffffffff1690565b614f3e6132b160408701516effffffffffffffffffffffffffffff1690565b958682118015614fa1575b614c755786821080614f89575b614c3357614f6790614d3a84614fa9565b614f7b836000526002602052604060002090565b91614d9f610c7284866155a2565b5060016080840151614f9a81611da4565b1615614f56565b508115614f49565b61043d90614fc2606082015151610140830151906118f6565b80516001600160a01b03166000908152600160205260409020549061268a565b909161043d92811015614ffb575b60051b8101906146ae565b615003611980565b614ff0565b615010615017565b6002600055565b60016000540361502357565b60046040517f7fa8a987000000000000000000000000000000000000000000000000000000008152fd5b9092813b1561512d57604051926000947f23b872dd000000000000000000000000000000000000000000000000000000008652806004528160245282604452858060648180885af1156150a65750505050604052606052565b8593943d6150e9575b5060a4947ff486bc870000000000000000000000000000000000000000000000000000000085526004526024526044526064526001608452fd5b601f3d0160051c9060051c908060030291808211615114575b505060205a91011061209857856150af565b8080600392028380020360091c92030201018680615102565b507f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b929093833b1561526857604051936080519160a0519360c051956000987ff242432a000000000000000000000000000000000000000000000000000000008a528060045281602452826044528360645260a06084528960a452898060c48180895af1156151d857505050505060805260a05260c052604052606052565b89949550883d61521b575b5060a4957ff486bc87000000000000000000000000000000000000000000000000000000008652600452602452604452606452608452fd5b601f3d0160051c9060051c90806003029180821161524f575b505060205a91011061524657866151e3565b843d81803e3d90fd5b8080600392028380020360091c92030201018780615234565b837f5f15d6720000000000000000000000000000000000000000000000000000000060005260045260246000fd5b42109081156152d4575b506152aa57600190565b60046040517f6f7eac26000000000000000000000000000000000000000000000000000000008152fd5b9050421015386152a0565b42109081156152f8575b506152f357600190565b600090565b9050421015386152e9565b9091336001600160a01b0383161461559d5761531d6127b4565b926000937f190100000000000000000000000000000000000000000000000000000000000085526002526022526042832090836022528380528392815191601f198101805184604103918860018411938415615532575b508514851515169788156153c3575b5050505050505050156153935750565b60049061539e612895565b7f4f7fb80d000000000000000000000000000000000000000000000000000000008152fd5b909192939495969750604082527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc8501937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0855196019660208b60648a519b7f1626ba7e000000000000000000000000000000000000000000000000000000009d8e8b528c520188845afa998a615469575b505050505252523880808080808080615383565b8b51036154765780615455565b908a913b61550a576154e257640101000000821a156154b757807f815e1d640000000000000000000000000000000000000000000000000000000060049252fd5b6024917f1f003d0a000000000000000000000000000000000000000000000000000000008252600452fd5b807f8baa579f0000000000000000000000000000000000000000000000000000000060049252fd5b6004827f4f7fb80d000000000000000000000000000000000000000000000000000000008152fd5b9850506040840180519060608601518b1a99615569575b89865288835260208b60808560015afa5083835287865252885138615374565b9850601b8160ff1c01987f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82168152615549565b505050565b905460ff8160081c16615614576effffffffffffffffffffffffffffff8160101c1690816155d3575b505050600190565b60881c11156155e35780806155cb565b602490604051907f10fda3e10000000000000000000000000000000000000000000000000000000082526004820152fd5b602482604051907f1a5155740000000000000000000000000000000000000000000000000000000082526004820152fd5b906000905460ff8160081c16615694576effffffffffffffffffffffffffffff8160101c16908161567a575b50505050600190565b60881c111561568a578080615671565b6155e35750600090565b50905050600090565b90929160019060048110156156fd575b11806156ea575b806156d7575b6156c5575b50505050565b6156ce9361570a565b388080806156bf565b506001600160a01b0382163314156156ba565b506001600160a01b0384163314156156b4565b6157056107bc565b6156ad565b6000919290829161032295604051906001600160a01b0360208301937f0e1d31dc00000000000000000000000000000000000000000000000000000000855288602485015233604485015216606483015260848201526084815261576d8161027d565b51915afa615e78565b90815180825260208080930193019160005b828110615796575050505090565b909192938260a0600192875180516157ad816107ec565b8252808401516001600160a01b03168483015260408082015190830152606080820151908301526080908101519082015201950193929101615788565b90815180825260208080930193019160005b82811061580a575050505090565b909192938260c060019287518051615821816107ec565b8252808401516001600160a01b039081168584015260408083015190840152606080830151908401526080808301519084015260a0918201511690820152019501939291016157fc565b906004821015611dc45752565b6060519081815260208091019160809160005b828110615899575050505090565b83518552938101939281019260010161588b565b90815180825260208080930193019160005b8281106158cd575050505090565b8351855293810193928101926001016158bf565b90815180825260208092019182818360051b85019501936000915b84831061590c5750505050505090565b909192939495848061595e83856001950387528a518051825261593584820151858401906136bc565b60408082015190830152606080820151908301526080809101519160a0809282015201906158ad565b98019301930191949392906158fc565b92615b02906001600160a01b0361043d9694615b0f94875216602086015260a06040860152805160a080870152610140906159b482880182516001600160a01b03169052565b6080615af1615a286159f38a6159dc6020870151610160809301906001600160a01b03169052565b6040860151906101808d01526102a08c0190615776565b60608501517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec08c8303016101a08d01526157ea565b615a3a838501516101c08c019061586b565b60a08401516101e08b015260c08401516102008b015260e08401516102208b015261010094858501516102408c015261012094858101516102608d015201516102808b0152615aa1602087015160c08c01906effffffffffffffffffffffffffffff169052565b60408601516effffffffffffffffffffffffffffff1660e08b015260608601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6095868c840301908c0152610148565b930151918784030190870152610148565b8381036060850152615878565b9160808184039101526158e1565b939061043d95936001600160a01b03615b0f94615cb393885216602087015260a06040870152805160a08088015261014090615b6482890182516001600160a01b03169052565b6080615ca2615bd8615ba38b6020860151615b8d61016091828401906001600160a01b03169052565b61018060408801519201526102a08d0190615776565b60608501518c82037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec0016101a08e01526157ea565b615bea838501516101c08d019061586b565b60a08401516101e08c015260c08401516102008c015260e08401516102208c015261010094858501516102408d0152610120948c6102608783015191015201516102808c0152615c52602087015160c08d01906effffffffffffffffffffffffffffff169052565b60408601516effffffffffffffffffffffffffffff1660e08c015260608601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6095868d840301908d0152610148565b930151918884030190880152610148565b9084820360608601526158ad565b909591929493600190615cd381611da4565b1180615d8f575b80615d7c575b615ced575b505050505050565b6080810151511580615d73575b15615d155750615d0a945061570a565b388080808080615ce5565b6000935083929450615d6061576d615d6e9760405192839160208301957f33131570000000000000000000000000000000000000000000000000000000008752338b6024860161596e565b03601f1981018352826102d1565b615d0a565b50855115615cfa565b506001600160a01b038416331415615ce0565b506001600160a01b038216331415615cda565b919692939594600190615db481611da4565b1180615e65575b80615e52575b615dcf575b50505050505050565b6080820151511580615e49575b15615df9575050615ded945061570a565b38808080808080615dc6565b600094508493955061576d615e4497615d6060405193849260208401967f33131570000000000000000000000000000000000000000000000000000000008852338c60248701615b1d565b615ded565b50805115615ddc565b506001600160a01b038516331415615dc1565b506001600160a01b038316331415615dbb565b15615f0f577f0e1d31dc000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000600060203d14615f04575b1603615ed35750565b602490604051907ffb5014fc0000000000000000000000000000000000000000000000000000000082526004820152fd5b602081803e51615eca565b602490615f1a612895565b604051907ffb5014fc0000000000000000000000000000000000000000000000000000000082526004820152fdfea26469706673582212200d53e9d4f26a00cc6af37b012c26f8d770777dfea74c99c52ea7d855f909a12a64736f6c634300080e003300000000000000000000000000000000f9490004c11cef243f5400493c00ad63000000000000000000000000000000000000000000 +``` + +3. Validate deployments were successful by checking that `Seaport` is returned by `seth --to-ascii $(seth call 0x00000000006c3852cbEf3e08E8dF289169EdE581 'name()')` + +## Verifying Seaport and ConduitController +After `Seaport` and `ConduitController` are deployed, they are verified as follows: + +1. Ensure that `EXPLORER_API_KEY` and `NETWORK_RPC` are set in `.env` appropriatly. +2. Verify `ConduitController` by calling: + +``` +npx hardhat verify --network verificationNetwork "0x00000000F9490004C11Cef243f5400493c00Ad63" +``` + +3. Verify `Seaport` by calling: + +``` +npx hardhat verify --network verificationNetwork "0x00000000006c3852cbEf3e08E8dF289169EdE581" "0x00000000F9490004C11Cef243f5400493c00Ad63" +``` + diff --git a/docs/FunctionSignatures.md b/docs/FunctionSignatures.md new file mode 100644 index 000000000..fbf79c87a --- /dev/null +++ b/docs/FunctionSignatures.md @@ -0,0 +1,13 @@ +# Function Signatures + +0xfb0f3ee1 = fulfillBasicOrder\ +0xb3a34c4c = fulfillOrder\ +0xe7acab24 = fulfillAdvancedOrder\ +0xa8174404 = matchOrders\ +0x55944a42 = matchAdvancedOrders\ +0xed98a574 = fulfillAvailableOrders\ +0x87201b41 = fulfillAvailableAdvancedOrders + +0xfd9f1e10 = cancel\ +0x88147732 = validate\ +0x5b34b966 = incrementCounter diff --git a/hardhat-coverage.config.ts b/hardhat-coverage.config.ts index bea7c4d82..b676a36d1 100644 --- a/hardhat-coverage.config.ts +++ b/hardhat-coverage.config.ts @@ -1,13 +1,11 @@ -import * as dotenv from "dotenv"; +import type { HardhatUserConfig } from "hardhat/config"; -import { HardhatUserConfig, task } from "hardhat/config"; +import "dotenv/config"; import "@nomiclabs/hardhat-waffle"; import "@typechain/hardhat"; import "hardhat-gas-reporter"; import "solidity-coverage"; -dotenv.config(); - // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more diff --git a/hardhat-reference-coverage.config.ts b/hardhat-reference-coverage.config.ts index 1b4b935a9..d0bf43af5 100644 --- a/hardhat-reference-coverage.config.ts +++ b/hardhat-reference-coverage.config.ts @@ -1,13 +1,11 @@ -import * as dotenv from "dotenv"; +import type { HardhatUserConfig } from "hardhat/config"; -import { HardhatUserConfig, task } from "hardhat/config"; +import "dotenv/config"; import "@nomiclabs/hardhat-waffle"; import "@typechain/hardhat"; import "hardhat-gas-reporter"; import "solidity-coverage"; -dotenv.config(); - // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more diff --git a/hardhat-reference.config.ts b/hardhat-reference.config.ts index 5ae11e791..17cab50f5 100644 --- a/hardhat-reference.config.ts +++ b/hardhat-reference.config.ts @@ -1,15 +1,14 @@ -import * as dotenv from "dotenv"; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; +import { subtask } from "hardhat/config"; + +import type { HardhatUserConfig } from "hardhat/config"; -import { HardhatUserConfig, subtask, task } from "hardhat/config"; +import "dotenv/config"; import "@nomiclabs/hardhat-waffle"; import "@typechain/hardhat"; import "hardhat-gas-reporter"; import "solidity-coverage"; -dotenv.config(); - -import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; - // Filter Reference Contracts subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( async (_, __, runSuper) => { diff --git a/hardhat.config.ts b/hardhat.config.ts index 2676323cf..00d9e2aab 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,15 +1,16 @@ -import * as dotenv from "dotenv"; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; +import { subtask } from "hardhat/config"; + +import type { HardhatUserConfig } from "hardhat/config"; -import { HardhatUserConfig, subtask } from "hardhat/config"; +import "dotenv/config"; +import "@nomiclabs/hardhat-ethers"; import "@nomiclabs/hardhat-waffle"; +import "@nomiclabs/hardhat-etherscan"; import "@typechain/hardhat"; import "hardhat-gas-reporter"; import "solidity-coverage"; -import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; - -dotenv.config(); - // Filter Reference Contracts subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( async (_, __, runSuper) => { @@ -64,11 +65,17 @@ const config: HardhatUserConfig = { blockGasLimit: 30_000_000, throwOnCallFailures: false, }, + verificationNetwork: { + url: process.env.NETWORK_RPC ?? "", + }, }, gasReporter: { enabled: process.env.REPORT_GAS !== undefined, currency: "USD", }, + etherscan: { + apiKey: process.env.EXPLORER_API_KEY, + }, // specify separate cache for hardhat, since it could possibly conflict with foundry's paths: { cache: "hh-cache" }, }; diff --git a/package.json b/package.json index c6b30f07a..596baa956 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "hardhat": "https://github.com/0age/hardhat/releases/download/viaIR-2.9.3/hardhat-v2.9.3.tgz" }, "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.4", + "@nomiclabs/hardhat-ethers": "^2.0.6", + "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.1", "@rari-capital/solmate": "^6.2.0", "@typechain/ethers-v5": "^10.0.0", @@ -63,66 +64,20 @@ "profile": "REPORT_GAS=true hardhat test --config ./hardhat.config.ts", "coverage": "hardhat coverage --config ./hardhat-coverage.config.ts --solcoverjs ./config/.solcover.js", "coverage:ref": "REFERENCE=true hardhat coverage --config ./hardhat-reference-coverage.config.ts --solcoverjs ./config/.solcover-reference.js", - "lint:check": "prettier --check **.sol && prettier --check **.js && prettier --check **.ts && hardhat compile --config ./hardhat.config.ts && npx solhint --config ./config/.solhint.json --ignore-path ./config/.solhintignore 'contracts/**/*.sol'", - "lint:fix": "prettier --write **.sol && prettier --write **.js && prettier --write **.ts", + "lint:check": "yarn lint:check:format && yarn lint:check:solhint && yarn lint:check:eslint", + "lint:check:format": "prettier --check **.{sol,js,ts}", + "lint:check:solhint": "yarn build && solhint --config ./config/.solhint.json --ignore-path ./config/.solhintignore contracts/**/*.sol", + "lint:check:eslint": "eslint . --ext js,ts", + "lint:fix": "yarn lint:fix:format && yarn lint:fix:eslint", + "lint:fix:format": "prettier --write **.{sol,js,ts}", + "lint:fix:eslint": "eslint --fix . --ext js,ts", "test:forge": "FOUNDRY_PROFILE=reference forge build; FOUNDRY_PROFILE=optimized forge build; FOUNDRY_PROFILE=test forge test -vvv", - "test:lite": "FOUNDRY_PROFILE=reference forge build; FOUNDRY_PROFILE=lite forge test -vvv", + "test:forge:lite": "FOUNDRY_PROFILE=reference forge build; FOUNDRY_PROFILE=lite forge test -vvv", "prepare": "husky install" }, "lint-staged": { "*.sol": "prettier --write", "*.js": "prettier --write", "*.ts": "prettier --write" - }, - "prettier": { - "overrides": [ - { - "files": "*.sol", - "options": { - "tabWidth": 4, - "printWidth": 80, - "bracketSpacing": true - } - } - ] - }, - "eslintConfig": { - "env": { - "browser": false, - "es2021": true, - "mocha": true, - "node": true - }, - "plugins": [ - "@typescript-eslint", - "import" - ], - "extends": [ - "standard", - "plugin:prettier/recommended", - "eslint:recommended", - "plugin:import/recommended", - "plugin:import/typescript" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 12 - }, - "rules": { - "node/no-unsupported-features/es-syntax": [ - "error", - { - "ignores": [ - "modules" - ] - } - ] - } - }, - "eslintIgnore": [ - "node_modules", - "artifacts", - "cache", - "coverage" - ] + } } diff --git a/reference/ReferenceConsideration.sol b/reference/ReferenceConsideration.sol index 35ea3a61c..f34eea45c 100644 --- a/reference/ReferenceConsideration.sol +++ b/reference/ReferenceConsideration.sol @@ -29,7 +29,7 @@ import { OrderToExecute, AccumulatorStruct } from "./lib/ReferenceConsiderationS * @author 0age * @custom:coauthor d1ll0n * @custom:coauthor transmissions11 - * @custom:version rc-1.1 + * @custom:version 1.1-reference * @notice Consideration is a generalized ETH/ERC20/ERC721/ERC1155 marketplace. * It minimizes external calls to the greatest extent possible and * provides lightweight methods for common routes as well as more diff --git a/reference/lib/ReferenceAssertions.sol b/reference/lib/ReferenceAssertions.sol index bc0188425..a188aebfa 100644 --- a/reference/lib/ReferenceAssertions.sol +++ b/reference/lib/ReferenceAssertions.sol @@ -95,23 +95,27 @@ contract ReferenceAssertions is /** * @dev Internal pure function to validate calldata offsets for dynamic - * types in BasicOrderParameters. This ensures that functions using the - * calldata object normally will be using the same data as optimized - * functions. Note that no parameters are supplied as all basic order - * functions use the same calldata encoding. + * types in BasicOrderParameters and other parameters. This ensures + * that functions using the calldata object normally will be using the + * same data as the assembly functions and that values that are bound + * to a given range are within that range. Note that no parameters are + * supplied as all basic order functions use the same calldata + * encoding. */ - function _assertValidBasicOrderParameterOffsets() internal pure { + function _assertValidBasicOrderParameters() internal pure { /* * Checks: * 1. Order parameters struct offset == 0x20 * 2. Additional recipients arr offset == 0x200 * 3. Signature offset == 0x240 + (recipients.length * 0x40) + * 4. BasicOrderType between 0 and 23 (i.e. < 24) */ // Declare a boolean designating basic order parameter offset validity. bool validOffsets = (abi.decode(msg.data[4:36], (uint256)) == 32 && abi.decode(msg.data[548:580], (uint256)) == 576 && abi.decode(msg.data[580:612], (uint256)) == - 608 + 64 * abi.decode(msg.data[612:644], (uint256))); + 608 + 64 * abi.decode(msg.data[612:644], (uint256))) && + abi.decode(msg.data[292:324], (uint256)) < 24; // Revert with an error if basic order parameter offsets are invalid. if (!validOffsets) { diff --git a/reference/lib/ReferenceBasicOrderFulfiller.sol b/reference/lib/ReferenceBasicOrderFulfiller.sol index 995530ea0..12f47cfd9 100644 --- a/reference/lib/ReferenceBasicOrderFulfiller.sol +++ b/reference/lib/ReferenceBasicOrderFulfiller.sol @@ -580,7 +580,7 @@ contract ReferenceBasicOrderFulfiller is ReferenceOrderValidator { // Verify that calldata offsets for all dynamic types were produced by // default encoding. This is only required on the optimized contract, // but is included here to maintain parity. - _assertValidBasicOrderParameterOffsets(); + _assertValidBasicOrderParameters(); // Ensure supplied consideration array length is not less than original. _assertConsiderationLengthIsNotLessThanOriginalConsiderationLength( diff --git a/reference/lib/ReferenceConsiderationBase.sol b/reference/lib/ReferenceConsiderationBase.sol index aeef28021..036f08d75 100644 --- a/reference/lib/ReferenceConsiderationBase.sol +++ b/reference/lib/ReferenceConsiderationBase.sol @@ -27,7 +27,7 @@ contract ReferenceConsiderationBase is { // Declare constants for name, version, and reentrancy sentinel values. string internal constant _NAME = "Consideration"; - string internal constant _VERSION = "rc.1.1"; + string internal constant _VERSION = "1.1-reference"; uint256 internal constant _NOT_ENTERED = 1; uint256 internal constant _ENTERED = 2; diff --git a/reference/lib/ReferenceGettersAndDerivers.sol b/reference/lib/ReferenceGettersAndDerivers.sol index 60461d6ba..4f26a6ec9 100644 --- a/reference/lib/ReferenceGettersAndDerivers.sol +++ b/reference/lib/ReferenceGettersAndDerivers.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; -import { ConsiderationItem, OfferItem, OrderParameters } from "contracts/lib/ConsiderationStructs.sol"; +import { ConsiderationItem, OfferItem, OrderParameters } from "../../contracts/lib/ConsiderationStructs.sol"; import { ReferenceConsiderationBase } from "./ReferenceConsiderationBase.sol"; diff --git a/reference/lib/ReferenceOrderCombiner.sol b/reference/lib/ReferenceOrderCombiner.sol index 570fb33e2..a604d13ed 100644 --- a/reference/lib/ReferenceOrderCombiner.sol +++ b/reference/lib/ReferenceOrderCombiner.sol @@ -27,6 +27,8 @@ import { ReferenceFulfillmentApplier } from "./ReferenceFulfillmentApplier.sol"; import "contracts/lib/ConsiderationConstants.sol"; +import { SeaportInterface } from "contracts/interfaces/SeaportInterface.sol"; + /** * @title OrderCombiner * @author 0age @@ -195,7 +197,9 @@ contract ReferenceOrderCombiner is bytes32[] memory orderHashes = new bytes32[](totalOrders); // Check if we are in a match function - bool nonMatchFn = msg.sig != 0x55944a42 && msg.sig != 0xa8174404; + bool nonMatchFn = msg.sig != + SeaportInterface.matchAdvancedOrders.selector && + msg.sig != SeaportInterface.matchOrders.selector; bool anyNativeOfferItems; // Iterate over each order. @@ -244,11 +248,8 @@ contract ReferenceOrderCombiner is // Otherwise, track the order hash in question. orderHashes[i] = orderHash; - // Skip underflow check as maximumFulfilled is nonzero. - unchecked { - // Decrement the number of fulfilled orders. - maximumFulfilled--; - } + // Decrement the number of fulfilled orders. + maximumFulfilled--; // Place the start time for the order on the stack. uint256 startTime = advancedOrder.parameters.startTime; @@ -493,11 +494,8 @@ contract ReferenceOrderCombiner is // If offerer and recipient on the execution are the same... if (execution.item.recipient == execution.offerer) { - // Executions start at 0, infeasible to increment > 2^256. - unchecked { - // Increment total filtered executions. - ++totalFilteredExecutions; - } + // Increment total filtered executions. + ++totalFilteredExecutions; } else { // Otherwise, assign the execution to the executions array. executions[i - totalFilteredExecutions] = execution; @@ -522,11 +520,8 @@ contract ReferenceOrderCombiner is // If offerer and recipient on the execution are the same... if (execution.item.recipient == execution.offerer) { - // Executions start at 0, infeasible to increment > 2^256. - unchecked { - // Increment total filtered executions. - ++totalFilteredExecutions; - } + // Increment total filtered executions. + ++totalFilteredExecutions; } else { // Otherwise, assign the execution to the executions array. executions[ @@ -782,11 +777,8 @@ contract ReferenceOrderCombiner is // If offerer and recipient on the execution are the same... if (execution.item.recipient == execution.offerer) { - // Executions start at 0, infeasible to increment > 2^256. - unchecked { - // Increment total filtered executions. - ++totalFilteredExecutions; - } + // Increment total filtered executions. + ++totalFilteredExecutions; } else { // Otherwise, assign the execution to the executions array. executions[i - totalFilteredExecutions] = execution; diff --git a/reference/lib/ReferenceSignatureVerification.sol b/reference/lib/ReferenceSignatureVerification.sol index 73729e658..2999629aa 100644 --- a/reference/lib/ReferenceSignatureVerification.sol +++ b/reference/lib/ReferenceSignatureVerification.sol @@ -19,8 +19,8 @@ contract ReferenceSignatureVerification is SignatureVerificationErrors { /** * @dev Internal view function to verify the signature of an order. An * ERC-1271 fallback will be attempted if either the signature length - * is not 32 or 33 bytes or if the recovered signer does not match the - * supplied signer. Note that in cases where a 32 or 33 byte signature + * is not 64 or 65 bytes or if the recovered signer does not match the + * supplied signer. Note that in cases where a 64 or 65 byte signature * is supplied, only standard ECDSA signatures that recover to a * non-zero address are supported. * @@ -71,12 +71,9 @@ contract ReferenceSignatureVerification is SignatureVerificationErrors { address recoveredSigner = ecrecover(digest, v, r, s); // Disallow invalid signers. - if (recoveredSigner == address(0)) { + if (recoveredSigner == address(0) || recoveredSigner != signer) { revert InvalidSigner(); // Should a signer be recovered, but it doesn't match the signer... - } else if (recoveredSigner != signer) { - // Attempt EIP-1271 static call to signer in case it's a contract. - _assertValidEIP1271Signature(signer, digest, signature); } } diff --git a/reference/lib/ReferenceVerifiers.sol b/reference/lib/ReferenceVerifiers.sol index 5615eff38..442d055e9 100644 --- a/reference/lib/ReferenceVerifiers.sol +++ b/reference/lib/ReferenceVerifiers.sol @@ -62,8 +62,8 @@ contract ReferenceVerifiers is /** * @dev Internal view function to verify the signature of an order. An * ERC-1271 fallback will be attempted if either the signature length - * is not 32 or 33 bytes or if the recovered signer does not match the - * supplied offerer. Note that in cases where a 32 or 33 byte signature + * is not 64 or 65 bytes or if the recovered signer does not match the + * supplied offerer. Note that in cases where a 64 or 65 byte signature * is supplied, only standard ECDSA signatures that recover to a * non-zero address are supported. * diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts new file mode 100644 index 000000000..c23d85152 --- /dev/null +++ b/test/advanced.spec.ts @@ -0,0 +1,4213 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; + +import { merkleTree } from "./utils/criteria"; +import { + buildOrderStatus, + buildResolver, + defaultAcceptOfferMirrorFulfillment, + defaultBuyNowMirrorFulfillment, + getItemETH, + random128, + randomBN, + randomHex, + toBN, + toFulfillment, + toFulfillmentComponents, + toKey, +} from "./utils/encoding"; +import { seaportFixture } from "./utils/fixtures"; +import { + VERSION, + minRandom, + simulateAdvancedMatchOrders, + simulateMatchOrders, +} from "./utils/helpers"; +import { faucet, whileImpersonating } from "./utils/impersonate"; + +import type { + ConduitInterface, + ConsiderationInterface, + TestERC1155, + TestERC20, + TestERC721, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { AdvancedOrder, ConsiderationItem } from "./utils/types"; +import type { Wallet } from "ethers"; + +const { parseEther } = ethers.utils; + +describe(`Advanced orders (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 testERC1155: TestERC1155; + let testERC1155Two: TestERC1155; + let testERC20: TestERC20; + let testERC721: TestERC721; + + let checkExpectedEvents: SeaportFixtures["checkExpectedEvents"]; + let createMirrorAcceptOfferOrder: SeaportFixtures["createMirrorAcceptOfferOrder"]; + let createMirrorBuyNowOrder: SeaportFixtures["createMirrorBuyNowOrder"]; + let createOrder: SeaportFixtures["createOrder"]; + let getTestItem1155: SeaportFixtures["getTestItem1155"]; + let getTestItem1155WithCriteria: SeaportFixtures["getTestItem1155WithCriteria"]; + let getTestItem20: SeaportFixtures["getTestItem20"]; + let getTestItem721: SeaportFixtures["getTestItem721"]; + let getTestItem721WithCriteria: SeaportFixtures["getTestItem721WithCriteria"]; + let mint1155: SeaportFixtures["mint1155"]; + let mint721: SeaportFixtures["mint721"]; + let mint721s: SeaportFixtures["mint721s"]; + let mintAndApprove1155: SeaportFixtures["mintAndApprove1155"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + let set1155ApprovalForAll: SeaportFixtures["set1155ApprovalForAll"]; + let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; + let withBalanceChecks: SeaportFixtures["withBalanceChecks"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + checkExpectedEvents, + conduitKeyOne, + conduitOne, + createMirrorAcceptOfferOrder, + createMirrorBuyNowOrder, + createOrder, + getTestItem1155, + getTestItem1155WithCriteria, + getTestItem20, + getTestItem721, + getTestItem721WithCriteria, + marketplaceContract, + mint1155, + mint721, + mint721s, + mintAndApprove1155, + mintAndApprove721, + mintAndApproveERC20, + set1155ApprovalForAll, + set721ApprovalForAll, + testERC1155, + testERC1155Two, + testERC20, + testERC721, + withBalanceChecks, + } = await seaportFixture(owner)); + }); + + let seller: Wallet; + let buyer: Wallet; + let zone: Wallet; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + seller = new ethers.Wallet(randomHex(32), provider); + buyer = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + for (const wallet of [seller, buyer, zone]) { + await faucet(wallet.address, provider); + } + }); + + describe("Partial fills", async () => { + it("Partial fills (standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 2; // fill two tenths or one fifth + order.denominator = 10; // fill two tenths or one fifth + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 2, 10) + ); + + order.numerator = 1; // fill one half + order.denominator = 2; // fill one half + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 14, 20) + ); + + // Fill remaining; only 3/10ths will be fillable + order.numerator = 1; // fill one half + order.denominator = 2; // fill one half + + const ordersClone = [{ ...order }] as AdvancedOrder[]; + for (const [, clonedOrder] of Object.entries(ordersClone)) { + clonedOrder.parameters.startTime = order.parameters.startTime; + clonedOrder.parameters.endTime = order.parameters.endTime; + + for (const [j, offerItem] of Object.entries( + clonedOrder.parameters.offer + )) { + offerItem.startAmount = order.parameters.offer[+j].startAmount; + offerItem.endAmount = order.parameters.offer[+j].endAmount; + } + + for (const [j, considerationItem] of Object.entries( + clonedOrder.parameters.consideration + )) { + considerationItem.startAmount = + order.parameters.consideration[+j].startAmount; + considerationItem.endAmount = + order.parameters.consideration[+j].endAmount; + } + } + + ordersClone[0].numerator = 3; + ordersClone[0].denominator = 10; + + await withBalanceChecks(ordersClone, 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: ordersClone[0], + orderHash, + fulfiller: buyer.address, + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 40, 40) + ); + }); + it("Partial fills (standard, additional permutations)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 2; // fill two tenths or one fifth + order.denominator = 10; // fill two tenths or one fifth + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 2, 10) + ); + + order.numerator = 1; // fill one tenth + order.denominator = 10; // fill one tenth + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 3, 10) + ); + + // Fill all available; only 7/10ths will be fillable + order.numerator = 1; // fill all available + order.denominator = 1; // fill all available + + const ordersClone = [{ ...order }] as AdvancedOrder[]; + for (const [, clonedOrder] of Object.entries(ordersClone)) { + clonedOrder.parameters.startTime = order.parameters.startTime; + clonedOrder.parameters.endTime = order.parameters.endTime; + + for (const [j, offerItem] of Object.entries( + clonedOrder.parameters.offer + )) { + offerItem.startAmount = order.parameters.offer[+j].startAmount; + offerItem.endAmount = order.parameters.offer[+j].endAmount; + } + + for (const [j, considerationItem] of Object.entries( + clonedOrder.parameters.consideration + )) { + considerationItem.startAmount = + order.parameters.consideration[+j].startAmount; + considerationItem.endAmount = + order.parameters.consideration[+j].endAmount; + } + } + + ordersClone[0].numerator = 7; + ordersClone[0].denominator = 10; + + await withBalanceChecks(ordersClone, 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: ordersClone[0], + orderHash, + fulfiller: buyer.address, + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 10, 10) + ); + }); + it("Partial fills (match)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 2; // fill two tenths or one fifth + order.denominator = 10; // fill two tenths or one fifth + + let mirrorObject; + mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + let executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract.connect(owner).matchAdvancedOrders( + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorObject.mirrorOrder, + orderHash: mirrorObject.mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 2, 10) + ); + + order.numerator = 1; // fill one tenth + order.denominator = 10; // fill one tenth + + mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); + + executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + owner, + value + ); + + const tx2 = marketplaceContract.connect(owner).matchAdvancedOrders( + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + { + value, + } + ); + const receipt2 = await (await tx2).wait(); + await checkExpectedEvents( + tx2, + receipt2, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorObject.mirrorOrder, + orderHash: mirrorObject.mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 3, 10) + ); + + // Fill all available; only 7/10ths will be fillable + order.numerator = 7; // fill all available + order.denominator = 10; // fill all available + + mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); + + executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + owner, + value + ); + + const tx3 = await marketplaceContract.connect(owner).matchAdvancedOrders( + [order, mirrorObject.mirrorOrder], + [], // no criteria resolvers + fulfillments, + { + value, + } + ); + const receipt3 = await tx3.wait(); + await checkExpectedEvents( + tx3, + receipt3, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorObject.mirrorOrder, + orderHash: mirrorObject.mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 10, 10) + ); + }); + + it("Simplifies fraction when numerator/denominator would overflow", async () => { + const numer1 = toBN(2).pow(100); + const denom1 = toBN(2).pow(101); + const numer2 = toBN(2).pow(20); + const denom2 = toBN(2).pow(22); + const amt = 8; + await mintAndApproveERC20(buyer, marketplaceContract.address, amt); + // Seller mints nft + const { nftId } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000, + undefined, + amt + ); + + const offer = [getTestItem1155(nftId, amt, amt)]; + + const consideration = [getTestItem20(amt, amt, seller.address)]; + const { order, orderHash, value } = await createOrder( + seller, + undefined, + offer, + consideration, + 1, // PARTIAL_OPEN + undefined, + undefined, + undefined, + undefined, + undefined, + true + ); + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // 1/2 + order.numerator = numer1 as any; // would error here if cast to number (due to overflow) + order.denominator = denom1 as any; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), buyer.address, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, numer1, denom1) + ); + + order.numerator = +numer2; + order.denominator = +denom2; + + await marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), buyer.address, { + value, + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, toBN(3), toBN(4)) + ); + }); + + it("Reverts when numerator/denominator overflow", async () => { + const prime1 = toBN(2).pow(7).sub(1); + const prime2 = toBN(2).pow(61).sub(1); + const prime3 = toBN(2).pow(107).sub(1); + const amt = prime1.mul(prime2).mul(prime3); + await mintAndApproveERC20(buyer, marketplaceContract.address, amt); + // Seller mints nft + const { nftId } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000, + undefined, + amt + ); + + const offer = [getTestItem1155(nftId, amt, amt)]; + + const consideration = [getTestItem20(amt, amt, seller.address)]; + const { order, orderHash, value } = await createOrder( + seller, + undefined, + offer, + consideration, + 1, // PARTIAL_OPEN + undefined, + undefined, + undefined, + undefined, + undefined, + true + ); + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // 1/2 + order.numerator = 1; + order.denominator = prime2 as any; // would error here if cast to number (due to overflow) + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), buyer.address, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, toBN(1), prime2) + ); + + order.numerator = prime1 as any; // would error here if cast to number (due to overflow) + order.denominator = prime3 as any; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), buyer.address, { + value, + }) + ).to.be.revertedWith( + "0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" + ); + }); + }); + + describe("Criteria-based orders", async () => { + it("Criteria-based offer item ERC721 (standard)", async () => { + // Seller mints nfts + const [nftId, secondNFTId, thirdNFTId] = await mint721s(seller, 3); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + it("Criteria-based offer item ERC1155 (standard)", async () => { + // Seller mints nfts + const { nftId } = await mint1155(seller); + + // Seller approves marketplace contract to transfer NFTs + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree([nftId]); + + const offer = [getTestItem1155WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + it("Criteria-based offer item (standard, collection-level)", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + getTestItem721WithCriteria(ethers.constants.HashZero, toBN(1), toBN(1)), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [buildResolver(0, 0, 0, nftId, [])]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + it("Criteria-based offer item ERC721 (match)", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [ + [[[1, 0]], [[0, 0]]], + [[[0, 0]], [[1, 0]]], + [[[1, 1]], [[0, 1]]], + [[[1, 2]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + criteriaResolvers + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("Criteria-based offer item ERC1155 (match)", async () => { + // Seller mints nfts + const { nftId } = await mint1155(seller); + + // Seller approves marketplace contract to transfer NFTs + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree([nftId]); + + const offer = [getTestItem1155WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [ + [[[1, 0]], [[0, 0]]], + [[[0, 0]], [[1, 0]]], + [[[1, 1]], [[0, 1]]], + [[[1, 2]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + criteriaResolvers + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("Criteria-based offer item (match, collection-level)", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + getTestItem721WithCriteria(ethers.constants.HashZero, toBN(1), toBN(1)), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [buildResolver(0, 0, 0, nftId, [])]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [ + [[[1, 0]], [[0, 0]]], + [[[0, 0]], [[1, 0]]], + [[[1, 1]], [[0, 1]]], + [[[1, 2]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + criteriaResolvers + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("Criteria-based consideration item (standard)", async () => { + // buyer mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(buyer.address, nftId); + await testERC721.mint(buyer.address, secondNFTId); + await testERC721.mint(buyer.address, thirdNFTId); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem721WithCriteria(root, toBN(1), toBN(1), seller.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks( + [order], + value.mul(-1), + criteriaResolvers, + async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + } + ); + }); + it("Criteria-based consideration item ERC1155 (standard)", async () => { + // buyer mints nfts + const { nftId } = await mint1155(buyer); + + // Seller approves marketplace contract to transfer NFTs + await set1155ApprovalForAll(buyer, marketplaceContract.address, true); + + const { root, proofs } = merkleTree([nftId]); + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem1155WithCriteria(root, toBN(1), toBN(1), seller.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks( + [order], + value.mul(-1), + criteriaResolvers, + async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + } + ); + }); + it("Criteria-based wildcard consideration item (standard)", async () => { + // buyer mints nft + const nftId = await mintAndApprove721(buyer, marketplaceContract.address); + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem721WithCriteria( + ethers.constants.HashZero, + toBN(1), + toBN(1), + seller.address + ), + ]; + + const criteriaResolvers = [buildResolver(0, 1, 0, nftId, [])]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await withBalanceChecks( + [order], + value.mul(-1), + criteriaResolvers, + async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + } + ); + }); + it("Criteria-based consideration item ERC721 (match)", async () => { + // Fulfiller mints nft + const nftId = await mint721(buyer); + const tokenAmount = minRandom(100); + + // Fulfiller approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Offerer mints ERC20 + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Fulfiller mints ERC20 + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const { root, proofs } = merkleTree([nftId]); + + const offer = [ + // Offerer (Seller) + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + // Fulfiller (Buyer) + { + itemType: 4, // ERC721WithCriteria + token: testERC721.address, + identifierOrCriteria: toBN(root), + startAmount: toBN(1), + endAmount: toBN(1), + recipient: seller.address, + }, + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + criteriaResolvers + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("Criteria-based consideration item ERC1155 (match)", async () => { + // Fulfiller mints nft + const { nftId } = await mint1155(buyer); + const tokenAmount = minRandom(100); + + // Fulfiller approves marketplace contract to transfer NFT + await set1155ApprovalForAll(buyer, marketplaceContract.address, true); + + // Offerer mints ERC20 + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Fulfiller mints ERC20 + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const { root, proofs } = merkleTree([nftId]); + + const offer = [ + // Offerer (Seller) + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + // Fulfiller (Buyer) + { + itemType: 5, // ERC1155_WITH_CRITERIA + token: testERC1155.address, + identifierOrCriteria: toBN(root), + startAmount: toBN(1), + endAmount: toBN(1), + recipient: seller.address, + }, + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + criteriaResolvers + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + + describe("Ascending / Descending amounts", async () => { + it("Ascending offer amount (standard)", async () => { + // Seller mints nft + const nftId = randomBN(); + const startAmount = toBN(randomBN(2)); + const endAmount = startAmount.mul(2); + await testERC1155.mint(seller.address, nftId, endAmount.mul(10)); + + // Seller approves marketplace contract to transfer NFTs + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [getTestItem1155(nftId, startAmount, endAmount, undefined)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Ascending consideration amount (standard)", async () => { + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Buyer mints nft + const nftId = randomBN(); + const startAmount = toBN(randomBN(2)); + const endAmount = startAmount.mul(2); + await testERC1155.mint(buyer.address, nftId, endAmount.mul(10)); + + // Buyer approves marketplace contract to transfer NFTs + await set1155ApprovalForAll(buyer, marketplaceContract.address, true); + + // Buyer needs to approve marketplace to transfer ERC20 tokens too (as it's a standard fulfillment) + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem1155( + nftId, + startAmount, + endAmount, + undefined, + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Ascending offer amount (match)", async () => { + // Seller mints nft + const nftId = randomBN(); + const startAmount = toBN(randomBN(2)); + const endAmount = startAmount.mul(2); + await testERC1155.mint(seller.address, nftId, endAmount.mul(10)); + + // Seller approves marketplace contract to transfer NFTs + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [getTestItem1155(nftId, startAmount, endAmount, undefined)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + }); + + describe("Sequenced Orders", async () => { + it("Match A => B => C => A", async () => { + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + const secondNFTId = await mintAndApprove721( + buyer, + marketplaceContract.address + ); + const thirdNFTId = await mintAndApprove721( + owner, + marketplaceContract.address + ); + + const offerOne = [ + getTestItem721(nftId, toBN(1), toBN(1), undefined, testERC721.address), + ]; + + const considerationOne = [ + getTestItem721( + secondNFTId, + toBN(1), + toBN(1), + seller.address, + testERC721.address + ), + ]; + + const { order: orderOne, orderHash: orderHashOne } = await createOrder( + seller, + zone, + offerOne, + considerationOne, + 0 // FULL_OPEN + ); + + const offerTwo = [ + getTestItem721( + secondNFTId, + toBN(1), + toBN(1), + undefined, + testERC721.address + ), + ]; + + const considerationTwo = [ + getTestItem721( + thirdNFTId, + toBN(1), + toBN(1), + buyer.address, + testERC721.address + ), + ]; + + const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( + buyer, + zone, + offerTwo, + considerationTwo, + 0 // FULL_OPEN + ); + + const offerThree = [ + getTestItem721( + thirdNFTId, + toBN(1), + toBN(1), + undefined, + testERC721.address + ), + ]; + + const considerationThree = [ + getTestItem721( + nftId, + toBN(1), + toBN(1), + owner.address, + testERC721.address + ), + ]; + + const { order: orderThree, orderHash: orderHashThree } = + await createOrder( + owner, + 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) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [orderOne, orderTwo, orderThree], + [], // no criteria resolvers + fulfillments, + owner, + 0 // no value + ); + + expect(executions.length).to.equal(fulfillments.length); + + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [orderOne, orderTwo, orderThree], + [], + fulfillments, + { + value: 0, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderTwo, + orderHash: orderHashTwo, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderThree, + orderHash: orderHashThree, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + }); + it("Match with fewer executions when one party has multiple orders that coincide", 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, orderHash: orderHashOne } = 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, orderHash: orderHashTwo } = 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, orderHash: orderHashThree } = + 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) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [orderOne, orderTwo, orderThree], + [], // no criteria resolvers + fulfillments, + owner, + 0 // no value + ); + + expect(executions.length).to.equal(fulfillments.length - 1); + + const tx = marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [orderOne, orderTwo, orderThree], + [], + fulfillments, + { + value: 0, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderTwo, + orderHash: orderHashTwo, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderThree, + orderHash: orderHashThree, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + + describe("Order groups", async () => { + it("Multiple offer components at once", async () => { + // Seller mints NFTs + const { nftId, amount } = await mint1155(seller, 2); + + // Seller approves marketplace contract to transfer NFT + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + // Buyer mints ERC20s + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount.mul(2) + ); + + const offerOne = [getTestItem1155(nftId, amount, amount)]; + + const considerationOne = [ + getTestItem20(tokenAmount, tokenAmount, seller.address), + ]; + + const { order: orderOne, orderHash: orderHashOne } = await createOrder( + seller, + zone, + offerOne, + considerationOne, + 0 // FULL_OPEN + ); + + const offerTwo = [getTestItem1155(nftId, amount, amount)]; + + const considerationTwo = [ + getTestItem20(tokenAmount, tokenAmount, seller.address), + ]; + + const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( + seller, + zone, + offerTwo, + considerationTwo, + 0 // FULL_OPEN + ); + + const offerThree = [ + getTestItem20(tokenAmount.mul(2), tokenAmount.mul(2)), + ]; + + const considerationThree = [ + getTestItem1155( + nftId, + amount.mul(2), + amount.mul(2), + undefined, + buyer.address + ), + ]; + + const { order: orderThree, orderHash: orderHashThree } = + await createOrder( + buyer, + zone, + offerThree, + considerationThree, + 0 // FULL_OPEN + ); + + const fulfillments = [ + [ + [ + [0, 0], + [1, 0], + ], + [[2, 0]], + ], + [[[2, 0]], [[0, 0]]], + [[[2, 0]], [[1, 0]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [orderOne, orderTwo, orderThree], + [], // no criteria resolvers + fulfillments, + owner, + 0 // no value + ); + + expect(executions.length).to.equal(fulfillments.length); + + const tx = marketplaceContract + .connect(buyer) + .matchAdvancedOrders( + [orderOne, orderTwo, orderThree], + [], + fulfillments, + { + value: 0, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderTwo, + orderHash: orderHashTwo, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderThree, + orderHash: orderHashThree, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + [], + true + ); + + expect( + toBN("0x" + receipt.events![3].data.slice(66)).toString() + ).to.equal(amount.mul(2).toString()); + + return receipt; + }); + it("Multiple consideration components at once", async () => { + // Seller mints NFTs + const { nftId, amount } = await mint1155(seller, 2); + + // Seller approves marketplace contract to transfer NFT + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + // Buyer mints ERC20s + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount.mul(2) + ); + + const offerOne = [ + getTestItem1155(nftId, amount.mul(2), amount.mul(2), undefined), + ]; + + const considerationOne = [ + getTestItem20(tokenAmount.mul(2), tokenAmount.mul(2), seller.address), + ]; + + const { order: orderOne, orderHash: orderHashOne } = await createOrder( + seller, + zone, + offerOne, + considerationOne, + 0 // FULL_OPEN + ); + + const offerTwo = [getTestItem20(tokenAmount, tokenAmount)]; + + const considerationTwo = [ + getTestItem1155(nftId, amount, amount, undefined, buyer.address), + ]; + + const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( + buyer, + zone, + offerTwo, + considerationTwo, + 0 // FULL_OPEN + ); + + const offerThree = [getTestItem20(tokenAmount, tokenAmount)]; + + const considerationThree = [ + getTestItem1155(nftId, amount, amount, undefined, buyer.address), + ]; + + const { order: orderThree, orderHash: orderHashThree } = + await createOrder( + buyer, + zone, + offerThree, + considerationThree, + 0 // FULL_OPEN + ); + + const fulfillments = [ + [ + [[0, 0]], + [ + [1, 0], + [2, 0], + ], + ], + [[[1, 0]], [[0, 0]]], + [[[2, 0]], [[0, 0]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateAdvancedMatchOrders( + marketplaceContract, + [orderOne, orderTwo, orderThree], + [], // no criteria resolvers + fulfillments, + owner, + 0 // no value + ); + + expect(executions.length).to.equal(fulfillments.length); + + await whileImpersonating(buyer.address, provider, async () => { + const tx = marketplaceContract + .connect(buyer) + .matchAdvancedOrders( + [orderOne, orderTwo, orderThree], + [], + fulfillments, + { + value: 0, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderTwo, + orderHash: orderHashTwo, + fulfiller: ethers.constants.AddressZero, + }, + { + order: orderThree, + orderHash: orderHashThree, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions, + [], + true + ); + + // TODO: include balance checks on the duplicate ERC20 transfers + + return receipt; + }); + }); + }); + + describe("Complex ERC1155 transfers", async () => { + it("ERC1155 <=> ETH (match)", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = + await mintAndApprove1155(seller, marketplaceContract.address); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(secondNftId, secondAmount, secondAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(5); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("ERC1155 <=> ETH (match, three items)", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + seller + ); + + // Seller mints third nft + const { nftId: thirdNftId, amount: thirdAmount } = + await mintAndApprove1155(seller, marketplaceContract.address); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(secondNftId, secondAmount, secondAmount), + getTestItem1155(thirdNftId, thirdAmount, thirdAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[0, 2]], [[1, 2]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(6); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("ERC1155 <=> ETH (match via conduit)", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = + await mintAndApprove1155(seller, conduitOne.address); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(secondNftId, secondAmount, secondAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(5); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("ERC1155 <=> ETH (match, single item)", async () => { + // Seller mints first nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const consideration: ConsiderationItem[] = []; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [toFulfillment([[0, 0]], [[1, 0]])]; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(1); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("ERC1155 <=> ETH (match, single 1155)", async () => { + // Seller mints first nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("ERC1155 <=> ETH (match, two different 1155 contracts)", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const secondNftId = toBN(randomBN(4)); + const secondAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, secondNftId, secondAmount); + + // Seller approves marketplace contract to transfer NFTs + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + await expect( + testERC1155Two + .connect(seller) + .setApprovalForAll(marketplaceContract.address, true) + ) + .to.emit(testERC1155Two, "ApprovalForAll") + .withArgs(seller.address, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + testERC1155Two.address + ), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(5); + + await marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + }); + it("ERC1155 <=> ETH (match, one single and one with two 1155's)", async () => { + // Seller mints first nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + // Seller mints second nft + const secondNftId = toBN(randomBN(4)); + const secondAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, secondNftId, secondAmount); + + // Seller mints third nft + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(seller); + + // Seller approves marketplace contract to transfer NFTs + + await expect( + testERC1155Two + .connect(seller) + .setApprovalForAll(marketplaceContract.address, true) + ) + .to.emit(testERC1155Two, "ApprovalForAll") + .withArgs(seller.address, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + testERC1155Two.address + ), + getTestItem1155(thirdNftId, thirdAmount, thirdAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[0, 2]], [[1, 2]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(6); + + await marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + }); + it("ERC1155 <=> ETH (match, two different groups of 1155's)", async () => { + // Seller mints first nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + // Seller mints second nft + const secondNftId = toBN(randomBN(4)); + const secondAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, secondNftId, secondAmount); + + // Seller mints third nft + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(seller); + + // Seller mints fourth nft + const fourthNftId = toBN(randomBN(4)); + const fourthAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, fourthNftId, fourthAmount); + + // Seller approves marketplace contract to transfer NFTs + + await expect( + testERC1155Two + .connect(seller) + .setApprovalForAll(marketplaceContract.address, true) + ) + .to.emit(testERC1155Two, "ApprovalForAll") + .withArgs(seller.address, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + testERC1155Two.address + ), + getTestItem1155(thirdNftId, thirdAmount, thirdAmount), + getTestItem1155( + fourthNftId, + fourthAmount, + fourthAmount, + testERC1155Two.address + ), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[0, 2]], [[1, 2]]], + [[[0, 3]], [[1, 3]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(7); + + await marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + }); + }); + + describe("Fulfill Available Orders", async () => { + it("Can fulfill a single order via fulfillAvailableOrders", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address, + 10 + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [toFulfillmentComponents([[0, 0]])]; + + const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]].map( + toFulfillmentComponents + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [order], + offerComponents, + considerationComponents, + toKey(0), + 100, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Can fulfill a single order via fulfillAvailableAdvancedOrders", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address, + 11 + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, 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 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Can fulfill a single order via fulfillAvailableAdvancedOrders with recipient specified", 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, orderHash, 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 }], + ]; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + owner.address, + 100, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + recipient: owner.address, + }, + ]); + + return receipt; + }); + }); + it("Can fulfill and aggregate multiple orders via fulfillAvailableOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 1, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + toFulfillmentComponents([ + [0, 0], + [1, 0], + ]), + ]; + + const considerationComponents = [ + [ + [0, 0], + [1, 0], + ], + [ + [0, 1], + [1, 1], + ], + [ + [0, 2], + [1, 2], + ], + ].map(toFulfillmentComponents); + + await whileImpersonating(buyer.address, provider, async () => { + await withBalanceChecks( + [orderOne, orderTwo], + 0, + undefined, + async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [orderOne, orderTwo], + offerComponents, + considerationComponents, + toKey(0), + 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 + ); + }); + }); + it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 2, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + toFulfillmentComponents([ + [0, 0], + [1, 0], + ]), + ]; + + const considerationComponents = [ + [ + [0, 0], + [1, 0], + ], + [ + [0, 1], + [1, 1], + ], + [ + [0, 2], + [1, 2], + ], + ].map(toFulfillmentComponents); + + await whileImpersonating(buyer.address, provider, async () => { + 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 + ); + }); + }); + it("Can fulfill and aggregate a max number of multiple orders via fulfillAvailableOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 3, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { order: orderTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 1, itemIndex: 0 }], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + ], + ]; + + await whileImpersonating(buyer.address, provider, async () => { + await withBalanceChecks( + [orderOne], + 0, + undefined, + async () => { + const { executions } = await marketplaceContract + .connect(buyer) + .callStatic.fulfillAvailableOrders( + [orderOne, orderTwo], + offerComponents, + considerationComponents, + toKey(0), + 1, + { + value: value.mul(2), + } + ); + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [orderOne, orderTwo], + offerComponents, + considerationComponents, + toKey(0), + 1, + { + value: value.mul(2), + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: buyer.address, + }, + ], + executions + ); + + return receipt; + }, + 1 + ); + }); + }); + it("Can fulfill and aggregate a max number of multiple orders via fulfillAvailableAdvancedOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 4, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { order: orderTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + ], + ]; + + await whileImpersonating(buyer.address, provider, async () => { + await withBalanceChecks( + [orderOne], + 0, + undefined, + async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [orderOne, orderTwo], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 1, + { + value: value.mul(2), + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: buyer.address, + }, + ], + [], + [], + false, + 1 + ); + + return receipt; + }, + 1 + ); + }); + }); + it("Can fulfill and aggregate multiple orders via fulfillAvailableOrders with failing orders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 5, + 100000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // second order is expired + const { order: orderTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + // third order will be cancelled + const { + order: orderThree, + orderHash: orderHashThree, + orderComponents, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHashThree, seller.address, zone.address); + + // fourth order will be filled + const { order: orderFour, orderHash: orderHashFour } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can fill it + await withBalanceChecks([orderFour], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(orderFour, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order: orderFour, + orderHash: orderHashFour, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + { orderIndex: 3, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + { orderIndex: 3, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + { orderIndex: 2, itemIndex: 1 }, + { orderIndex: 3, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + { orderIndex: 2, itemIndex: 2 }, + { orderIndex: 3, itemIndex: 2 }, + ], + ]; + + await withBalanceChecks([orderOne], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [orderOne, orderTwo, orderThree, orderFour], + offerComponents, + considerationComponents, + toKey(0), + 100, + { + value: value.mul(4), + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders with failing orders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 6, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // second order is expired + const { order: orderTwo } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + // third order will be cancelled + const { + order: orderThree, + orderHash: orderHashThree, + orderComponents, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHashThree, seller.address, zone.address); + + // fourth order will be filled + const { order: orderFour, orderHash: orderHashFour } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can fill it + await withBalanceChecks([orderFour], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(orderFour, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order: orderFour, + orderHash: orderHashFour, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + { orderIndex: 3, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + { orderIndex: 3, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + { orderIndex: 2, itemIndex: 1 }, + { orderIndex: 3, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + { orderIndex: 2, itemIndex: 2 }, + { orderIndex: 3, itemIndex: 2 }, + ], + ]; + + await withBalanceChecks([orderOne], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [orderOne, orderTwo, orderThree, orderFour], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value: value.mul(4), + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order: orderOne, + orderHash: orderHashOne, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders with failing components including criteria", async () => { + // Seller mints first nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 1, + 7, + 10000 + ); + + // Seller mints second nft + + // Seller mints nfts for criteria-based item + const criteriaNftId = randomBN(); + const secondCriteriaNFTId = randomBN(); + const thirdCriteriaNFTId = randomBN(); + + await testERC721.mint(seller.address, criteriaNftId); + await testERC721.mint(seller.address, secondCriteriaNFTId); + await testERC721.mint(seller.address, thirdCriteriaNFTId); + + const tokenIds = [criteriaNftId, secondCriteriaNFTId, thirdCriteriaNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const offerTwo = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(1, 0, 0, criteriaNftId, proofs[criteriaNftId.toString()]), + ]; + + const { + order: orderOne, + orderHash: orderHashOne, + value, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // second order is expired + const { order: orderTwo } = await createOrder( + seller, + zone, + offerTwo, + consideration, + 0, // FULL_OPEN + criteriaResolvers, + "EXPIRED" + ); + + const offerComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 1, itemIndex: 0 }], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + ], + ]; + + await withBalanceChecks([orderOne], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [orderOne, orderTwo], + criteriaResolvers, + 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, + }, + ]); + + return receipt; + }); + }); + }); +}); diff --git a/test/basic.spec.ts b/test/basic.spec.ts new file mode 100644 index 000000000..91896a5e7 --- /dev/null +++ b/test/basic.spec.ts @@ -0,0 +1,3626 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; + +import { deployContract } from "./utils/contracts"; +import { + convertSignatureToEIP2098, + defaultAcceptOfferMirrorFulfillment, + defaultBuyNowMirrorFulfillment, + getBasicOrderExecutions, + getBasicOrderParameters, + getItemETH, + random128, + randomBN, + randomHex, + toAddress, + toBN, + toKey, +} from "./utils/encoding"; +import { seaportFixture } from "./utils/fixtures"; +import { VERSION, minRandom, simulateMatchOrders } from "./utils/helpers"; +import { faucet, whileImpersonating } from "./utils/impersonate"; + +import type { + ConduitInterface, + ConsiderationInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + TestERC20, + TestERC721, + TestZone, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { Wallet } from "ethers"; + +const { parseEther, keccak256 } = ethers.utils; + +/** + * Buy now or accept offer for a single ERC721 or ERC1155 in exchange for + * ETH, WETH or ERC20 + */ +describe(`Basic buy now or accept offer flows (Seaport v${VERSION})`, function () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + let conduitKeyOne: string; + let conduitOne: ConduitInterface; + let EIP1271WalletFactory: EIP1271Wallet__factory; + let marketplaceContract: ConsiderationInterface; + let stubZone: TestZone; + let testERC20: TestERC20; + let testERC721: TestERC721; + + let checkExpectedEvents: SeaportFixtures["checkExpectedEvents"]; + let createMirrorAcceptOfferOrder: SeaportFixtures["createMirrorAcceptOfferOrder"]; + let createMirrorBuyNowOrder: SeaportFixtures["createMirrorBuyNowOrder"]; + let createOrder: SeaportFixtures["createOrder"]; + let getTestItem1155: SeaportFixtures["getTestItem1155"]; + let getTestItem20: SeaportFixtures["getTestItem20"]; + let getTestItem721: SeaportFixtures["getTestItem721"]; + let mint721: SeaportFixtures["mint721"]; + let mintAndApprove1155: SeaportFixtures["mintAndApprove1155"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; + let withBalanceChecks: SeaportFixtures["withBalanceChecks"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + checkExpectedEvents, + conduitKeyOne, + conduitOne, + createMirrorAcceptOfferOrder, + createMirrorBuyNowOrder, + createOrder, + EIP1271WalletFactory, + getTestItem1155, + getTestItem20, + getTestItem721, + marketplaceContract, + mint721, + mintAndApprove1155, + mintAndApprove721, + mintAndApproveERC20, + set721ApprovalForAll, + stubZone, + testERC20, + testERC721, + withBalanceChecks, + } = await seaportFixture(owner)); + }); + + let seller: Wallet; + let buyer: Wallet; + let zone: Wallet; + + let sellerContract: EIP1271Wallet; + let buyerContract: EIP1271Wallet; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + seller = new ethers.Wallet(randomHex(32), provider); + buyer = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + sellerContract = await EIP1271WalletFactory.deploy(seller.address); + buyerContract = await EIP1271WalletFactory.deploy(buyer.address); + + for (const wallet of [seller, buyer, zone, sellerContract, buyerContract]) { + await faucet(wallet.address, provider); + } + }); + + describe("A single ERC721 is to be transferred", async () => { + describe("[Buy now] User fulfills a sell order for a single ERC721", async () => { + it("ERC721 <=> ETH (standard)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + return receipt; + }); + }); + it("ERC721 <=> ETH (standard via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard with tip)", async () => { + 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, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // Add a tip + order.parameters.consideration.push( + getItemETH(parseEther("1"), parseEther("1"), owner.address) + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value: value.add(parseEther("1")), + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard with restricted order)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2 // FULL_RESTRICTED + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard with restricted order and extra data)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2 // FULL_RESTRICTED + ); + + order.extraData = "0x1234"; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard with restricted order, specified recipient and extra data)", async () => { + 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, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2 // FULL_RESTRICTED + ); + + order.extraData = "0x1234"; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), owner.address, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + recipient: owner.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic, minimal and listed off-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic, minimal and verified on-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + // Validate the order from any account + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, ethers.constants.AddressZero); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard, minimal and listed off-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (standard, minimal and verified on-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(toBN(1), toBN(1), ethers.constants.AddressZero), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + // Validate the order from any account + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, ethers.constants.AddressZero); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (advanced, minimal and listed off-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (advanced, minimal and verified on-chain)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; + + const { order, orderHash, value } = await createOrder( + seller, + ethers.constants.AddressZero, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + ethers.constants.HashZero, + true // extraCheap + ); + + // Validate the order from any account + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, ethers.constants.AddressZero); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic with tips)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order, + false, + [ + { + amount: parseEther("2"), + recipient: `0x0000000000000000000000000000000000000001`, + }, + { + amount: parseEther("3"), + recipient: `0x0000000000000000000000000000000000000002`, + }, + { + amount: parseEther("4"), + recipient: `0x0000000000000000000000000000000000000003`, + }, + ] + ); + + order.parameters.consideration.push( + getItemETH( + parseEther("2"), + parseEther("2"), + "0x0000000000000000000000000000000000000001" + ) + ); + + order.parameters.consideration.push( + getItemETH( + parseEther("3"), + parseEther("3"), + "0x0000000000000000000000000000000000000002" + ) + ); + + order.parameters.consideration.push( + getItemETH( + parseEther("4"), + parseEther("4"), + "0x0000000000000000000000000000000000000003" + ) + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: value.add(parseEther("9")), + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic with restricted order)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2 // FULL_RESTRICTED + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic with partial restricted order)", async () => { + // Seller mints nft + const nftId = randomBN(); + await testERC721.mint(seller.address, nftId); + + // Seller approves marketplace contract to transfer NFT + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC721 + .connect(seller) + .setApprovalForAll(marketplaceContract.address, true) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(seller.address, marketplaceContract.address, true); + }); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + { + itemType: 0, // ETH + token: ethers.constants.AddressZero, + identifierOrCriteria: toBN(0), // ignored for ETH + startAmount: ethers.utils.parseEther("10"), + endAmount: ethers.utils.parseEther("10"), + recipient: seller.address, + }, + { + itemType: 0, // ETH + token: ethers.constants.AddressZero, + identifierOrCriteria: toBN(0), // ignored for ETH + startAmount: ethers.utils.parseEther("1"), + endAmount: ethers.utils.parseEther("1"), + recipient: zone.address, + }, + { + itemType: 0, // ETH + token: ethers.constants.AddressZero, + identifierOrCriteria: toBN(0), // ignored for ETH + startAmount: ethers.utils.parseEther("1"), + endAmount: ethers.utils.parseEther("1"), + recipient: owner.address, + }, + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 3 // PARTIAL_RESTRICTED + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await whileImpersonating(buyer.address, provider, async () => { + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { value }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { order, orderHash, fulfiller: buyer.address }, + ]); + + return receipt; + }); + }); + }); + it("ERC721 <=> ETH (basic, already validated)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // Validate the order from any account + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic, EIP-2098 signature)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // Convert signature to EIP 2098 + expect(order.signature.length).to.equal(132); + order.signature = convertSignatureToEIP2098(order.signature); + expect(order.signature.length).to.equal(130); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (basic, extra ether supplied and returned to caller)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: value.add(1), + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ETH (match)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC721 <=> ETH (match via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC721 <=> ETH (match, extra eth supplied and returned to caller)", async () => { + 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value: value.add(101), + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC721 <=> ERC20 (standard)", async () => { + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + return receipt; + }); + }); + it("ERC721 <=> ERC20 (standard via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic)", async () => { + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic, EIP-1271 signature)", async () => { + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (EIP-1271 signature on non-ECDSA 64 bytes)", async () => { + const sellerContract = await deployContract( + "EIP1271Wallet", + seller, + seller.address + ); + + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller + ); + + const signature = `0x`.padEnd(130, "f"); + + const basicOrderParameters = { + ...getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ), + signature, + }; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (EIP-1271 signature on non-ECDSA 65 bytes)", async () => { + const sellerContract = await deployContract( + "EIP1271Wallet", + seller, + seller.address + ); + + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller + ); + + // Compute the digest based on the order hash + const { domainSeparator } = await marketplaceContract.information(); + const digest = keccak256( + `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` + ); + + await sellerContract.registerDigest(digest, true); + + const signature = `0x`.padEnd(132, "f"); + + const basicOrderParameters = { + ...getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ), + signature, + }; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + + await sellerContract.registerDigest(digest, false); + }); + it("ERC721 <=> ERC20 (basic, EIP-1271 signature w/ non-standard length)", async () => { + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller + ); + + const basicOrderParameters = { + ...getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ), + signature: "0x", + }; + + // Fails before seller contract approves the digest (note that any + // non-standard signature length is treated as a contract signature) + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.revertedWith("BadContractSignature"); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.reverted; + } + + // Compute the digest based on the order hash + const { domainSeparator } = await marketplaceContract.information(); + const digest = keccak256( + `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` + ); + + // Seller approves the digest + await sellerContract.connect(seller).registerDigest(digest, true); + + // Now it succeeds + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (match)", async () => { + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC721 <=> ERC20 (match via conduit)", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + describe("[Accept offer] User accepts a buy offer on a single ERC721", async () => { + // Note: ETH is not a possible case + it("ERC721 <=> ERC20 (standard)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Buyer approves marketplace contract to transfer ERC20 tokens too + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (standard, via conduit)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20(seller, conduitOne.address, tokenAmount); + + // Buyer approves marketplace contract to transfer ERC20 tokens + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (standard, fulfilled via conduit)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves conduit contract to transfer NFT + await set721ApprovalForAll(buyer, conduitOne.address, true); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Buyer approves conduit to transfer ERC20 tokens + await expect( + testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, conduitOne.address, tokenAmount); + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, conduitKeyOne); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ]); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 4, // ERC721ForERC20 + order + ); + + await withBalanceChecks([order], toBN(0), undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ], + getBasicOrderExecutions(order, buyer.address, conduitKeyOne) + ); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic, many via conduit)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20(seller, conduitOne.address, tokenAmount); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(1, 1, zone.address), + ]; + + for (let i = 1; i <= 50; ++i) { + consideration.push(getTestItem20(i, i, toAddress(i + 10000))); + } + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const basicOrderParameters = getBasicOrderParameters( + 4, // ERC721ForERC20 + order + ); + + await withBalanceChecks([order], toBN(0), undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ], + getBasicOrderExecutions(order, buyer.address, conduitKeyOne) + ); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (basic, fulfilled via conduit)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves conduit contract to transfer NFT + await set721ApprovalForAll(buyer, conduitOne.address, true); + + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 4, // ERC721ForERC20 + order, + conduitKeyOne + ); + + await withBalanceChecks([order], toBN(0), undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ], + getBasicOrderExecutions(order, buyer.address, conduitKeyOne) + ); + + return receipt; + }); + }); + it("ERC721 <=> ERC20 (match)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves marketplace contract to transfer NFT + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder(buyer, zone, order); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC721 <=> ERC20 (match via conduit)", async () => { + // Buyer mints nft + const nftId = await mint721(buyer); + + // Buyer approves conduit contract to transfer NFT + await set721ApprovalForAll(buyer, conduitOne.address, true); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem721(nftId, 1, 1, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + [], + conduitKeyOne + ); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + }); + + describe("A single ERC1155 is to be transferred", async () => { + describe("[Buy now] User fulfills a sell order for a single ERC1155", async () => { + it("ERC1155 <=> ETH (standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ETH (standard via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ETH (basic)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 1, // EthForERC1155 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ETH (basic via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const basicOrderParameters = getBasicOrderParameters( + 1, // EthForERC1155 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ETH (match)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC1155 <=> ETH (match via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC1155 <=> ERC20 (standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (standard via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (basic)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 3, // ERC20ForERC1155 + order + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ], + getBasicOrderExecutions(order, buyer.address, conduitKeyOne) + ); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (basic via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const basicOrderParameters = getBasicOrderParameters(3, order); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (match)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC1155 <=> ERC20 (match via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + conduitOne.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const offer = [getTestItem1155(nftId, amount, amount)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + seller.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + describe("[Accept offer] User accepts a buy offer on a single ERC1155", async () => { + // Note: ETH is not a possible case + it("ERC1155 <=> ERC20 (standard)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + marketplaceContract.address + ); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Buyer approves marketplace contract to transfer ERC20 tokens too + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0)); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (standard, fulfilled via conduit)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + conduitOne.address + ); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // Buyer approves conduit to transfer ERC20 tokens + await expect( + testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, conduitOne.address, tokenAmount); + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, conduitKeyOne); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ]); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (basic)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + marketplaceContract.address + ); + + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 5, // ERC1155ForERC20 + order + ); + + await withBalanceChecks([order], toBN(0), undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ], + getBasicOrderExecutions(order, buyer.address, "") + ); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (basic, fulfilled via conduit)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + conduitOne.address + ); + + // Seller mints ERC20 + const tokenAmount = toBN(random128()); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 5, // ERC1155ForERC20 + order, + conduitKeyOne + ); + + await withBalanceChecks([order], toBN(0), undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters); + + const executions = getBasicOrderExecutions( + order, + buyer.address, + conduitKeyOne + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ], + executions + ); + + return receipt; + }); + }); + it("ERC1155 <=> ERC20 (match)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + marketplaceContract.address + ); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder(buyer, zone, order); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("ERC1155 <=> ERC20 (match via conduit)", async () => { + // Buyer mints nft + const { nftId, amount } = await mintAndApprove1155( + buyer, + conduitOne.address + ); + + // Seller mints ERC20 + const tokenAmount = minRandom(100); + await mintAndApproveERC20( + seller, + marketplaceContract.address, + tokenAmount + ); + + // NOTE: Buyer does not need to approve marketplace for ERC20 tokens + + const offer = [ + getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), + ]; + + const consideration = [ + getTestItem1155(nftId, amount, amount, undefined, seller.address), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = + await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + [], + conduitKeyOne + ); + + const fulfillments = defaultAcceptOfferMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + }); +}); diff --git a/test/conduit.spec.ts b/test/conduit.spec.ts new file mode 100644 index 000000000..72d1eb4a9 --- /dev/null +++ b/test/conduit.spec.ts @@ -0,0 +1,1471 @@ +import { expect } from "chai"; +import { randomInt } from "crypto"; +import { ethers, network } from "hardhat"; + +import { deployContract } from "./utils/contracts"; +import { + getItemETH, + random128, + randomBN, + randomHex, + toAddress, + toBN, + toFulfillment, +} from "./utils/encoding"; +import { + fixtureERC1155, + fixtureERC20, + fixtureERC721, + seaportFixture, +} from "./utils/fixtures"; +import { + VERSION, + getCustomRevertSelector, + minRandom, + simulateMatchOrders, +} from "./utils/helpers"; +import { faucet, whileImpersonating } from "./utils/impersonate"; + +import type { + ConduitControllerInterface, + ConduitInterface, + Conduit__factory, + ConsiderationInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + TestERC1155, + TestERC20, + TestERC721, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { Wallet } from "ethers"; + +const { parseEther } = ethers.utils; + +describe(`Conduit tests (Seaport v${VERSION})`, function () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + let conduitController: ConduitControllerInterface; + let conduitImplementation: Conduit__factory; + let conduitKeyOne: string; + let conduitOne: ConduitInterface; + let EIP1271WalletFactory: EIP1271Wallet__factory; + let marketplaceContract: ConsiderationInterface; + let testERC1155: TestERC1155; + let testERC1155Two: TestERC1155; + let testERC20: TestERC20; + let testERC721: TestERC721; + + let createMirrorBuyNowOrder: SeaportFixtures["createMirrorBuyNowOrder"]; + let createOrder: SeaportFixtures["createOrder"]; + let createTransferWithApproval: SeaportFixtures["createTransferWithApproval"]; + let deployNewConduit: SeaportFixtures["deployNewConduit"]; + let getTestItem1155: SeaportFixtures["getTestItem1155"]; + let mint1155: SeaportFixtures["mint1155"]; + let mint721: SeaportFixtures["mint721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + let set1155ApprovalForAll: SeaportFixtures["set1155ApprovalForAll"]; + let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + conduitController, + conduitImplementation, + conduitKeyOne, + conduitOne, + createMirrorBuyNowOrder, + createOrder, + createTransferWithApproval, + deployNewConduit, + EIP1271WalletFactory, + getTestItem1155, + marketplaceContract, + mint1155, + mint721, + mintAndApproveERC20, + set1155ApprovalForAll, + set721ApprovalForAll, + testERC1155, + testERC1155Two, + testERC20, + testERC721, + } = await seaportFixture(owner)); + }); + + let seller: Wallet; + let buyer: Wallet; + let zone: Wallet; + + let sellerContract: EIP1271Wallet; + let buyerContract: EIP1271Wallet; + + let tempConduit: ConduitInterface; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + seller = new ethers.Wallet(randomHex(32), provider); + buyer = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + sellerContract = await EIP1271WalletFactory.deploy(seller.address); + buyerContract = await EIP1271WalletFactory.deploy(buyer.address); + + // Deploy a new conduit + tempConduit = await deployNewConduit(owner); + + for (const wallet of [seller, buyer, zone, sellerContract, buyerContract]) { + await faucet(wallet.address, provider); + } + }); + + it("Adds a channel, and executes transfers (ERC1155 with batch)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + await testERC1155.mint(seller.address, nftId, amount.mul(2)); + await testERC1155.mint(seller.address, secondNftId, secondAmount.mul(2)); + await set1155ApprovalForAll(seller, tempConduit.address, true); + + await tempConduit.connect(seller).executeWithBatch1155( + [], + [ + { + token: testERC1155.address, + from: seller.address, + to: buyer.address, + ids: [nftId, secondNftId], + amounts: [amount, secondAmount], + }, + { + token: testERC1155.address, + from: seller.address, + to: buyer.address, + ids: [secondNftId, nftId], + amounts: [secondAmount, amount], + }, + ] + ); + }); + + it("Adds a channel, and executes only batch transfers (ERC1155 with batch)", async () => { + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + await testERC1155.mint(seller.address, nftId, amount.mul(2)); + await testERC1155.mint(seller.address, secondNftId, secondAmount.mul(2)); + await set1155ApprovalForAll(seller, tempConduit.address, true); + + await tempConduit.connect(seller).executeBatch1155([ + { + token: testERC1155.address, + from: seller.address, + to: buyer.address, + ids: [nftId, secondNftId], + amounts: [amount, secondAmount], + }, + { + token: testERC1155.address, + from: seller.address, + to: buyer.address, + ids: [secondNftId, nftId], + amounts: [secondAmount, amount], + }, + ]); + }); + + it("Adds a channel, and executes transfers (ERC721)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Seller mints nft + const nftId = randomBN(); + await testERC721.mint(seller.address, nftId); + + const secondNftId = randomBN(); + await testERC721.mint(seller.address, secondNftId); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); + expect(await testERC721.ownerOf(secondNftId)).to.equal(seller.address); + + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC721.connect(seller).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(seller.address, tempConduit.address, true); + }); + + await tempConduit.connect(seller).execute([ + { + itemType: 2, // ERC721 + token: testERC721.address, + from: seller.address, + to: buyer.address, + identifier: nftId, + amount: ethers.BigNumber.from(1), + }, + { + itemType: 2, // ERC721 + token: testERC721.address, + from: seller.address, + to: buyer.address, + identifier: secondNftId, + amount: ethers.BigNumber.from(1), + }, + ]); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); + expect(await testERC721.ownerOf(secondNftId)).to.equal(buyer.address); + }); + + it("Adds a channel, and executes transfers (ERC721 + ERC20)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Seller mints nft + const nftId = randomBN(); + await testERC721.mint(seller.address, nftId); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); + + // Set approval of nft + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC721.connect(seller).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(seller.address, tempConduit.address, true); + }); + + const tokenAmount = minRandom(100); + await testERC20.mint(seller.address, tokenAmount); + + // Check balance + expect(await testERC20.balanceOf(seller.address)).to.equal(tokenAmount); + + // Seller approves conduit contract to transfer tokens + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC20.connect(seller).approve(tempConduit.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(seller.address, tempConduit.address, tokenAmount); + }); + + // Send an ERC721 and (token amount - 100) ERC20 tokens + await tempConduit.connect(seller).execute([ + { + itemType: 2, // ERC721 + token: testERC721.address, + from: seller.address, + to: buyer.address, + identifier: nftId, + amount: ethers.BigNumber.from(1), + }, + { + itemType: 1, // ERC20 + token: testERC20.address, + from: seller.address, + to: buyer.address, + identifier: 0, + amount: tokenAmount.sub(100), + }, + ]); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); + // Check balance + expect(await testERC20.balanceOf(seller.address)).to.equal(100); + expect(await testERC20.balanceOf(buyer.address)).to.equal( + tokenAmount.sub(100) + ); + }); + + it("Adds a channel, and executes transfers (ERC721 + ERC1155)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Seller mints nft + const nftId = randomBN(); + await testERC721.mint(seller.address, nftId); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); + + // Set approval of nft + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC721.connect(seller).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(seller.address, tempConduit.address, true); + }); + + const secondNftId = random128(); + const amount = random128().add(1); + await testERC1155.mint(seller.address, secondNftId, amount); + + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC1155.connect(seller).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC1155, "ApprovalForAll") + .withArgs(seller.address, tempConduit.address, true); + }); + + // Check ownership + expect(await testERC1155.balanceOf(seller.address, secondNftId)).to.equal( + amount + ); + + // Send an ERC721 and ERC1155 + await tempConduit.connect(seller).execute([ + { + itemType: 2, // ERC721 + token: testERC721.address, + from: seller.address, + to: buyer.address, + identifier: nftId, + amount: ethers.BigNumber.from(1), + }, + { + itemType: 3, // ERC1155 + token: testERC1155.address, + from: seller.address, + to: buyer.address, + identifier: secondNftId, + amount: amount.sub(10), + }, + ]); + + // Check ownership + expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); + // Check balance + expect(await testERC1155.balanceOf(seller.address, secondNftId)).to.equal( + 10 + ); + expect(await testERC1155.balanceOf(buyer.address, secondNftId)).to.equal( + amount.sub(10) + ); + }); + + it("Adds a channel, and executes transfers (ERC20 + ERC1155)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Seller mints nft + const tokenAmount = minRandom(100).div(100); + await testERC20.mint(seller.address, tokenAmount); + + // Check balance + expect(await testERC20.balanceOf(seller.address)).to.equal(tokenAmount); + + // Seller approves conduit contract to transfer tokens + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC20.connect(seller).approve(tempConduit.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(seller.address, tempConduit.address, tokenAmount); + }); + + const nftId = random128(); + const erc1155amount = random128().add(1); + await testERC1155.mint(seller.address, nftId, erc1155amount); + + await whileImpersonating(seller.address, provider, async () => { + await expect( + testERC1155.connect(seller).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC1155, "ApprovalForAll") + .withArgs(seller.address, tempConduit.address, true); + }); + + // Check ownership + expect(await testERC1155.balanceOf(seller.address, nftId)).to.equal( + erc1155amount + ); + + // Send an ERC20 and ERC1155 + await tempConduit.connect(seller).execute([ + { + itemType: 1, // ERC20 + token: testERC20.address, + from: seller.address, + to: buyer.address, + identifier: 0, + amount: tokenAmount.sub(100), + }, + { + itemType: 3, // ERC1155 + token: testERC1155.address, + from: seller.address, + to: buyer.address, + identifier: nftId, + amount: erc1155amount.sub(10), + }, + ]); + + // Check balance + expect(await testERC20.balanceOf(seller.address)).to.equal(100); + expect(await testERC20.balanceOf(buyer.address)).to.equal( + tokenAmount.sub(100) + ); + expect(await testERC1155.balanceOf(seller.address, nftId)).to.equal(10); + expect(await testERC1155.balanceOf(buyer.address, nftId)).to.equal( + erc1155amount.sub(10) + ); + }); + + it("Adds a channel, and executes transfers (ERC20 + ERC721 + ERC1155)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + testERC20, + seller, + 1, + tempConduit.address, + seller.address, + buyer.address + ); + + // Create/Approve Y amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + testERC721, + seller, + 2, + tempConduit.address, + seller.address, + buyer.address + ); + + // Create/Approve Z amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + testERC1155, + seller, + 3, + tempConduit.address, + seller.address, + buyer.address + ); + + // Send an ERC20, ERC721, and ERC1155 + await tempConduit + .connect(seller) + .execute([erc20Transfer, erc721Transfer, erc1155Transfer]); + + // Check ownership + expect(await testERC721.ownerOf(erc721Transfer.identifier)).to.equal( + buyer.address + ); + // Check balance + expect(await testERC20.balanceOf(seller.address)).to.equal(0); + expect(await testERC20.balanceOf(buyer.address)).to.equal( + erc20Transfer.amount + ); + expect( + await testERC1155.balanceOf(seller.address, erc1155Transfer.identifier) + ).to.equal(0); + expect( + await testERC1155.balanceOf(buyer.address, erc1155Transfer.identifier) + ).to.equal(erc1155Transfer.amount); + }); + + it("Adds a channel, and executes transfers (many token types)", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + // Get 3 numbers whose value adds to Item Amount and minimum 1. + const itemsToCreate = 64; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + seller, + 1, + tempConduit.address, + seller.address, + buyer.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + seller, + 2, + tempConduit.address, + seller.address, + buyer.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + seller, + 3, + tempConduit.address, + seller.address, + buyer.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + // Send the transfers + await tempConduit.connect(seller).execute(transfers); + + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get itemType, token, from, to, amount, identifier + const itemType = transfers[i].itemType; + const token = contracts[i]; + const from = transfers[i].from; + const to = transfers[i].to; + const amount = transfers[i].amount; + const identifier = transfers[i].identifier; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf(from) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf(to) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(to); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(from, identifier)).to.equal(0); + expect(await token.balanceOf(to, identifier)).to.equal(amount); + break; + } + } + }); + + it("Reverts on calls to batch transfer 1155 items with no contract on a conduit", async () => { + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, owner.address, true); + }); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + await set1155ApprovalForAll(owner, tempConduit.address, true); + + await expect( + tempConduit.connect(owner).executeWithBatch1155( + [], + [ + { + token: ethers.constants.AddressZero, + from: owner.address, + to: buyer.address, + ids: [nftId, secondNftId], + amounts: [amount, secondAmount], + }, + ] + ) + ).to.be.revertedWith("NoContract"); + }); + + it("Reverts on calls to only batch transfer 1155 items with no contract on a conduit", async () => { + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, owner.address, true); + }); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + await set1155ApprovalForAll(owner, tempConduit.address, true); + + await expect( + tempConduit.connect(owner).executeBatch1155([ + { + token: ethers.constants.AddressZero, + from: owner.address, + to: buyer.address, + ids: [nftId, secondNftId], + amounts: [amount, secondAmount], + }, + ]) + ).to.be.revertedWith("NoContract"); + }); + + it("ERC1155 batch transfer reverts with revert data if it has sufficient gas", async () => { + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + await expect( + tempConduit.connect(seller).executeWithBatch1155( + [], + [ + { + token: testERC1155.address, + from: seller.address, + to: buyer.address, + ids: [1], + amounts: [1], + }, + ] + ) + ).to.be.revertedWith("NOT_AUTHORIZED"); + }); + if (!process.env.REFERENCE) { + it("ERC1155 batch transfer sends no data", async () => { + const receiver = await deployContract("ERC1155BatchRecipient", owner); + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( + owner, + 2 + ); + + await testERC1155.mint(seller.address, nftId, amount.mul(2)); + await testERC1155.mint(seller.address, secondNftId, secondAmount.mul(2)); + await testERC1155.mint(seller.address, thirdNftId, thirdAmount.mul(2)); + await set1155ApprovalForAll(seller, tempConduit.address, true); + + await tempConduit.connect(seller).executeWithBatch1155( + [], + [ + { + token: testERC1155.address, + from: seller.address, + to: receiver.address, + ids: [nftId, secondNftId, thirdNftId], + amounts: [amount, secondAmount, thirdAmount], + }, + { + token: testERC1155.address, + from: seller.address, + to: receiver.address, + ids: [secondNftId, nftId], + amounts: [secondAmount, amount], + }, + ] + ); + }); + + it("ERC1155 batch transfer reverts with generic error if it has insufficient gas to copy revert data", async () => { + const receiver = await deployContract("ExcessReturnDataRecipient", owner); + // Owner updates conduit channel to allow seller access + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, seller.address, true); + }); + + await expect( + tempConduit.connect(seller).executeWithBatch1155( + [], + [ + { + token: receiver.address, + from: seller.address, + to: receiver.address, + ids: [1], + amounts: [1], + }, + ] + ) + ).to.be.revertedWith( + `ERC1155BatchTransferGenericFailure("${receiver.address}", "${seller.address}", "${receiver.address}", [1], [1])` + ); + }); + } + + it("Makes batch transfer 1155 items through a conduit", async () => { + const tempConduitKey = owner.address + "ff00000000000000000000f1"; + + const { conduit: tempConduitAddress } = await conduitController.getConduit( + tempConduitKey + ); + + await conduitController + .connect(owner) + .createConduit(tempConduitKey, owner.address); + + const tempConduit = conduitImplementation.attach(tempConduitAddress); + + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, owner.address, true); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(owner, 2); + + const { nftId: nftId4, amount: amount4 } = await mint1155(owner, 2); + + const { nftId: nftId5, amount: amount5 } = await mint1155(owner, 2); + + const { nftId: nftId6, amount: amount6 } = await mint1155(owner, 2); + + const { nftId: nftId7, amount: amount7 } = await mint1155(owner, 2); + + const { nftId: nftId8, amount: amount8 } = await mint1155(owner, 2); + + const { nftId: nftId9, amount: amount9 } = await mint1155(owner, 2); + + const { nftId: nftId10, amount: amount10 } = await mint1155(owner, 2); + + await set1155ApprovalForAll(owner, tempConduit.address, true); + + await tempConduit.connect(owner).executeWithBatch1155( + [], + [ + { + token: testERC1155.address, + from: owner.address, + to: buyer.address, + ids: [ + nftId, + secondNftId, + thirdNftId, + nftId4, + nftId5, + nftId6, + nftId7, + nftId8, + nftId9, + nftId10, + ], + amounts: [ + amount, + secondAmount, + thirdAmount, + amount4, + amount5, + amount6, + amount7, + amount8, + amount9, + amount10, + ], + }, + ] + ); + }); + + it("Performs complex batch transfer through a conduit", async () => { + const tempConduitKey = owner.address + "f100000000000000000000f1"; + + const { conduit: tempConduitAddress } = await conduitController.getConduit( + tempConduitKey + ); + + await conduitController + .connect(owner) + .createConduit(tempConduitKey, owner.address); + + const tempConduit = conduitImplementation.attach(tempConduitAddress); + + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, owner.address, true); + + const { nftId, amount } = await mint1155(owner, 2); + + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + owner, + 2 + ); + + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(owner, 2); + + const { nftId: nftId4, amount: amount4 } = await mint1155(owner, 2); + + const { nftId: nftId5, amount: amount5 } = await mint1155( + owner, + 2, + testERC1155Two + ); + + const { nftId: nftId6, amount: amount6 } = await mint1155( + owner, + 2, + testERC1155Two + ); + + const { nftId: nftId7, amount: amount7 } = await mint1155( + owner, + 2, + testERC1155Two + ); + + const { nftId: nftId8, amount: amount8 } = await mint1155( + owner, + 2, + testERC1155Two + ); + + const amount9 = toBN(randomBN(4)).add(1); + await mintAndApproveERC20(owner, tempConduit.address, amount9.mul(2)); + + const nftId10 = await mint721(owner); + + await set1155ApprovalForAll(owner, tempConduit.address, true); + + await expect( + testERC1155Two.connect(owner).setApprovalForAll(tempConduit.address, true) + ) + .to.emit(testERC1155Two, "ApprovalForAll") + .withArgs(owner.address, tempConduit.address, true); + + await set721ApprovalForAll(owner, tempConduit.address, true); + + const newAddress = toAddress(12345); + + await tempConduit.connect(owner).executeWithBatch1155( + [ + { + itemType: 1, + token: testERC20.address, + from: owner.address, + to: newAddress, + identifier: toBN(0), + amount: amount9, + }, + { + itemType: 2, + token: testERC721.address, + from: owner.address, + to: newAddress, + identifier: nftId10, + amount: toBN(1), + }, + ], + [ + { + token: testERC1155.address, + from: owner.address, + to: newAddress, + ids: [nftId, secondNftId, thirdNftId, nftId4], + amounts: [amount, secondAmount, thirdAmount, amount4], + }, + { + token: testERC1155Two.address, + from: owner.address, + to: newAddress, + ids: [nftId5, nftId6, nftId7, nftId8], + amounts: [amount5, amount6, amount7, amount8], + }, + ] + ); + + expect(await testERC1155.balanceOf(newAddress, nftId)).to.equal(amount); + expect(await testERC1155.balanceOf(newAddress, secondNftId)).to.equal( + secondAmount + ); + expect(await testERC1155.balanceOf(newAddress, thirdNftId)).to.equal( + thirdAmount + ); + expect(await testERC1155.balanceOf(newAddress, nftId4)).to.equal(amount4); + + expect(await testERC1155Two.balanceOf(newAddress, nftId5)).to.equal( + amount5 + ); + expect(await testERC1155Two.balanceOf(newAddress, nftId6)).to.equal( + amount6 + ); + expect(await testERC1155Two.balanceOf(newAddress, nftId7)).to.equal( + amount7 + ); + expect(await testERC1155Two.balanceOf(newAddress, nftId8)).to.equal( + amount8 + ); + + expect(await testERC20.balanceOf(newAddress)).to.equal(amount9); + expect(await testERC721.ownerOf(nftId10)).to.equal(newAddress); + }); + + it("ERC1155 <=> ETH (match, two different groups of 1155's)", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const secondNftId = toBN(randomBN(4)); + const secondAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, secondNftId, secondAmount); + + // Seller mints third nft + const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(seller); + + // Seller mints fourth nft + const fourthNftId = toBN(randomBN(4)); + const fourthAmount = toBN(randomBN(4)); + await testERC1155Two.mint(seller.address, fourthNftId, fourthAmount); + + // Seller approves marketplace contract to transfer NFTs + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + await expect( + testERC1155Two + .connect(seller) + .setApprovalForAll(marketplaceContract.address, true) + ) + .to.emit(testERC1155Two, "ApprovalForAll") + .withArgs(seller.address, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + testERC1155Two.address + ), + getTestItem1155(thirdNftId, thirdAmount, thirdAmount), + getTestItem1155( + fourthNftId, + fourthAmount, + fourthAmount, + testERC1155Two.address + ), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[0, 2]], [[1, 2]]], + [[[0, 3]], [[1, 3]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(7); + + await marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + }); + + it("Reverts when attempting to update a conduit channel when call is not from controller", async () => { + await expect( + conduitOne + .connect(owner) + .updateChannel(ethers.constants.AddressZero, true) + ).to.be.revertedWith("InvalidController"); + }); + + it("Reverts when attempting to execute transfers on a conduit when not called from a channel", async () => { + const expectedRevertReason = + getCustomRevertSelector("ChannelClosed(address)") + + owner.address.slice(2).padStart(64, "0").toLowerCase(); + + const tx = await conduitOne.connect(owner).populateTransaction.execute([]); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect(conduitOne.connect(owner).execute([])).to.be.reverted; + }); + + it("Reverts when attempting to execute with 1155 transfers on a conduit when not called from a channel", async () => { + await expect( + conduitOne.connect(owner).executeWithBatch1155([], []) + ).to.be.revertedWith("ChannelClosed"); + }); + + it("Reverts when attempting to execute batch 1155 transfers on a conduit when not called from a channel", async () => { + await expect( + conduitOne.connect(owner).executeBatch1155([]) + ).to.be.revertedWith("ChannelClosed"); + }); + + it("Retrieves the owner of a conduit", async () => { + const ownerOf = await conduitController.ownerOf(conduitOne.address); + expect(ownerOf).to.equal(owner.address); + + await expect( + conduitController.connect(owner).ownerOf(buyer.address) + ).to.be.revertedWith("NoConduit"); + }); + + it("Retrieves the key of a conduit", async () => { + const key = await conduitController.getKey(conduitOne.address); + expect(key.toLowerCase()).to.equal(conduitKeyOne.toLowerCase()); + + await expect( + conduitController.connect(owner).getKey(buyer.address) + ).to.be.revertedWith("NoConduit"); + }); + + it("Retrieves the status of a conduit channel", async () => { + let isOpen = await conduitController.getChannelStatus( + conduitOne.address, + marketplaceContract.address + ); + expect(isOpen).to.be.true; + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + seller.address + ); + expect(isOpen).to.be.false; + + await expect( + conduitController + .connect(owner) + .getChannelStatus(buyer.address, seller.address) + ).to.be.revertedWith("NoConduit"); + }); + + it("Retrieves conduit channels from the controller", async () => { + const totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(1); + + await expect( + conduitController.connect(owner).getTotalChannels(buyer.address) + ).to.be.revertedWith("NoConduit"); + + const firstChannel = await conduitController.getChannel( + conduitOne.address, + 0 + ); + expect(firstChannel).to.equal(marketplaceContract.address); + + await expect( + conduitController + .connect(owner) + .getChannel(buyer.address, +totalChannels - 1) + ).to.be.revertedWith("NoConduit"); + + await expect( + conduitController.connect(owner).getChannel(conduitOne.address, 1) + ).to.be.revertedWith("ChannelOutOfRange"); + + await expect( + conduitController.connect(owner).getChannel(conduitOne.address, 2) + ).to.be.revertedWith("ChannelOutOfRange"); + + const channels = await conduitController.getChannels(conduitOne.address); + expect(channels.length).to.equal(1); + expect(channels[0]).to.equal(marketplaceContract.address); + + await expect( + conduitController.connect(owner).getChannels(buyer.address) + ).to.be.revertedWith("NoConduit"); + }); + + it("Adds and removes channels", async () => { + // Get number of open channels + let totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(1); + + let isOpen = await conduitController.getChannelStatus( + conduitOne.address, + marketplaceContract.address + ); + expect(isOpen).to.be.true; + + // No-op + await expect( + conduitController + .connect(owner) + .updateChannel(conduitOne.address, marketplaceContract.address, true) + ).to.be.reverted; // ChannelStatusAlreadySet + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + marketplaceContract.address + ); + expect(isOpen).to.be.true; + + // Get number of open channels + totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(1); + + await conduitController + .connect(owner) + .updateChannel(conduitOne.address, seller.address, true); + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + seller.address + ); + expect(isOpen).to.be.true; + + // Get number of open channels + totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(2); + + await conduitController + .connect(owner) + .updateChannel(conduitOne.address, marketplaceContract.address, false); + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + marketplaceContract.address + ); + expect(isOpen).to.be.false; + + // Get number of open channels + totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(1); + + await conduitController + .connect(owner) + .updateChannel(conduitOne.address, seller.address, false); + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + seller.address + ); + expect(isOpen).to.be.false; + + // Get number of open channels + totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(0); + + await conduitController + .connect(owner) + .updateChannel(conduitOne.address, marketplaceContract.address, true); + + isOpen = await conduitController.getChannelStatus( + conduitOne.address, + marketplaceContract.address + ); + expect(isOpen).to.be.true; + + // Get number of open channels + totalChannels = await conduitController.getTotalChannels( + conduitOne.address + ); + expect(totalChannels).to.equal(1); + }); + + it("Reverts on an attempt to move an unsupported item", async () => { + await conduitController + .connect(owner) + .updateChannel(conduitOne.address, seller.address, true); + + const isOpen = await conduitController.getChannelStatus( + conduitOne.address, + seller.address + ); + expect(isOpen).to.be.true; + + await expect( + conduitOne.connect(seller).executeWithBatch1155( + [ + { + itemType: 0, // NATIVE (invalid) + token: ethers.constants.AddressZero, + from: conduitOne.address, + to: seller.address, + identifier: 0, + amount: 0, + }, + ], + [] + ) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts when attempting to create a conduit not scoped to the creator", async () => { + await expect( + conduitController + .connect(owner) + .createConduit(ethers.constants.HashZero, owner.address) + ).to.be.revertedWith("InvalidCreator"); + }); + + it("Reverts when attempting to create a conduit that already exists", async () => { + await expect( + conduitController + .connect(owner) + .createConduit(conduitKeyOne, owner.address) + ).to.be.revertedWith(`ConduitAlreadyExists("${conduitOne.address}")`); + }); + + it("Reverts when attempting to update a channel for an unowned conduit", async () => { + await expect( + conduitController + .connect(buyer) + .updateChannel(conduitOne.address, buyer.address, true) + ).to.be.revertedWith(`CallerIsNotOwner("${conduitOne.address}")`); + }); + + it("Retrieves no initial potential owner for new conduit", async () => { + const potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(ethers.constants.AddressZero); + + await expect( + conduitController.connect(owner).getPotentialOwner(buyer.address) + ).to.be.revertedWith("NoConduit"); + }); + + it("Lets the owner transfer ownership via a two-stage process", async () => { + await expect( + conduitController + .connect(buyer) + .transferOwnership(conduitOne.address, buyer.address) + ).to.be.revertedWith("CallerIsNotOwner"); + + await expect( + conduitController + .connect(owner) + .transferOwnership(conduitOne.address, ethers.constants.AddressZero) + ).to.be.revertedWith("NewPotentialOwnerIsZeroAddress"); + + await expect( + conduitController + .connect(owner) + .transferOwnership(seller.address, buyer.address) + ).to.be.revertedWith("NoConduit"); + + let potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(ethers.constants.AddressZero); + + await conduitController.transferOwnership( + conduitOne.address, + buyer.address + ); + + potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(buyer.address); + + await expect( + conduitController + .connect(owner) + .transferOwnership(conduitOne.address, buyer.address) + ).to.be.revertedWith("NewPotentialOwnerAlreadySet"); + + await expect( + conduitController + .connect(buyer) + .cancelOwnershipTransfer(conduitOne.address) + ).to.be.revertedWith("CallerIsNotOwner"); + + await expect( + conduitController.connect(owner).cancelOwnershipTransfer(seller.address) + ).to.be.revertedWith("NoConduit"); + + await conduitController.cancelOwnershipTransfer(conduitOne.address); + + potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(ethers.constants.AddressZero); + + await expect( + conduitController + .connect(owner) + .cancelOwnershipTransfer(conduitOne.address) + ).to.be.revertedWith("NoPotentialOwnerCurrentlySet"); + + await conduitController.transferOwnership( + conduitOne.address, + buyer.address + ); + + potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(buyer.address); + + await expect( + conduitController.connect(buyer).acceptOwnership(seller.address) + ).to.be.revertedWith("NoConduit"); + + await expect( + conduitController.connect(seller).acceptOwnership(conduitOne.address) + ).to.be.revertedWith("CallerIsNotNewPotentialOwner"); + + await conduitController.connect(buyer).acceptOwnership(conduitOne.address); + + potentialOwner = await conduitController.getPotentialOwner( + conduitOne.address + ); + expect(potentialOwner).to.equal(ethers.constants.AddressZero); + + const ownerOf = await conduitController.ownerOf(conduitOne.address); + expect(ownerOf).to.equal(buyer.address); + }); +}); diff --git a/test/counter.spec.ts b/test/counter.spec.ts new file mode 100644 index 000000000..96982e7aa --- /dev/null +++ b/test/counter.spec.ts @@ -0,0 +1,906 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; + +import { + buildOrderStatus, + getItemETH, + randomBN, + randomHex, + toKey, +} from "./utils/encoding"; +import { seaportFixture } from "./utils/fixtures"; +import { VERSION, getCustomRevertSelector } from "./utils/helpers"; +import { faucet } from "./utils/impersonate"; + +import type { ConsiderationInterface } from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { Wallet } from "ethers"; + +const { parseEther } = ethers.utils; + +describe(`Validate, cancel, and increment counter flows (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 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, + marketplaceContract, + mintAndApprove721, + withBalanceChecks, + } = await seaportFixture(owner)); + }); + + let seller: Wallet; + let buyer: Wallet; + let zone: Wallet; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + seller = new ethers.Wallet(randomHex(32), provider); + buyer = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + for (const wallet of [seller, buyer, zone]) { + await faucet(wallet.address, provider); + } + }); + + describe("Validate", async () => { + it("Validate signed order and fill it with no signature", async () => { + // Seller mints an 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const signature = order.signature; + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.eq( + buildOrderStatus(false, false, 0, 0) + ); + + // cannot fill it with no signature yet + order.signature = "0x"; + + if (!process.env.REFERENCE) { + const expectedRevertReason = + getCustomRevertSelector("InvalidSignature()"); + + let tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + let returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + // cannot validate it with no signature from a random account + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + + tx = await marketplaceContract + .connect(owner) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + } else { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + // cannot validate it with no signature from a random account + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + } + + // can validate it once you add the signature back + order.signature = signature; + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.eq(buildOrderStatus(true, false, 0, 0)); + + // Can validate it repeatedly, but no event after the first time + await marketplaceContract.connect(owner).validate([order, order]); + + // Fulfill the order without a signature + order.signature = "0x"; + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + + const finalStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...finalStatus }).to.deep.eq( + buildOrderStatus(true, false, 1, 1) + ); + + // cannot validate it once it's been fully filled + await expect( + marketplaceContract.connect(owner).validate([order]) + ).to.be.revertedWith("OrderAlreadyFilled"); + }); + it("Validate unsigned order from offerer and fill it with no signature", async () => { + // Seller mints an 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + order.signature = "0x"; + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.eq( + buildOrderStatus(false, false, 0, 0) + ); + + if (!process.env.REFERENCE) { + // cannot fill it with no signature yet + const expectedRevertReason = + getCustomRevertSelector("InvalidSignature()"); + + let tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + let returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + // cannot validate it with no signature from a random account + tx = await marketplaceContract + .connect(owner) + .populateTransaction.validate([order]); + returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + } else { + // cannot fill it with no signature yet + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + // cannot validate it with no signature from a random account + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + } + + // can validate it from the seller + await expect(marketplaceContract.connect(seller).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.eq(buildOrderStatus(true, false, 0, 0)); + + // Fulfill the order without a signature + order.signature = "0x"; + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + + const finalStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...finalStatus }).to.deep.eq( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Cannot validate a cancelled order", async () => { + // Seller mints an nft + const nftId = randomBN(); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const signature = order.signature; + + order.signature = "0x"; + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.eq( + buildOrderStatus(false, false, 0, 0) + ); + + if (!process.env.REFERENCE) { + // cannot fill it with no signature yet + const expectedRevertReason = + getCustomRevertSelector("InvalidSignature()"); + + let tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + let returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + tx = await marketplaceContract + .connect(owner) + .populateTransaction.validate([order]); + returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + // cannot validate it with no signature from a random account + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + } else { + // cannot fill it with no signature yet + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + + // cannot validate it with no signature from a random account + await expect(marketplaceContract.connect(owner).validate([order])).to.be + .reverted; + } + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHash, seller.address, zone.address); + + // cannot validate it from the seller + await expect( + marketplaceContract.connect(seller).validate([order]) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + // cannot validate it with a signature either + order.signature = signature; + await expect( + marketplaceContract.connect(owner).validate([order]) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.eq(buildOrderStatus(false, true, 0, 0)); + }); + }); + + describe("Cancel", async () => { + it("Can cancel an order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // cannot cancel it from a random account + await expect( + marketplaceContract.connect(owner).cancel([orderComponents]) + ).to.be.revertedWith("InvalidCanceller"); + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.eq( + buildOrderStatus(false, false, 0, 0) + ); + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHash, seller.address, zone.address); + + // cannot fill the order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.eq(buildOrderStatus(false, true, 0, 0)); + }); + it("Can cancel a validated order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // cannot cancel it from a random account + await expect( + marketplaceContract.connect(owner).cancel([orderComponents]) + ).to.be.revertedWith("InvalidCanceller"); + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // Can validate it + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.equal( + buildOrderStatus(true, false, 0, 0) + ); + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHash, seller.address, zone.address); + + // cannot fill the order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + const finalStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...finalStatus }).to.deep.equal( + buildOrderStatus(false, true, 0, 0) + ); + }); + it("Can cancel an order from the zone", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // cannot cancel it from a random account + await expect( + marketplaceContract.connect(owner).cancel([orderComponents]) + ).to.be.revertedWith("InvalidCanceller"); + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // can cancel it from the zone + await expect(marketplaceContract.connect(zone).cancel([orderComponents])) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHash, seller.address, zone.address); + + // cannot fill the order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.equal( + buildOrderStatus(false, true, 0, 0) + ); + }); + it("Can cancel a validated order from a zone", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const initialStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...initialStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // Can validate it + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + // cannot cancel it from a random account + await expect( + marketplaceContract.connect(owner).cancel([orderComponents]) + ).to.be.revertedWith("InvalidCanceller"); + + const newStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...newStatus }).to.deep.equal( + buildOrderStatus(true, false, 0, 0) + ); + + // can cancel it from the zone + await expect(marketplaceContract.connect(zone).cancel([orderComponents])) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHash, seller.address, zone.address); + + // cannot fill the order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); + + const finalStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...finalStatus }).to.deep.equal( + buildOrderStatus(false, true, 0, 0) + ); + }); + }); + + describe("Increment Counter", async () => { + it("Can increment the counter", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + let { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const counter = await marketplaceContract.getCounter(seller.address); + expect(counter).to.equal(0); + expect(orderComponents.counter).to.equal(counter); + + // can increment the counter + await expect(marketplaceContract.connect(seller).incrementCounter()) + .to.emit(marketplaceContract, "CounterIncremented") + .withArgs(1, seller.address); + + const newCounter = await marketplaceContract.getCounter(seller.address); + expect(newCounter).to.equal(1); + + if (!process.env.REFERENCE) { + // Cannot fill order anymore + const expectedRevertReason = getCustomRevertSelector("InvalidSigner()"); + + const tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } else { + // Cannot fill order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + + const newOrderDetails = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + order = newOrderDetails.order; + orderHash = newOrderDetails.orderHash; + value = newOrderDetails.value; + orderComponents = newOrderDetails.orderComponents; + + expect(orderComponents.counter).to.equal(newCounter); + + // Can fill order with new counter + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("Can increment the counter and implicitly cancel a validated order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + let { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const counter = await marketplaceContract.getCounter(seller.address); + expect(counter).to.equal(0); + expect(orderComponents.counter).to.equal(counter); + + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + // can increment the counter + await expect(marketplaceContract.connect(seller).incrementCounter()) + .to.emit(marketplaceContract, "CounterIncremented") + .withArgs(1, seller.address); + + const newCounter = await marketplaceContract.getCounter(seller.address); + expect(newCounter).to.equal(1); + + if (!process.env.REFERENCE) { + // Cannot fill order anymore + const expectedRevertReason = getCustomRevertSelector("InvalidSigner()"); + + const tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } else { + // Cannot fill order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + + const newOrderDetails = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + order = newOrderDetails.order; + orderHash = newOrderDetails.orderHash; + value = newOrderDetails.value; + orderComponents = newOrderDetails.orderComponents; + + expect(orderComponents.counter).to.equal(newCounter); + + // Can fill order with new counter + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + it("Can increment the counter as the zone and implicitly cancel a validated order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + let { order, orderHash, value, orderComponents } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const counter = await marketplaceContract.getCounter(seller.address); + expect(counter).to.equal(0); + expect(orderComponents.counter).to.equal(counter); + + await expect(marketplaceContract.connect(owner).validate([order])) + .to.emit(marketplaceContract, "OrderValidated") + .withArgs(orderHash, seller.address, zone.address); + + // can increment the counter as the offerer + await expect(marketplaceContract.connect(seller).incrementCounter()) + .to.emit(marketplaceContract, "CounterIncremented") + .withArgs(1, seller.address); + + const newCounter = await marketplaceContract.getCounter(seller.address); + expect(newCounter).to.equal(1); + + if (!process.env.REFERENCE) { + // Cannot fill order anymore + const expectedRevertReason = getCustomRevertSelector("InvalidSigner()"); + + const tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillOrder(order, toKey(0), { + value, + }); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } else { + // Cannot fill order anymore + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + + const newOrderDetails = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + order = newOrderDetails.order; + orderHash = newOrderDetails.orderHash; + value = newOrderDetails.value; + orderComponents = newOrderDetails.orderComponents; + + expect(orderComponents.counter).to.equal(newCounter); + + // Can fill order with new counter + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ]); + + return receipt; + }); + }); + }); +}); diff --git a/test/findings/AdditionalRecipientsOffByOne.spec.ts b/test/findings/AdditionalRecipientsOffByOne.spec.ts index 3a26f1110..f9d38de85 100644 --- a/test/findings/AdditionalRecipientsOffByOne.spec.ts +++ b/test/findings/AdditionalRecipientsOffByOne.spec.ts @@ -1,17 +1,21 @@ import { expect } from "chai"; -import { constants, Wallet } from "ethers"; -import { +import { constants } from "ethers"; +import { hexZeroPad } from "ethers/lib/utils"; +import { network } from "hardhat"; +import { getScuffedContract } from "scuffed-abi"; + +import { buildOrderStatus, getBasicOrderParameters } from "../utils/encoding"; +import { seaportFixture } from "../utils/fixtures"; +import { getWalletWithEther } from "../utils/impersonate"; + +import type { ConsiderationInterface, TestERC20, TestERC721, } from "../../typechain-types"; -import { buildOrderStatus, getBasicOrderParameters } from "../utils/encoding"; -import { seaportFixture, SeaportFixtures } from "../utils/fixtures"; -import { getWalletWithEther } from "../utils/impersonate"; -import { AdvancedOrder, ConsiderationItem } from "../utils/types"; -import { getScuffedContract } from "scuffed-abi"; -import { hexZeroPad } from "ethers/lib/utils"; -import { network } from "hardhat"; +import type { SeaportFixtures } from "../utils/fixtures"; +import type { AdvancedOrder, ConsiderationItem } from "../utils/types"; +import type { Wallet } from "ethers"; const IS_FIXED = true; @@ -19,16 +23,19 @@ describe("Additional recipients off by one error allows skipping second consider let alice: Wallet; let bob: Wallet; let carol: Wallet; + let order: AdvancedOrder; let orderHash: string; + + let marketplaceContract: ConsiderationInterface; let testERC20: TestERC20; let testERC721: TestERC721; - let marketplaceContract: ConsiderationInterface; - let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; - let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + + let createOrder: SeaportFixtures["createOrder"]; let getTestItem20: SeaportFixtures["getTestItem20"]; let getTestItem721: SeaportFixtures["getTestItem721"]; - let createOrder: SeaportFixtures["createOrder"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; after(async () => { await network.provider.request({ @@ -40,18 +47,21 @@ describe("Additional recipients off by one error allows skipping second consider alice = await getWalletWithEther(); bob = await getWalletWithEther(); carol = await getWalletWithEther(); + ({ - mintAndApprove721, - mintAndApproveERC20, - marketplaceContract, + createOrder, getTestItem20, getTestItem721, - createOrder, + marketplaceContract, + mintAndApprove721, + mintAndApproveERC20, testERC20, testERC721, } = await seaportFixture(await getWalletWithEther())); + // ERC721 with ID = 1 await mintAndApprove721(alice, marketplaceContract.address, 1); + // ERC20 with amount = 1100 await mintAndApproveERC20(bob, marketplaceContract.address, 1100); }); diff --git a/test/findings/CriteriaResolverUnhashedLeaves.spec.ts b/test/findings/CriteriaResolverUnhashedLeaves.spec.ts index cf0ecc633..dc0ed292e 100644 --- a/test/findings/CriteriaResolverUnhashedLeaves.spec.ts +++ b/test/findings/CriteriaResolverUnhashedLeaves.spec.ts @@ -1,16 +1,20 @@ import { expect } from "chai"; -import { BigNumber, constants, Wallet } from "ethers"; +import { constants } from "ethers"; import { network } from "hardhat"; -import { + +import { merkleTree } from "../utils/criteria"; +import { buildResolver, toBN, toKey } from "../utils/encoding"; +import { seaportFixture } from "../utils/fixtures"; +import { getWalletWithEther } from "../utils/impersonate"; + +import type { ConsiderationInterface, TestERC20, TestERC721, } from "../../typechain-types"; -import { buildResolver, toBN, toKey } from "../utils/encoding"; -import { seaportFixture, SeaportFixtures } from "../utils/fixtures"; -import { getWalletWithEther } from "../utils/impersonate"; -import { AdvancedOrder } from "../utils/types"; -const { merkleTree } = require("../utils/criteria"); +import type { SeaportFixtures } from "../utils/fixtures"; +import type { AdvancedOrder } from "../utils/types"; +import type { BigNumber, Wallet } from "ethers"; const IS_FIXED = true; @@ -18,16 +22,20 @@ describe("Criteria resolver allows root hash to be given as a leaf", async () => let alice: Wallet; let bob: Wallet; let carol: Wallet; + let order: AdvancedOrder; + + let marketplaceContract: ConsiderationInterface; let testERC20: TestERC20; let testERC721: TestERC721; - let marketplaceContract: ConsiderationInterface; - let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; - let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + + let createOrder: SeaportFixtures["createOrder"]; let getTestItem20: SeaportFixtures["getTestItem20"]; let getTestItem721WithCriteria: SeaportFixtures["getTestItem721WithCriteria"]; - let createOrder: SeaportFixtures["createOrder"]; let mint721s: SeaportFixtures["mint721s"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; + let tokenIds: BigNumber[]; let root: string; @@ -44,20 +52,23 @@ describe("Criteria resolver allows root hash to be given as a leaf", async () => alice = await getWalletWithEther(); bob = await getWalletWithEther(); carol = await getWalletWithEther(); + ({ - mintAndApproveERC20, - marketplaceContract, + createOrder, getTestItem20, getTestItem721WithCriteria, + marketplaceContract, + mint721s, + mintAndApproveERC20, set721ApprovalForAll, - createOrder, testERC20, testERC721, - mint721s, } = await seaportFixture(await getWalletWithEther())); + await mintAndApproveERC20(alice, marketplaceContract.address, 1000); await set721ApprovalForAll(bob, marketplaceContract.address); await set721ApprovalForAll(carol, marketplaceContract.address); + tokenIds = await mint721s(bob, 10); ({ root } = merkleTree(tokenIds)); }); @@ -98,7 +109,7 @@ describe("Criteria resolver allows root hash to be given as a leaf", async () => .fulfillAdvancedOrder( order, [criteriaResolver], - toKey(false), + toKey(0), carol.address ); }); @@ -120,7 +131,7 @@ describe("Criteria resolver allows root hash to be given as a leaf", async () => .fulfillAdvancedOrder( order, [criteriaResolver], - toKey(false), + toKey(0), carol.address ) ).to.be.revertedWith("InvalidProof"); diff --git a/test/findings/FulfillmentOverflowWithMissingItems.spec.ts b/test/findings/FulfillmentOverflowWithMissingItems.spec.ts index d009539a6..6c064e75a 100644 --- a/test/findings/FulfillmentOverflowWithMissingItems.spec.ts +++ b/test/findings/FulfillmentOverflowWithMissingItems.spec.ts @@ -1,31 +1,38 @@ import { expect } from "chai"; -import { constants, Wallet } from "ethers"; +import { constants } from "ethers"; import { network } from "hardhat"; -import { + +import { toFulfillment } from "../utils/encoding"; +import { seaportFixture } from "../utils/fixtures"; +import { getWalletWithEther } from "../utils/impersonate"; + +import type { ConsiderationInterface, TestERC20, TestERC721, } from "../../typechain-types"; -import { toFulfillment } from "../utils/encoding"; -import { seaportFixture, SeaportFixtures } from "../utils/fixtures"; -import { getWalletWithEther } from "../utils/impersonate"; -import { AdvancedOrder, OfferItem } from "../utils/types"; +import type { SeaportFixtures } from "../utils/fixtures"; +import type { AdvancedOrder, OfferItem } from "../utils/types"; +import type { Wallet } from "ethers"; const IS_FIXED = true; describe("Fulfillment applier allows overflow when a missing item is provided", async () => { let alice: Wallet; let bob: Wallet; + let order: AdvancedOrder; let maliciousOrder: AdvancedOrder; + + let marketplaceContract: ConsiderationInterface; let testERC20: TestERC20; let testERC721: TestERC721; - let marketplaceContract: ConsiderationInterface; - let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; - let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + + let createOrder: SeaportFixtures["createOrder"]; let getTestItem20: SeaportFixtures["getTestItem20"]; let getTestItem721: SeaportFixtures["getTestItem721"]; - let createOrder: SeaportFixtures["createOrder"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; after(async () => { await network.provider.request({ @@ -37,20 +44,24 @@ describe("Fulfillment applier allows overflow when a missing item is provided", if (process.env.REFERENCE) { this.skip(); } + alice = await getWalletWithEther(); bob = await getWalletWithEther(); + ({ - mintAndApprove721, - mintAndApproveERC20, - marketplaceContract, + createOrder, getTestItem20, getTestItem721, - createOrder, + marketplaceContract, + mintAndApprove721, + mintAndApproveERC20, testERC20, testERC721, } = await seaportFixture(await getWalletWithEther())); + // ERC721 with ID = 1 await mintAndApprove721(alice, marketplaceContract.address, 1); + // ERC20 with amount = 1100 await mintAndApproveERC20(bob, marketplaceContract.address, 1); }); diff --git a/test/findings/PartialFillFractionOverflow.spec.ts b/test/findings/PartialFillFractionOverflow.spec.ts index 5da4d3785..413d096af 100644 --- a/test/findings/PartialFillFractionOverflow.spec.ts +++ b/test/findings/PartialFillFractionOverflow.spec.ts @@ -1,15 +1,19 @@ import { expect } from "chai"; -import { constants, Wallet } from "ethers"; +import { constants } from "ethers"; import { network } from "hardhat"; -import { + +import { buildOrderStatus, toBN, toKey } from "../utils/encoding"; +import { seaportFixture } from "../utils/fixtures"; +import { getWalletWithEther } from "../utils/impersonate"; + +import type { ConsiderationInterface, TestERC1155, TestERC20, } from "../../typechain-types"; -import { buildOrderStatus, toBN, toKey } from "../utils/encoding"; -import { seaportFixture, SeaportFixtures } from "../utils/fixtures"; -import { getWalletWithEther } from "../utils/impersonate"; -import { AdvancedOrder, ConsiderationItem } from "../utils/types"; +import type { SeaportFixtures } from "../utils/fixtures"; +import type { AdvancedOrder, ConsiderationItem } from "../utils/types"; +import type { Wallet } from "ethers"; const IS_FIXED = true; @@ -17,16 +21,19 @@ describe("Partial fill fractions can overflow to reset an order", async () => { let alice: Wallet; let bob: Wallet; let carol: Wallet; + let order: AdvancedOrder; let orderHash: string; - let testERC20: TestERC20; - let testERC1155: TestERC1155; + let marketplaceContract: ConsiderationInterface; + let testERC1155: TestERC1155; + let testERC20: TestERC20; + + let createOrder: SeaportFixtures["createOrder"]; + let getTestItem1155: SeaportFixtures["getTestItem1155"]; + let getTestItem20: SeaportFixtures["getTestItem20"]; let mintAndApprove1155: SeaportFixtures["mintAndApprove1155"]; let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; - let getTestItem20: SeaportFixtures["getTestItem20"]; - let getTestItem1155: SeaportFixtures["getTestItem1155"]; - let createOrder: SeaportFixtures["createOrder"]; after(async () => { await network.provider.request({ @@ -38,19 +45,22 @@ describe("Partial fill fractions can overflow to reset an order", async () => { if (process.env.REFERENCE) { this.skip(); } + alice = await getWalletWithEther(); bob = await getWalletWithEther(); carol = await getWalletWithEther(); + ({ + createOrder, + getTestItem1155, + getTestItem20, + marketplaceContract, mintAndApprove1155, mintAndApproveERC20, - marketplaceContract, - getTestItem20, - getTestItem1155, - createOrder, - testERC20, testERC1155, + testERC20, } = await seaportFixture(await getWalletWithEther())); + await mintAndApprove1155(alice, marketplaceContract.address, 1, 1, 10); await mintAndApproveERC20(bob, marketplaceContract.address, 500); await mintAndApproveERC20(carol, marketplaceContract.address, 4500); @@ -107,7 +117,7 @@ describe("Partial fill fractions can overflow to reset an order", async () => { order.denominator = 2; await marketplaceContract .connect(bob) - .fulfillAdvancedOrder(order, [], toKey(false), bob.address); + .fulfillAdvancedOrder(order, [], toKey(0), bob.address); expect(await testERC1155.balanceOf(bob.address, 1)).to.eq(1); }); @@ -122,7 +132,7 @@ describe("Partial fill fractions can overflow to reset an order", async () => { order.denominator = toBN(2).pow(119); await marketplaceContract .connect(carol) - .fulfillAdvancedOrder(order, [], toKey(false), carol.address); + .fulfillAdvancedOrder(order, [], toKey(0), carol.address); }); it("Carol receives one 1155 token from Alice", async () => { @@ -149,12 +159,12 @@ describe("Partial fill fractions can overflow to reset an order", async () => { order.denominator = toBN(2).pow(2); await marketplaceContract .connect(carol) - .fulfillAdvancedOrder(order, [], toKey(false), carol.address); + .fulfillAdvancedOrder(order, [], toKey(0), carol.address); order.numerator = toBN(2).pow(118); order.denominator = toBN(2).pow(119); await marketplaceContract .connect(carol) - .fulfillAdvancedOrder(order, [], toKey(false), carol.address); + .fulfillAdvancedOrder(order, [], toKey(0), carol.address); } }); diff --git a/test/foundry/CeilEquivalenceTest.t.sol b/test/foundry/CeilEquivalenceTest.t.sol index 168233ac6..ba8f64c03 100644 --- a/test/foundry/CeilEquivalenceTest.t.sol +++ b/test/foundry/CeilEquivalenceTest.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; contract CeilEquivalenceTest { function testCeilEquivalence(uint256 numerator, uint256 denominator) diff --git a/test/foundry/FulfillAdvancedOrder.t.sol b/test/foundry/FulfillAdvancedOrder.t.sol index d606d6f46..30418dc45 100644 --- a/test/foundry/FulfillAdvancedOrder.t.sol +++ b/test/foundry/FulfillAdvancedOrder.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OneWord } from "../../contracts/lib/ConsiderationConstants.sol"; import { OrderType, ItemType } from "../../contracts/lib/ConsiderationEnums.sol"; diff --git a/test/foundry/FulfillAdvancedOrderCriteria.t.sol b/test/foundry/FulfillAdvancedOrderCriteria.t.sol index e8207b1b2..31526298b 100644 --- a/test/foundry/FulfillAdvancedOrderCriteria.t.sol +++ b/test/foundry/FulfillAdvancedOrderCriteria.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseOrderTest } from "./utils/BaseOrderTest.sol"; import { Merkle } from "murky/Merkle.sol"; diff --git a/test/foundry/FulfillAvailableAdvancedOrder.t.sol b/test/foundry/FulfillAvailableAdvancedOrder.t.sol index ebed17425..bc962e5f4 100644 --- a/test/foundry/FulfillAvailableAdvancedOrder.t.sol +++ b/test/foundry/FulfillAvailableAdvancedOrder.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol"; import { AdditionalRecipient } from "../../contracts/lib/ConsiderationStructs.sol"; diff --git a/test/foundry/FulfillAvailableAdvancedOrderCriteria.t.sol b/test/foundry/FulfillAvailableAdvancedOrderCriteria.t.sol index 79f7abe38..9ce720e52 100644 --- a/test/foundry/FulfillAvailableAdvancedOrderCriteria.t.sol +++ b/test/foundry/FulfillAvailableAdvancedOrderCriteria.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseOrderTest } from "./utils/BaseOrderTest.sol"; import { Merkle } from "murky/Merkle.sol"; diff --git a/test/foundry/FulfillBasicOrderTest.t.sol b/test/foundry/FulfillBasicOrderTest.t.sol index c5373756b..34efa7c89 100644 --- a/test/foundry/FulfillBasicOrderTest.t.sol +++ b/test/foundry/FulfillBasicOrderTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT //Author: CupOJoseph -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol"; import { AdditionalRecipient, Order } from "../../contracts/lib/ConsiderationStructs.sol"; diff --git a/test/foundry/FulfillOrderTest.t.sol b/test/foundry/FulfillOrderTest.t.sol index 3d05a820a..29228ee42 100644 --- a/test/foundry/FulfillOrderTest.t.sol +++ b/test/foundry/FulfillOrderTest.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol"; import { AdditionalRecipient } from "../../contracts/lib/ConsiderationStructs.sol"; diff --git a/test/foundry/FullfillAvailableOrder.t.sol b/test/foundry/FullfillAvailableOrder.t.sol index 8b8f67127..c602a2fc7 100644 --- a/test/foundry/FullfillAvailableOrder.t.sol +++ b/test/foundry/FullfillAvailableOrder.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol"; import { ConsiderationInterface } from "../../contracts/interfaces/ConsiderationInterface.sol"; diff --git a/test/foundry/GetterTests.t.sol b/test/foundry/GetterTests.t.sol index 079323047..b8f85c222 100644 --- a/test/foundry/GetterTests.t.sol +++ b/test/foundry/GetterTests.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseConsiderationTest } from "./utils/BaseConsiderationTest.sol"; diff --git a/test/foundry/MatchAdvancedOrder.t.sol b/test/foundry/MatchAdvancedOrder.t.sol index 006f3dab3..7902ee172 100644 --- a/test/foundry/MatchAdvancedOrder.t.sol +++ b/test/foundry/MatchAdvancedOrder.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, ItemType } from "../../contracts/lib/ConsiderationEnums.sol"; import { Order } from "../../contracts/lib/ConsiderationStructs.sol"; diff --git a/test/foundry/MatchOrders.t.sol b/test/foundry/MatchOrders.t.sol index 39dec3dfc..7c69ad752 100644 --- a/test/foundry/MatchOrders.t.sol +++ b/test/foundry/MatchOrders.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, ItemType } from "../../contracts/lib/ConsiderationEnums.sol"; import { Order, Fulfillment, OfferItem, OrderParameters, ConsiderationItem, OrderComponents, FulfillmentComponent } from "../../contracts/lib/ConsiderationStructs.sol"; diff --git a/test/foundry/NonReentrant.t.sol b/test/foundry/NonReentrant.t.sol index f23f60da3..1f60bbc4a 100644 --- a/test/foundry/NonReentrant.t.sol +++ b/test/foundry/NonReentrant.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol"; import { ConsiderationInterface } from "../../contracts/interfaces/ConsiderationInterface.sol"; diff --git a/test/foundry/SignatureVerification.t.sol b/test/foundry/SignatureVerification.t.sol new file mode 100644 index 000000000..71237528c --- /dev/null +++ b/test/foundry/SignatureVerification.t.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { SignatureVerification } from "../../contracts/lib/SignatureVerification.sol"; +import { ReferenceSignatureVerification } from "../../reference/lib/ReferenceSignatureVerification.sol"; +import { GettersAndDerivers } from "../../contracts/lib/GettersAndDerivers.sol"; +import { ReferenceGettersAndDerivers } from "../../reference/lib/ReferenceGettersAndDerivers.sol"; +import { BaseOrderTest } from "./utils/BaseOrderTest.sol"; +import { OrderParameters } from "../../contracts/lib/ConsiderationStructs.sol"; +import { ConsiderationInterface } from "../../contracts/interfaces/ConsiderationInterface.sol"; + +interface GetterAndDeriver { + function deriveOrderHash( + OrderParameters memory orderParameters, + uint256 counter + ) external returns (bytes32 orderHash); + + function domainSeparator() external returns (bytes32); + + function deriveEIP712Digest(bytes32 _domainSeparator_, bytes32 orderHash) + external + returns (bytes32 value); +} + +contract GettersAndDeriversImpl is GetterAndDeriver, GettersAndDerivers { + constructor(address conduitController) + GettersAndDerivers(conduitController) + {} + + function deriveOrderHash( + OrderParameters memory orderParameters, + uint256 counter + ) public view returns (bytes32 orderHash) { + return _deriveOrderHash(orderParameters, counter); + } + + function domainSeparator() public view returns (bytes32) { + return _domainSeparator(); + } + + function deriveEIP712Digest(bytes32 _domainSeparator_, bytes32 orderHash) + public + pure + returns (bytes32 value) + { + return _deriveEIP712Digest(_domainSeparator_, orderHash); + } +} + +contract ReferenceGettersAndDeriversImpl is + GetterAndDeriver, + ReferenceGettersAndDerivers +{ + constructor(address conduitController) + ReferenceGettersAndDerivers(conduitController) + {} + + function deriveOrderHash( + OrderParameters memory orderParameters, + uint256 counter + ) public view returns (bytes32 orderHash) { + return _deriveOrderHash(orderParameters, counter); + } + + function domainSeparator() public view returns (bytes32) { + return _domainSeparator(); + } + + function deriveEIP712Digest(bytes32 _domainSeparator_, bytes32 orderHash) + public + pure + returns (bytes32 value) + { + return _deriveEIP712Digest(_domainSeparator_, orderHash); + } +} + +contract SignatureVerifierLogic is BaseOrderTest, SignatureVerification { + GetterAndDeriver getterAndDeriver; + + constructor( + address _conduitController, + ConsiderationInterface _consideration + ) { + getterAndDeriver = GetterAndDeriver( + new GettersAndDeriversImpl(address(_conduitController)) + ); + + vm.label(address(getterAndDeriver), "getterAndDeriver"); + consideration = _consideration; + } + + function signatureVerificationDirtyScratchSpace() external { + addErc721OfferItem(1); + addEthConsiderationItem(alice, 1); + + // create order where alice is offerer, but signer is *BOB* + configureOrderParameters(alice); + _configureOrderComponents(consideration.getCounter(alice)); + bytes32 orderHash = consideration.getOrderHash(baseOrderComponents); + bytes memory signature = signOrder(consideration, bobPk, orderHash); + + bytes32 domainSeparator = getterAndDeriver.domainSeparator(); + bytes32 digest = getterAndDeriver.deriveEIP712Digest( + domainSeparator, + orderHash + ); + + // store bob's address in scratch space + assembly { + mstore(0x0, sload(bob.slot)) + } + + _assertValidSignature(alice, digest, signature); + } +} + +contract ReferenceSignatureVerifierLogic is + BaseOrderTest, + ReferenceSignatureVerification +{ + GetterAndDeriver getterAndDeriver; + + constructor( + address _conduitController, + ConsiderationInterface _consideration + ) { + getterAndDeriver = GetterAndDeriver( + new ReferenceGettersAndDeriversImpl(address(_conduitController)) + ); + vm.label(address(getterAndDeriver), "referenceGetterAndDeriver"); + consideration = _consideration; + } + + function referenceSignatureVerificationDirtyScratchSpace() external { + addErc721OfferItem(1); + addEthConsiderationItem(alice, 1); + + // create order where alice is offerer, but signer is *BOB* + configureOrderParameters(alice); + _configureOrderComponents(consideration.getCounter(alice)); + bytes32 orderHash = consideration.getOrderHash(baseOrderComponents); + bytes memory signature = signOrder(consideration, bobPk, orderHash); + + bytes32 domainSeparator = getterAndDeriver.domainSeparator(); + bytes32 digest = getterAndDeriver.deriveEIP712Digest( + domainSeparator, + orderHash + ); + + // store bob's address in scratch space + assembly { + mstore(0x0, sload(bob.slot)) + } + + _assertValidSignature(alice, digest, signature); + } +} + +contract SignatureVerificationTest is BaseOrderTest { + function test(function() external fn) internal { + try fn() {} catch (bytes memory reason) { + assertPass(reason); + } + } + + function testSignatureVerification() public { + SignatureVerifierLogic logic = new SignatureVerifierLogic( + address(conduitController), + consideration + ); + vm.expectRevert(abi.encodeWithSignature("InvalidSigner()")); + logic.signatureVerificationDirtyScratchSpace(); + ReferenceSignatureVerifierLogic referenceLogic = new ReferenceSignatureVerifierLogic( + address(referenceConduitController), + referenceConsideration + ); + vm.expectRevert(abi.encodeWithSignature("InvalidSigner()")); + referenceLogic.referenceSignatureVerificationDirtyScratchSpace(); + } +} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 88e7683de..45381c508 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; // prettier-ignore import { BaseConsiderationTest } from "./utils/BaseConsiderationTest.sol"; diff --git a/test/foundry/conduit/BaseConduitTest.sol b/test/foundry/conduit/BaseConduitTest.sol index da7d87236..57636b5cf 100644 --- a/test/foundry/conduit/BaseConduitTest.sol +++ b/test/foundry/conduit/BaseConduitTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseConsiderationTest } from "../utils/BaseConsiderationTest.sol"; import { ConduitTransfer, ConduitItemType, ConduitBatch1155Transfer } from "../../../contracts/conduit/lib/ConduitStructs.sol"; diff --git a/test/foundry/conduit/ConduitExecute.t.sol b/test/foundry/conduit/ConduitExecute.t.sol index 1cc43ab7c..258652d2a 100644 --- a/test/foundry/conduit/ConduitExecute.t.sol +++ b/test/foundry/conduit/ConduitExecute.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseConsiderationTest } from "../utils/BaseConsiderationTest.sol"; import { ConduitTransfer, ConduitItemType } from "../../../contracts/conduit/lib/ConduitStructs.sol"; diff --git a/test/foundry/conduit/ConduitExecuteBatch1155.t.sol b/test/foundry/conduit/ConduitExecuteBatch1155.t.sol index 8d41f7259..1e390da97 100644 --- a/test/foundry/conduit/ConduitExecuteBatch1155.t.sol +++ b/test/foundry/conduit/ConduitExecuteBatch1155.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseConsiderationTest } from "../utils/BaseConsiderationTest.sol"; import { ConduitTransfer, ConduitBatch1155Transfer, ConduitItemType } from "../../../contracts/conduit/lib/ConduitStructs.sol"; diff --git a/test/foundry/conduit/ConduitExecuteWithBatch1155.t.sol b/test/foundry/conduit/ConduitExecuteWithBatch1155.t.sol index 3d2c9ed93..a5bafb5e0 100644 --- a/test/foundry/conduit/ConduitExecuteWithBatch1155.t.sol +++ b/test/foundry/conduit/ConduitExecuteWithBatch1155.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { Conduit } from "../../../contracts/conduit/Conduit.sol"; import { ConduitController } from "../../../contracts/conduit/ConduitController.sol"; diff --git a/test/foundry/interfaces/OwnableDelegateProxy.sol b/test/foundry/interfaces/OwnableDelegateProxy.sol index c4cb97ecd..ddd5b3770 100644 --- a/test/foundry/interfaces/OwnableDelegateProxy.sol +++ b/test/foundry/interfaces/OwnableDelegateProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; interface OwnableDelegateProxy { function name() external returns (string memory); diff --git a/test/foundry/interfaces/ProxyRegistry.sol b/test/foundry/interfaces/ProxyRegistry.sol index 90fa938bc..5f95e1bc7 100644 --- a/test/foundry/interfaces/ProxyRegistry.sol +++ b/test/foundry/interfaces/ProxyRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { OwnableDelegateProxy } from "./OwnableDelegateProxy.sol"; diff --git a/test/foundry/token/ERC721.sol b/test/foundry/token/ERC721.sol index 751cebb17..f532d1aff 100644 --- a/test/foundry/token/ERC721.sol +++ b/test/foundry/token/ERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.0; +pragma solidity ^0.8.0; /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol) diff --git a/test/foundry/utils/ArithmeticUtil.sol b/test/foundry/utils/ArithmeticUtil.sol index 6c59092ba..c3e83a625 100644 --- a/test/foundry/utils/ArithmeticUtil.sol +++ b/test/foundry/utils/ArithmeticUtil.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; library ArithmeticUtil { ///@dev utility function to avoid overflows when multiplying fuzzed uints with widths <256 diff --git a/test/foundry/utils/BaseConsiderationTest.sol b/test/foundry/utils/BaseConsiderationTest.sol index 2b4d9ed34..0c2b6d4e1 100644 --- a/test/foundry/utils/BaseConsiderationTest.sol +++ b/test/foundry/utils/BaseConsiderationTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ConduitController } from "../../../contracts/conduit/ConduitController.sol"; import { ConsiderationInterface } from "../../../contracts/interfaces/ConsiderationInterface.sol"; diff --git a/test/foundry/utils/BaseOrderTest.sol b/test/foundry/utils/BaseOrderTest.sol index 084e43dcd..f0b835405 100644 --- a/test/foundry/utils/BaseOrderTest.sol +++ b/test/foundry/utils/BaseOrderTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BaseConsiderationTest } from "./BaseConsiderationTest.sol"; import { stdStorage, StdStorage } from "forge-std/Test.sol"; diff --git a/test/foundry/utils/DifferentialTest.sol b/test/foundry/utils/DifferentialTest.sol index a002a19f7..43103b242 100644 --- a/test/foundry/utils/DifferentialTest.sol +++ b/test/foundry/utils/DifferentialTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { Test } from "forge-std/Test.sol"; contract DifferentialTest is Test { diff --git a/test/foundry/utils/ERC1155Recipient.sol b/test/foundry/utils/ERC1155Recipient.sol index 075d7d741..f6213d709 100644 --- a/test/foundry/utils/ERC1155Recipient.sol +++ b/test/foundry/utils/ERC1155Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ERC1155TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC1155.sol"; diff --git a/test/foundry/utils/ERC721Recipient.sol b/test/foundry/utils/ERC721Recipient.sol index 2a09d0f69..66d47134f 100644 --- a/test/foundry/utils/ERC721Recipient.sol +++ b/test/foundry/utils/ERC721Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ERC721TokenReceiver } from "@rari-capital/solmate/src/tokens/ERC721.sol"; diff --git a/test/foundry/utils/ExternalCounter.sol b/test/foundry/utils/ExternalCounter.sol index 290563785..b26390251 100644 --- a/test/foundry/utils/ExternalCounter.sol +++ b/test/foundry/utils/ExternalCounter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; contract ExternalCounter { uint256 public value; diff --git a/test/foundry/utils/OfferConsiderationItemAdder.sol b/test/foundry/utils/OfferConsiderationItemAdder.sol index 989378b92..196e9100c 100644 --- a/test/foundry/utils/OfferConsiderationItemAdder.sol +++ b/test/foundry/utils/OfferConsiderationItemAdder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { ConsiderationItem, OfferItem, ItemType } from "../../../contracts/lib/ConsiderationStructs.sol"; import { TestTokenMinter } from "./TestTokenMinter.sol"; diff --git a/test/foundry/utils/PseudoRandom.sol b/test/foundry/utils/PseudoRandom.sol index dc438c569..2495b5245 100644 --- a/test/foundry/utils/PseudoRandom.sol +++ b/test/foundry/utils/PseudoRandom.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; contract PseudoRandom { bytes32 seedHash; diff --git a/test/foundry/utils/StructCopier.sol b/test/foundry/utils/StructCopier.sol index ac8f08491..95c802408 100644 --- a/test/foundry/utils/StructCopier.sol +++ b/test/foundry/utils/StructCopier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BasicOrderParameters, CriteriaResolver, AdvancedOrder, AdditionalRecipient, OfferItem, Order, ConsiderationItem, Fulfillment, FulfillmentComponent, OrderParameters, OrderComponents } from "../../../contracts/lib/ConsiderationStructs.sol"; import { ConsiderationInterface } from "../../../contracts/interfaces/ConsiderationInterface.sol"; diff --git a/test/foundry/utils/TestTokenMinter.sol b/test/foundry/utils/TestTokenMinter.sol index 623d63aa1..0729eb39b 100644 --- a/test/foundry/utils/TestTokenMinter.sol +++ b/test/foundry/utils/TestTokenMinter.sol @@ -1,5 +1,5 @@ // SPDX-Identifier: MIT -pragma solidity >=0.8.7; +pragma solidity ^0.8.7; import { TestERC1155 } from "../../../contracts/test/TestERC1155.sol"; import { TestERC20 } from "../../../contracts/test/TestERC20.sol"; diff --git a/test/foundry/utils/reentrancy/ReentrantEnums.sol b/test/foundry/utils/reentrancy/ReentrantEnums.sol index e3b03d725..0ba1abb77 100644 --- a/test/foundry/utils/reentrancy/ReentrantEnums.sol +++ b/test/foundry/utils/reentrancy/ReentrantEnums.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; /** * @dev Enum of functions that set the reentrancy guard diff --git a/test/foundry/utils/reentrancy/ReentrantStructs.sol b/test/foundry/utils/reentrancy/ReentrantStructs.sol index 0e089b552..b860fca1b 100644 --- a/test/foundry/utils/reentrancy/ReentrantStructs.sol +++ b/test/foundry/utils/reentrancy/ReentrantStructs.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.13; +pragma solidity ^0.8.13; import { BasicOrderParameters, OfferItem, ConsiderationItem, OrderParameters, OrderComponents, Fulfillment, FulfillmentComponent, Execution, Order, AdvancedOrder, OrderStatus, CriteriaResolver } from "../../../../contracts/lib/ConsiderationStructs.sol"; struct FulfillBasicOrderParameters { diff --git a/test/getter.spec.ts b/test/getter.spec.ts new file mode 100644 index 000000000..b8a4fc4fa --- /dev/null +++ b/test/getter.spec.ts @@ -0,0 +1,74 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; + +import { randomHex } from "./utils/encoding"; +import { seaportFixture } from "./utils/fixtures"; +import { VERSION } from "./utils/helpers"; +import { faucet } from "./utils/impersonate"; + +import type { + ConduitControllerInterface, + ConsiderationInterface, +} from "../typechain-types"; + +const { keccak256, toUtf8Bytes } = ethers.utils; + +describe(`Getter tests (Seaport v${VERSION})`, function () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + let conduitController: ConduitControllerInterface; + let directMarketplaceContract: ConsiderationInterface; + let marketplaceContract: ConsiderationInterface; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ conduitController, directMarketplaceContract, marketplaceContract } = + await seaportFixture(owner)); + }); + + it("gets correct name", async () => { + const name = await marketplaceContract.name(); + expect(name).to.equal(process.env.REFERENCE ? "Consideration" : "Seaport"); + + const directName = await directMarketplaceContract.name(); + expect(directName).to.equal("Consideration"); + }); + + it("gets correct version, domain separator and conduit controller", async () => { + const name = process.env.REFERENCE ? "Consideration" : "Seaport"; + const { + version, + domainSeparator, + conduitController: controller, + } = await marketplaceContract.information(); + + const typehash = keccak256( + toUtf8Bytes( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ) + ); + const namehash = keccak256(toUtf8Bytes(name)); + const versionhash = keccak256(toUtf8Bytes(version)); + const { chainId } = await provider.getNetwork(); + const chainIdEncoded = chainId.toString(16).padStart(64, "0"); + const addressEncoded = marketplaceContract.address + .slice(2) + .padStart(64, "0"); + expect(domainSeparator).to.equal( + keccak256( + `0x${typehash.slice(2)}${namehash.slice(2)}${versionhash.slice( + 2 + )}${chainIdEncoded}${addressEncoded}` + ) + ); + expect(controller).to.equal(conduitController.address); + }); +}); diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 574752a2a..000000000 --- a/test/index.js +++ /dev/null @@ -1,16673 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const { expect } = require("chai"); -const { - constants, - utils: { parseEther, keccak256, toUtf8Bytes }, -} = require("ethers"); -const { ethers, network } = require("hardhat"); -const { - faucet, - whileImpersonating, - getWalletWithEther, -} = require("./utils/impersonate"); -const { merkleTree } = require("./utils/criteria"); -const { - randomHex, - random128, - toAddress, - toKey, - convertSignatureToEIP2098, - getBasicOrderParameters, - getItemETH, - toBN, - randomBN, - toFulfillment, - toFulfillmentComponents, - getBasicOrderExecutions, - buildResolver, - buildOrderStatus, - defaultBuyNowMirrorFulfillment, - defaultAcceptOfferMirrorFulfillment, -} = require("./utils/encoding"); -const { randomInt } = require("crypto"); -const { - fixtureERC20, - fixtureERC721, - fixtureERC1155, - seaportFixture, -} = require("./utils/fixtures"); -const { deployContract } = require("./utils/contracts"); - -const VERSION = !process.env.REFERENCE ? "1.1" : "rc.1.1"; - -const minRandom = (min) => randomBN(10).add(min); - -const getCustomRevertSelector = (customErrorString) => - ethers.utils - .keccak256(ethers.utils.toUtf8Bytes(customErrorString)) - .slice(0, 10); - -describe(`Consideration (version: ${VERSION}) — initial test suite`, function () { - const provider = ethers.provider; - let zone; - let marketplaceContract; - let testERC20; - let testERC721; - let testERC1155; - let testERC1155Two; - let owner; - let withBalanceChecks; - let EIP1271WalletFactory; - let reenterer; - let stubZone; - let conduitController; - let conduitImplementation; - let conduitOne; - let conduitKeyOne; - let directMarketplaceContract; - let mintAndApproveERC20; - let getTestItem20; - let set721ApprovalForAll; - let mint721; - let mint721s; - let mintAndApprove721; - let getTestItem721; - let getTestItem721WithCriteria; - let set1155ApprovalForAll; - let mint1155; - let mintAndApprove1155; - let getTestItem1155WithCriteria; - let getTestItem1155; - let deployNewConduit; - let createTransferWithApproval; - let createOrder; - let createMirrorBuyNowOrder; - let createMirrorAcceptOfferOrder; - let checkExpectedEvents; - - const simulateMatchOrders = async (orders, fulfillments, caller, value) => { - return marketplaceContract - .connect(caller) - .callStatic.matchOrders(orders, fulfillments, { - value, - }); - }; - - const simulateAdvancedMatchOrders = async ( - orders, - criteriaResolvers, - fulfillments, - caller, - value - ) => { - return marketplaceContract - .connect(caller) - .callStatic.matchAdvancedOrders(orders, criteriaResolvers, fulfillments, { - value, - }); - }; - - after(async () => { - await network.provider.request({ - method: "hardhat_reset", - }); - }); - - before(async () => { - owner = new ethers.Wallet(randomHex(32), provider); - - await Promise.all( - [owner].map((wallet) => faucet(wallet.address, provider)) - ); - - ({ - EIP1271WalletFactory, - reenterer, - conduitController, - conduitImplementation, - conduitKeyOne, - conduitOne, - deployNewConduit, - testERC20, - mintAndApproveERC20, - getTestItem20, - testERC721, - set721ApprovalForAll, - mint721, - mint721s, - mintAndApprove721, - getTestItem721, - getTestItem721WithCriteria, - testERC1155, - set1155ApprovalForAll, - mint1155, - mintAndApprove1155, - getTestItem1155WithCriteria, - getTestItem1155, - testERC1155Two, - createTransferWithApproval, - marketplaceContract, - directMarketplaceContract, - stubZone, - createOrder, - createMirrorBuyNowOrder, - createMirrorAcceptOfferOrder, - withBalanceChecks, - checkExpectedEvents, - } = await seaportFixture(owner)); - }); - - describe("Getter tests", async () => { - it("gets correct name", async () => { - const name = await marketplaceContract.name(); - expect(name).to.equal( - process.env.REFERENCE ? "Consideration" : "Seaport" - ); - - const directName = await directMarketplaceContract.name(); - expect(directName).to.equal("Consideration"); - }); - it("gets correct version, domain separator and conduit controller", async () => { - const name = process.env.REFERENCE ? "Consideration" : "Seaport"; - const { - version, - domainSeparator, - conduitController: controller, - } = await marketplaceContract.information(); - - const typehash = keccak256( - toUtf8Bytes( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ) - ); - const namehash = keccak256(toUtf8Bytes(name)); - const versionhash = keccak256(toUtf8Bytes(version)); - const { chainId } = await provider.getNetwork(); - const chainIdEncoded = chainId.toString(16).padStart(64, "0"); - const addressEncoded = marketplaceContract.address - .slice(2) - .padStart(64, "0"); - expect(domainSeparator).to.equal( - keccak256( - `0x${typehash.slice(2)}${namehash.slice(2)}${versionhash.slice( - 2 - )}${chainIdEncoded}${addressEncoded}` - ) - ); - expect(controller).to.equal(conduitController.address); - }); - }); - - // Buy now or accept offer for a single ERC721 or ERC1155 in exchange for - // ETH, WETH or ERC20 - describe("Basic buy now or accept offer flows", async () => { - let seller; - let sellerContract; - let buyerContract; - let buyer; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - seller = new ethers.Wallet(randomHex(32), provider); - buyer = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - - sellerContract = await EIP1271WalletFactory.deploy(seller.address); - buyerContract = await EIP1271WalletFactory.deploy(buyer.address); - - await Promise.all( - [seller, buyer, zone, sellerContract, buyerContract].map((wallet) => - faucet(wallet.address, provider) - ) - ); - }); - - describe("A single ERC721 is to be transferred", async () => { - describe("[Buy now] User fulfills a sell order for a single ERC721", async () => { - it("ERC721 <=> ETH (standard)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - return receipt; - }); - }); - it("ERC721 <=> ETH (standard via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard with tip)", async () => { - 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, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // Add a tip - order.parameters.consideration.push( - getItemETH(parseEther("1"), parseEther("1"), owner.address) - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value: value.add(parseEther("1")), - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard with restricted order)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2 // FULL_RESTRICTED - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard with restricted order and extra data)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2 // FULL_RESTRICTED - ); - - order.extraData = "0x1234"; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard with restricted order, specified recipient and extra data)", async () => { - 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, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2 // FULL_RESTRICTED - ); - - order.extraData = "0x1234"; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), owner.address, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - recipient: owner.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic, minimal and listed off-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic, minimal and verified on-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - // Validate the order from any account - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, constants.AddressZero); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard, minimal and listed off-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (standard, minimal and verified on-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(toBN(1), toBN(1), constants.AddressZero), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - // Validate the order from any account - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, constants.AddressZero); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (advanced, minimal and listed off-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (advanced, minimal and verified on-chain)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [getItemETH(toBN(1), toBN(1), seller.address)]; - - const { order, orderHash, value } = await createOrder( - seller, - constants.AddressZero, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - constants.HashZero, - true // extraCheap - ); - - // Validate the order from any account - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, constants.AddressZero); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic with tips)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order, - false, - [ - { - amount: parseEther("2"), - recipient: `0x0000000000000000000000000000000000000001`, - }, - { - amount: parseEther("3"), - recipient: `0x0000000000000000000000000000000000000002`, - }, - { - amount: parseEther("4"), - recipient: `0x0000000000000000000000000000000000000003`, - }, - ] - ); - - order.parameters.consideration.push( - getItemETH( - parseEther("2"), - parseEther("2"), - "0x0000000000000000000000000000000000000001" - ) - ); - - order.parameters.consideration.push( - getItemETH( - parseEther("3"), - parseEther("3"), - "0x0000000000000000000000000000000000000002" - ) - ); - - order.parameters.consideration.push( - getItemETH( - parseEther("4"), - parseEther("4"), - "0x0000000000000000000000000000000000000003" - ) - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: value.add(parseEther("9")), - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic with restricted order)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2 // FULL_RESTRICTED - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic with partial restricted order)", async () => { - // Seller mints nft - const nftId = randomBN(); - await testERC721.mint(seller.address, nftId); - - // Seller approves marketplace contract to transfer NFT - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC721 - .connect(seller) - .setApprovalForAll(marketplaceContract.address, true) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(seller.address, marketplaceContract.address, true); - }); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - { - itemType: 0, // ETH - token: constants.AddressZero, - identifierOrCriteria: 0, // ignored for ETH - startAmount: ethers.utils.parseEther("10"), - endAmount: ethers.utils.parseEther("10"), - recipient: seller.address, - }, - { - itemType: 0, // ETH - token: constants.AddressZero, - identifierOrCriteria: 0, // ignored for ETH - startAmount: ethers.utils.parseEther("1"), - endAmount: ethers.utils.parseEther("1"), - recipient: zone.address, - }, - { - itemType: 0, // ETH - token: constants.AddressZero, - identifierOrCriteria: 0, // ignored for ETH - startAmount: ethers.utils.parseEther("1"), - endAmount: ethers.utils.parseEther("1"), - recipient: owner.address, - }, - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 3 // PARTIAL_RESTRICTED - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await whileImpersonating(buyer.address, provider, async () => { - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { value }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { order, orderHash, fulfiller: buyer.address }, - ]); - - return receipt; - }); - }); - }); - it("ERC721 <=> ETH (basic, already validated)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // Validate the order from any account - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic, EIP-2098 signature)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // Convert signature to EIP 2098 - expect(order.signature.length).to.equal(132); - order.signature = convertSignatureToEIP2098(order.signature); - expect(order.signature.length).to.equal(130); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (basic, extra ether supplied and returned to caller)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: value.add(1), - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ETH (match)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC721 <=> ETH (match via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC721 <=> ETH (match, extra eth supplied and returned to caller)", async () => { - 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value: value.add(101), - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC721 <=> ERC20 (standard)", async () => { - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - return receipt; - }); - }); - it("ERC721 <=> ERC20 (standard via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic)", async () => { - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic, EIP-1271 signature)", async () => { - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs( - sellerContract.address, - marketplaceContract.address, - true - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (EIP-1271 signature on non-ECDSA 64 bytes)", async () => { - const sellerContract = await deployContract( - "EIP1271Wallet", - seller, - seller.address - ); - - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs( - sellerContract.address, - marketplaceContract.address, - true - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller - ); - - // Compute the digest based on the order hash - const { domainSeparator } = await marketplaceContract.information(); - const digest = keccak256( - `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` - ); - - const signature = `0x`.padEnd(130, "f"); - - const basicOrderParameters = { - ...getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ), - signature, - }; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (EIP-1271 signature on non-ECDSA 65 bytes)", async () => { - const sellerContract = await deployContract( - "EIP1271Wallet", - seller, - seller.address - ); - - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs( - sellerContract.address, - marketplaceContract.address, - true - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller - ); - - // Compute the digest based on the order hash - const { domainSeparator } = await marketplaceContract.information(); - const digest = keccak256( - `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` - ); - - await sellerContract.registerDigest(digest, true); - - const signature = `0x`.padEnd(132, "f"); - - const basicOrderParameters = { - ...getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ), - signature, - }; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - - await sellerContract.registerDigest(digest, false); - }); - it("ERC721 <=> ERC20 (basic, EIP-1271 signature w/ non-standard length)", async () => { - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs( - sellerContract.address, - marketplaceContract.address, - true - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller - ); - - const basicOrderParameters = { - ...getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ), - signature: "0x", - }; - - // Fails before seller contract approves the digest (note that any - // non-standard signature length is treated as a contract signature) - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.revertedWith("BadContractSignature"); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.reverted; - } - - // Compute the digest based on the order hash - const { domainSeparator } = await marketplaceContract.information(); - const digest = keccak256( - `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` - ); - - // Seller approves the digest - await sellerContract.connect(seller).registerDigest(digest, true); - - // Now it succeeds - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (match)", async () => { - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC721 <=> ERC20 (match via conduit)", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - describe("[Accept offer] User accepts a buy offer on a single ERC721", async () => { - // Note: ETH is not a possible case - it("ERC721 <=> ERC20 (standard)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Buyer approves marketplace contract to transfer ERC20 tokens too - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (standard, via conduit)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20(seller, conduitOne.address, tokenAmount); - - // Buyer approves marketplace contract to transfer ERC20 tokens - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (standard, fulfilled via conduit)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves conduit contract to transfer NFT - await set721ApprovalForAll(buyer, conduitOne.address, true); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Buyer approves conduit to transfer ERC20 tokens - await expect( - testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, conduitOne.address, tokenAmount); - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, conduitKeyOne); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ]); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 4, // ERC721ForERC20 - order - ); - - await withBalanceChecks([order], toBN(0), null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ], - getBasicOrderExecutions(order, buyer.address) - ); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic, many via conduit)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20(seller, conduitOne.address, tokenAmount); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(1, 1, zone.address), - ]; - - for (let i = 1; i <= 50; ++i) { - consideration.push( - getTestItem20(i, i, toAddress(parseInt(i) + 10000)) - ); - } - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const basicOrderParameters = getBasicOrderParameters( - 4, // ERC721ForERC20 - order - ); - - await withBalanceChecks([order], toBN(0), null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ], - getBasicOrderExecutions(order, buyer.address) - ); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (basic, fulfilled via conduit)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves conduit contract to transfer NFT - await set721ApprovalForAll(buyer, conduitOne.address, true); - - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 4, // ERC721ForERC20 - order, - conduitKeyOne - ); - - await withBalanceChecks([order], toBN(0), null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ], - getBasicOrderExecutions(order, buyer.address, conduitKeyOne) - ); - - return receipt; - }); - }); - it("ERC721 <=> ERC20 (match)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder(buyer, zone, order); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC721 <=> ERC20 (match via conduit)", async () => { - // Buyer mints nft - const nftId = await mint721(buyer); - - // Buyer approves conduit contract to transfer NFT - await set721ApprovalForAll(buyer, conduitOne.address, true); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem721(nftId, 1, 1, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - [], - conduitKeyOne - ); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - }); - - describe("A single ERC1155 is to be transferred", async () => { - describe("[Buy now] User fulfills a sell order for a single ERC1155", async () => { - it("ERC1155 <=> ETH (standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ETH (standard via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ETH (basic)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 1, // EthForERC1155 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ETH (basic via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const basicOrderParameters = getBasicOrderParameters( - 1, // EthForERC1155 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ETH (match)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC1155 <=> ETH (match via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC1155 <=> ERC20 (standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (standard via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (basic)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 3, // ERC20ForERC1155 - order - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ], - getBasicOrderExecutions(order, buyer.address) - ); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (basic via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const basicOrderParameters = getBasicOrderParameters(3, order); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (match)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC1155 <=> ERC20 (match via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - conduitOne.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const offer = [getTestItem1155(nftId, amount, amount)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - describe("[Accept offer] User accepts a buy offer on a single ERC1155", async () => { - // Note: ETH is not a possible case - it("ERC1155 <=> ERC20 (standard)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - marketplaceContract.address - ); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Buyer approves marketplace contract to transfer ERC20 tokens too - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false)); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (standard, fulfilled via conduit)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - conduitOne.address - ); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Buyer approves conduit to transfer ERC20 tokens - await expect( - testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, conduitOne.address, tokenAmount); - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, conduitKeyOne); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ]); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (basic)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - marketplaceContract.address - ); - - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 5, // ERC1155ForERC20 - order - ); - - await withBalanceChecks([order], toBN(0), null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ], - getBasicOrderExecutions(order, buyer.address) - ); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (basic, fulfilled via conduit)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - conduitOne.address - ); - - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 5, // ERC1155ForERC20 - order, - conduitKeyOne - ); - - await withBalanceChecks([order], toBN(0), null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters); - - const executions = getBasicOrderExecutions( - order, - buyer.address, - conduitKeyOne - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ], - executions - ); - - return receipt; - }); - }); - it("ERC1155 <=> ERC20 (match)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - marketplaceContract.address - ); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder(buyer, zone, order); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("ERC1155 <=> ERC20 (match via conduit)", async () => { - // Buyer mints nft - const { nftId, amount } = await mintAndApprove1155( - buyer, - conduitOne.address - ); - - // Seller mints ERC20 - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // NOTE: Buyer does not need to approve marketplace for ERC20 tokens - - const offer = [ - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - getTestItem1155(nftId, amount, amount, undefined, seller.address), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - [], - conduitKeyOne - ); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - }); - }); - - describe("Validate, cancel, and increment counter flows", async () => { - let seller; - let buyer; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - seller = new ethers.Wallet(randomHex(32), provider); - buyer = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - await Promise.all( - [seller, buyer, zone].map((wallet) => faucet(wallet.address, provider)) - ); - }); - - describe("Validate", async () => { - it("Validate signed order and fill it with no signature", async () => { - // Seller mints an 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const signature = order.signature; - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.eq( - buildOrderStatus(false, false, 0, 0) - ); - - // cannot fill it with no signature yet - order.signature = "0x"; - - if (!process.env.REFERENCE) { - const expectedRevertReason = - getCustomRevertSelector("InvalidSignature()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - // cannot validate it with no signature from a random account - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - - tx = await marketplaceContract - .connect(owner) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - // cannot validate it with no signature from a random account - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - } - - // can validate it once you add the signature back - order.signature = signature; - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.eq( - buildOrderStatus(true, false, 0, 0) - ); - - // Can validate it repeatedly, but no event after the first time - await marketplaceContract.connect(owner).validate([order, order]); - - // Fulfill the order without a signature - order.signature = "0x"; - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - - const finalStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...finalStatus }).to.deep.eq( - buildOrderStatus(true, false, 1, 1) - ); - - // cannot validate it once it's been fully filled - await expect( - marketplaceContract.connect(owner).validate([order]) - ).to.be.revertedWith("OrderAlreadyFilled", orderHash); - }); - it("Validate unsigned order from offerer and fill it with no signature", async () => { - // Seller mints an 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - order.signature = "0x"; - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.eq( - buildOrderStatus(false, false, 0, 0) - ); - - if (!process.env.REFERENCE) { - // cannot fill it with no signature yet - const expectedRevertReason = - getCustomRevertSelector("InvalidSignature()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - // cannot validate it with no signature from a random account - tx = await marketplaceContract - .connect(owner) - .populateTransaction.validate([order]); - returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - } else { - // cannot fill it with no signature yet - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - // cannot validate it with no signature from a random account - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - } - - // can validate it from the seller - await expect(marketplaceContract.connect(seller).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.eq( - buildOrderStatus(true, false, 0, 0) - ); - - // Fulfill the order without a signature - order.signature = "0x"; - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - - const finalStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...finalStatus }).to.deep.eq( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Cannot validate a cancelled order", async () => { - // Seller mints an nft - const nftId = randomBN(); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const signature = order.signature; - - order.signature = "0x"; - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.eq( - buildOrderStatus(false, false, 0, 0) - ); - - if (!process.env.REFERENCE) { - // cannot fill it with no signature yet - const expectedRevertReason = - getCustomRevertSelector("InvalidSignature()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - tx = await marketplaceContract - .connect(owner) - .populateTransaction.validate([order]); - returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - // cannot validate it with no signature from a random account - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - } else { - // cannot fill it with no signature yet - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - - // cannot validate it with no signature from a random account - await expect(marketplaceContract.connect(owner).validate([order])).to - .be.reverted; - } - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHash, seller.address, zone.address); - - // cannot validate it from the seller - await expect( - marketplaceContract.connect(seller).validate([order]) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - // cannot validate it with a signature either - order.signature = signature; - await expect( - marketplaceContract.connect(owner).validate([order]) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.eq( - buildOrderStatus(false, true, 0, 0) - ); - }); - }); - - describe("Cancel", async () => { - it("Can cancel an order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // cannot cancel it from a random account - await expect( - marketplaceContract.connect(owner).cancel([orderComponents]) - ).to.be.revertedWith("InvalidCanceller"); - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.eq( - buildOrderStatus(false, false, 0, 0) - ); - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHash, seller.address, zone.address); - - // cannot fill the order anymore - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.eq( - buildOrderStatus(false, true, 0, 0) - ); - }); - it("Can cancel a validated order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // cannot cancel it from a random account - await expect( - marketplaceContract.connect(owner).cancel([orderComponents]) - ).to.be.revertedWith("InvalidCanceller"); - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // Can validate it - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.equal( - buildOrderStatus(true, false, 0, 0) - ); - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHash, seller.address, zone.address); - - // cannot fill the order anymore - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - const finalStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...finalStatus }).to.deep.equal( - buildOrderStatus(false, true, 0, 0) - ); - }); - it("Can cancel an order from the zone", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // cannot cancel it from a random account - await expect( - marketplaceContract.connect(owner).cancel([orderComponents]) - ).to.be.revertedWith("InvalidCanceller"); - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // can cancel it from the zone - await expect( - marketplaceContract.connect(zone).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHash, seller.address, zone.address); - - // cannot fill the order anymore - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.equal( - buildOrderStatus(false, true, 0, 0) - ); - }); - it("Can cancel a validated order from a zone", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const initialStatus = await marketplaceContract.getOrderStatus( - orderHash - ); - expect({ ...initialStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // Can validate it - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - // cannot cancel it from a random account - await expect( - marketplaceContract.connect(owner).cancel([orderComponents]) - ).to.be.revertedWith("InvalidCanceller"); - - const newStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...newStatus }).to.deep.equal( - buildOrderStatus(true, false, 0, 0) - ); - - // can cancel it from the zone - await expect( - marketplaceContract.connect(zone).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHash, seller.address, zone.address); - - // cannot fill the order anymore - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`OrderIsCancelled("${orderHash}")`); - - const finalStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...finalStatus }).to.deep.equal( - buildOrderStatus(false, true, 0, 0) - ); - }); - }); - - describe("Increment Counter", async () => { - it("Can increment the counter", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - let { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const counter = await marketplaceContract.getCounter(seller.address); - expect(counter).to.equal(0); - expect(orderComponents.counter).to.equal(counter); - - // can increment the counter - await expect(marketplaceContract.connect(seller).incrementCounter()) - .to.emit(marketplaceContract, "CounterIncremented") - .withArgs(1, seller.address); - - const newCounter = await marketplaceContract.getCounter(seller.address); - expect(newCounter).to.equal(1); - - if (!process.env.REFERENCE) { - // Cannot fill order anymore - const expectedRevertReason = - getCustomRevertSelector("InvalidSigner()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } else { - // Cannot fill order anymore - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - - const newOrderDetails = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - order = newOrderDetails.order; - orderHash = newOrderDetails.orderHash; - value = newOrderDetails.value; - orderComponents = newOrderDetails.orderComponents; - - expect(orderComponents.counter).to.equal(newCounter); - - // Can fill order with new counter - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("Can increment the counter and implicitly cancel a validated order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - let { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const counter = await marketplaceContract.getCounter(seller.address); - expect(counter).to.equal(0); - expect(orderComponents.counter).to.equal(counter); - - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - // can increment the counter - await expect(marketplaceContract.connect(seller).incrementCounter()) - .to.emit(marketplaceContract, "CounterIncremented") - .withArgs(1, seller.address); - - const newCounter = await marketplaceContract.getCounter(seller.address); - expect(newCounter).to.equal(1); - - if (!process.env.REFERENCE) { - // Cannot fill order anymore - const expectedRevertReason = - getCustomRevertSelector("InvalidSigner()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } else { - // Cannot fill order anymore - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - - const newOrderDetails = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - order = newOrderDetails.order; - orderHash = newOrderDetails.orderHash; - value = newOrderDetails.value; - orderComponents = newOrderDetails.orderComponents; - - expect(orderComponents.counter).to.equal(newCounter); - - // Can fill order with new counter - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - it("Can increment the counter as the zone and implicitly cancel a validated order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - let { order, orderHash, value, orderComponents } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const counter = await marketplaceContract.getCounter(seller.address); - expect(counter).to.equal(0); - expect(orderComponents.counter).to.equal(counter); - - await expect(marketplaceContract.connect(owner).validate([order])) - .to.emit(marketplaceContract, "OrderValidated") - .withArgs(orderHash, seller.address, zone.address); - - // can increment the counter as the offerer - await expect(marketplaceContract.connect(seller).incrementCounter()) - .to.emit(marketplaceContract, "CounterIncremented") - .withArgs(1, seller.address); - - const newCounter = await marketplaceContract.getCounter(seller.address); - expect(newCounter).to.equal(1); - - if (!process.env.REFERENCE) { - // Cannot fill order anymore - const expectedRevertReason = - getCustomRevertSelector("InvalidSigner()"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillOrder(order, toKey(false), { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } else { - // Cannot fill order anymore - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - - const newOrderDetails = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - order = newOrderDetails.order; - orderHash = newOrderDetails.orderHash; - value = newOrderDetails.value; - orderComponents = newOrderDetails.orderComponents; - - expect(orderComponents.counter).to.equal(newCounter); - - // Can fill order with new counter - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ]); - - return receipt; - }); - }); - }); - }); - - describe("Advanced orders", async () => { - let seller; - let buyer; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - seller = new ethers.Wallet(randomHex(32), provider); - buyer = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - await Promise.all( - [seller, buyer, zone].map((wallet) => faucet(wallet.address, provider)) - ); - }); - - describe("Partial fills", async () => { - it("Partial fills (standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 2; // fill two tenths or one fifth - order.denominator = 10; // fill two tenths or one fifth - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 2, 10) - ); - - order.numerator = 1; // fill one half - order.denominator = 2; // fill one half - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 14, 20) - ); - - // Fill remaining; only 3/10ths will be fillable - order.numerator = 1; // fill one half - order.denominator = 2; // fill one half - - const ordersClone = JSON.parse(JSON.stringify([order])); - for (const [, clonedOrder] of Object.entries(ordersClone)) { - clonedOrder.parameters.startTime = order.parameters.startTime; - clonedOrder.parameters.endTime = order.parameters.endTime; - - for (const [j, offerItem] of Object.entries( - clonedOrder.parameters.offer - )) { - offerItem.startAmount = order.parameters.offer[j].startAmount; - offerItem.endAmount = order.parameters.offer[j].endAmount; - } - - for (const [j, considerationItem] of Object.entries( - clonedOrder.parameters.consideration - )) { - considerationItem.startAmount = - order.parameters.consideration[j].startAmount; - considerationItem.endAmount = - order.parameters.consideration[j].endAmount; - } - } - - ordersClone[0].numerator = 3; - ordersClone[0].denominator = 10; - - await withBalanceChecks(ordersClone, 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: ordersClone[0], - orderHash, - fulfiller: buyer.address, - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 40, 40) - ); - }); - it("Partial fills (standard, additional permutations)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 2; // fill two tenths or one fifth - order.denominator = 10; // fill two tenths or one fifth - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 2, 10) - ); - - order.numerator = 1; // fill one tenth - order.denominator = 10; // fill one tenth - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 3, 10) - ); - - // Fill all available; only 7/10ths will be fillable - order.numerator = 1; // fill all available - order.denominator = 1; // fill all available - - const ordersClone = JSON.parse(JSON.stringify([order])); - for (const [, clonedOrder] of Object.entries(ordersClone)) { - clonedOrder.parameters.startTime = order.parameters.startTime; - clonedOrder.parameters.endTime = order.parameters.endTime; - - for (const [j, offerItem] of Object.entries( - clonedOrder.parameters.offer - )) { - offerItem.startAmount = order.parameters.offer[j].startAmount; - offerItem.endAmount = order.parameters.offer[j].endAmount; - } - - for (const [j, considerationItem] of Object.entries( - clonedOrder.parameters.consideration - )) { - considerationItem.startAmount = - order.parameters.consideration[j].startAmount; - considerationItem.endAmount = - order.parameters.consideration[j].endAmount; - } - } - - ordersClone[0].numerator = 7; - ordersClone[0].denominator = 10; - - await withBalanceChecks(ordersClone, 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: ordersClone[0], - orderHash, - fulfiller: buyer.address, - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 10, 10) - ); - }); - it("Partial fills (match)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 2; // fill two tenths or one fifth - order.denominator = 10; // fill two tenths or one fifth - - let mirrorObject; - mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - let executions = await simulateAdvancedMatchOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract.connect(owner).matchAdvancedOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorObject.mirrorOrder, - orderHash: mirrorObject.mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 2, 10) - ); - - order.numerator = 1; // fill one tenth - order.denominator = 10; // fill one tenth - - mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); - - executions = await simulateAdvancedMatchOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - owner, - value - ); - - const tx2 = marketplaceContract.connect(owner).matchAdvancedOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - { - value, - } - ); - const receipt2 = await (await tx2).wait(); - await checkExpectedEvents( - tx2, - receipt2, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorObject.mirrorOrder, - orderHash: mirrorObject.mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 3, 10) - ); - - // Fill all available; only 7/10ths will be fillable - order.numerator = 7; // fill all available - order.denominator = 10; // fill all available - - mirrorObject = await createMirrorBuyNowOrder(buyer, zone, order); - - executions = await simulateAdvancedMatchOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - owner, - value - ); - - const tx3 = await marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorObject.mirrorOrder], - [], // no criteria resolvers - fulfillments, - { - value, - } - ); - const receipt3 = await tx3.wait(); - await checkExpectedEvents( - tx3, - receipt3, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorObject.mirrorOrder, - orderHash: mirrorObject.mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 10, 10) - ); - }); - - it("Simplifies fraction when numerator/denominator would overflow", async () => { - const numer1 = toBN(2).pow(100); - const denom1 = toBN(2).pow(101); - const numer2 = toBN(2).pow(20); - const denom2 = toBN(2).pow(22); - const amt = 8; - await mintAndApproveERC20(buyer, marketplaceContract.address, amt); - // Seller mints nft - const { nftId } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000, - undefined, - amt - ); - - const offer = [getTestItem1155(nftId, amt, amt)]; - - const consideration = [getTestItem20(amt, amt, seller.address)]; - const { order, orderHash, value } = await createOrder( - seller, - undefined, - offer, - consideration, - 1, // PARTIAL_OPEN - undefined, - undefined, - undefined, - undefined, - undefined, - true - ); - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // 1/2 - order.numerator = numer1; - order.denominator = denom1; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), buyer.address, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, numer1, denom1) - ); - - order.numerator = numer2; - order.denominator = denom2; - - await marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), buyer.address, { - value, - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, toBN(3), toBN(4)) - ); - }); - - it("Reverts when numerator/denominator overflow", async () => { - const prime1 = toBN(2).pow(7).sub(1); - const prime2 = toBN(2).pow(61).sub(1); - const prime3 = toBN(2).pow(107).sub(1); - const amt = prime1.mul(prime2).mul(prime3); - await mintAndApproveERC20(buyer, marketplaceContract.address, amt); - // Seller mints nft - const { nftId } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000, - undefined, - amt - ); - - const offer = [getTestItem1155(nftId, amt, amt)]; - - const consideration = [getTestItem20(amt, amt, seller.address)]; - const { order, orderHash, value } = await createOrder( - seller, - undefined, - offer, - consideration, - 1, // PARTIAL_OPEN - undefined, - undefined, - undefined, - undefined, - undefined, - true - ); - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // 1/2 - order.numerator = 1; - order.denominator = prime2; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), buyer.address, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, toBN(1), prime2) - ); - - order.numerator = prime1; - order.denominator = prime3; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), buyer.address, { - value, - }) - ).to.be.revertedWith( - "0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" - ); - }); - }); - - describe("Criteria-based orders", async () => { - it("Criteria-based offer item ERC721 (standard)", async () => { - // Seller mints nfts - const [nftId, secondNFTId, thirdNFTId] = await mint721s(seller, 3); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - it("Criteria-based offer item ERC1155 (standard)", async () => { - // Seller mints nfts - const { nftId, amount } = await mint1155(seller); - - // Seller approves marketplace contract to transfer NFTs - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree([nftId]); - - const offer = [getTestItem1155WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - it("Criteria-based offer item (standard, collection-level)", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem721WithCriteria(constants.HashZero, toBN(1), toBN(1)), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [buildResolver(0, 0, 0, nftId, [])]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - it("Criteria-based offer item ERC721 (match)", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [ - [[[1, 0]], [[0, 0]]], - [[[0, 0]], [[1, 0]]], - [[[1, 1]], [[0, 1]]], - [[[1, 2]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateAdvancedMatchOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions, - criteriaResolvers - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("Criteria-based offer item ERC1155 (match)", async () => { - // Seller mints nfts - const { nftId, amount } = await mint1155(seller); - - // Seller approves marketplace contract to transfer NFTs - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree([nftId]); - - const offer = [getTestItem1155WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [ - [[[1, 0]], [[0, 0]]], - [[[0, 0]], [[1, 0]]], - [[[1, 1]], [[0, 1]]], - [[[1, 2]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateAdvancedMatchOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions, - criteriaResolvers - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("Criteria-based offer item (match, collection-level)", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem721WithCriteria(constants.HashZero, toBN(1), toBN(1)), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [buildResolver(0, 0, 0, nftId, [])]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [ - [[[1, 0]], [[0, 0]]], - [[[0, 0]], [[1, 0]]], - [[[1, 1]], [[0, 1]]], - [[[1, 2]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateAdvancedMatchOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions, - criteriaResolvers - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("Criteria-based consideration item (standard)", async () => { - // buyer mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(buyer.address, nftId); - await testERC721.mint(buyer.address, secondNFTId); - await testERC721.mint(buyer.address, thirdNFTId); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem721WithCriteria(root, toBN(1), toBN(1), seller.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks( - [order], - value.mul(-1), - criteriaResolvers, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - } - ); - }); - it("Criteria-based consideration item ERC1155 (standard)", async () => { - // buyer mints nfts - const { nftId, amount } = await mint1155(buyer); - - // Seller approves marketplace contract to transfer NFTs - await set1155ApprovalForAll(buyer, marketplaceContract.address, true); - - const { root, proofs } = merkleTree([nftId]); - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem1155WithCriteria(root, toBN(1), toBN(1), seller.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks( - [order], - value.mul(-1), - criteriaResolvers, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - } - ); - }); - it("Criteria-based wildcard consideration item (standard)", async () => { - // buyer mints nft - const nftId = await mintAndApprove721( - buyer, - marketplaceContract.address - ); - const tokenAmount = minRandom(100); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem721WithCriteria( - constants.HashZero, - toBN(1), - toBN(1), - seller.address - ), - ]; - - const criteriaResolvers = [buildResolver(0, 1, 0, nftId, [])]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await withBalanceChecks( - [order], - value.mul(-1), - criteriaResolvers, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - } - ); - }); - it("Criteria-based consideration item ERC721 (match)", async () => { - // Fulfiller mints nft - const nftId = await mint721(buyer); - const tokenAmount = minRandom(100); - - // Fulfiller approves marketplace contract to transfer NFT - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - // Offerer mints ERC20 - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Fulfiller mints ERC20 - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const { root, proofs } = merkleTree([nftId]); - - const offer = [ - // Offerer (Seller) - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - // Fulfiller (Buyer) - { - itemType: 4, // ERC721WithCriteria - token: testERC721.address, - identifierOrCriteria: root, - startAmount: toBN(1), - endAmount: toBN(1), - recipient: seller.address, - }, - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateAdvancedMatchOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions, - criteriaResolvers - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("Criteria-based consideration item ERC1155 (match)", async () => { - // Fulfiller mints nft - const { nftId, amount } = await mint1155(buyer); - const tokenAmount = minRandom(100); - - // Fulfiller approves marketplace contract to transfer NFT - await set1155ApprovalForAll(buyer, marketplaceContract.address, true); - - // Offerer mints ERC20 - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Fulfiller mints ERC20 - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const { root, proofs } = merkleTree([nftId]); - - const offer = [ - // Offerer (Seller) - getTestItem20(tokenAmount.sub(100), tokenAmount.sub(100)), - ]; - - const consideration = [ - // Fulfiller (Buyer) - { - itemType: 5, // ERC1155_WITH_CRITERIA - token: testERC1155.address, - identifierOrCriteria: root, - startAmount: toBN(1), - endAmount: toBN(1), - recipient: seller.address, - }, - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 1, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder, mirrorOrderHash } = - await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = defaultAcceptOfferMirrorFulfillment; - - const executions = await simulateAdvancedMatchOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - ], - executions, - criteriaResolvers - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - - describe("Ascending / Descending amounts", async () => { - it("Ascending offer amount (standard)", async () => { - // Seller mints nft - const nftId = randomBN(); - const startAmount = toBN(randomBN(2)); - const endAmount = startAmount.mul(2); - await testERC1155.mint(seller.address, nftId, endAmount.mul(10)); - - // Seller approves marketplace contract to transfer NFTs - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, startAmount, endAmount, undefined), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Ascending consideration amount (standard)", async () => { - // Seller mints ERC20 - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - seller, - marketplaceContract.address, - tokenAmount - ); - - // Buyer mints nft - const nftId = randomBN(); - const startAmount = toBN(randomBN(2)); - const endAmount = startAmount.mul(2); - await testERC1155.mint(buyer.address, nftId, endAmount.mul(10)); - - // Buyer approves marketplace contract to transfer NFTs - await set1155ApprovalForAll(buyer, marketplaceContract.address, true); - - // Buyer needs to approve marketplace to transfer ERC20 tokens too (as it's a standard fulfillment) - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem1155( - nftId, - startAmount, - endAmount, - undefined, - seller.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Ascending offer amount (match)", async () => { - // Seller mints nft - const nftId = randomBN(); - const startAmount = toBN(randomBN(2)); - const endAmount = startAmount.mul(2); - await testERC1155.mint(seller.address, nftId, endAmount.mul(10)); - - // Seller approves marketplace contract to transfer NFTs - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, startAmount, endAmount, undefined), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - }); - - describe("Sequenced Orders", async () => { - it("Match A => B => C => A", async () => { - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - const secondNFTId = await mintAndApprove721( - buyer, - marketplaceContract.address - ); - const thirdNFTId = await mintAndApprove721( - owner, - marketplaceContract.address - ); - - const offerOne = [ - getTestItem721( - nftId, - toBN(1), - toBN(1), - undefined, - testERC721.address - ), - ]; - - const considerationOne = [ - getTestItem721( - secondNFTId, - toBN(1), - toBN(1), - seller.address, - testERC721.address - ), - ]; - - const { order: orderOne, orderHash: orderHashOne } = await createOrder( - seller, - zone, - offerOne, - considerationOne, - 0 // FULL_OPEN - ); - - const offerTwo = [ - getTestItem721( - secondNFTId, - toBN(1), - toBN(1), - undefined, - testERC721.address - ), - ]; - - const considerationTwo = [ - getTestItem721( - thirdNFTId, - toBN(1), - toBN(1), - buyer.address, - testERC721.address - ), - ]; - - const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( - buyer, - zone, - offerTwo, - considerationTwo, - 0 // FULL_OPEN - ); - - const offerThree = [ - getTestItem721( - thirdNFTId, - toBN(1), - toBN(1), - undefined, - testERC721.address - ), - ]; - - const considerationThree = [ - getTestItem721( - nftId, - toBN(1), - toBN(1), - owner.address, - testERC721.address - ), - ]; - - const { order: orderThree, orderHash: orderHashThree } = - await createOrder( - owner, - 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) - ); - - const executions = await simulateAdvancedMatchOrders( - [orderOne, orderTwo, orderThree], - [], // no criteria resolvers - fulfillments, - owner, - 0 // no value - ); - - expect(executions.length).to.equal(fulfillments.length); - - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [orderOne, orderTwo, orderThree], - [], - fulfillments, - { - value: 0, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderTwo, - orderHash: orderHashTwo, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderThree, - orderHash: orderHashThree, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - }); - it("Match with fewer executions when one party has multiple orders that coincide", 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, orderHash: orderHashOne } = 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, orderHash: orderHashTwo } = 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, orderHash: orderHashThree } = - 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) - ); - - const executions = await simulateAdvancedMatchOrders( - [orderOne, orderTwo, orderThree], - [], // no criteria resolvers - fulfillments, - owner, - 0 // no value - ); - - expect(executions.length).to.equal(fulfillments.length - 1); - - const tx = marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [orderOne, orderTwo, orderThree], - [], - fulfillments, - { - value: 0, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: constants.AddressZero, - }, - { - order: orderTwo, - orderHash: orderHashTwo, - fulfiller: constants.AddressZero, - }, - { - order: orderThree, - orderHash: orderHashThree, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - - describe("Order groups", async () => { - it("Multiple offer components at once", async () => { - // Seller mints NFTs - const { nftId, amount } = await mint1155(seller, 2); - - // Seller approves marketplace contract to transfer NFT - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - // Buyer mints ERC20s - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount.mul(2) - ); - - const offerOne = [getTestItem1155(nftId, amount, amount)]; - - const considerationOne = [ - getTestItem20(tokenAmount, tokenAmount, seller.address), - ]; - - const { order: orderOne, orderHash: orderHashOne } = await createOrder( - seller, - zone, - offerOne, - considerationOne, - 0 // FULL_OPEN - ); - - const offerTwo = [getTestItem1155(nftId, amount, amount)]; - - const considerationTwo = [ - getTestItem20(tokenAmount, tokenAmount, seller.address), - ]; - - const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( - seller, - zone, - offerTwo, - considerationTwo, - 0 // FULL_OPEN - ); - - const offerThree = [ - getTestItem20(tokenAmount.mul(2), tokenAmount.mul(2)), - ]; - - const considerationThree = [ - getTestItem1155( - nftId, - amount.mul(2), - amount.mul(2), - undefined, - buyer.address - ), - ]; - - const { order: orderThree, orderHash: orderHashThree } = - await createOrder( - buyer, - zone, - offerThree, - considerationThree, - 0 // FULL_OPEN - ); - - const fulfillments = [ - [ - [ - [0, 0], - [1, 0], - ], - [[2, 0]], - ], - [[[2, 0]], [[0, 0]]], - [[[2, 0]], [[1, 0]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateAdvancedMatchOrders( - [orderOne, orderTwo, orderThree], - [], // no criteria resolvers - fulfillments, - owner, - 0 // no value - ); - - expect(executions.length).to.equal(fulfillments.length); - - const tx = marketplaceContract - .connect(buyer) - .matchAdvancedOrders( - [orderOne, orderTwo, orderThree], - [], - fulfillments, - { - value: 0, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: constants.AddressZero, - }, - { - order: orderTwo, - orderHash: orderHashTwo, - fulfiller: constants.AddressZero, - }, - { - order: orderThree, - orderHash: orderHashThree, - fulfiller: constants.AddressZero, - }, - ], - executions, - [], - true - ); - - expect( - toBN("0x" + receipt.events[3].data.slice(66)).toString() - ).to.equal(amount.mul(2).toString()); - - return receipt; - }); - it("Multiple consideration components at once", async () => { - // Seller mints NFTs - const { nftId, amount } = await mint1155(seller, 2); - - // Seller approves marketplace contract to transfer NFT - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - // Buyer mints ERC20s - const tokenAmount = toBN(random128()); - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount.mul(2) - ); - - const offerOne = [ - getTestItem1155(nftId, amount.mul(2), amount.mul(2), undefined), - ]; - - const considerationOne = [ - getTestItem20(tokenAmount.mul(2), tokenAmount.mul(2), seller.address), - ]; - - const { order: orderOne, orderHash: orderHashOne } = await createOrder( - seller, - zone, - offerOne, - considerationOne, - 0 // FULL_OPEN - ); - - const offerTwo = [getTestItem20(tokenAmount, tokenAmount)]; - - const considerationTwo = [ - getTestItem1155(nftId, amount, amount, undefined, buyer.address), - ]; - - const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( - buyer, - zone, - offerTwo, - considerationTwo, - 0 // FULL_OPEN - ); - - const offerThree = [getTestItem20(tokenAmount, tokenAmount)]; - - const considerationThree = [ - getTestItem1155(nftId, amount, amount, undefined, buyer.address), - ]; - - const { order: orderThree, orderHash: orderHashThree } = - await createOrder( - buyer, - zone, - offerThree, - considerationThree, - 0 // FULL_OPEN - ); - - const fulfillments = [ - [ - [[0, 0]], - [ - [1, 0], - [2, 0], - ], - ], - [[[1, 0]], [[0, 0]]], - [[[2, 0]], [[0, 0]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateAdvancedMatchOrders( - [orderOne, orderTwo, orderThree], - [], // no criteria resolvers - fulfillments, - owner, - 0 // no value - ); - - expect(executions.length).to.equal(fulfillments.length); - - await whileImpersonating(buyer.address, provider, async () => { - const tx = marketplaceContract - .connect(buyer) - .matchAdvancedOrders( - [orderOne, orderTwo, orderThree], - [], - fulfillments, - { - value: 0, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: constants.AddressZero, - }, - { - order: orderTwo, - orderHash: orderHashTwo, - fulfiller: constants.AddressZero, - }, - { - order: orderThree, - orderHash: orderHashThree, - fulfiller: constants.AddressZero, - }, - ], - executions, - [], - true - ); - - // TODO: include balance checks on the duplicate ERC20 transfers - - return receipt; - }); - }); - }); - - describe("Complex ERC1155 transfers", async () => { - it("ERC1155 <=> ETH (match)", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = - await mintAndApprove1155(seller, marketplaceContract.address); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(secondNftId, secondAmount, secondAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(5); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("ERC1155 <=> ETH (match, three items)", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - seller - ); - - // Seller mints third nft - const { nftId: thirdNftId, amount: thirdAmount } = - await mintAndApprove1155(seller, marketplaceContract.address); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(secondNftId, secondAmount, secondAmount), - getTestItem1155(thirdNftId, thirdAmount, thirdAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[0, 2]], [[1, 2]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(6); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("ERC1155 <=> ETH (match via conduit)", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = - await mintAndApprove1155(seller, conduitOne.address); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(secondNftId, secondAmount, secondAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(5); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("ERC1155 <=> ETH (match, single item)", async () => { - // Seller mints first nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const consideration = []; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [toFulfillment([[0, 0]], [[1, 0]])]; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(1); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("ERC1155 <=> ETH (match, single 1155)", async () => { - // Seller mints first nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("ERC1155 <=> ETH (match, two different 1155 contracts)", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const secondNftId = toBN(randomBN(4)); - const secondAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, secondNftId, secondAmount); - - // Seller approves marketplace contract to transfer NFTs - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - await expect( - testERC1155Two - .connect(seller) - .setApprovalForAll(marketplaceContract.address, true) - ) - .to.emit(testERC1155Two, "ApprovalForAll") - .withArgs(seller.address, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - testERC1155Two.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(5); - - await marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - }); - it("ERC1155 <=> ETH (match, one single and one with two 1155's)", async () => { - // Seller mints first nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - // Seller mints second nft - const secondNftId = toBN(randomBN(4)); - const secondAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, secondNftId, secondAmount); - - // Seller mints third nft - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( - seller - ); - - // Seller approves marketplace contract to transfer NFTs - - await expect( - testERC1155Two - .connect(seller) - .setApprovalForAll(marketplaceContract.address, true) - ) - .to.emit(testERC1155Two, "ApprovalForAll") - .withArgs(seller.address, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - testERC1155Two.address - ), - getTestItem1155(thirdNftId, thirdAmount, thirdAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[0, 2]], [[1, 2]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(6); - - await marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - }); - it("ERC1155 <=> ETH (match, two different groups of 1155's)", async () => { - // Seller mints first nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - // Seller mints second nft - const secondNftId = toBN(randomBN(4)); - const secondAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, secondNftId, secondAmount); - - // Seller mints third nft - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( - seller - ); - - // Seller mints fourth nft - const fourthNftId = toBN(randomBN(4)); - const fourthAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, fourthNftId, fourthAmount); - - // Seller approves marketplace contract to transfer NFTs - - await expect( - testERC1155Two - .connect(seller) - .setApprovalForAll(marketplaceContract.address, true) - ) - .to.emit(testERC1155Two, "ApprovalForAll") - .withArgs(seller.address, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - testERC1155Two.address - ), - getTestItem1155(thirdNftId, thirdAmount, thirdAmount), - getTestItem1155( - fourthNftId, - fourthAmount, - fourthAmount, - testERC1155Two.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[0, 2]], [[1, 2]]], - [[[0, 3]], [[1, 3]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(7); - - await marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - }); - }); - - describe("Fulfill Available Orders", async () => { - it("Can fulfill a single order via fulfillAvailableOrders", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address, - 10 - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [toFulfillmentComponents([[0, 0]])]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]].map( - toFulfillmentComponents - ); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [order], - offerComponents, - considerationComponents, - toKey(false), - 100, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Can fulfill a single order via fulfillAvailableAdvancedOrders", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address, - 11 - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [[[0, 0]]]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Can fulfill a single order via fulfillAvailableAdvancedOrders with recipient specified", 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, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [[[0, 0]]]; - - const considerationComponents = [[[0, 0]], [[0, 1]]]; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - owner.address, - 100, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - recipient: owner.address, - }, - ]); - - return receipt; - }); - }); - it("Can fulfill and aggregate multiple orders via fulfillAvailableOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 1, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - toFulfillmentComponents([ - [0, 0], - [1, 0], - ]), - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - ], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - ].map(toFulfillmentComponents); - - await whileImpersonating(buyer.address, provider, async () => { - await withBalanceChecks( - [orderOne, orderTwo], - 0, - null, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [orderOne, orderTwo], - offerComponents, - considerationComponents, - toKey(false), - 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 - ); - }); - }); - it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 2, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { order: orderTwo, orderHash: orderHashTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - toFulfillmentComponents([ - [0, 0], - [1, 0], - ]), - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - ], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - ].map(toFulfillmentComponents); - - await whileImpersonating(buyer.address, provider, async () => { - await withBalanceChecks( - [orderOne, orderTwo], - 0, - null, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [orderOne, orderTwo], - [], - offerComponents, - considerationComponents, - toKey(false), - 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 - ); - }); - }); - it("Can fulfill and aggregate a max number of multiple orders via fulfillAvailableOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 3, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { order: orderTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 0], - [1, 0], - ], - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - ], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - ]; - - await whileImpersonating(buyer.address, provider, async () => { - await withBalanceChecks( - [orderOne], - 0, - null, - async () => { - const { executions } = await marketplaceContract - .connect(buyer) - .callStatic.fulfillAvailableOrders( - [orderOne, orderTwo], - offerComponents, - considerationComponents, - toKey(false), - 1, - { - value: value.mul(2), - } - ); - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [orderOne, orderTwo], - offerComponents, - considerationComponents, - toKey(false), - 1, - { - value: value.mul(2), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: buyer.address, - }, - ], - executions - ); - - return receipt; - }, - 1 - ); - }); - }); - it("Can fulfill and aggregate a max number of multiple orders via fulfillAvailableAdvancedOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 4, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { order: orderTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 0], - [1, 0], - ], - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - ], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - ]; - - await whileImpersonating(buyer.address, provider, async () => { - await withBalanceChecks( - [orderOne], - 0, - null, - async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [orderOne, orderTwo], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 1, - { - value: value.mul(2), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: buyer.address, - }, - ], - [], - [], - false, - 1 - ); - - return receipt; - }, - 1 - ); - }); - }); - it("Can fulfill and aggregate multiple orders via fulfillAvailableOrders with failing orders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 5, - 100000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // second order is expired - const { order: orderTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - // third order will be cancelled - const { - order: orderThree, - orderHash: orderHashThree, - orderComponents, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHashThree, seller.address, zone.address); - - // fourth order will be filled - const { order: orderFour, orderHash: orderHashFour } = - await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can fill it - await withBalanceChecks([orderFour], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(orderFour, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderFour, - orderHash: orderHashFour, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - - const offerComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - [3, 0], - ], - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - [3, 0], - ], - [ - [0, 1], - [1, 1], - [2, 1], - [3, 1], - ], - [ - [0, 2], - [1, 2], - [2, 2], - [3, 2], - ], - ]; - - await withBalanceChecks([orderOne], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [orderOne, orderTwo, orderThree, orderFour], - offerComponents, - considerationComponents, - toKey(false), - 100, - { - value: value.mul(4), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders with failing orders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 6, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // second order is expired - const { order: orderTwo } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - // third order will be cancelled - const { - order: orderThree, - orderHash: orderHashThree, - orderComponents, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHashThree, seller.address, zone.address); - - // fourth order will be filled - const { order: orderFour, orderHash: orderHashFour } = - await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can fill it - await withBalanceChecks([orderFour], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(orderFour, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderFour, - orderHash: orderHashFour, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - - const offerComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - [3, 0], - ], - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - [3, 0], - ], - [ - [0, 1], - [1, 1], - [2, 1], - [3, 1], - ], - [ - [0, 2], - [1, 2], - [2, 2], - [3, 2], - ], - ]; - - await withBalanceChecks([orderOne], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [orderOne, orderTwo, orderThree, orderFour], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value: value.mul(4), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Can fulfill and aggregate multiple orders via fulfillAvailableAdvancedOrders with failing components including criteria", async () => { - // Seller mints first nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 1, - 7, - 10000 - ); - - // Seller mints second nft - - // Seller mints nfts for criteria-based item - const criteriaNftId = randomBN(); - const secondCriteriaNFTId = randomBN(); - const thirdCriteriaNFTId = randomBN(); - - await testERC721.mint(seller.address, criteriaNftId); - await testERC721.mint(seller.address, secondCriteriaNFTId); - await testERC721.mint(seller.address, thirdCriteriaNFTId); - - const tokenIds = [ - criteriaNftId, - secondCriteriaNFTId, - thirdCriteriaNFTId, - ]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const offerTwo = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver( - 1, - 0, - 0, - criteriaNftId, - proofs[criteriaNftId.toString()] - ), - ]; - - const { - order: orderOne, - orderHash: orderHashOne, - value, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // second order is expired - const { order: orderTwo } = await createOrder( - seller, - zone, - offerTwo, - consideration, - 0, // FULL_OPEN - criteriaResolvers, - "EXPIRED" - ); - - const offerComponents = [[[0, 0]], [[1, 0]]]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - ], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - ]; - - await withBalanceChecks([orderOne], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [orderOne, orderTwo], - criteriaResolvers, - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value: value.mul(2), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderOne, - orderHash: orderHashOne, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - }); - }); - - describe("Conduit tests", async () => { - let seller; - let buyer; - let sellerContract; - let buyerContract; - let tempConduit; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - seller = new ethers.Wallet(randomHex(32), provider); - buyer = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - - sellerContract = await EIP1271WalletFactory.deploy(seller.address); - buyerContract = await EIP1271WalletFactory.deploy(buyer.address); - - // Deploy a new conduit - tempConduit = await deployNewConduit(owner); - - await Promise.all( - [seller, buyer, zone, sellerContract, buyerContract].map((wallet) => - faucet(wallet.address, provider) - ) - ); - }); - - it("Adds a channel, and executes transfers (ERC1155 with batch)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - await testERC1155.mint(seller.address, nftId, amount.mul(2)); - await testERC1155.mint(seller.address, secondNftId, secondAmount.mul(2)); - await set1155ApprovalForAll(seller, tempConduit.address, true); - - await tempConduit.connect(seller).executeWithBatch1155( - [], - [ - { - token: testERC1155.address, - from: seller.address, - to: buyer.address, - ids: [nftId, secondNftId], - amounts: [amount, secondAmount], - }, - { - token: testERC1155.address, - from: seller.address, - to: buyer.address, - ids: [secondNftId, nftId], - amounts: [secondAmount, amount], - }, - ] - ); - }); - - it("Adds a channel, and executes only batch transfers (ERC1155 with batch)", async () => { - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - await testERC1155.mint(seller.address, nftId, amount.mul(2)); - await testERC1155.mint(seller.address, secondNftId, secondAmount.mul(2)); - await set1155ApprovalForAll(seller, tempConduit.address, true); - - await tempConduit.connect(seller).executeBatch1155([ - { - token: testERC1155.address, - from: seller.address, - to: buyer.address, - ids: [nftId, secondNftId], - amounts: [amount, secondAmount], - }, - { - token: testERC1155.address, - from: seller.address, - to: buyer.address, - ids: [secondNftId, nftId], - amounts: [secondAmount, amount], - }, - ]); - }); - - it("Adds a channel, and executes transfers (ERC721)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Seller mints nft - const nftId = randomBN(); - await testERC721.mint(seller.address, nftId); - - const secondNftId = randomBN(); - await testERC721.mint(seller.address, secondNftId); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); - expect(await testERC721.ownerOf(secondNftId)).to.equal(seller.address); - - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC721 - .connect(seller) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(seller.address, tempConduit.address, true); - }); - - await tempConduit.connect(seller).execute([ - { - itemType: 2, // ERC721 - token: testERC721.address, - from: seller.address, - to: buyer.address, - identifier: nftId, - amount: ethers.BigNumber.from(1), - }, - { - itemType: 2, // ERC721 - token: testERC721.address, - from: seller.address, - to: buyer.address, - identifier: secondNftId, - amount: ethers.BigNumber.from(1), - }, - ]); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); - expect(await testERC721.ownerOf(secondNftId)).to.equal(buyer.address); - }); - - it("Adds a channel, and executes transfers (ERC721 + ERC20)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Seller mints nft - const nftId = randomBN(); - await testERC721.mint(seller.address, nftId); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); - - // Set approval of nft - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC721 - .connect(seller) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(seller.address, tempConduit.address, true); - }); - - const tokenAmount = minRandom(100); - await testERC20.mint(seller.address, tokenAmount); - - // Check balance - expect(await testERC20.balanceOf(seller.address)).to.equal(tokenAmount); - - // Seller approves conduit contract to transfer tokens - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC20.connect(seller).approve(tempConduit.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(seller.address, tempConduit.address, tokenAmount); - }); - - // Send an ERC721 and (token amount - 100) ERC20 tokens - await tempConduit.connect(seller).execute([ - { - itemType: 2, // ERC721 - token: testERC721.address, - from: seller.address, - to: buyer.address, - identifier: nftId, - amount: ethers.BigNumber.from(1), - }, - { - itemType: 1, // ERC20 - token: testERC20.address, - from: seller.address, - to: buyer.address, - identifier: 0, - amount: tokenAmount.sub(100), - }, - ]); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); - // Check balance - expect(await testERC20.balanceOf(seller.address)).to.equal(100); - expect(await testERC20.balanceOf(buyer.address)).to.equal( - tokenAmount.sub(100) - ); - }); - - it("Adds a channel, and executes transfers (ERC721 + ERC1155)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Seller mints nft - const nftId = randomBN(); - await testERC721.mint(seller.address, nftId); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(seller.address); - - // Set approval of nft - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC721 - .connect(seller) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(seller.address, tempConduit.address, true); - }); - - const secondNftId = random128(); - const amount = random128().add(1); - await testERC1155.mint(seller.address, secondNftId, amount); - - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC1155 - .connect(seller) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC1155, "ApprovalForAll") - .withArgs(seller.address, tempConduit.address, true); - }); - - // Check ownership - expect(await testERC1155.balanceOf(seller.address, secondNftId)).to.equal( - amount - ); - - // Send an ERC721 and ERC1155 - await tempConduit.connect(seller).execute([ - { - itemType: 2, // ERC721 - token: testERC721.address, - from: seller.address, - to: buyer.address, - identifier: nftId, - amount: ethers.BigNumber.from(1), - }, - { - itemType: 3, // ERC1155 - token: testERC1155.address, - from: seller.address, - to: buyer.address, - identifier: secondNftId, - amount: amount.sub(10), - }, - ]); - - // Check ownership - expect(await testERC721.ownerOf(nftId)).to.equal(buyer.address); - // Check balance - expect(await testERC1155.balanceOf(seller.address, secondNftId)).to.equal( - 10 - ); - expect(await testERC1155.balanceOf(buyer.address, secondNftId)).to.equal( - amount.sub(10) - ); - }); - - it("Adds a channel, and executes transfers (ERC20 + ERC1155)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Seller mints nft - const tokenAmount = minRandom(100).div(100); - await testERC20.mint(seller.address, tokenAmount); - - // Check balance - expect(await testERC20.balanceOf(seller.address)).to.equal(tokenAmount); - - // Seller approves conduit contract to transfer tokens - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC20.connect(seller).approve(tempConduit.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(seller.address, tempConduit.address, tokenAmount); - }); - - const nftId = random128(); - const erc1155amount = random128().add(1); - await testERC1155.mint(seller.address, nftId, erc1155amount); - - await whileImpersonating(seller.address, provider, async () => { - await expect( - testERC1155 - .connect(seller) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC1155, "ApprovalForAll") - .withArgs(seller.address, tempConduit.address, true); - }); - - // Check ownership - expect(await testERC1155.balanceOf(seller.address, nftId)).to.equal( - erc1155amount - ); - - // Send an ERC20 and ERC1155 - await tempConduit.connect(seller).execute([ - { - itemType: 1, // ERC20 - token: testERC20.address, - from: seller.address, - to: buyer.address, - identifier: 0, - amount: tokenAmount.sub(100), - }, - { - itemType: 3, // ERC1155 - token: testERC1155.address, - from: seller.address, - to: buyer.address, - identifier: nftId, - amount: erc1155amount.sub(10), - }, - ]); - - // Check balance - expect(await testERC20.balanceOf(seller.address)).to.equal(100); - expect(await testERC20.balanceOf(buyer.address)).to.equal( - tokenAmount.sub(100) - ); - expect(await testERC1155.balanceOf(seller.address, nftId)).to.equal(10); - expect(await testERC1155.balanceOf(buyer.address, nftId)).to.equal( - erc1155amount.sub(10) - ); - }); - - it("Adds a channel, and executes transfers (ERC20 + ERC721 + ERC1155)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - testERC20, - seller, - 1, - tempConduit.address, - seller.address, - buyer.address - ); - - // Create/Approve Y amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - testERC721, - seller, - 2, - tempConduit.address, - seller.address, - buyer.address - ); - - // Create/Approve Z amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - testERC1155, - seller, - 3, - tempConduit.address, - seller.address, - buyer.address - ); - - // Send an ERC20, ERC721, and ERC1155 - await tempConduit - .connect(seller) - .execute([erc20Transfer, erc721Transfer, erc1155Transfer]); - - // Check ownership - expect(await testERC721.ownerOf(erc721Transfer.identifier)).to.equal( - buyer.address - ); - // Check balance - expect(await testERC20.balanceOf(seller.address)).to.equal(0); - expect(await testERC20.balanceOf(buyer.address)).to.equal( - erc20Transfer.amount - ); - expect( - await testERC1155.balanceOf(seller.address, erc1155Transfer.identifier) - ).to.equal(0); - expect( - await testERC1155.balanceOf(buyer.address, erc1155Transfer.identifier) - ).to.equal(erc1155Transfer.amount); - }); - - it("Adds a channel, and executes transfers (many token types)", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 64; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - const erc20Contracts = [numERC20s]; - const erc20Transfers = [numERC20s]; - - const erc721Contracts = [numEC721s]; - const erc721Transfers = [numEC721s]; - - const erc1155Contracts = [numERC1155s]; - const erc1155Transfers = [numERC1155s]; - - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - seller, - 1, - tempConduit.address, - seller.address, - buyer.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - seller, - 2, - tempConduit.address, - seller.address, - buyer.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - seller, - 3, - tempConduit.address, - seller.address, - buyer.address - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - const contracts = erc20Contracts.concat( - erc721Contracts, - erc1155Contracts - ); - // Send the transfers - await tempConduit.connect(seller).execute(transfers); - - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, from, to, amount, identifier - itemType = transfers[i].itemType; - token = contracts[i]; - from = transfers[i].from; - to = transfers[i].to; - amount = transfers[i].amount; - identifier = transfers[i].identifier; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect(await token.balanceOf(from)).to.equal(0); - expect(await token.balanceOf(to)).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect(await token.ownerOf(identifier)).to.equal(to); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(from, identifier)).to.equal(0); - expect(await token.balanceOf(to, identifier)).to.equal(amount); - break; - } - } - }); - - it("Reverts on calls to batch transfer 1155 items with no contract on a conduit", async () => { - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, owner.address, true); - }); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - await set1155ApprovalForAll(owner, tempConduit.address, true); - - await expect( - tempConduit.connect(owner).executeWithBatch1155( - [], - [ - { - token: constants.AddressZero, - from: owner.address, - to: buyer.address, - ids: [nftId, secondNftId], - amounts: [amount, secondAmount], - }, - ] - ) - ).to.be.revertedWith("NoContract"); - }); - - it("Reverts on calls to only batch transfer 1155 items with no contract on a conduit", async () => { - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, owner.address, true); - }); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - await set1155ApprovalForAll(owner, tempConduit.address, true); - - await expect( - tempConduit.connect(owner).executeBatch1155([ - { - token: constants.AddressZero, - from: owner.address, - to: buyer.address, - ids: [nftId, secondNftId], - amounts: [amount, secondAmount], - }, - ]) - ).to.be.revertedWith("NoContract"); - }); - - it("ERC1155 batch transfer reverts with revert data if it has sufficient gas", async () => { - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - await expect( - tempConduit.connect(seller).executeWithBatch1155( - [], - [ - { - token: testERC1155.address, - from: seller.address, - to: buyer.address, - ids: [1], - amounts: [1], - }, - ] - ) - ).to.be.revertedWith("NOT_AUTHORIZED"); - }); - if (!process.env.REFERENCE) { - it("ERC1155 batch transfer sends no data", async () => { - const receiver = await deployContract("ERC1155BatchRecipient", owner); - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( - owner, - 2 - ); - - await testERC1155.mint(seller.address, nftId, amount.mul(2)); - await testERC1155.mint( - seller.address, - secondNftId, - secondAmount.mul(2) - ); - await testERC1155.mint(seller.address, thirdNftId, thirdAmount.mul(2)); - await set1155ApprovalForAll(seller, tempConduit.address, true); - - await tempConduit.connect(seller).executeWithBatch1155( - [], - [ - { - token: testERC1155.address, - from: seller.address, - to: receiver.address, - ids: [nftId, secondNftId, thirdNftId], - amounts: [amount, secondAmount, thirdAmount], - }, - { - token: testERC1155.address, - from: seller.address, - to: receiver.address, - ids: [secondNftId, nftId], - amounts: [secondAmount, amount], - }, - ] - ); - }); - - it("ERC1155 batch transfer reverts with generic error if it has insufficient gas to copy revert data", async () => { - const receiver = await deployContract( - "ExcessReturnDataRecipient", - owner - ); - // Owner updates conduit channel to allow seller access - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, seller.address, true); - }); - - await expect( - tempConduit.connect(seller).executeWithBatch1155( - [], - [ - { - token: receiver.address, - from: seller.address, - to: receiver.address, - ids: [1], - amounts: [1], - }, - ] - ) - ).to.be.revertedWith( - `ERC1155BatchTransferGenericFailure("${receiver.address}", "${seller.address}", "${receiver.address}", [1], [1])` - ); - }); - } - - it("Makes batch transfer 1155 items through a conduit", async () => { - const tempConduitKey = owner.address + "ff00000000000000000000f1"; - - const { conduit: tempConduitAddress } = - await conduitController.getConduit(tempConduitKey); - - await conduitController - .connect(owner) - .createConduit(tempConduitKey, owner.address); - - const tempConduit = conduitImplementation.attach(tempConduitAddress); - - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, owner.address, true); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( - owner, - 2 - ); - - const { nftId: nftId4, amount: amount4 } = await mint1155(owner, 2); - - const { nftId: nftId5, amount: amount5 } = await mint1155(owner, 2); - - const { nftId: nftId6, amount: amount6 } = await mint1155(owner, 2); - - const { nftId: nftId7, amount: amount7 } = await mint1155(owner, 2); - - const { nftId: nftId8, amount: amount8 } = await mint1155(owner, 2); - - const { nftId: nftId9, amount: amount9 } = await mint1155(owner, 2); - - const { nftId: nftId10, amount: amount10 } = await mint1155(owner, 2); - - await set1155ApprovalForAll(owner, tempConduit.address, true); - - await tempConduit.connect(owner).executeWithBatch1155( - [], - [ - { - token: testERC1155.address, - from: owner.address, - to: buyer.address, - ids: [ - nftId, - secondNftId, - thirdNftId, - nftId4, - nftId5, - nftId6, - nftId7, - nftId8, - nftId9, - nftId10, - ], - amounts: [ - amount, - secondAmount, - thirdAmount, - amount4, - amount5, - amount6, - amount7, - amount8, - amount9, - amount10, - ], - }, - ] - ); - }); - - it("Performs complex batch transfer through a conduit", async () => { - const tempConduitKey = owner.address + "f100000000000000000000f1"; - - const { conduit: tempConduitAddress } = - await conduitController.getConduit(tempConduitKey); - - await conduitController - .connect(owner) - .createConduit(tempConduitKey, owner.address); - - const tempConduit = conduitImplementation.attach(tempConduitAddress); - - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, owner.address, true); - - const { nftId, amount } = await mint1155(owner, 2); - - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - owner, - 2 - ); - - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155( - owner, - 2 - ); - - const { nftId: nftId4, amount: amount4 } = await mint1155(owner, 2); - - const { nftId: nftId5, amount: amount5 } = await mint1155( - owner, - 2, - testERC1155Two - ); - - const { nftId: nftId6, amount: amount6 } = await mint1155( - owner, - 2, - testERC1155Two - ); - - const { nftId: nftId7, amount: amount7 } = await mint1155( - owner, - 2, - testERC1155Two - ); - - const { nftId: nftId8, amount: amount8 } = await mint1155( - owner, - 2, - testERC1155Two - ); - - const amount9 = toBN(randomBN(4)).add(1); - await mintAndApproveERC20(owner, tempConduit.address, amount9.mul(2)); - - const nftId10 = await mint721(owner); - - await set1155ApprovalForAll(owner, tempConduit.address, true); - - await expect( - testERC1155Two - .connect(owner) - .setApprovalForAll(tempConduit.address, true) - ) - .to.emit(testERC1155Two, "ApprovalForAll") - .withArgs(owner.address, tempConduit.address, true); - - await set721ApprovalForAll(owner, tempConduit.address, true); - - const newAddress = toAddress(12345); - - await tempConduit.connect(owner).executeWithBatch1155( - [ - { - itemType: 1, - token: testERC20.address, - from: owner.address, - to: newAddress, - identifier: toBN(0), - amount: amount9, - }, - { - itemType: 2, - token: testERC721.address, - from: owner.address, - to: newAddress, - identifier: nftId10, - amount: toBN(1), - }, - ], - [ - { - token: testERC1155.address, - from: owner.address, - to: newAddress, - ids: [nftId, secondNftId, thirdNftId, nftId4], - amounts: [amount, secondAmount, thirdAmount, amount4], - }, - { - token: testERC1155Two.address, - from: owner.address, - to: newAddress, - ids: [nftId5, nftId6, nftId7, nftId8], - amounts: [amount5, amount6, amount7, amount8], - }, - ] - ); - - expect(await testERC1155.balanceOf(newAddress, nftId)).to.equal(amount); - expect(await testERC1155.balanceOf(newAddress, secondNftId)).to.equal( - secondAmount - ); - expect(await testERC1155.balanceOf(newAddress, thirdNftId)).to.equal( - thirdAmount - ); - expect(await testERC1155.balanceOf(newAddress, nftId4)).to.equal(amount4); - - expect(await testERC1155Two.balanceOf(newAddress, nftId5)).to.equal( - amount5 - ); - expect(await testERC1155Two.balanceOf(newAddress, nftId6)).to.equal( - amount6 - ); - expect(await testERC1155Two.balanceOf(newAddress, nftId7)).to.equal( - amount7 - ); - expect(await testERC1155Two.balanceOf(newAddress, nftId8)).to.equal( - amount8 - ); - - expect(await testERC20.balanceOf(newAddress)).to.equal(amount9); - expect(await testERC721.ownerOf(nftId10)).to.equal(newAddress); - }); - - it("ERC1155 <=> ETH (match, two different groups of 1155's)", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const secondNftId = toBN(randomBN(4)); - const secondAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, secondNftId, secondAmount); - - // Seller mints third nft - const { nftId: thirdNftId, amount: thirdAmount } = await mint1155(seller); - - // Seller mints fourth nft - const fourthNftId = toBN(randomBN(4)); - const fourthAmount = toBN(randomBN(4)); - await testERC1155Two.mint(seller.address, fourthNftId, fourthAmount); - - // Seller approves marketplace contract to transfer NFTs - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - await expect( - testERC1155Two - .connect(seller) - .setApprovalForAll(marketplaceContract.address, true) - ) - .to.emit(testERC1155Two, "ApprovalForAll") - .withArgs(seller.address, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - testERC1155Two.address - ), - getTestItem1155(thirdNftId, thirdAmount, thirdAmount), - getTestItem1155( - fourthNftId, - fourthAmount, - fourthAmount, - testERC1155Two.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[0, 2]], [[1, 2]]], - [[[0, 3]], [[1, 3]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(7); - - await marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - }); - - it("Reverts when attempting to update a conduit channel when call is not from controller", async () => { - await expect( - conduitOne.connect(owner).updateChannel(constants.AddressZero, true) - ).to.be.revertedWith("InvalidController"); - }); - - it("Reverts when attempting to execute transfers on a conduit when not called from a channel", async () => { - let expectedRevertReason = - getCustomRevertSelector("ChannelClosed(address)") + - owner.address.slice(2).padStart(64, "0").toLowerCase(); - - let tx = await conduitOne.connect(owner).populateTransaction.execute([]); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect(conduitOne.connect(owner).execute([])).to.be.reverted; - }); - - it("Reverts when attempting to execute with 1155 transfers on a conduit when not called from a channel", async () => { - await expect( - conduitOne.connect(owner).executeWithBatch1155([], []) - ).to.be.revertedWith("ChannelClosed", owner); - }); - - it("Reverts when attempting to execute batch 1155 transfers on a conduit when not called from a channel", async () => { - await expect( - conduitOne.connect(owner).executeBatch1155([]) - ).to.be.revertedWith("ChannelClosed", owner); - }); - - it("Retrieves the owner of a conduit", async () => { - const ownerOf = await conduitController.ownerOf(conduitOne.address); - expect(ownerOf).to.equal(owner.address); - - await expect( - conduitController.connect(owner).ownerOf(buyer.address) - ).to.be.revertedWith("NoConduit"); - }); - - it("Retrieves the key of a conduit", async () => { - const key = await conduitController.getKey(conduitOne.address); - expect(key.toLowerCase()).to.equal(conduitKeyOne.toLowerCase()); - - await expect( - conduitController.connect(owner).getKey(buyer.address) - ).to.be.revertedWith("NoConduit"); - }); - - it("Retrieves the status of a conduit channel", async () => { - let isOpen = await conduitController.getChannelStatus( - conduitOne.address, - marketplaceContract.address - ); - expect(isOpen).to.be.true; - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - seller.address - ); - expect(isOpen).to.be.false; - - await expect( - conduitController - .connect(owner) - .getChannelStatus(buyer.address, seller.address) - ).to.be.revertedWith("NoConduit"); - }); - - it("Retrieves conduit channels from the controller", async () => { - const totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(1); - - await expect( - conduitController.connect(owner).getTotalChannels(buyer.address) - ).to.be.revertedWith("NoConduit"); - - const firstChannel = await conduitController.getChannel( - conduitOne.address, - 0 - ); - expect(firstChannel).to.equal(marketplaceContract.address); - - await expect( - conduitController - .connect(owner) - .getChannel(buyer.address, totalChannels - 1) - ).to.be.revertedWith("NoConduit"); - - await expect( - conduitController.connect(owner).getChannel(conduitOne.address, 1) - ).to.be.revertedWith("ChannelOutOfRange", conduitOne.address); - - await expect( - conduitController.connect(owner).getChannel(conduitOne.address, 2) - ).to.be.revertedWith("ChannelOutOfRange", conduitOne.address); - - const channels = await conduitController.getChannels(conduitOne.address); - expect(channels.length).to.equal(1); - expect(channels[0]).to.equal(marketplaceContract.address); - - await expect( - conduitController.connect(owner).getChannels(buyer.address) - ).to.be.revertedWith("NoConduit"); - }); - - it("Adds and removes channels", async () => { - // Get number of open channels - let totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(1); - - let isOpen = await conduitController.getChannelStatus( - conduitOne.address, - marketplaceContract.address - ); - expect(isOpen).to.be.true; - - // No-op - await expect( - conduitController - .connect(owner) - .updateChannel(conduitOne.address, marketplaceContract.address, true) - ).to.be.reverted; // ChannelStatusAlreadySet - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - marketplaceContract.address - ); - expect(isOpen).to.be.true; - - // Get number of open channels - totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(1); - - await conduitController - .connect(owner) - .updateChannel(conduitOne.address, seller.address, true); - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - seller.address - ); - expect(isOpen).to.be.true; - - // Get number of open channels - totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(2); - - await conduitController - .connect(owner) - .updateChannel(conduitOne.address, marketplaceContract.address, false); - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - marketplaceContract.address - ); - expect(isOpen).to.be.false; - - // Get number of open channels - totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(1); - - await conduitController - .connect(owner) - .updateChannel(conduitOne.address, seller.address, false); - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - seller.address - ); - expect(isOpen).to.be.false; - - // Get number of open channels - totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(0); - - await conduitController - .connect(owner) - .updateChannel(conduitOne.address, marketplaceContract.address, true); - - isOpen = await conduitController.getChannelStatus( - conduitOne.address, - marketplaceContract.address - ); - expect(isOpen).to.be.true; - - // Get number of open channels - totalChannels = await conduitController.getTotalChannels( - conduitOne.address - ); - expect(totalChannels).to.equal(1); - }); - - it("Reverts on an attempt to move an unsupported item", async () => { - await conduitController - .connect(owner) - .updateChannel(conduitOne.address, seller.address, true); - - const isOpen = await conduitController.getChannelStatus( - conduitOne.address, - seller.address - ); - expect(isOpen).to.be.true; - - await expect( - conduitOne.connect(seller).executeWithBatch1155( - [ - { - itemType: 0, // NATIVE (invalid) - token: constants.AddressZero, - from: conduitOne.address, - to: seller.address, - identifier: 0, - amount: 0, - }, - ], - [] - ) - ).to.be.revertedWith("InvalidItemType"); - }); - - it("Reverts when attempting to create a conduit not scoped to the creator", async () => { - await expect( - conduitController - .connect(owner) - .createConduit(constants.HashZero, owner.address) - ).to.be.revertedWith("InvalidCreator"); - }); - - it("Reverts when attempting to create a conduit that already exists", async () => { - await expect( - conduitController - .connect(owner) - .createConduit(conduitKeyOne, owner.address) - ).to.be.revertedWith(`ConduitAlreadyExists("${conduitOne.address}")`); - }); - - it("Reverts when attempting to update a channel for an unowned conduit", async () => { - await expect( - conduitController - .connect(buyer) - .updateChannel(conduitOne.address, buyer.address, true) - ).to.be.revertedWith(`CallerIsNotOwner("${conduitOne.address}")`); - }); - - it("Retrieves no initial potential owner for new conduit", async () => { - const potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(constants.AddressZero); - - await expect( - conduitController.connect(owner).getPotentialOwner(buyer.address) - ).to.be.revertedWith("NoConduit"); - }); - - it("Lets the owner transfer ownership via a two-stage process", async () => { - await expect( - conduitController - .connect(buyer) - .transferOwnership(conduitOne.address, buyer.address) - ).to.be.revertedWith("CallerIsNotOwner", conduitOne.address); - - await expect( - conduitController - .connect(owner) - .transferOwnership(conduitOne.address, constants.AddressZero) - ).to.be.revertedWith( - "NewPotentialOwnerIsZeroAddress", - conduitOne.address - ); - - await expect( - conduitController - .connect(owner) - .transferOwnership(seller.address, buyer.address) - ).to.be.revertedWith("NoConduit"); - - let potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(constants.AddressZero); - - await conduitController.transferOwnership( - conduitOne.address, - buyer.address - ); - - potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(buyer.address); - - await expect( - conduitController - .connect(owner) - .transferOwnership(conduitOne.address, buyer.address) - ).to.be.revertedWith( - "NewPotentialOwnerAlreadySet", - conduitOne.address, - buyer.address - ); - - await expect( - conduitController - .connect(buyer) - .cancelOwnershipTransfer(conduitOne.address) - ).to.be.revertedWith("CallerIsNotOwner", conduitOne.address); - - await expect( - conduitController.connect(owner).cancelOwnershipTransfer(seller.address) - ).to.be.revertedWith("NoConduit"); - - await conduitController.cancelOwnershipTransfer(conduitOne.address); - - potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(constants.AddressZero); - - await expect( - conduitController - .connect(owner) - .cancelOwnershipTransfer(conduitOne.address) - ).to.be.revertedWith("NoPotentialOwnerCurrentlySet", conduitOne.address); - - await conduitController.transferOwnership( - conduitOne.address, - buyer.address - ); - - potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(buyer.address); - - await expect( - conduitController.connect(buyer).acceptOwnership(seller.address) - ).to.be.revertedWith("NoConduit"); - - await expect( - conduitController.connect(seller).acceptOwnership(conduitOne.address) - ).to.be.revertedWith("CallerIsNotNewPotentialOwner", conduitOne.address); - - await conduitController - .connect(buyer) - .acceptOwnership(conduitOne.address); - - potentialOwner = await conduitController.getPotentialOwner( - conduitOne.address - ); - expect(potentialOwner).to.equal(constants.AddressZero); - - const ownerOf = await conduitController.ownerOf(conduitOne.address); - expect(ownerOf).to.equal(buyer.address); - }); - }); - - describe("TransferHelper tests", async () => { - let sender; - let recipient; - let senderContract; - let recipientContract; - let tempTransferHelper; - let tempConduit; - let tempConduitKey; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - sender = new ethers.Wallet(randomHex(32), provider); - recipient = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - - senderContract = await EIP1271WalletFactory.deploy(sender.address); - recipientContract = await EIP1271WalletFactory.deploy(recipient.address); - - tempConduitKey = owner.address + randomHex(12).slice(2); - tempConduit = await deployNewConduit(owner, tempConduitKey); - - await Promise.all( - [sender, recipient, zone, senderContract, recipientContract].map( - (wallet) => faucet(wallet.address, provider) - ) - ); - - // Deploy a new TransferHelper with the tempConduitController address - const transferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - tempTransferHelper = await transferHelperFactory.deploy( - conduitController.address - ); - - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, tempTransferHelper.address, true); - }); - }); - - it("Executes transfers (many token types) with a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - const erc20Contracts = [numERC20s]; - const erc20Transfers = [numERC20s]; - - const erc721Contracts = [numEC721s]; - const erc721Transfers = [numEC721s]; - - const erc1155Contracts = [numERC1155s]; - const erc1155Transfers = [numERC1155s]; - - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempConduit.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempConduit.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempConduit.address - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - const contracts = erc20Contracts.concat( - erc721Contracts, - erc1155Contracts - ); - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer(transfers, recipient.address, tempConduitKey); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect(await token.balanceOf(sender.address)).to.equal(0); - expect(await token.balanceOf(recipient.address)).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect(await token.ownerOf(identifier)).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf(recipient.address, identifier) - ).to.equal(amount); - break; - } - } - }); - - it("Executes transfers (many token types) without a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - - const erc20Contracts = [numERC20s]; - const erc20Transfers = [numERC20s]; - - const erc721Contracts = [numEC721s]; - const erc721Transfers = [numEC721s]; - - const erc1155Contracts = [numERC1155s]; - const erc1155Transfers = [numERC1155s]; - - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - - const transfers = erc20Transfers.concat( - erc721Transfers, - erc1155Transfers - ); - const contracts = erc20Contracts.concat( - erc721Contracts, - erc1155Contracts - ); - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer( - transfers, - recipient.address, - ethers.utils.formatBytes32String("") - ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect(await token.balanceOf(sender.address)).to.equal(0); - expect(await token.balanceOf(recipient.address)).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect(await token.ownerOf(identifier)).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf(recipient.address, identifier) - ).to.equal(amount); - break; - } - } - }); - - it("Reverts on native token transfers", async () => { - const ethTransferHelperItems = [ - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - ethTransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidItemType"); - }); - - it("Reverts on invalid ERC20 identifier", async () => { - const erc20TransferHelperItems = [ - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 5, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 4, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc20TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC20Identifier"); - }); - - it("Reverts on invalid ERC721 transfer amount", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 10, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); - - it("Reverts on invalid ERC721 recipient", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - tempERC721Contract.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721Recipient"); - }); - - it("Reverts on invalid function selector", async () => { - const invalidRecipientFactory = await ethers.getContractFactory( - "InvalidERC721Recipient" - ); - invalidRecipient = await invalidRecipientFactory.deploy(); - - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - invalidRecipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721Recipient"); - }); - - it("Reverts on nonexistent conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("0xabc") - ) - ).to.be.revertedWith("InvalidConduit"); - }); - - it("Reverts on error in ERC721 receiver", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - // Deploy mock ERC721 receiver - const mockERC721ReceiverFactory = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - mockERC721Receiver = await mockERC721ReceiverFactory.deploy( - 0xabcd0000, - 1 - ); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - mockERC721Receiver.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("ERC721ReceiverMock: reverting"); - }); - - it("Reverts with custom error in conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - // Invalid item type - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("InvalidItemType"); - }); - - it("Reverts with bubbled up string error from call to conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - // Call will revert since ERC721 tokens have not been minted - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); - }); - - it("Reverts with bubbled up panic error from call to conduit", async () => { - // Deploy mock ERC20 - const mockERC20PanicFactory = await ethers.getContractFactory( - "TestERC20Panic" - ); - mockERC20Panic = await mockERC20PanicFactory.deploy(); - - const transferHelperItems = [ - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 20, - }, - ]; - - if (!process.env.REFERENCE) { - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - tempConduitKey - ) - ).to.be.revertedWith("ConduitErrorPanic(18)"); - } else { - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - tempConduitKey - ) - ).to.be.reverted; - } - }); - }); - - describe("Reverts", async () => { - let seller; - let buyer; - let sellerContract; - let buyerContract; - - beforeEach(async () => { - // Setup basic buyer/seller wallets with ETH - seller = new ethers.Wallet(randomHex(32), provider); - buyer = new ethers.Wallet(randomHex(32), provider); - zone = new ethers.Wallet(randomHex(32), provider); - - sellerContract = await EIP1271WalletFactory.deploy(seller.address); - buyerContract = await EIP1271WalletFactory.deploy(buyer.address); - - await Promise.all( - [seller, buyer, zone, sellerContract, buyerContract].map((wallet) => - faucet(wallet.address, provider) - ) - ); - }); - - describe("Misconfigured orders", async () => { - it("Reverts on bad fraction amounts", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 0; - order.denominator = 10; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("BadFraction"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 0; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("BadFraction"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 2; - order.denominator = 1; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("BadFraction"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 2; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 2) - ); - }); - it("Reverts on inexact fraction amounts", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 8191; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("InexactFraction"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 2; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 2) - ); - }); - it("Reverts on partial fill attempt when not supported by order", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 2; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("PartialFillsNotEnabledForOrder"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 1; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Reverts on partially filled order via basic fulfillment", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 2; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 2) - ); - - const basicOrderParameters = getBasicOrderParameters( - 1, // EthForERC1155 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith(`OrderPartiallyFilled("${orderHash}")`); - }); - it("Reverts on fully filled order", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - order.numerator = 1; - order.denominator = 1; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`OrderAlreadyFilled("${orderHash}")`); - }); - it("Reverts on non-zero unused item parameters (identifier set on native, basic)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - consideration[0].identifierOrCriteria = amount; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 1, // EthForERC1155 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith(`UnusedItemParameters`); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - }); - it("Reverts on non-zero unused item parameters (identifier set on ERC20, basic)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - consideration[0].identifierOrCriteria = amount; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 3, // ERC20ForERC1155 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith(`UnusedItemParameters`); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - }); - it("Reverts on non-zero unused item parameters (token set on native, standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - consideration[0].token = seller.address; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`UnusedItemParameters`); - }); - it("Reverts on non-zero unused item parameters (identifier set on native, standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - consideration[0].identifierOrCriteria = amount; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`UnusedItemParameters`); - }); - it("Reverts on non-zero unused item parameters (identifier set on ERC20, standard)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - consideration[0].identifierOrCriteria = amount; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`UnusedItemParameters`); - }); - it("Reverts on inadequate consideration items", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 1 // PARTIAL_OPEN - ); - - // Remove a consideration item, but do not reduce - // totalOriginalConsiderationItems as MissingOriginalConsiderationItems - // is being tested for - order.parameters.consideration.pop(); - - const orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("MissingOriginalConsiderationItems"); - }); - it("Reverts on invalid submitter when required by order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 2 // FULL_RESTRICTED - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - zone, - value - ); - - expect(executions.length).to.equal(4); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.reverted; - } - - const tx = marketplaceContract - .connect(zone) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("Reverts on invalid signatures", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const originalSignature = order.signature; - - // set an invalid V value - order.signature = order.signature.slice(0, -2) + "01"; - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - let expectedRevertReason = - getCustomRevertSelector("BadSignatureV(uint8)") + - "1".padStart(64, "0"); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillBasicOrder(basicOrderParameters, { - value, - }); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.reverted; - - // construct an invalid signature - basicOrderParameters.signature = "0x".padEnd(130, "f") + "1c"; - - expectedRevertReason = getCustomRevertSelector("InvalidSigner()"); - - tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillBasicOrder(basicOrderParameters, { - value, - }); - expect(provider.call(tx)).to.be.revertedWith("InvalidSigner"); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.reverted; - - basicOrderParameters.signature = originalSignature; - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Reverts on invalid 1271 signature", async () => { - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(sellerContract.address, marketplaceContract.address, true); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(40, 40, zone.address), - getTestItem20(40, 40, owner.address), - ]; - - const { order } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - zone // wrong signer - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.revertedWith("BAD SIGNER"); - }); - it("Reverts on invalid contract 1271 signature and contract does not supply a revert reason", async () => { - await sellerContract.connect(owner).revertWithMessage(false); - - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(sellerContract.address, marketplaceContract.address, true); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - zone // wrong signer - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.revertedWith("BadContractSignature"); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.reverted; - } - }); - it("Reverts on invalid contract 1271 signature and contract does not return magic value", async () => { - await sellerContract.connect(owner).setValid(false); - - // Seller mints nft to contract - const nftId = await mint721(sellerContract); - - // Seller approves marketplace contract to transfer NFT - await expect( - sellerContract - .connect(seller) - .approveNFT(testERC721.address, marketplaceContract.address) - ) - .to.emit(testERC721, "ApprovalForAll") - .withArgs(sellerContract.address, marketplaceContract.address, true); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getTestItem20( - tokenAmount.sub(100), - tokenAmount.sub(100), - sellerContract.address - ), - getTestItem20(50, 50, zone.address), - getTestItem20(50, 50, owner.address), - ]; - - const { order } = await createOrder( - sellerContract, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20ForERC721 - order - ); - - if (!process.env.REFERENCE) { - const expectedRevertReason = getCustomRevertSelector( - "BadContractSignature()" - ); - - let tx = await marketplaceContract - .connect(buyer) - .populateTransaction.fulfillBasicOrder(basicOrderParameters); - let returnData = await provider.call(tx); - expect(returnData).to.equal(expectedRevertReason); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.reverted; - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters) - ).to.be.reverted; - } - - await sellerContract.connect(owner).setValid(true); - }); - it("Reverts on restricted order where isValidOrder reverts with no data", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2, // FULL_RESTRICTED, - [], - null, - seller, - "0x".padEnd(65, "0") + "2" - ); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - - order.extraData = "0x0102030405"; - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; - } - }); - it("Reverts on restricted order where isValidOrder returns non-magic value", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - stubZone, - offer, - consideration, - 2, // FULL_RESTRICTED, - [], - null, - seller, - "0x".padEnd(65, "0") + "3" - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.reverted; - } - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - - order.extraData = "0x01"; - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; - } - }); - it("Reverts on missing offer or consideration components", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - let fulfillments = [ - { - offerComponents: [], - considerationComponents: [], - }, - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { value }) - ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); - - fulfillments = [ - { - offerComponents: [], - considerationComponents: [ - { - orderIndex: 0, - itemIndex: 0, - }, - ], - }, - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { value }) - ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); - - fulfillments = [ - { - offerComponents: [ - { - orderIndex: 0, - itemIndex: 0, - }, - ], - considerationComponents: [], - }, - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); - - fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("Reverts on mismatched offer and consideration components", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - let fulfillments = [toFulfillment([[0, 0]], [[0, 0]])]; - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith( - "MismatchedFulfillmentOfferAndConsiderationComponents" - ); - - fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("Reverts on mismatched offer components", async () => { - // Seller mints nft - const nftId = await mint721(seller); - - const secondNFTId = await mint721(seller); - - // Seller approves marketplace contract to transfer NFT - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - { - itemType: 2, // ERC721 - token: testERC721.address, - identifierOrCriteria: nftId, - startAmount: toBN(1), - endAmount: toBN(1), - }, - { - itemType: 2, // ERC721 - token: testERC721.address, - identifierOrCriteria: secondNFTId, - startAmount: toBN(1), - endAmount: toBN(1), - }, - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [ - [ - [0, 0], - [0, 1], - ], - [[1, 0]], - ], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on mismatched consideration components", async () => { - // Seller mints nft - const nftId = await mint721(seller); - - const secondNFTId = await mint721(seller); - - // Seller approves marketplace contract to transfer NFT - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - { - itemType: 2, // ERC721 - token: testERC721.address, - identifierOrCriteria: nftId, - startAmount: toBN(1), - endAmount: toBN(1), - }, - { - itemType: 2, // ERC721 - token: testERC721.address, - identifierOrCriteria: secondNFTId, - startAmount: toBN(1), - endAmount: toBN(1), - }, - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getTestItem20(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [ - [[0, 0]], - [ - [1, 0], - [1, 1], - ], - ], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillment component with out-of-range order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [ - [[2, 0]], - [ - [1, 0], - [1, 1], - ], - ], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillment component with out-of-range offer item", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 5]], [[1, 0]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillment component with out-of-range initial order on fulfillAvailableOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [ - getTestItem1155(nftId, amount.div(2), amount.div(2)), - getTestItem1155(nftId, amount.div(2), amount.div(2)), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [5, 0], - [0, 0], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [order], - offerComponents, - considerationComponents, - toKey(false), - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillment component with out-of-range initial offer item on fulfillAvailableOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [ - getTestItem1155(nftId, amount.div(2), amount.div(2)), - getTestItem1155(nftId, amount.div(2), amount.div(2)), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 5], - [0, 0], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - let success = false; - - try { - const tx = await marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [order], - offerComponents, - considerationComponents, - toKey(false), - 100, - { - value, - } - ); - - const receipt = await tx.wait(); - success = receipt.status; - } catch (err) {} - - expect(success).to.be.false; // TODO: fix out-of-gas - }); - it("Reverts on fulfillment component with out-of-range subsequent offer item on fulfillAvailableOrders", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [ - getTestItem1155(nftId, amount.div(2), amount.div(2)), - getTestItem1155(nftId, amount.div(2), amount.div(2)), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 0], - [0, 5], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [order], - offerComponents, - considerationComponents, - toKey(false), - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillment component with out-of-range consideration item", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 5]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on unmet consideration items", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith( - `ConsiderationNotMet(0, 2, ${parseEther("1").toString()}` - ); - }); - it("Reverts on fulfillAvailableAdvancedOrders with empty fulfillment component", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [[]]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("MissingFulfillmentComponentOnAggregation(0)"); - }); - it("Reverts on fulfillAvailableAdvancedOrders with out-of-range initial offer order", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 2); - - // Seller approves marketplace contract to transfer NFT - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(nftId, amount, amount, undefined), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [2, 0], - [0, 0], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillAvailableAdvancedOrders with out-of-range offer order", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 2); - - // Seller approves marketplace contract to transfer NFT - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(nftId, amount, amount, undefined), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 0], - [2, 0], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillAvailableAdvancedOrders with mismatched offer components", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId), getTestItem20(1, 1)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [ - [ - [0, 0], - [0, 1], - ], - ]; - - const considerationComponents = [[[0, 0]], [[0, 1]], [[0, 2]]]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillAvailableAdvancedOrders with out-of-range consideration order", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [[[0, 0]]]; - - const considerationComponents = [ - [ - [0, 0], - [2, 1], - ], - [[2, 2]], - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillAvailableAdvancedOrders with mismatched consideration components", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - { - itemType: 2, // ERC721 - token: testERC721.address, - identifierOrCriteria: nftId, - startAmount: toBN(1), - endAmount: toBN(1), - recipient: zone.address, - }, - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const offerComponents = [[[0, 0]]]; - - const considerationComponents = [ - [ - [0, 0], - [0, 1], - ], - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value, - } - ) - ).to.be.revertedWith("InvalidFulfillmentComponentData"); - }); - it("Reverts on fulfillAvailableAdvancedOrders no available components", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - // first order is expired - const { order: orderOne, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - // second order will be cancelled - const { - order: orderTwo, - orderHash: orderHashTwo, - orderComponents, - } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can cancel it - await expect( - marketplaceContract.connect(seller).cancel([orderComponents]) - ) - .to.emit(marketplaceContract, "OrderCancelled") - .withArgs(orderHashTwo, seller.address, zone.address); - - // third order will be filled - const { order: orderThree, orderHash: orderHashThree } = - await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // can fill it - await withBalanceChecks([orderThree], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillOrder(orderThree, toKey(false), { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order: orderThree, - orderHash: orderHashThree, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - - const offerComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - ], - ]; - - const considerationComponents = [ - [ - [0, 0], - [1, 0], - [2, 0], - ], - [ - [0, 1], - [1, 1], - [2, 1], - ], - [ - [0, 2], - [1, 2], - [2, 2], - ], - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [orderOne, orderTwo, orderThree], - [], - offerComponents, - considerationComponents, - toKey(false), - constants.AddressZero, - 100, - { - value: value.mul(3), - } - ) - ).to.be.revertedWith("NoSpecifiedOrdersAvailable"); - }); - it("Reverts on out-of-range criteria resolvers", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - let criteriaResolvers = [ - buildResolver(3, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("OrderCriteriaResolverOutOfRange"); - - criteriaResolvers = [ - buildResolver(0, 0, 5, nftId, proofs[nftId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("OfferCriteriaResolverOutOfRange"); - - criteriaResolvers = [ - buildResolver(0, 1, 5, nftId, proofs[nftId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("ConsiderationCriteriaResolverOutOfRange"); - - criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - if (process.env.REFERENCE) { - it("Reverts on out-of-range criteria resolver (match)", async () => { - // Seller mints nfts - const nftId = await mint721(seller); - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - 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"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - let criteriaResolvers = [ - buildResolver(3, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder } = await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("OrderCriteriaResolverOutOfRange"); - - criteriaResolvers = [ - buildResolver(0, 0, 5, nftId, proofs[nftId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("OfferCriteriaResolverOutOfRange"); - - criteriaResolvers = [ - buildResolver(0, 1, 5, nftId, proofs[nftId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("ConsiderationCriteriaResolverOutOfRange"); - }); - } - it("Reverts on unresolved criteria items", async () => { - // Seller and buyer both mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(buyer.address, secondNFTId); - - const tokenIds = [nftId, secondNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - // Buyer approves marketplace contract to transfer NFTs - await set721ApprovalForAll(buyer, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getTestItem721WithCriteria(root, toBN(1), toBN(1), owner.address), - ]; - - let criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("UnresolvedConsiderationCriteria"); - - criteriaResolvers = [ - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("UnresolvedOfferCriteria"); - - criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - if (process.env.REFERENCE) { - it("Reverts on unresolved criteria items (match)", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - - const tokenIds = [nftId, secondNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getTestItem721WithCriteria(root, toBN(1), toBN(1), owner.address), - ]; - - let criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { mirrorOrder } = await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("UnresolvedConsiderationCriteria"); - - criteriaResolvers = [ - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("UnresolvedOfferCriteria"); - - criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), - ]; - }); - } - it("Reverts on attempts to resolve criteria for non-criteria item", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { proofs } = merkleTree(tokenIds); - - const offer = [ - getTestItem721( - nftId, - toBN(1), - toBN(1), - undefined, - testERC721.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("CriteriaNotEnabledForItem"); - }); - if (process.env.REFERENCE) { - it("Reverts on attempts to resolve criteria for non-criteria item (match)", async () => { - // Seller mints nfts - const nftId = await mint721(seller); - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree([nftId]); - - const offer = [ - getTestItem721( - root, - toBN(1), - toBN(1), - undefined, - testERC721.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - const { mirrorOrder } = await createMirrorAcceptOfferOrder( - buyer, - zone, - order, - criteriaResolvers - ); - - const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders( - [order, mirrorOrder], - criteriaResolvers, - fulfillments, - { - value, - } - ) - ).to.be.revertedWith("CriteriaNotEnabledForItem"); - }); - } - it("Reverts on offer amount overflow", async () => { - const { testERC20: testERC20Two } = await fixtureERC20(owner); - // Buyer mints nfts - const nftId = await mintAndApprove721( - buyer, - marketplaceContract.address - ); - - await testERC20Two.mint(seller.address, constants.MaxUint256); - // Seller approves marketplace contract to transfer NFTs - await testERC20Two - .connect(seller) - .approve(marketplaceContract.address, constants.MaxUint256); - - const offer = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - ]; - - const consideration = [getTestItem721(nftId, 1, 1, seller.address)]; - - const offer2 = [getTestItem721(nftId, 1, 1)]; - const consideration2 = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - buyer.address, - testERC20Two.address - ), - ]; - - const fulfillments = [ - toFulfillment( - [ - [0, 0], - [0, 1], - ], - [[1, 0]] - ), - toFulfillment([[1, 0]], [[0, 0]]), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 1 - ); - - const { order: order2 } = await createOrder( - buyer, - zone, - offer2, - consideration2, - 1 - ); - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders([order, order2], [], fulfillments) - ).to.be.revertedWith( - "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" - ); - }); - - it("Reverts on offer amount overflow when another amount is 0", async () => { - const { testERC20: testERC20Two } = await fixtureERC20(owner); - // Buyer mints nfts - const nftId = await mintAndApprove721( - buyer, - marketplaceContract.address - ); - - await testERC20Two.mint(seller.address, constants.MaxUint256); - // Seller approves marketplace contract to transfer NFTs - await testERC20Two - .connect(seller) - .approve(marketplaceContract.address, constants.MaxUint256); - - const offer = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - getTestItem20(0, 0, undefined, testERC20Two.address), - ]; - - const consideration = [getTestItem721(nftId, 1, 1, seller.address)]; - - const offer2 = [getTestItem721(nftId, 1, 1)]; - const consideration2 = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - buyer.address, - testERC20Two.address - ), - ]; - - const fulfillments = [ - toFulfillment( - [ - [0, 0], - [0, 1], - [0, 2], - ], - [[1, 0]] - ), - toFulfillment([[1, 0]], [[0, 0]]), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 1 - ); - - const { order: order2 } = await createOrder( - buyer, - zone, - offer2, - consideration2, - 1 - ); - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders([order, order2], [], fulfillments) - ).to.be.revertedWith( - "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" - ); - }); - - it("Reverts on consideration amount overflow", async () => { - const { testERC20: testERC20Two } = await fixtureERC20(owner); - // Buyer mints nfts - const nftId = await mintAndApprove721( - buyer, - marketplaceContract.address - ); - - await testERC20Two.mint(seller.address, constants.MaxUint256); - // Seller approves marketplace contract to transfer NFTs - await testERC20Two - .connect(seller) - .approve(marketplaceContract.address, constants.MaxUint256); - - const offer = [getTestItem721(nftId, 1, 1)]; - - const consideration = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - seller.address, - testERC20Two.address - ), - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - seller.address, - testERC20Two.address - ), - ]; - - const offer2 = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - ]; - const consideration2 = [getTestItem721(nftId, 1, 1, buyer.address)]; - - const fulfillments = [ - toFulfillment( - [[1, 0]], - [ - [0, 0], - [0, 1], - ] - ), - toFulfillment([[0, 0]], [[1, 0]]), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 1 - ); - - const { order: order2 } = await createOrder( - buyer, - zone, - offer2, - consideration2, - 1 - ); - - await expect( - marketplaceContract - .connect(owner) - .matchAdvancedOrders([order, order2], [], fulfillments) - ).to.be.revertedWith( - "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" - ); - }); - - it("Reverts on consideration amount overflow when another amount is 0", async () => { - const { testERC20: testERC20Two } = await fixtureERC20(owner); - // Buyer mints nfts - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - await testERC20Two.mint(buyer.address, constants.MaxUint256); - // Seller approves marketplace contract to transfer NFTs - await testERC20Two - .connect(buyer) - .approve(marketplaceContract.address, constants.MaxUint256); - - const offer = [getTestItem721(nftId, 1, 1)]; - - const consideration = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - seller.address, - testERC20Two.address - ), - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - seller.address, - testERC20Two.address - ), - getTestItem20(0, 0, seller.address, testERC20Two.address), - ]; - - const offer2 = [ - getTestItem20( - constants.MaxUint256, - constants.MaxUint256, - undefined, - testERC20Two.address - ), - ]; - const consideration2 = [getTestItem721(nftId, 1, 1, buyer.address)]; - - const fulfillments = [ - toFulfillment( - [[1, 0]], - [ - [0, 0], - [0, 1], - [0, 2], - ] - ), - toFulfillment([[0, 0]], [[1, 0]]), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 1 - ); - - const { order: order2 } = await createOrder( - buyer, - zone, - offer2, - consideration2, - 1 - ); - - await expect( - marketplaceContract.matchAdvancedOrders( - [order, order2], - [], - fulfillments - ) - ).to.be.revertedWith( - "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" - ); - }); - - it("Reverts on invalid criteria proof", async () => { - // Seller mints nfts - const nftId = randomBN(); - const secondNFTId = randomBN(); - const thirdNFTId = randomBN(); - - await testERC721.mint(seller.address, nftId); - await testERC721.mint(seller.address, secondNFTId); - await testERC721.mint(seller.address, thirdNFTId); - - const tokenIds = [nftId, secondNFTId, thirdNFTId]; - - // Seller approves marketplace contract to transfer NFTs - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - const { root, proofs } = merkleTree(tokenIds); - - const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const criteriaResolvers = [ - buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - criteriaResolvers - ); - - criteriaResolvers[0].identifier = - criteriaResolvers[0].identifier.add(1); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("InvalidProof"); - - criteriaResolvers[0].identifier = - criteriaResolvers[0].identifier.sub(1); - - await withBalanceChecks([order], 0, criteriaResolvers, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - criteriaResolvers, - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - criteriaResolvers - ); - - return receipt; - }); - }); - it("Reverts on attempts to transfer >1 ERC721 in single transfer", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [ - getTestItem721( - nftId, - toBN(2), - toBN(2), - undefined, - testERC721.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); - it("Reverts on attempts to transfer >1 ERC721 in single transfer (basic)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [ - getTestItem721( - nftId, - toBN(2), - toBN(2), - undefined, - testERC721.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); - it("Reverts on attempts to transfer >1 ERC721 in single transfer via conduit", async () => { - const nftId = await mintAndApprove721(seller, conduitOne.address, true); - - const offer = [ - getTestItem721( - nftId, - toBN(2), - toBN(2), - undefined, - testERC721.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); - }); - - describe("Out of timespan", async () => { - it("Reverts on orders that have not started (standard)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "NOT_STARTED" - ); - - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - it("Reverts on orders that have expired (standard)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - await expect( - marketplaceContract.connect(buyer).fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - it("Reverts on orders that have not started (basic)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "NOT_STARTED" - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - it("Reverts on orders that have expired (basic)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - it("Reverts on orders that have not started (match)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "NOT_STARTED" - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], defaultBuyNowMirrorFulfillment, { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - it("Reverts on orders that have expired (match)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - "EXPIRED" - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], defaultBuyNowMirrorFulfillment, { - value, - }) - ).to.be.revertedWith("InvalidTime"); - }); - }); - - describe("Insufficient amounts and bad items", async () => { - it("Reverts when no ether is supplied (basic)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: toBN(0), - }) - ).to.be.revertedWith("InvalidMsgValue"); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Reverts when not enough ether is supplied (basic)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: toBN(1), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: value.sub(1), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await withBalanceChecks([order], 0, null, async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents(tx, receipt, [ - { - order, - orderHash, - fulfiller: buyer.address, - }, - ]); - - return receipt; - }); - }); - it("Reverts when not enough ether is supplied as offer item (match)", async () => { - // NOTE: this is a ridiculous scenario, buyer is paying the seller's offer - const offer = [getItemETH(parseEther("10"), parseEther("10"))]; - - const consideration = [ - getItemETH(parseEther("1"), parseEther("1"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await expect( - marketplaceContract - .connect(buyer) - .matchOrders([order, mirrorOrder], fulfillments, { - value: toBN(1), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await expect( - marketplaceContract - .connect(buyer) - .matchOrders([order, mirrorOrder], fulfillments, { - value: parseEther("9.999999"), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await marketplaceContract - .connect(buyer) - .matchOrders([order, mirrorOrder], fulfillments, { - value: parseEther("13"), - }); - }); - it("Reverts when not enough ether is supplied (standard + advanced)", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(amount.mul(1000), amount.mul(1000), seller.address), - getItemETH(amount.mul(10), amount.mul(10), zone.address), - getItemETH(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value: toBN(1), - } - ) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value: value.sub(1), - } - ) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // fulfill with a tiny bit extra to test for returning eth - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value: value.add(1), - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Reverts when not enough ether is supplied (match)", 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), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = defaultBuyNowMirrorFulfillment; - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(4); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value: toBN(1), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value: value.sub(1), - }) - ).to.be.revertedWith("InsufficientEtherSupplied"); - - await whileImpersonating(owner.address, provider, async () => { - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - }); - it("Reverts when ether is supplied to a non-payable route (basic)", async () => { - // Seller mints nft - const nftId = randomBN(); - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH( - parseEther("1"), - parseEther("1"), - marketplaceContract.address - ), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 2, // ERC20_TO_ERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: 1, - }) - ).to.be.revertedWith("InvalidMsgValue(1)"); - }); - - it(`Reverts when ether transfer fails (returndata)${ - process.env.REFERENCE ? " — SKIPPED ON REFERENCE" : "" - }`, async () => { - if (process.env.REFERENCE) { - return; - } - - const recipient = await ( - await ethers.getContractFactory("ExcessReturnDataRecipient") - ).deploy(); - const setup = async () => { - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Seller approves marketplace contract to transfer NFT - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - // Buyer approves marketplace contract to transfer tokens - - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), recipient.address), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - return basicOrderParameters; - }; - let basicOrderParameters = await setup(); - const baseGas = await marketplaceContract - .connect(buyer) - .estimateGas.fulfillBasicOrder(basicOrderParameters, { - value: parseEther("12"), - }); - - // TODO: clean *this* up - basicOrderParameters = await setup(); - await recipient.setRevertDataSize(1); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: parseEther("12"), - gasLimit: hre.__SOLIDITY_COVERAGE_RUNNING - ? baseGas.add(35000) - : baseGas.add(1000), - }) - ).to.be.revertedWith("EtherTransferGenericFailure"); - }); - - it("Reverts when ether transfer fails (basic)", async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Seller approves marketplace contract to transfer NFT - await set721ApprovalForAll(seller, marketplaceContract.address, true); - - // Buyer approves marketplace contract to transfer tokens - - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH( - parseEther("1"), - parseEther("1"), - marketplaceContract.address - ), - ]; - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value: parseEther("12"), - }) - ).to.be.revertedWith( - `EtherTransferGenericFailure("${ - marketplaceContract.address - }", ${parseEther("1").toString()})` - ); - }); - it("Reverts when tokens are not approved", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; // panic code thrown by underlying 721 - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // Buyer approves marketplace contract to transfer tokens - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Reverts when 1155 token transfer reverts", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 10000); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("NOT_AUTHORIZED"); - }); - it("Reverts when 1155 token transfer reverts (via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 10000); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`NOT_AUTHORIZED`); - }); - - // Skip this test when testing the reference contract - if (!process.env.REFERENCE) { - it("Reverts when 1155 token transfer reverts (via conduit, returndata)", async () => { - const recipient = await ( - await ethers.getContractFactory("ExcessReturnDataRecipient") - ).deploy(); - - const setup = async () => { - // seller mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(seller.address, tokenAmount); - - // Seller approves conduit contract to transfer tokens - await expect( - testERC20.connect(seller).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(seller.address, conduitOne.address, tokenAmount); - - // Buyer mints nft - const nftId = randomBN(); - const amount = toBN(randomBN(2)); - await testERC1155.mint(buyer.address, nftId, amount.mul(10000)); - - // Buyer approves conduit contract to transfer NFTs - await expect( - testERC1155 - .connect(buyer) - .setApprovalForAll(conduitOne.address, true) - ) - .to.emit(testERC1155, "ApprovalForAll") - .withArgs(buyer.address, conduitOne.address, true); - - const offer = [getTestItem20(tokenAmount, tokenAmount)]; - - const consideration = [ - getTestItem1155( - nftId, - amount.mul(10), - amount.mul(10), - undefined, - recipient.address - ), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - return { - order, - value, - }; - }; - - const { order: initialOrder, value } = await setup(); - const baseGas = await marketplaceContract - .connect(buyer) - .estimateGas.fulfillAdvancedOrder( - initialOrder, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ); - - // TODO: clean *this* up - const { order } = await setup(); - await recipient.setRevertDataSize(1); - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - gasLimit: baseGas.add(74000), - } - ) - ).to.be.revertedWith("InvalidCallToConduit"); - }); - } - - it("Reverts when transferred item amount is zero", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem1155(nftId, 0, 0, undefined)]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith("MissingItemAmount"); - }); - it("Reverts when ERC20 tokens return falsey values", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // block transfers - await testERC20.blockTransfer(true); - - expect(await testERC20.blocked()).to.be.true; - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; // TODO: hardhat can't find error msg on IR pipeline - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await testERC20.blockTransfer(false); - - expect(await testERC20.blocked()).to.be.false; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Works when ERC20 tokens return falsey values", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address, - 10000 - ); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves marketplace contract to transfer tokens - - await expect( - testERC20 - .connect(buyer) - .approve(marketplaceContract.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, marketplaceContract.address, tokenAmount); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await testERC20.setNoReturnData(true); - - expect(await testERC20.noReturnData()).to.be.true; - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: toKey(false), - }, - ], - null, - [] - ); - - return receipt; - }); - - const orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - - await testERC20.setNoReturnData(false); - - expect(await testERC20.noReturnData()).to.be.false; - }); - it("Reverts when ERC20 tokens return falsey values (via conduit)", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 10000); - - // Seller approves conduit contract to transfer NFTs - await set1155ApprovalForAll(seller, conduitOne.address, true); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves conduit contract to transfer tokens - - await expect( - testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, conduitOne.address, tokenAmount); - - // Seller approves conduit contract to transfer tokens - await expect( - testERC20.connect(seller).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(seller.address, conduitOne.address, tokenAmount); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - // block transfers - await testERC20.blockTransfer(true); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith( - `BadReturnValueFromERC20OnTransfer("${testERC20.address}", "${ - buyer.address - }", "${seller.address}", ${amount.mul(1000).toString()})` - ); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; - } - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await testERC20.blockTransfer(false); - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ], - null, - [] - ); - - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Reverts when providing non-existent conduit", async () => { - // Seller mints nft - const { nftId, amount } = await mint1155(seller, 10000); - - // Seller approves conduit contract to transfer NFTs - await set1155ApprovalForAll(seller, conduitOne.address, true); - - // Buyer mints ERC20 - const tokenAmount = minRandom(100); - await testERC20.mint(buyer.address, tokenAmount); - - // Buyer approves conduit contract to transfer tokens - await expect( - testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(buyer.address, conduitOne.address, tokenAmount); - - // Seller approves conduit contract to transfer tokens - await expect( - testERC20.connect(seller).approve(conduitOne.address, tokenAmount) - ) - .to.emit(testERC20, "Approval") - .withArgs(seller.address, conduitOne.address, tokenAmount); - - const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; - - const consideration = [ - getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), - getTestItem20(amount.mul(10), amount.mul(10), zone.address), - getTestItem20(amount.mul(20), amount.mul(20), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const badKey = constants.HashZero.slice(0, -1) + "2"; - - const missingConduit = await conduitController.getConduit(badKey); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], badKey, constants.AddressZero, { - value, - }) - ).to.be.revertedWith("InvalidConduit", badKey, missingConduit); - - let orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - await withBalanceChecks([order], 0, [], async () => { - const tx = marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: buyer.address, - fulfillerConduitKey: conduitKeyOne, - }, - ], - null, - null - ); - return receipt; - }); - - orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(true, false, 1, 1) - ); - }); - it("Reverts when 1155 tokens are not approved", async () => { - // Seller mints first nft - const { nftId } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - seller - ); - - const offer = [ - getTestItem1155(nftId, 0, 0), - getTestItem1155(secondNftId, secondAmount, secondAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("MissingItemAmount"); - }); - it("Reverts when 1155 tokens are not approved", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - seller - ); - - const offer = [ - getTestItem1155(nftId, amount, amount, undefined), - getTestItem1155(secondNftId, secondAmount, secondAmount), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("NOT_AUTHORIZED"); - - const orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - - // Seller approves marketplace contract to transfer NFT - - await set1155ApprovalForAll(seller, marketplaceContract.address, true); - - const executions = await simulateMatchOrders( - [order, mirrorOrder], - fulfillments, - owner, - value - ); - - expect(executions.length).to.equal(5); - - const tx = marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }); - const receipt = await (await tx).wait(); - await checkExpectedEvents( - tx, - receipt, - [ - { - order, - orderHash, - fulfiller: constants.AddressZero, - }, - { - order: mirrorOrder, - orderHash: mirrorOrderHash, - fulfiller: constants.AddressZero, - }, - ], - executions - ); - return receipt; - }); - it("Reverts when token account with no code is supplied", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const consideration = [ - getTestItem20(amount, amount, seller.address, constants.AddressZero), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; // TODO: look into the revert reason more thoroughly - // Transaction reverted: function returned an unexpected amount of data - }); - it("Reverts when 721 account with no code is supplied", async () => { - const offer = [getTestItem721(0, 1, 1, undefined, buyer.address)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { value } - ) - ).to.be.revertedWith(`NoContract("${buyer.address}")`); - }); - it("Reverts when 1155 account with no code is supplied", async () => { - const amount = toBN(randomBN(2)); - - const offer = [ - getTestItem1155(0, amount, amount, constants.AddressZero), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`NoContract("${constants.AddressZero}")`); - }); - it("Reverts when 1155 account with no code is supplied (via conduit)", async () => { - const amount = toBN(randomBN(2)); - - const offer = [ - getTestItem1155(0, amount, amount, constants.AddressZero), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith(`NoContract("${constants.AddressZero}")`); - }); - it("Reverts when non-token account is supplied as the token", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const consideration = [ - getTestItem20( - amount, - amount, - seller.address, - marketplaceContract.address - ), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith( - `TokenTransferGenericFailure("${marketplaceContract.address}", "${ - buyer.address - }", "${seller.address}", 0, ${amount.toString()})` - ); - }); - it("Reverts when non-token account is supplied as the token fulfilled via conduit", async () => { - // Seller mints nft - const { nftId, amount } = await mintAndApprove1155( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem1155(nftId, amount, amount, undefined)]; - - const consideration = [ - getTestItem20( - amount, - amount, - seller.address, - marketplaceContract.address - ), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - conduitKeyOne, - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith( - `TokenTransferGenericFailure("${marketplaceContract.address}", "${ - buyer.address - }", "${seller.address}", 0, ${amount.toString()})` - ); - }); - it("Reverts when non-1155 account is supplied as the token", async () => { - const amount = toBN(randomBN(2)); - - const offer = [ - getTestItem1155(0, amount, amount, marketplaceContract.address), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.revertedWith( - `TokenTransferGenericFailure("${marketplaceContract.address}", "${ - seller.address - }", "${buyer.address}", 0, ${amount.toString()})` - ); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder( - order, - [], - toKey(false), - constants.AddressZero, - { - value, - } - ) - ).to.be.reverted; - } - }); - it("Reverts when 1155 token is not approved via conduit", async () => { - // Seller mints first nft - const { nftId, amount } = await mint1155(seller); - - // Seller mints second nft - const { nftId: secondNftId, amount: secondAmount } = await mint1155( - seller - ); - - const offer = [ - getTestItem1155(nftId, amount, amount, testERC1155.address), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - testERC1155.address - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("NOT_AUTHORIZED"); - - const orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - }); - it("Reverts when 1155 token with no code is supplied as the token via conduit", async () => { - // Seller mints first nft - const nftId = toBN(randomBN(4)); - const amount = toBN(randomBN(4)); - - // Seller mints second nft - const secondNftId = toBN(randomBN(4)); - const secondAmount = toBN(randomBN(4)); - - const offer = [ - getTestItem1155(nftId, amount, amount, constants.AddressZero), - getTestItem1155( - secondNftId, - secondAmount, - secondAmount, - constants.AddressZero - ), - ]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - getItemETH(parseEther("1"), parseEther("1"), zone.address), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, orderHash, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0, // FULL_OPEN - [], - null, - seller, - constants.HashZero, - conduitKeyOne - ); - - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - - const fulfillments = [ - [[[0, 0]], [[1, 0]]], - [[[0, 1]], [[1, 1]]], - [[[1, 0]], [[0, 0]]], - [[[1, 0]], [[0, 1]]], - [[[1, 0]], [[0, 2]]], - ].map(([offerArr, considerationArr]) => - toFulfillment(offerArr, considerationArr) - ); - - await expect( - marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value, - }) - ).to.be.revertedWith("NoContract", constants.AddressZero); - - const orderStatus = await marketplaceContract.getOrderStatus(orderHash); - - expect({ ...orderStatus }).to.deep.equal( - buildOrderStatus(false, false, 0, 0) - ); - }); - it("Reverts when non-payable ether recipient is supplied", 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"), - marketplaceContract.address - ), - getItemETH(parseEther("1"), parseEther("1"), owner.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillBasicOrder(basicOrderParameters, { - value, - }) - ).to.be.revertedWith( - `EtherTransferGenericFailure("${ - marketplaceContract.address - }", ${parseEther("1").toString()})` - ); - }); - }); - - describe("Basic Order Calldata", () => { - let calldata, value; - - before(async () => { - // Seller mints nft - const nftId = await mintAndApprove721( - seller, - marketplaceContract.address - ); - - const offer = [getTestItem721(nftId)]; - - const consideration = [ - getItemETH(parseEther("10"), parseEther("10"), seller.address), - ]; - let order; - ({ order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - )); - - const basicOrderParameters = getBasicOrderParameters( - 0, // EthForERC721 - order - ); - - ({ data: calldata } = - await marketplaceContract.populateTransaction.fulfillBasicOrder( - basicOrderParameters - )); - }); - - it("Reverts if BasicOrderParameters has non-default offset", async () => { - const badData = [calldata.slice(0, 73), "1", calldata.slice(74)].join( - "" - ); - expect(badData.length).to.eq(calldata.length); - - await expect( - buyer.sendTransaction({ - to: marketplaceContract.address, - data: badData, - value, - }) - ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); - }); - - it("Reverts if additionalRecipients has non-default offset", async () => { - const badData = [ - calldata.slice(0, 1161), - "1", - calldata.slice(1162), - ].join(""); - - await expect( - buyer.sendTransaction({ - to: marketplaceContract.address, - data: badData, - value, - }) - ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); - }); - - it("Reverts if signature has non-default offset", async () => { - const badData = [ - calldata.slice(0, 1161), - "2", - calldata.slice(1162), - ].join(""); - - await expect( - buyer.sendTransaction({ - to: marketplaceContract.address, - data: badData, - value, - }) - ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); - }); - }); - - describe("Reentrancy", async () => { - it("Reverts on a reentrant call", 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"), reenterer.address), - ]; - - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - // prepare the reentrant call on the reenterer - const callData = marketplaceContract.interface.encodeFunctionData( - "fulfillOrder", - [order, toKey(false)] - ); - const tx = await reenterer.prepare( - marketplaceContract.address, - 0, - callData - ); - await tx.wait(); - - if (!process.env.REFERENCE) { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.revertedWith("NoReentrantCalls"); - } else { - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { - value, - }) - ).to.be.reverted; - } - }); - }); - - describe("ETH offer items", async () => { - let ethAmount; - const tokenAmount = minRandom(100); - let offer; - let consideration; - let seller; - let buyer; - - before(async () => { - ethAmount = parseEther("1"); - seller = await getWalletWithEther(); - buyer = await getWalletWithEther(); - zone = new ethers.Wallet(randomHex(32), provider); - offer = [getItemETH(ethAmount, ethAmount)]; - consideration = [ - getTestItem20(tokenAmount, tokenAmount, seller.address), - ]; - }); - - it("fulfillOrder reverts if any offer item is ETH", async () => { - const { order, value } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillOrder(order, toKey(false), { value }) - ).to.be.revertedWith("InvalidNativeOfferItem"); - }); - - it("fulfillAdvancedOrder reverts if any offer item is ETH", async () => { - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAdvancedOrder(order, [], toKey(false), buyer.address, { - value: ethAmount, - }) - ).to.be.revertedWith("InvalidNativeOfferItem"); - }); - - it("fulfillAvailableOrders reverts if any offer item is ETH", async () => { - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableOrders( - [order], - [[[0, 0]]], - [[[0, 0]]], - toKey(false), - 100, - { value: ethAmount } - ) - ).to.be.revertedWith("InvalidNativeOfferItem"); - }); - - it("fulfillAvailableAdvancedOrders reverts if any offer item is ETH", async () => { - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - - await expect( - marketplaceContract - .connect(buyer) - .fulfillAvailableAdvancedOrders( - [order], - [], - [[[0, 0]]], - [[[0, 0]]], - toKey(false), - buyer.address, - 100, - { value: ethAmount } - ) - ).to.be.revertedWith("InvalidNativeOfferItem"); - }); - - it("matchOrders allows fulfilling with native offer items", async () => { - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - const fulfillments = [ - toFulfillment([[0, 0]], [[1, 0]]), - toFulfillment([[1, 0]], [[0, 0]]), - ]; - - await marketplaceContract - .connect(owner) - .matchOrders([order, mirrorOrder], fulfillments, { - value: ethAmount, - }); - }); - - it("matchAdvancedOrders allows fulfilling with native offer items", async () => { - await mintAndApproveERC20( - buyer, - marketplaceContract.address, - tokenAmount - ); - - const { order } = await createOrder( - seller, - zone, - offer, - consideration, - 0 // FULL_OPEN - ); - const { mirrorOrder } = await createMirrorBuyNowOrder( - buyer, - zone, - order - ); - const fulfillments = [ - toFulfillment([[0, 0]], [[1, 0]]), - toFulfillment([[1, 0]], [[0, 0]]), - ]; - - await marketplaceContract - .connect(owner) - .matchAdvancedOrders([order, mirrorOrder], [], fulfillments, { - value: ethAmount, - }); - }); - }); - }); -}); diff --git a/test/revert.spec.ts b/test/revert.spec.ts new file mode 100644 index 000000000..bdb8fd72f --- /dev/null +++ b/test/revert.spec.ts @@ -0,0 +1,5936 @@ +import { expect } from "chai"; +import hre, { ethers, network } from "hardhat"; + +import { merkleTree } from "./utils/criteria"; +import { + buildOrderStatus, + buildResolver, + defaultBuyNowMirrorFulfillment, + getBasicOrderParameters, + getItemETH, + randomBN, + randomHex, + toBN, + toFulfillment, + toKey, +} from "./utils/encoding"; +import { fixtureERC20, seaportFixture } from "./utils/fixtures"; +import { + VERSION, + getCustomRevertSelector, + minRandom, + simulateMatchOrders, +} from "./utils/helpers"; +import { + faucet, + getWalletWithEther, + whileImpersonating, +} from "./utils/impersonate"; + +import type { + ConduitInterface, + ConsiderationInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + Reenterer, + TestERC1155, + TestERC20, + TestERC721, + TestZone, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { ConsiderationItem, Fulfillment, OfferItem } from "./utils/types"; +import type { BigNumber, Wallet } from "ethers"; + +const { parseEther } = ethers.utils; + +describe(`Reverts (Seaport v${VERSION})`, function () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + let conduitKeyOne: string; + let conduitOne: ConduitInterface; + let EIP1271WalletFactory: EIP1271Wallet__factory; + let marketplaceContract: ConsiderationInterface; + let reenterer: Reenterer; + let stubZone: TestZone; + let testERC1155: TestERC1155; + let testERC20: TestERC20; + let testERC721: TestERC721; + + let checkExpectedEvents: SeaportFixtures["checkExpectedEvents"]; + let createMirrorAcceptOfferOrder: SeaportFixtures["createMirrorAcceptOfferOrder"]; + let createMirrorBuyNowOrder: SeaportFixtures["createMirrorBuyNowOrder"]; + let createOrder: SeaportFixtures["createOrder"]; + let getTestItem1155: SeaportFixtures["getTestItem1155"]; + let getTestItem20: SeaportFixtures["getTestItem20"]; + let getTestItem721: SeaportFixtures["getTestItem721"]; + let getTestItem721WithCriteria: SeaportFixtures["getTestItem721WithCriteria"]; + let mint1155: SeaportFixtures["mint1155"]; + let mint721: SeaportFixtures["mint721"]; + let mintAndApprove1155: SeaportFixtures["mintAndApprove1155"]; + let mintAndApprove721: SeaportFixtures["mintAndApprove721"]; + let mintAndApproveERC20: SeaportFixtures["mintAndApproveERC20"]; + let set1155ApprovalForAll: SeaportFixtures["set1155ApprovalForAll"]; + let set721ApprovalForAll: SeaportFixtures["set721ApprovalForAll"]; + let withBalanceChecks: SeaportFixtures["withBalanceChecks"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + checkExpectedEvents, + conduitKeyOne, + conduitOne, + createMirrorAcceptOfferOrder, + createMirrorBuyNowOrder, + createOrder, + EIP1271WalletFactory, + getTestItem1155, + getTestItem20, + getTestItem721, + getTestItem721WithCriteria, + marketplaceContract, + mint1155, + mint721, + mintAndApprove1155, + mintAndApprove721, + mintAndApproveERC20, + reenterer, + set1155ApprovalForAll, + set721ApprovalForAll, + stubZone, + testERC1155, + testERC20, + testERC721, + withBalanceChecks, + } = await seaportFixture(owner)); + }); + + let seller: Wallet; + let buyer: Wallet; + let zone: Wallet; + + let sellerContract: EIP1271Wallet; + let buyerContract: EIP1271Wallet; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + seller = new ethers.Wallet(randomHex(32), provider); + buyer = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + sellerContract = await EIP1271WalletFactory.deploy(seller.address); + buyerContract = await EIP1271WalletFactory.deploy(buyer.address); + + for (const wallet of [seller, buyer, zone, sellerContract, buyerContract]) { + await faucet(wallet.address, provider); + } + }); + + describe("Misconfigured orders", async () => { + it("Reverts on bad fraction amounts", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 0; + order.denominator = 10; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("BadFraction"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 0; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("BadFraction"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 2; + order.denominator = 1; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("BadFraction"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 2; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 2) + ); + }); + it("Reverts on inexact fraction amounts", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 8191; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("InexactFraction"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 2; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 2) + ); + }); + it("Reverts on partial fill attempt when not supported by order", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 2; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("PartialFillsNotEnabledForOrder"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 1; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Reverts on partially filled order via basic fulfillment", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 2; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 2) + ); + + const basicOrderParameters = getBasicOrderParameters( + 1, // EthForERC1155 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith(`OrderPartiallyFilled("${orderHash}")`); + }); + it("Reverts on fully filled order", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + order.numerator = 1; + order.denominator = 1; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`OrderAlreadyFilled("${orderHash}")`); + }); + it("Reverts on non-zero unused item parameters (identifier set on native, basic)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + consideration[0].identifierOrCriteria = amount; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 1, // EthForERC1155 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith(`UnusedItemParameters`); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + }); + it("Reverts on non-zero unused item parameters (identifier set on ERC20, basic)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + consideration[0].identifierOrCriteria = amount; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 3, // ERC20ForERC1155 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith(`UnusedItemParameters`); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + }); + it("Reverts on non-zero unused item parameters (token set on native, standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + consideration[0].token = seller.address; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`UnusedItemParameters`); + }); + it("Reverts on non-zero unused item parameters (identifier set on native, standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + consideration[0].identifierOrCriteria = amount; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`UnusedItemParameters`); + }); + it("Reverts on non-zero unused item parameters (identifier set on ERC20, standard)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + consideration[0].identifierOrCriteria = amount; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`UnusedItemParameters`); + }); + it("Reverts on inadequate consideration items", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 1 // PARTIAL_OPEN + ); + + // Remove a consideration item, but do not reduce + // totalOriginalConsiderationItems as MissingOriginalConsiderationItems + // is being tested for + order.parameters.consideration.pop(); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("MissingOriginalConsiderationItems"); + }); + it("Reverts on invalid submitter when required by order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 2 // FULL_RESTRICTED + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + zone, + value + ); + + expect(executions.length).to.equal(4); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.reverted; + } + + const tx = marketplaceContract + .connect(zone) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("Reverts on invalid signatures", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const originalSignature = order.signature; + + // set an invalid V value + order.signature = order.signature.slice(0, -2) + "01"; + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + let expectedRevertReason = + getCustomRevertSelector("BadSignatureV(uint8)") + "1".padStart(64, "0"); + + let tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillBasicOrder(basicOrderParameters, { + value, + }); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.reverted; + + // construct an invalid signature + basicOrderParameters.signature = "0x".padEnd(130, "f") + "1c"; + + expectedRevertReason = getCustomRevertSelector("InvalidSigner()"); + + tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillBasicOrder(basicOrderParameters, { + value, + }); + expect(provider.call(tx)).to.be.revertedWith("InvalidSigner"); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.reverted; + + basicOrderParameters.signature = originalSignature; + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Reverts on invalid 1271 signature", async () => { + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(40, 40, zone.address), + getTestItem20(40, 40, owner.address), + ]; + + const { order } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + zone // wrong signer + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.revertedWith("BAD SIGNER"); + }); + it("Reverts on invalid contract 1271 signature and contract does not supply a revert reason", async () => { + await sellerContract.connect(owner).revertWithMessage(false); + + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + zone // wrong signer + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.revertedWith("BadContractSignature"); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.reverted; + } + }); + it("Reverts on invalid contract 1271 signature and contract does not return magic value", async () => { + await sellerContract.connect(owner).setValid(false); + + // Seller mints nft to contract + const nftId = await mint721(sellerContract); + + // Seller approves marketplace contract to transfer NFT + await expect( + sellerContract + .connect(seller) + .approveNFT(testERC721.address, marketplaceContract.address) + ) + .to.emit(testERC721, "ApprovalForAll") + .withArgs(sellerContract.address, marketplaceContract.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getTestItem20( + tokenAmount.sub(100), + tokenAmount.sub(100), + sellerContract.address + ), + getTestItem20(50, 50, zone.address), + getTestItem20(50, 50, owner.address), + ]; + + const { order } = await createOrder( + sellerContract, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20ForERC721 + order + ); + + if (!process.env.REFERENCE) { + const expectedRevertReason = getCustomRevertSelector( + "BadContractSignature()" + ); + + const tx = await marketplaceContract + .connect(buyer) + .populateTransaction.fulfillBasicOrder(basicOrderParameters); + const returnData = await provider.call(tx); + expect(returnData).to.equal(expectedRevertReason); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.reverted; + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters) + ).to.be.reverted; + } + + await sellerContract.connect(owner).setValid(true); + }); + it("Reverts on restricted order where isValidOrder reverts with no data", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2, // FULL_RESTRICTED, + [], + null, + seller, + "0x".padEnd(65, "0") + "2" + ); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + + order.extraData = "0x0102030405"; + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; + } + }); + it("Reverts on restricted order where isValidOrder returns non-magic value", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + stubZone, + offer, + consideration, + 2, // FULL_RESTRICTED, + [], + null, + seller, + "0x".padEnd(65, "0") + "3" + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.reverted; + } + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + + order.extraData = "0x01"; + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`InvalidRestrictedOrder("${orderHash}")`); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; + } + }); + it("Reverts on missing offer or consideration components", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + let fulfillments: Fulfillment[] = [ + { + offerComponents: [], + considerationComponents: [], + }, + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { value }) + ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); + + fulfillments = [ + { + offerComponents: [], + considerationComponents: [ + { + orderIndex: 0, + itemIndex: 0, + }, + ], + }, + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { value }) + ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); + + fulfillments = [ + { + offerComponents: [ + { + orderIndex: 0, + itemIndex: 0, + }, + ], + considerationComponents: [], + }, + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("OfferAndConsiderationRequiredOnFulfillment"); + + fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("Reverts on mismatched offer and consideration components", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + let fulfillments = [toFulfillment([[0, 0]], [[0, 0]])]; + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith( + "MismatchedFulfillmentOfferAndConsiderationComponents" + ); + + fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("Reverts on mismatched offer components", async () => { + // Seller mints nft + const nftId = await mint721(seller); + + const secondNFTId = await mint721(seller); + + // Seller approves marketplace contract to transfer NFT + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + { + itemType: 2, // ERC721 + token: testERC721.address, + identifierOrCriteria: nftId, + startAmount: toBN(1), + endAmount: toBN(1), + }, + { + itemType: 2, // ERC721 + token: testERC721.address, + identifierOrCriteria: secondNFTId, + startAmount: toBN(1), + endAmount: toBN(1), + }, + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [ + [ + [0, 0], + [0, 1], + ], + [[1, 0]], + ], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on mismatched consideration components", async () => { + // Seller mints nft + const nftId = await mint721(seller); + + const secondNFTId = await mint721(seller); + + // Seller approves marketplace contract to transfer NFT + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + { + itemType: 2, // ERC721 + token: testERC721.address, + identifierOrCriteria: nftId, + startAmount: toBN(1), + endAmount: toBN(1), + }, + { + itemType: 2, // ERC721 + token: testERC721.address, + identifierOrCriteria: secondNFTId, + startAmount: toBN(1), + endAmount: toBN(1), + }, + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getTestItem20(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [ + [[0, 0]], + [ + [1, 0], + [1, 1], + ], + ], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillment component with out-of-range order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [ + [[2, 0]], + [ + [1, 0], + [1, 1], + ], + ], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillment component with out-of-range offer item", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 5]], [[1, 0]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillment component with out-of-range initial order on fulfillAvailableOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [ + getTestItem1155(nftId, amount.div(2), amount.div(2)), + getTestItem1155(nftId, amount.div(2), amount.div(2)), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 5, itemIndex: 0 }, + { orderIndex: 0, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [order], + offerComponents, + considerationComponents, + toKey(0), + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillment component with out-of-range initial offer item on fulfillAvailableOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [ + getTestItem1155(nftId, amount.div(2), amount.div(2)), + getTestItem1155(nftId, amount.div(2), amount.div(2)), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 5 }, + { orderIndex: 0, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + let success = false; + + try { + const tx = await marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [order], + offerComponents, + considerationComponents, + toKey(0), + 100, + { + value, + } + ); + + const receipt = await tx.wait(); + success = receipt.status === 1; + } catch (err) {} + + expect(success).to.be.false; // TODO: fix out-of-gas + }); + it("Reverts on fulfillment component with out-of-range subsequent offer item on fulfillAvailableOrders", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [ + getTestItem1155(nftId, amount.div(2), amount.div(2)), + getTestItem1155(nftId, amount.div(2), amount.div(2)), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 0, itemIndex: 5 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [order], + offerComponents, + considerationComponents, + toKey(0), + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillment component with out-of-range consideration item", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 5]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on unmet consideration items", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith( + `ConsiderationNotMet(0, 2, ${parseEther("1").toString()}` + ); + }); + it("Reverts on fulfillAvailableAdvancedOrders with empty fulfillment component", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [[]]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("MissingFulfillmentComponentOnAggregation(0)"); + }); + it("Reverts on fulfillAvailableAdvancedOrders with out-of-range initial offer order", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 2); + + // Seller approves marketplace contract to transfer NFT + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(nftId, amount, amount, undefined), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 2, itemIndex: 0 }, + { orderIndex: 0, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillAvailableAdvancedOrders with out-of-range offer order", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 2); + + // Seller approves marketplace contract to transfer NFT + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(nftId, amount, amount, undefined), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillAvailableAdvancedOrders with mismatched offer components", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId), getTestItem20(1, 1)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 0, itemIndex: 1 }, + ], + ]; + const considerationComponents = [ + [{ orderIndex: 0, itemIndex: 0 }], + [{ orderIndex: 0, itemIndex: 1 }], + [{ orderIndex: 0, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillAvailableAdvancedOrders with out-of-range consideration order", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.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: 2, itemIndex: 1 }, + ], + [{ orderIndex: 2, itemIndex: 2 }], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillAvailableAdvancedOrders with mismatched consideration components", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + { + itemType: 2, // ERC721 + token: testERC721.address, + identifierOrCriteria: nftId, + startAmount: toBN(1), + endAmount: toBN(1), + recipient: 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 }, + ], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value, + } + ) + ).to.be.revertedWith("InvalidFulfillmentComponentData"); + }); + it("Reverts on fulfillAvailableAdvancedOrders no available components", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount.div(2), amount.div(2))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + // first order is expired + const { order: orderOne, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + // second order will be cancelled + const { + order: orderTwo, + orderHash: orderHashTwo, + orderComponents, + } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can cancel it + await expect( + marketplaceContract.connect(seller).cancel([orderComponents]) + ) + .to.emit(marketplaceContract, "OrderCancelled") + .withArgs(orderHashTwo, seller.address, zone.address); + + // third order will be filled + const { order: orderThree, orderHash: orderHashThree } = + await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // can fill it + await withBalanceChecks([orderThree], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillOrder(orderThree, toKey(0), { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order: orderThree, + orderHash: orderHashThree, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + + const offerComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + ], + ]; + const considerationComponents = [ + [ + { orderIndex: 0, itemIndex: 0 }, + { orderIndex: 1, itemIndex: 0 }, + { orderIndex: 2, itemIndex: 0 }, + ], + [ + { orderIndex: 0, itemIndex: 1 }, + { orderIndex: 1, itemIndex: 1 }, + { orderIndex: 2, itemIndex: 1 }, + ], + [ + { orderIndex: 0, itemIndex: 2 }, + { orderIndex: 1, itemIndex: 2 }, + { orderIndex: 2, itemIndex: 2 }, + ], + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [orderOne, orderTwo, orderThree], + [], + offerComponents, + considerationComponents, + toKey(0), + ethers.constants.AddressZero, + 100, + { + value: value.mul(3), + } + ) + ).to.be.revertedWith("NoSpecifiedOrdersAvailable"); + }); + it("Reverts on out-of-range criteria resolvers", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + let criteriaResolvers = [ + buildResolver(3, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("OrderCriteriaResolverOutOfRange"); + + criteriaResolvers = [ + buildResolver(0, 0, 5, nftId, proofs[nftId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("OfferCriteriaResolverOutOfRange"); + + criteriaResolvers = [ + buildResolver(0, 1, 5, nftId, proofs[nftId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("ConsiderationCriteriaResolverOutOfRange"); + + criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + if (process.env.REFERENCE) { + it("Reverts on out-of-range criteria resolver (match)", async () => { + // Seller mints nfts + const nftId = await mint721(seller); + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + 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"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + let criteriaResolvers = [ + buildResolver(3, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder } = await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("OrderCriteriaResolverOutOfRange"); + + criteriaResolvers = [ + buildResolver(0, 0, 5, nftId, proofs[nftId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("OfferCriteriaResolverOutOfRange"); + + criteriaResolvers = [ + buildResolver(0, 1, 5, nftId, proofs[nftId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("ConsiderationCriteriaResolverOutOfRange"); + }); + } + it("Reverts on unresolved criteria items", async () => { + // Seller and buyer both mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(buyer.address, secondNFTId); + + const tokenIds = [nftId, secondNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + // Buyer approves marketplace contract to transfer NFTs + await set721ApprovalForAll(buyer, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getTestItem721WithCriteria(root, toBN(1), toBN(1), owner.address), + ]; + + let criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("UnresolvedConsiderationCriteria"); + + criteriaResolvers = [ + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("UnresolvedOfferCriteria"); + + criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + if (process.env.REFERENCE) { + it("Reverts on unresolved criteria items (match)", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + + const tokenIds = [nftId, secondNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getTestItem721WithCriteria(root, toBN(1), toBN(1), owner.address), + ]; + + let criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { mirrorOrder } = await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("UnresolvedConsiderationCriteria"); + + criteriaResolvers = [ + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("UnresolvedOfferCriteria"); + + criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + buildResolver(0, 1, 0, secondNFTId, proofs[secondNFTId.toString()]), + ]; + }); + } + it("Reverts on attempts to resolve criteria for non-criteria item", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { proofs } = merkleTree(tokenIds); + + const offer = [ + getTestItem721(nftId, toBN(1), toBN(1), undefined, testERC721.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("CriteriaNotEnabledForItem"); + }); + if (process.env.REFERENCE) { + it("Reverts on attempts to resolve criteria for non-criteria item (match)", async () => { + // Seller mints nfts + const nftId = await mint721(seller); + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree([nftId]); + + const offer = [ + getTestItem721(root, toBN(1), toBN(1), undefined, testERC721.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + const { mirrorOrder } = await createMirrorAcceptOfferOrder( + buyer, + zone, + order, + criteriaResolvers + ); + + const fulfillments = [toFulfillment([[1, 0]], [[0, 0]])]; + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders( + [order, mirrorOrder], + criteriaResolvers, + fulfillments, + { + value, + } + ) + ).to.be.revertedWith("CriteriaNotEnabledForItem"); + }); + } + it("Reverts on offer amount overflow", async () => { + const { testERC20: testERC20Two } = await fixtureERC20(owner); + // Buyer mints nfts + const nftId = await mintAndApprove721(buyer, marketplaceContract.address); + + await testERC20Two.mint(seller.address, ethers.constants.MaxUint256); + // Seller approves marketplace contract to transfer NFTs + await testERC20Two + .connect(seller) + .approve(marketplaceContract.address, ethers.constants.MaxUint256); + + const offer = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + ]; + + const consideration = [getTestItem721(nftId, 1, 1, seller.address)]; + + const offer2 = [getTestItem721(nftId, 1, 1)]; + const consideration2 = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + buyer.address, + testERC20Two.address + ), + ]; + + const fulfillments = [ + toFulfillment( + [ + [0, 0], + [0, 1], + ], + [[1, 0]] + ), + toFulfillment([[1, 0]], [[0, 0]]), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 1 + ); + + const { order: order2 } = await createOrder( + buyer, + zone, + offer2, + consideration2, + 1 + ); + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders([order, order2], [], fulfillments) + ).to.be.revertedWith( + "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" + ); + }); + + it("Reverts on offer amount overflow when another amount is 0", async () => { + const { testERC20: testERC20Two } = await fixtureERC20(owner); + // Buyer mints nfts + const nftId = await mintAndApprove721(buyer, marketplaceContract.address); + + await testERC20Two.mint(seller.address, ethers.constants.MaxUint256); + // Seller approves marketplace contract to transfer NFTs + await testERC20Two + .connect(seller) + .approve(marketplaceContract.address, ethers.constants.MaxUint256); + + const offer = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + getTestItem20(0, 0, undefined, testERC20Two.address), + ]; + + const consideration = [getTestItem721(nftId, 1, 1, seller.address)]; + + const offer2 = [getTestItem721(nftId, 1, 1)]; + const consideration2 = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + buyer.address, + testERC20Two.address + ), + ]; + + const fulfillments = [ + toFulfillment( + [ + [0, 0], + [0, 1], + [0, 2], + ], + [[1, 0]] + ), + toFulfillment([[1, 0]], [[0, 0]]), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 1 + ); + + const { order: order2 } = await createOrder( + buyer, + zone, + offer2, + consideration2, + 1 + ); + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders([order, order2], [], fulfillments) + ).to.be.revertedWith( + "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" + ); + }); + + it("Reverts on consideration amount overflow", async () => { + const { testERC20: testERC20Two } = await fixtureERC20(owner); + // Buyer mints nfts + const nftId = await mintAndApprove721(buyer, marketplaceContract.address); + + await testERC20Two.mint(seller.address, ethers.constants.MaxUint256); + // Seller approves marketplace contract to transfer NFTs + await testERC20Two + .connect(seller) + .approve(marketplaceContract.address, ethers.constants.MaxUint256); + + const offer = [getTestItem721(nftId, 1, 1)]; + + const consideration = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + seller.address, + testERC20Two.address + ), + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + seller.address, + testERC20Two.address + ), + ]; + + const offer2 = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + ]; + const consideration2 = [getTestItem721(nftId, 1, 1, buyer.address)]; + + const fulfillments = [ + toFulfillment( + [[1, 0]], + [ + [0, 0], + [0, 1], + ] + ), + toFulfillment([[0, 0]], [[1, 0]]), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 1 + ); + + const { order: order2 } = await createOrder( + buyer, + zone, + offer2, + consideration2, + 1 + ); + + await expect( + marketplaceContract + .connect(owner) + .matchAdvancedOrders([order, order2], [], fulfillments) + ).to.be.revertedWith( + "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" + ); + }); + + it("Reverts on consideration amount overflow when another amount is 0", async () => { + const { testERC20: testERC20Two } = await fixtureERC20(owner); + // Buyer mints nfts + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + await testERC20Two.mint(buyer.address, ethers.constants.MaxUint256); + // Seller approves marketplace contract to transfer NFTs + await testERC20Two + .connect(buyer) + .approve(marketplaceContract.address, ethers.constants.MaxUint256); + + const offer = [getTestItem721(nftId, 1, 1)]; + + const consideration = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + seller.address, + testERC20Two.address + ), + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + seller.address, + testERC20Two.address + ), + getTestItem20(0, 0, seller.address, testERC20Two.address), + ]; + + const offer2 = [ + getTestItem20( + ethers.constants.MaxUint256, + ethers.constants.MaxUint256, + undefined, + testERC20Two.address + ), + ]; + const consideration2 = [getTestItem721(nftId, 1, 1, buyer.address)]; + + const fulfillments = [ + toFulfillment( + [[1, 0]], + [ + [0, 0], + [0, 1], + [0, 2], + ] + ), + toFulfillment([[0, 0]], [[1, 0]]), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 1 + ); + + const { order: order2 } = await createOrder( + buyer, + zone, + offer2, + consideration2, + 1 + ); + + await expect( + marketplaceContract.matchAdvancedOrders( + [order, order2], + [], + fulfillments + ) + ).to.be.revertedWith( + "panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)" + ); + }); + + it("Reverts on invalid criteria proof", async () => { + // Seller mints nfts + const nftId = randomBN(); + const secondNFTId = randomBN(); + const thirdNFTId = randomBN(); + + await testERC721.mint(seller.address, nftId); + await testERC721.mint(seller.address, secondNFTId); + await testERC721.mint(seller.address, thirdNFTId); + + const tokenIds = [nftId, secondNFTId, thirdNFTId]; + + // Seller approves marketplace contract to transfer NFTs + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + const { root, proofs } = merkleTree(tokenIds); + + const offer = [getTestItem721WithCriteria(root, toBN(1), toBN(1))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const criteriaResolvers = [ + buildResolver(0, 0, 0, nftId, proofs[nftId.toString()]), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + criteriaResolvers + ); + + criteriaResolvers[0].identifier = criteriaResolvers[0].identifier.add(1); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("InvalidProof"); + + criteriaResolvers[0].identifier = criteriaResolvers[0].identifier.sub(1); + + await withBalanceChecks([order], 0, criteriaResolvers, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + criteriaResolvers, + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + criteriaResolvers + ); + + return receipt; + }); + }); + it("Reverts on attempts to transfer >1 ERC721 in single transfer", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [ + getTestItem721(nftId, toBN(2), toBN(2), undefined, testERC721.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + it("Reverts on attempts to transfer >1 ERC721 in single transfer (basic)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [ + getTestItem721(nftId, toBN(2), toBN(2), undefined, testERC721.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + it("Reverts on attempts to transfer >1 ERC721 in single transfer via conduit", async () => { + const nftId = await mintAndApprove721(seller, conduitOne.address, 0); + + const offer = [ + getTestItem721(nftId, toBN(2), toBN(2), undefined, testERC721.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + }); + + describe("Out of timespan", async () => { + it("Reverts on orders that have not started (standard)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "NOT_STARTED" + ); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + it("Reverts on orders that have expired (standard)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + it("Reverts on orders that have not started (basic)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "NOT_STARTED" + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + it("Reverts on orders that have expired (basic)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + it("Reverts on orders that have not started (match)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "NOT_STARTED" + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], defaultBuyNowMirrorFulfillment, { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + it("Reverts on orders that have expired (match)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + "EXPIRED" + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], defaultBuyNowMirrorFulfillment, { + value, + }) + ).to.be.revertedWith("InvalidTime"); + }); + }); + + describe("Insufficient amounts and bad items", async () => { + it("Reverts when no ether is supplied (basic)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: toBN(0), + }) + ).to.be.revertedWith("InvalidMsgValue"); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Reverts when not enough ether is supplied (basic)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: toBN(1), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: value.sub(1), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await withBalanceChecks([order], 0, undefined, async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents(tx, receipt, [ + { + order, + orderHash, + fulfiller: buyer.address, + }, + ]); + + return receipt; + }); + }); + it("Reverts when not enough ether is supplied as offer item (match)", async () => { + // NOTE: this is a ridiculous scenario, buyer is paying the seller's offer + const offer = [getItemETH(parseEther("10"), parseEther("10"))]; + + const consideration = [ + getItemETH(parseEther("1"), parseEther("1"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await expect( + marketplaceContract + .connect(buyer) + .matchOrders([order, mirrorOrder], fulfillments, { + value: toBN(1), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await expect( + marketplaceContract + .connect(buyer) + .matchOrders([order, mirrorOrder], fulfillments, { + value: parseEther("9.999999"), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await marketplaceContract + .connect(buyer) + .matchOrders([order, mirrorOrder], fulfillments, { + value: parseEther("13"), + }); + }); + it("Reverts when not enough ether is supplied (standard + advanced)", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(amount.mul(1000), amount.mul(1000), seller.address), + getItemETH(amount.mul(10), amount.mul(10), zone.address), + getItemETH(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value: toBN(1), + } + ) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value: value.sub(1), + } + ) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // fulfill with a tiny bit extra to test for returning eth + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value: value.add(1), + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Reverts when not enough ether is supplied (match)", 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), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = defaultBuyNowMirrorFulfillment; + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(4); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value: toBN(1), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value: value.sub(1), + }) + ).to.be.revertedWith("InsufficientEtherSupplied"); + + await whileImpersonating(owner.address, provider, async () => { + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + }); + it("Reverts when ether is supplied to a non-payable route (basic)", async () => { + // Seller mints nft + const nftId = randomBN(); + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH( + parseEther("1"), + parseEther("1"), + marketplaceContract.address + ), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 2, // ERC20_TO_ERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: 1, + }) + ).to.be.revertedWith("InvalidMsgValue(1)"); + }); + + it(`Reverts when ether transfer fails (returndata)${ + process.env.REFERENCE ? " — SKIPPED ON REFERENCE" : "" + }`, async () => { + if (process.env.REFERENCE) { + return; + } + + const recipient = await ( + await ethers.getContractFactory("ExcessReturnDataRecipient") + ).deploy(); + const setup = async () => { + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Seller approves marketplace contract to transfer NFT + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + // Buyer approves marketplace contract to transfer tokens + + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), recipient.address), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + return basicOrderParameters; + }; + let basicOrderParameters = await setup(); + const baseGas = await marketplaceContract + .connect(buyer) + .estimateGas.fulfillBasicOrder(basicOrderParameters, { + value: parseEther("12"), + }); + + // TODO: clean *this* up + basicOrderParameters = await setup(); + await recipient.setRevertDataSize(1); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: parseEther("12"), + gasLimit: (hre as any).__SOLIDITY_COVERAGE_RUNNING + ? baseGas.add(35000) + : baseGas.add(1000), + }) + ).to.be.revertedWith("EtherTransferGenericFailure"); + }); + + it("Reverts when ether transfer fails (basic)", async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Seller approves marketplace contract to transfer NFT + await set721ApprovalForAll(seller, marketplaceContract.address, true); + + // Buyer approves marketplace contract to transfer tokens + + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH( + parseEther("1"), + parseEther("1"), + marketplaceContract.address + ), + ]; + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value: parseEther("12"), + }) + ).to.be.revertedWith( + `EtherTransferGenericFailure("${ + marketplaceContract.address + }", ${parseEther("1").toString()})` + ); + }); + it("Reverts when tokens are not approved", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; // panic code thrown by underlying 721 + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // Buyer approves marketplace contract to transfer tokens + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Reverts when 1155 token transfer reverts", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 10000); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("NOT_AUTHORIZED"); + }); + it("Reverts when 1155 token transfer reverts (via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 10000); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`NOT_AUTHORIZED`); + }); + + // Skip this test when testing the reference contract + if (!process.env.REFERENCE) { + it("Reverts when 1155 token transfer reverts (via conduit, returndata)", async () => { + const recipient = await ( + await ethers.getContractFactory("ExcessReturnDataRecipient") + ).deploy(); + + const setup = async () => { + // seller mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(seller.address, tokenAmount); + + // Seller approves conduit contract to transfer tokens + await expect( + testERC20.connect(seller).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(seller.address, conduitOne.address, tokenAmount); + + // Buyer mints nft + const nftId = randomBN(); + const amount = toBN(randomBN(2)); + await testERC1155.mint(buyer.address, nftId, amount.mul(10000)); + + // Buyer approves conduit contract to transfer NFTs + await expect( + testERC1155 + .connect(buyer) + .setApprovalForAll(conduitOne.address, true) + ) + .to.emit(testERC1155, "ApprovalForAll") + .withArgs(buyer.address, conduitOne.address, true); + + const offer = [getTestItem20(tokenAmount, tokenAmount)]; + + const consideration = [ + getTestItem1155( + nftId, + amount.mul(10), + amount.mul(10), + undefined, + recipient.address + ), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + undefined, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + return { + order, + value, + }; + }; + + const { order: initialOrder, value } = await setup(); + const baseGas = await marketplaceContract + .connect(buyer) + .estimateGas.fulfillAdvancedOrder( + initialOrder, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ); + + // TODO: clean *this* up + const { order } = await setup(); + await recipient.setRevertDataSize(1); + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + gasLimit: baseGas.add(74000), + } + ) + ).to.be.revertedWith("InvalidCallToConduit"); + }); + } + + it("Reverts when transferred item amount is zero", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem1155(nftId, 0, 0, undefined)]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("MissingItemAmount"); + }); + it("Reverts when ERC20 tokens return falsey values", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // block transfers + await testERC20.blockTransfer(true); + + expect(await testERC20.blocked()).to.be.true; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; // TODO: hardhat can't find error msg on IR pipeline + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await testERC20.blockTransfer(false); + + expect(await testERC20.blocked()).to.be.false; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Works when ERC20 tokens return falsey values", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address, + 10000 + ); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves marketplace contract to transfer tokens + + await expect( + testERC20 + .connect(buyer) + .approve(marketplaceContract.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, marketplaceContract.address, tokenAmount); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await testERC20.setNoReturnData(true); + + expect(await testERC20.noReturnData()).to.be.true; + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: toKey(0), + }, + ], + undefined, + [] + ); + + return receipt; + }); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + + await testERC20.setNoReturnData(false); + + expect(await testERC20.noReturnData()).to.be.false; + }); + it("Reverts when ERC20 tokens return falsey values (via conduit)", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 10000); + + // Seller approves conduit contract to transfer NFTs + await set1155ApprovalForAll(seller, conduitOne.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves conduit contract to transfer tokens + + await expect( + testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, conduitOne.address, tokenAmount); + + // Seller approves conduit contract to transfer tokens + await expect( + testERC20.connect(seller).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(seller.address, conduitOne.address, tokenAmount); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + // block transfers + await testERC20.blockTransfer(true); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith( + `BadReturnValueFromERC20OnTransfer("${testERC20.address}", "${ + buyer.address + }", "${seller.address}", ${amount.mul(1000).toString()})` + ); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; + } + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await testERC20.blockTransfer(false); + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ], + undefined, + [] + ); + + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Reverts when providing non-existent conduit", async () => { + // Seller mints nft + const { nftId, amount } = await mint1155(seller, 10000); + + // Seller approves conduit contract to transfer NFTs + await set1155ApprovalForAll(seller, conduitOne.address, true); + + // Buyer mints ERC20 + const tokenAmount = minRandom(100); + await testERC20.mint(buyer.address, tokenAmount); + + // Buyer approves conduit contract to transfer tokens + await expect( + testERC20.connect(buyer).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(buyer.address, conduitOne.address, tokenAmount); + + // Seller approves conduit contract to transfer tokens + await expect( + testERC20.connect(seller).approve(conduitOne.address, tokenAmount) + ) + .to.emit(testERC20, "Approval") + .withArgs(seller.address, conduitOne.address, tokenAmount); + + const offer = [getTestItem1155(nftId, amount.mul(10), amount.mul(10))]; + + const consideration = [ + getTestItem20(amount.mul(1000), amount.mul(1000), seller.address), + getTestItem20(amount.mul(10), amount.mul(10), zone.address), + getTestItem20(amount.mul(20), amount.mul(20), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const badKey = ethers.constants.HashZero.slice(0, -1) + "2"; + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + badKey, + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith("InvalidConduit"); + + let orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + await withBalanceChecks([order], 0, [], async () => { + const tx = marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: buyer.address, + fulfillerConduitKey: conduitKeyOne, + }, + ], + undefined, + undefined + ); + return receipt; + }); + + orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(true, false, 1, 1) + ); + }); + it("Reverts when 1155 tokens are not approved", async () => { + // Seller mints first nft + const { nftId } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + seller + ); + + const offer = [ + getTestItem1155(nftId, 0, 0), + getTestItem1155(secondNftId, secondAmount, secondAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("MissingItemAmount"); + }); + it("Reverts when 1155 tokens are not approved", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + seller + ); + + const offer = [ + getTestItem1155(nftId, amount, amount, undefined), + getTestItem1155(secondNftId, secondAmount, secondAmount), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const { mirrorOrder, mirrorOrderHash } = await createMirrorBuyNowOrder( + buyer, + zone, + order + ); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("NOT_AUTHORIZED"); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + + // Seller approves marketplace contract to transfer NFT + + await set1155ApprovalForAll(seller, marketplaceContract.address, true); + + const executions = await simulateMatchOrders( + marketplaceContract, + [order, mirrorOrder], + fulfillments, + owner, + value + ); + + expect(executions.length).to.equal(5); + + const tx = marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }); + const receipt = await (await tx).wait(); + await checkExpectedEvents( + tx, + receipt, + [ + { + order, + orderHash, + fulfiller: ethers.constants.AddressZero, + }, + { + order: mirrorOrder, + orderHash: mirrorOrderHash, + fulfiller: ethers.constants.AddressZero, + }, + ], + executions + ); + return receipt; + }); + it("Reverts when token account with no code is supplied", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const consideration = [ + getTestItem20( + amount, + amount, + seller.address, + ethers.constants.AddressZero + ), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; // TODO: look into the revert reason more thoroughly + // Transaction reverted: function returned an unexpected amount of data + }); + it("Reverts when 721 account with no code is supplied", async () => { + const offer = [getTestItem721(0, 1, 1, undefined, buyer.address)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { value } + ) + ).to.be.revertedWith(`NoContract("${buyer.address}")`); + }); + it("Reverts when 1155 account with no code is supplied", async () => { + const amount = toBN(randomBN(2)); + + const offer = [ + getTestItem1155(0, amount, amount, ethers.constants.AddressZero), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`NoContract("${ethers.constants.AddressZero}")`); + }); + it("Reverts when 1155 account with no code is supplied (via conduit)", async () => { + const amount = toBN(randomBN(2)); + + const offer = [ + getTestItem1155(0, amount, amount, ethers.constants.AddressZero), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith(`NoContract("${ethers.constants.AddressZero}")`); + }); + it("Reverts when non-token account is supplied as the token", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const consideration = [ + getTestItem20( + amount, + amount, + seller.address, + marketplaceContract.address + ), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith( + `TokenTransferGenericFailure("${marketplaceContract.address}", "${ + buyer.address + }", "${seller.address}", 0, ${amount.toString()})` + ); + }); + it("Reverts when non-token account is supplied as the token fulfilled via conduit", async () => { + // Seller mints nft + const { nftId, amount } = await mintAndApprove1155( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem1155(nftId, amount, amount, undefined)]; + + const consideration = [ + getTestItem20( + amount, + amount, + seller.address, + marketplaceContract.address + ), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + conduitKeyOne, + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith( + `TokenTransferGenericFailure("${marketplaceContract.address}", "${ + buyer.address + }", "${seller.address}", 0, ${amount.toString()})` + ); + }); + it("Reverts when non-1155 account is supplied as the token", async () => { + const amount = toBN(randomBN(2)); + + const offer = [ + getTestItem1155(0, amount, amount, marketplaceContract.address), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.revertedWith( + `TokenTransferGenericFailure("${marketplaceContract.address}", "${ + seller.address + }", "${buyer.address}", 0, ${amount.toString()})` + ); + } else { + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder( + order, + [], + toKey(0), + ethers.constants.AddressZero, + { + value, + } + ) + ).to.be.reverted; + } + }); + it("Reverts when 1155 token is not approved via conduit", async () => { + // Seller mints first nft + const { nftId, amount } = await mint1155(seller); + + // Seller mints second nft + const { nftId: secondNftId, amount: secondAmount } = await mint1155( + seller + ); + + const offer = [ + getTestItem1155(nftId, amount, amount, testERC1155.address), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + testERC1155.address + ), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("NOT_AUTHORIZED"); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + }); + it("Reverts when 1155 token with no code is supplied as the token via conduit", async () => { + // Seller mints first nft + const nftId = toBN(randomBN(4)); + const amount = toBN(randomBN(4)); + + // Seller mints second nft + const secondNftId = toBN(randomBN(4)); + const secondAmount = toBN(randomBN(4)); + + const offer = [ + getTestItem1155(nftId, amount, amount, ethers.constants.AddressZero), + getTestItem1155( + secondNftId, + secondAmount, + secondAmount, + ethers.constants.AddressZero + ), + ]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + getItemETH(parseEther("1"), parseEther("1"), zone.address), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, orderHash, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0, // FULL_OPEN + [], + null, + seller, + ethers.constants.HashZero, + conduitKeyOne + ); + + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + + const fulfillments = [ + [[[0, 0]], [[1, 0]]], + [[[0, 1]], [[1, 1]]], + [[[1, 0]], [[0, 0]]], + [[[1, 0]], [[0, 1]]], + [[[1, 0]], [[0, 2]]], + ].map(([offerArr, considerationArr]) => + toFulfillment(offerArr, considerationArr) + ); + + await expect( + marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value, + }) + ).to.be.revertedWith("NoContract"); + + const orderStatus = await marketplaceContract.getOrderStatus(orderHash); + + expect({ ...orderStatus }).to.deep.equal( + buildOrderStatus(false, false, 0, 0) + ); + }); + it("Reverts when non-payable ether recipient is supplied", 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"), + marketplaceContract.address + ), + getItemETH(parseEther("1"), parseEther("1"), owner.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillBasicOrder(basicOrderParameters, { + value, + }) + ).to.be.revertedWith( + `EtherTransferGenericFailure("${ + marketplaceContract.address + }", ${parseEther("1").toString()})` + ); + }); + }); + + describe("Basic Order Calldata", () => { + let calldata: string | undefined; + let value: BigNumber; + + before(async () => { + // Seller mints nft + const nftId = await mintAndApprove721( + seller, + marketplaceContract.address + ); + + const offer = [getTestItem721(nftId)]; + + const consideration = [ + getItemETH(parseEther("10"), parseEther("10"), seller.address), + ]; + let order; + ({ order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + )); + + const basicOrderParameters = getBasicOrderParameters( + 0, // EthForERC721 + order + ); + + ({ data: calldata } = + await marketplaceContract.populateTransaction.fulfillBasicOrder( + basicOrderParameters + )); + }); + + it("Reverts if BasicOrderParameters has non-default offset", async () => { + calldata = calldata as string; + const badData = [calldata.slice(0, 73), "1", calldata.slice(74)].join(""); + expect(badData.length).to.eq(calldata.length); + + await expect( + buyer.sendTransaction({ + to: marketplaceContract.address, + data: badData, + value, + }) + ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); + }); + + it("Reverts if additionalRecipients has non-default offset", async () => { + calldata = calldata as string; + const badData = [calldata.slice(0, 1161), "1", calldata.slice(1162)].join( + "" + ); + + await expect( + buyer.sendTransaction({ + to: marketplaceContract.address, + data: badData, + value, + }) + ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); + }); + + it("Reverts if signature has non-default offset", async () => { + calldata = calldata as string; + const badData = [calldata.slice(0, 1161), "2", calldata.slice(1162)].join( + "" + ); + + await expect( + buyer.sendTransaction({ + to: marketplaceContract.address, + data: badData, + value, + }) + ).to.be.revertedWith("InvalidBasicOrderParameterEncoding"); + }); + }); + + describe("Reentrancy", async () => { + it("Reverts on a reentrant call", 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"), reenterer.address), + ]; + + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + // prepare the reentrant call on the reenterer + const callData = marketplaceContract.interface.encodeFunctionData( + "fulfillOrder", + [order, toKey(0)] + ); + const tx = await reenterer.prepare( + marketplaceContract.address, + 0, + callData + ); + await tx.wait(); + + if (!process.env.REFERENCE) { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.revertedWith("NoReentrantCalls"); + } else { + await expect( + marketplaceContract.connect(buyer).fulfillOrder(order, toKey(0), { + value, + }) + ).to.be.reverted; + } + }); + }); + + describe("ETH offer items", async () => { + let ethAmount: BigNumber; + const tokenAmount = minRandom(100); + let offer: OfferItem[]; + let consideration: ConsiderationItem[]; + let seller: Wallet; + let buyer: Wallet; + + before(async () => { + ethAmount = parseEther("1"); + seller = await getWalletWithEther(); + buyer = await getWalletWithEther(); + zone = new ethers.Wallet(randomHex(32), provider); + offer = [getItemETH(ethAmount, ethAmount)]; + consideration = [getTestItem20(tokenAmount, tokenAmount, seller.address)]; + }); + + it("fulfillOrder reverts if any offer item is ETH", async () => { + const { order, value } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillOrder(order, toKey(0), { value }) + ).to.be.revertedWith("InvalidNativeOfferItem"); + }); + + it("fulfillAdvancedOrder reverts if any offer item is ETH", async () => { + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAdvancedOrder(order, [], toKey(0), buyer.address, { + value: ethAmount, + }) + ).to.be.revertedWith("InvalidNativeOfferItem"); + }); + + it("fulfillAvailableOrders reverts if any offer item is ETH", async () => { + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableOrders( + [order], + [[{ orderIndex: 0, itemIndex: 0 }]], + [[{ orderIndex: 0, itemIndex: 0 }]], + toKey(0), + 100, + { value: ethAmount } + ) + ).to.be.revertedWith("InvalidNativeOfferItem"); + }); + + it("fulfillAvailableAdvancedOrders reverts if any offer item is ETH", async () => { + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + + await expect( + marketplaceContract + .connect(buyer) + .fulfillAvailableAdvancedOrders( + [order], + [], + [[{ orderIndex: 0, itemIndex: 0 }]], + [[{ orderIndex: 0, itemIndex: 0 }]], + toKey(0), + buyer.address, + 100, + { value: ethAmount } + ) + ).to.be.revertedWith("InvalidNativeOfferItem"); + }); + + it("matchOrders allows fulfilling with native offer items", async () => { + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + const fulfillments = [ + toFulfillment([[0, 0]], [[1, 0]]), + toFulfillment([[1, 0]], [[0, 0]]), + ]; + + await marketplaceContract + .connect(owner) + .matchOrders([order, mirrorOrder], fulfillments, { + value: ethAmount, + }); + }); + + it("matchAdvancedOrders allows fulfilling with native offer items", async () => { + await mintAndApproveERC20( + buyer, + marketplaceContract.address, + tokenAmount + ); + + const { order } = await createOrder( + seller, + zone, + offer, + consideration, + 0 // FULL_OPEN + ); + const { mirrorOrder } = await createMirrorBuyNowOrder(buyer, zone, order); + const fulfillments = [ + toFulfillment([[0, 0]], [[1, 0]]), + toFulfillment([[1, 0]], [[0, 0]]), + ]; + + await marketplaceContract + .connect(owner) + .matchAdvancedOrders([order, mirrorOrder], [], fulfillments, { + value: ethAmount, + }); + }); + }); +}); diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts new file mode 100644 index 000000000..bdc65332a --- /dev/null +++ b/test/transferhelper.spec.ts @@ -0,0 +1,694 @@ +import { expect } from "chai"; +import { randomInt } from "crypto"; +import { ethers, network } from "hardhat"; + +import { randomHex } from "./utils/encoding"; +import { + fixtureERC1155, + fixtureERC20, + fixtureERC721, + seaportFixture, +} from "./utils/fixtures"; +import { VERSION } from "./utils/helpers"; +import { faucet, whileImpersonating } from "./utils/impersonate"; + +import type { + ConduitControllerInterface, + ConduitInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + TransferHelper, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; +import type { Wallet } from "ethers"; + +describe(`TransferHelper tests (Seaport v${VERSION})`, function () { + const { provider } = ethers; + const owner = new ethers.Wallet(randomHex(32), provider); + + let conduitController: ConduitControllerInterface; + let EIP1271WalletFactory: EIP1271Wallet__factory; + + let createTransferWithApproval: SeaportFixtures["createTransferWithApproval"]; + let deployNewConduit: SeaportFixtures["deployNewConduit"]; + + after(async () => { + await network.provider.request({ + method: "hardhat_reset", + }); + }); + + before(async () => { + await faucet(owner.address, provider); + + ({ + EIP1271WalletFactory, + conduitController, + deployNewConduit, + createTransferWithApproval, + } = await seaportFixture(owner)); + }); + + let sender: Wallet; + let recipient: Wallet; + let zone: Wallet; + + let senderContract: EIP1271Wallet; + let recipientContract: EIP1271Wallet; + + let tempConduit: ConduitInterface; + let tempConduitKey: string; + let tempTransferHelper: TransferHelper; + + beforeEach(async () => { + // Setup basic buyer/seller wallets with ETH + sender = new ethers.Wallet(randomHex(32), provider); + recipient = new ethers.Wallet(randomHex(32), provider); + zone = new ethers.Wallet(randomHex(32), provider); + + senderContract = await EIP1271WalletFactory.deploy(sender.address); + recipientContract = await EIP1271WalletFactory.deploy(recipient.address); + + tempConduitKey = owner.address + randomHex(12).slice(2); + tempConduit = await deployNewConduit(owner, tempConduitKey); + + for (const wallet of [ + sender, + recipient, + zone, + senderContract, + recipientContract, + ]) { + await faucet(wallet.address, provider); + } + + // Deploy a new TransferHelper with the tempConduitController address + const transferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + tempTransferHelper = await transferHelperFactory.deploy( + conduitController.address + ); + + await whileImpersonating(owner.address, provider, async () => { + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, tempTransferHelper.address, true); + }); + }); + + it("Executes transfers (many token types) with a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + recipient.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + recipient.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer(transfers, recipient.address, tempConduitKey); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf(sender.address) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + recipient.address + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal(0); + expect(await token.balanceOf(recipient.address, identifier)).to.equal( + amount + ); + break; + } + } + }); + + it("Executes transfers (many token types) without a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer( + transfers, + recipient.address, + ethers.utils.formatBytes32String("") + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf(sender.address) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + recipient.address + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal(0); + expect(await token.balanceOf(recipient.address, identifier)).to.equal( + amount + ); + break; + } + } + }); + + it("Reverts on native token transfers", async () => { + const ethTransferHelperItems = [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + ethTransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts on invalid ERC20 identifier", async () => { + const erc20TransferHelperItems = [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc20TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); + + it("Reverts on invalid ERC721 transfer amount", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + + it("Reverts on invalid ERC721 recipient", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + tempERC721Contract.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + + it("Reverts on invalid function selector", async () => { + const invalidRecipientFactory = await ethers.getContractFactory( + "InvalidERC721Recipient" + ); + const invalidRecipient = await invalidRecipientFactory.deploy(); + + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + invalidRecipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721Recipient"); + }); + + it("Reverts on nonexistent conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("0xabc") + ) + ).to.be.revertedWith("InvalidConduit"); + }); + + it("Reverts on error in ERC721 receiver", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + Buffer.from("abcd0000", "hex"), + 1 + ); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + mockERC721Receiver.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + }); + + it("Reverts with custom error in conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const transferHelperItems = [ + // Invalid item type + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("InvalidItemType"); + }); + + it("Reverts with bubbled up string error from call to conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // Call will revert since ERC721 tokens have not been minted + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); + }); + + it("Reverts with bubbled up panic error from call to conduit", async () => { + // Deploy mock ERC20 + const mockERC20PanicFactory = await ethers.getContractFactory( + "TestERC20Panic" + ); + const mockERC20Panic = await mockERC20PanicFactory.deploy(); + + const transferHelperItems = [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ]; + + if (!process.env.REFERENCE) { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith("ConduitErrorPanic(18)"); + } else { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.reverted; + } + }); +}); diff --git a/test/utils/contracts.ts b/test/utils/contracts.ts index 8eefbc74e..22473d1e7 100644 --- a/test/utils/contracts.ts +++ b/test/utils/contracts.ts @@ -1,13 +1,13 @@ import { ethers } from "hardhat"; -import { Contract } from "ethers"; -import { JsonRpcSigner } from "@ethersproject/providers"; -import * as dotenv from "dotenv"; -dotenv.config(); +import type { JsonRpcSigner } from "@ethersproject/providers"; +import type { Contract, Wallet } from "ethers"; + +import "dotenv/config"; export const deployContract = async ( name: string, - signer: JsonRpcSigner, + signer: JsonRpcSigner | Wallet, ...args: any[] ): Promise => { const references = new Map([ @@ -18,7 +18,7 @@ export const deployContract = async ( const nameWithReference = process.env.REFERENCE && references.has(name) - ? references.get(name) || name + ? references.get(name) ?? name : name; const f = await ethers.getContractFactory(nameWithReference, signer); diff --git a/test/utils/criteria.js b/test/utils/criteria.ts similarity index 57% rename from test/utils/criteria.js rename to test/utils/criteria.ts index 56149f6d5..b34dc536b 100644 --- a/test/utils/criteria.js +++ b/test/utils/criteria.ts @@ -1,7 +1,10 @@ -const { ethers } = require("ethers"); -const { bufferToHex, keccak256 } = require("ethereumjs-util"); +import { ethers } from "ethers"; -const merkleTree = (tokenIds) => { +const { keccak256 } = ethers.utils; + +type BufferElementPositionIndex = { [key: string]: number }; + +export const merkleTree = (tokenIds: ethers.BigNumber[]) => { const elements = tokenIds .map((tokenId) => Buffer.from(tokenId.toHexString().slice(2).padStart(64, "0"), "hex") @@ -11,19 +14,22 @@ const merkleTree = (tokenIds) => { return idx === 0 || !arr[idx - 1].equals(el); }); - const bufferElementPositionIndex = elements.reduce((memo, el, index) => { - memo[bufferToHex(el)] = index; - return memo; - }, {}); + const bufferElementPositionIndex = elements.reduce( + (memo: BufferElementPositionIndex, el, index) => { + memo["0x" + el.toString("hex")] = index; + return memo; + }, + {} + ); // Create layers const layers = getLayers(elements); - const root = bufferToHex(layers[layers.length - 1][0]); + const root = "0x" + layers[layers.length - 1][0].toString("hex"); const proofs = Object.fromEntries( elements.map((el) => [ - ethers.BigNumber.from("0x" + el.toString("hex")).toString(), + ethers.BigNumber.from(el).toString(), getHexProof(el, bufferElementPositionIndex, layers), ]) ); @@ -39,13 +45,13 @@ const merkleTree = (tokenIds) => { }; }; -const getLayers = (elements) => { +const getLayers = (elements: Buffer[]) => { if (elements.length === 0) { throw new Error("empty tree"); } const layers = []; - layers.push(elements.map((el) => keccak256(el))); + layers.push(elements.map((el) => Buffer.from(keccak256(el).slice(2), "hex"))); // Get next layer until we reach the root while (layers[layers.length - 1].length > 1) { @@ -55,8 +61,8 @@ const getLayers = (elements) => { return layers; }; -const getNextLayer = (elements) => { - return elements.reduce((layer, el, idx, arr) => { +const getNextLayer = (elements: Buffer[]) => { + return elements.reduce((layer: Buffer[], el, idx, arr) => { if (idx % 2 === 0) { // Hash the current element with its pair element layer.push(combinedHash(el, arr[idx + 1])); @@ -66,7 +72,7 @@ const getNextLayer = (elements) => { }, []); }; -const combinedHash = (first, second) => { +const combinedHash = (first: Buffer, second: Buffer) => { if (!first) { return second; } @@ -74,17 +80,24 @@ const combinedHash = (first, second) => { return first; } - return keccak256(Buffer.concat([first, second].sort(Buffer.compare))); + return Buffer.from( + keccak256(Buffer.concat([first, second].sort(Buffer.compare))).slice(2), + "hex" + ); }; -const getHexProof = (el, bufferElementPositionIndex, layers) => { - let idx = bufferElementPositionIndex[bufferToHex(el)]; +const getHexProof = ( + el: Buffer, + bufferElementPositionIndex: BufferElementPositionIndex, + layers: Buffer[][] +) => { + let idx = bufferElementPositionIndex["0x" + el.toString("hex")]; if (typeof idx !== "number") { throw new Error("Element does not exist in Merkle tree"); } - const proofBuffer = layers.reduce((proof, layer) => { + const proofBuffer = layers.reduce((proof: Buffer[], layer) => { const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; const pairElement = pairIdx < layer.length ? layer[pairIdx] : null; @@ -99,7 +112,3 @@ const getHexProof = (el, bufferElementPositionIndex, layers) => { return proofBuffer.map((el) => "0x" + el.toString("hex")); }; - -module.exports = Object.freeze({ - merkleTree, -}); diff --git a/test/utils/encoding.ts b/test/utils/encoding.ts index 8bfa67c5a..448b59d39 100644 --- a/test/utils/encoding.ts +++ b/test/utils/encoding.ts @@ -1,18 +1,18 @@ import { randomBytes as nodeRandomBytes } from "crypto"; -import { utils, BigNumber, constants, ContractTransaction } from "ethers"; +import { BigNumber, constants, utils } from "ethers"; import { getAddress, keccak256, toUtf8Bytes } from "ethers/lib/utils"; -import { + +import type { BasicOrderParameters, - BigNumberish, ConsiderationItem, CriteriaResolver, + Fulfillment, FulfillmentComponent, OfferItem, Order, OrderComponents, } from "./types"; - -export { BigNumberish }; +import type { BigNumberish, ContractTransaction } from "ethers"; const SeededRNG = require("./seeded-rng"); @@ -26,8 +26,6 @@ if (GAS_REPORT_MODE) { randomBytes = (n: number) => nodeRandomBytes(n).toString("hex"); } -// const randomBytes - export const randomHex = (bytes = 32) => `0x${randomBytes(bytes)}`; export const random128 = () => toBN(randomHex(16)); @@ -40,8 +38,8 @@ export const toHex = (n: BigNumberish, numBytes: number = 0) => { : typeof n === "string" ? hexRegex.test(n) ? n.replace(/0x/, "") - : (+n).toString(16) - : (+n).toString(16); + : Number(n).toString(16) + : Number(n).toString(16); return `0x${asHexString.padStart(numBytes * 2, "0")}`; }; @@ -80,8 +78,8 @@ export const convertSignatureToEIP2098 = (signature: string) => { export const getBasicOrderParameters = ( basicOrderRouteType: number, order: Order, - fulfillerConduitKey = false, - tips = [] + fulfillerConduitKey: string | boolean = false, + tips: { amount: BigNumber; recipient: string }[] = [] ): BasicOrderParameters => ({ offerer: order.parameters.offerer, zone: order.parameters.zone, @@ -102,7 +100,9 @@ export const getBasicOrderParameters = ( ), signature: order.signature, offererConduitKey: order.parameters.conduitKey, - fulfillerConduitKey: toKey(fulfillerConduitKey), + fulfillerConduitKey: toKey( + typeof fulfillerConduitKey === "string" ? fulfillerConduitKey : 0 + ), additionalRecipients: [ ...order.parameters.consideration .slice(1) @@ -189,10 +189,7 @@ export const toFulfillmentComponents = ( export const toFulfillment = ( offerArr: number[][], considerationsArr: number[][] -): { - offerComponents: FulfillmentComponent[]; - considerationComponents: FulfillmentComponent[]; -} => ({ +): Fulfillment => ({ offerComponents: toFulfillmentComponents(offerArr), considerationComponents: toFulfillmentComponents(considerationsArr), }); @@ -322,8 +319,8 @@ export const getBasicOrderExecutions = ( amount: offerItem.endAmount, recipient: fulfiller, }, - offerer: offerer, - conduitKey: conduitKey, + offerer, + conduitKey, }, { item: { diff --git a/test/utils/fixtures/conduit.ts b/test/utils/fixtures/conduit.ts index 2c4d3a42e..6c6c0fa3a 100644 --- a/test/utils/fixtures/conduit.ts +++ b/test/utils/fixtures/conduit.ts @@ -1,16 +1,19 @@ -/* eslint-disable camelcase */ import { expect } from "chai"; -import { constants, Wallet } from "ethers"; +import { constants } from "ethers"; import { getCreate2Address, keccak256 } from "ethers/lib/utils"; import hre, { ethers } from "hardhat"; -import { - ConduitControllerInterface, - ImmutableCreate2FactoryInterface, -} from "../../../typechain-types"; + import { deployContract } from "../contracts"; import { randomHex } from "../encoding"; import { whileImpersonating } from "../impersonate"; +import type { + ConduitControllerInterface, + Conduit__factory, + ImmutableCreate2FactoryInterface, +} from "../../../typechain-types"; +import type { Wallet } from "ethers"; + const deployConstants = require("../../../constants/constants"); export const conduitFixture = async ( @@ -18,10 +21,12 @@ export const conduitFixture = async ( owner: Wallet ) => { let conduitController: ConduitControllerInterface; - let conduitImplementation: any; + let conduitImplementation: Conduit__factory; if (process.env.REFERENCE) { - conduitImplementation = await ethers.getContractFactory("ReferenceConduit"); - conduitController = await deployContract("ConduitController", owner as any); + conduitImplementation = (await ethers.getContractFactory( + "ReferenceConduit" + )) as Conduit__factory; + conduitController = await deployContract("ConduitController", owner); } else { conduitImplementation = await ethers.getContractFactory("Conduit"); @@ -82,7 +87,7 @@ export const conduitFixture = async ( const deployNewConduit = async (owner: Wallet, conduitKey?: string) => { // Create a conduit key with a random salt const assignedConduitKey = - conduitKey || owner.address + randomHex(12).slice(2); + conduitKey ?? owner.address + randomHex(12).slice(2); const { conduit: tempConduitAddress } = await conduitController.getConduit( assignedConduitKey diff --git a/test/utils/fixtures/create2.ts b/test/utils/fixtures/create2.ts index 95c0d0e49..292e01192 100644 --- a/test/utils/fixtures/create2.ts +++ b/test/utils/fixtures/create2.ts @@ -1,9 +1,11 @@ import { expect } from "chai"; -import { Wallet } from "ethers"; import hre, { ethers } from "hardhat"; -import { ImmutableCreate2FactoryInterface } from "../../../typechain-types"; + import { faucet } from "../impersonate"; +import type { ImmutableCreate2FactoryInterface } from "../../../typechain-types"; +import type { Wallet } from "ethers"; + const deployConstants = require("../../../constants/constants"); export const create2FactoryFixture = async (owner: Wallet) => { diff --git a/test/utils/fixtures/index.ts b/test/utils/fixtures/index.ts index 0dad7240c..d7e475159 100644 --- a/test/utils/fixtures/index.ts +++ b/test/utils/fixtures/index.ts @@ -1,22 +1,30 @@ -/* eslint-disable no-unused-expressions */ import { expect } from "chai"; -import { - BigNumber, - constants, - Contract, - ContractReceipt, - ContractTransaction, - Wallet, -} from "ethers"; +import { Contract, constants } from "ethers"; import { ethers } from "hardhat"; + import { deployContract } from "../contracts"; import { toBN } from "../encoding"; -import { AdvancedOrder, CriteriaResolver } from "../types"; + import { conduitFixture } from "./conduit"; import { create2FactoryFixture } from "./create2"; import { marketplaceFixture } from "./marketplace"; import { tokensFixture } from "./tokens"; +import type { Reenterer } from "../../../typechain-types"; +import type { + AdvancedOrder, + ConsiderationItem, + CriteriaResolver, + OfferItem, +} from "../types"; +import type { + BigNumber, + BigNumberish, + ContractReceipt, + ContractTransaction, + Wallet, +} from "ethers"; + export { conduitFixture } from "./conduit"; export { fixtureERC20, @@ -29,7 +37,7 @@ const { provider } = ethers; export const seaportFixture = async (owner: Wallet) => { const EIP1271WalletFactory = await ethers.getContractFactory("EIP1271Wallet"); - const reenterer = await deployContract("Reenterer", owner as any); + const reenterer = await deployContract("Reenterer", owner); const { chainId } = await provider.getNetwork(); const create2Factory = await create2FactoryFixture(owner); const { @@ -369,9 +377,17 @@ export const seaportFixture = async (owner: Wallet) => { }; const checkTransferEvent = async ( - tx: any, - item: any, - { offerer, conduitKey, target }: any + tx: ContractTransaction | Promise, + item: (OfferItem | ConsiderationItem) & { + identifier?: string; + amount?: BigNumberish; + recipient?: string; + }, + { + offerer, + conduitKey, + target, + }: { offerer: string; conduitKey: string; target: string } ) => { const { itemType, @@ -381,7 +397,7 @@ export const seaportFixture = async (owner: Wallet) => { amount, recipient, } = item; - const identifier = id1 || id2; + const identifier = id1 ?? id2; const sender = getTransferSender(offerer, conduitKey); if ([1, 2, 5].includes(itemType)) { const contract = new Contract( @@ -402,7 +418,7 @@ export const seaportFixture = async (owner: Wallet) => { }; const checkExpectedEvents = async ( - tx: Promise, + tx: Promise | ContractTransaction, receipt: ContractReceipt, orderGroups: Array<{ order: AdvancedOrder; @@ -512,7 +528,7 @@ export const seaportFixture = async (owner: Wallet) => { const { offerer, conduitKey, consideration, offer } = order.parameters; const compareEventItems = async ( item: any, - orderItem: any, + orderItem: OfferItem | ConsiderationItem, isConsiderationItem: boolean ) => { expect(item.itemType).to.equal( @@ -585,7 +601,7 @@ export const seaportFixture = async (owner: Wallet) => { { ...item, amount }, { offerer: receipt.from, - conduitKey: fulfillerConduitKey, + conduitKey: fulfillerConduitKey!, target: receipt.to, } ); @@ -630,7 +646,7 @@ export const seaportFixture = async (owner: Wallet) => { if (offer.itemType === 1) { // ERC20 // search for transfer - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC20.interface.parseLog(x)) .filter( (x) => @@ -642,13 +658,13 @@ export const seaportFixture = async (owner: Wallet) => { ); expect(transferLogs.length).to.be.above(0); - for (const transferLog of transferLogs) { - // TODO: check each transferred amount - } + // TODO: check each transferred amount + // for (const transferLog of transferLogs) { + // } } else if (offer.itemType === 2) { // ERC721 // search for transfer - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC721.interface.parseLog(x)) .filter( (x) => @@ -666,7 +682,7 @@ export const seaportFixture = async (owner: Wallet) => { ); } else if (offer.itemType === 3) { // search for transfer - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC1155.interface.parseLog(x)) .filter( (x) => @@ -684,7 +700,7 @@ export const seaportFixture = async (owner: Wallet) => { : true)) ); - expect(transferLogs.length > 0).to.be.true; + expect(transferLogs.length).to.be.above(0); let found = false; for (const transferLog of transferLogs) { @@ -701,6 +717,7 @@ export const seaportFixture = async (owner: Wallet) => { } } + // eslint-disable-next-line no-unused-expressions expect(found).to.be.true; } } @@ -722,7 +739,7 @@ export const seaportFixture = async (owner: Wallet) => { if (consideration.itemType === 1) { // ERC20 // search for transfer - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC20.interface.parseLog(x)) .filter( (x) => @@ -731,14 +748,13 @@ export const seaportFixture = async (owner: Wallet) => { ); expect(transferLogs.length).to.be.above(0); - for (const transferLog of transferLogs) { - // TODO: check each transferred amount - } + // TODO: check each transferred amount + // for (const transferLog of transferLogs) { + // } } else if (consideration.itemType === 2) { // ERC721 // search for transfer - - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC721.interface.parseLog(x)) .filter( (x) => @@ -753,7 +769,7 @@ export const seaportFixture = async (owner: Wallet) => { ); } else if (consideration.itemType === 3) { // search for transfer - const transferLogs = (tokenEvents || []) + const transferLogs = (tokenEvents ?? []) .map((x) => testERC1155.interface.parseLog(x)) .filter( (x) => @@ -765,7 +781,7 @@ export const seaportFixture = async (owner: Wallet) => { x.args.to === consideration.recipient) ); - expect(transferLogs.length > 0).to.be.true; + expect(transferLogs.length).to.be.above(0); let found = false; for (const transferLog of transferLogs) { @@ -783,6 +799,7 @@ export const seaportFixture = async (owner: Wallet) => { } } + // eslint-disable-next-line no-unused-expressions expect(found).to.be.true; } } diff --git a/test/utils/fixtures/marketplace.ts b/test/utils/fixtures/marketplace.ts index fa1527b73..034e00b6d 100644 --- a/test/utils/fixtures/marketplace.ts +++ b/test/utils/fixtures/marketplace.ts @@ -1,14 +1,8 @@ import { expect } from "chai"; -import { constants, Wallet } from "ethers"; +import { constants } from "ethers"; import { keccak256, recoverAddress } from "ethers/lib/utils"; import hre, { ethers } from "hardhat"; -import { - ConduitInterface, - ConduitControllerInterface, - ImmutableCreate2FactoryInterface, - ConsiderationInterface, - TestZone, -} from "../../../typechain-types"; + import { deployContract } from "../contracts"; import { calculateOrderHash, @@ -16,18 +10,26 @@ import { randomHex, toBN, } from "../encoding"; -import { +import { VERSION } from "../helpers"; + +import type { + ConduitControllerInterface, + ConduitInterface, + ConsiderationInterface, + ImmutableCreate2FactoryInterface, + TestZone, +} from "../../../typechain-types"; +import type { AdvancedOrder, ConsiderationItem, CriteriaResolver, OfferItem, OrderComponents, } from "../types"; +import type { Contract, Wallet } from "ethers"; -const { orderType } = require("../../../eip-712-types/order"); const deployConstants = require("../../../constants/constants"); - -const VERSION = !process.env.REFERENCE ? "1.1" : "rc.1.1"; +const { orderType } = require("../../../eip-712-types/order"); export const marketplaceFixture = async ( create2Factory: ImmutableCreate2FactoryInterface, @@ -41,11 +43,12 @@ export const marketplaceFixture = async ( process.env.REFERENCE ? "ReferenceConsideration" : "Seaport" ); - const directMarketplaceContract = await deployContract( - process.env.REFERENCE ? "ReferenceConsideration" : "Consideration", - owner as any, - conduitController.address - ); + const directMarketplaceContract = + await deployContract( + process.env.REFERENCE ? "ReferenceConsideration" : "Consideration", + owner, + conduitController.address + ); const marketplaceContractAddress = await create2Factory.findCreate2Address( deployConstants.MARKETPLACE_CONTRACT_CREATION_SALT, @@ -78,20 +81,18 @@ export const marketplaceFixture = async ( .connect(owner) .updateChannel(conduitOne.address, marketplaceContract.address, true); - const stubZone: TestZone = await deployContract("TestZone", owner as any); + const stubZone = await deployContract("TestZone", owner); // Required for EIP712 signing const domainData = { name: process.env.REFERENCE ? "Consideration" : "Seaport", version: VERSION, - chainId: chainId, + chainId, verifyingContract: marketplaceContract.address, }; const getAndVerifyOrderHash = async (orderComponents: OrderComponents) => { - const orderHash = await marketplaceContract.getOrderHash( - orderComponents as any - ); + const orderHash = await marketplaceContract.getOrderHash(orderComponents); const derivedOrderHash = calculateOrderHash(orderComponents); expect(orderHash).to.equal(derivedOrderHash); return orderHash; @@ -100,7 +101,7 @@ export const marketplaceFixture = async ( // Returns signature const signOrder = async ( orderComponents: OrderComponents, - signer: Wallet + signer: Wallet | Contract ) => { const signature = await signer._signTypedData( domainData, @@ -122,8 +123,8 @@ export const marketplaceFixture = async ( }; const createOrder = async ( - offerer: Wallet, - zone: Wallet | undefined | string = undefined, + offerer: Wallet | Contract, + zone: TestZone | Wallet | undefined | string = undefined, offer: OfferItem[], consideration: ConsiderationItem[], orderType: number, @@ -145,7 +146,7 @@ export const marketplaceFixture = async ( const orderParameters = { offerer: offerer.address, zone: !extraCheap - ? (zone as Wallet).address || (zone as string) + ? (zone as Wallet).address ?? zone : constants.AddressZero, offer, consideration, @@ -177,7 +178,7 @@ export const marketplaceFixture = async ( totalSize, }; - const flatSig = await signOrder(orderComponents, signer || offerer); + const flatSig = await signOrder(orderComponents, signer ?? offerer); const order = { parameters: orderParameters, @@ -433,9 +434,9 @@ export const marketplaceFixture = async ( counter, }; - const flatSig = await signOrder(orderComponents as any, offerer); + const flatSig = await signOrder(orderComponents, offerer); - const mirrorOrderHash = await getAndVerifyOrderHash(orderComponents as any); + const mirrorOrderHash = await getAndVerifyOrderHash(orderComponents); const mirrorOrder = { parameters: orderParameters, diff --git a/test/utils/fixtures/tokens.ts b/test/utils/fixtures/tokens.ts index 9fddd4372..d634d4c4f 100644 --- a/test/utils/fixtures/tokens.ts +++ b/test/utils/fixtures/tokens.ts @@ -1,20 +1,24 @@ -/* eslint-disable camelcase */ -import { JsonRpcSigner } from "@ethersproject/providers"; import { expect } from "chai"; -import { BigNumber, constants, Wallet } from "ethers"; import { ethers } from "hardhat"; -import { TestERC1155, TestERC20, TestERC721 } from "../../../typechain-types"; + import { deployContract } from "../contracts"; import { - randomBN, - toBN, - BigNumberish, getOfferOrConsiderationItem, random128, + randomBN, + toBN, } from "../encoding"; import { whileImpersonating } from "../impersonate"; -export const fixtureERC20 = async (signer: JsonRpcSigner) => { +import type { + TestERC1155, + TestERC20, + TestERC721, +} from "../../../typechain-types"; +import type { JsonRpcSigner } from "@ethersproject/providers"; +import type { BigNumber, BigNumberish, Contract, Wallet } from "ethers"; + +export const fixtureERC20 = async (signer: JsonRpcSigner | Wallet) => { const testERC20: TestERC20 = await deployContract("TestERC20", signer); const mintAndApproveERC20 = async ( @@ -47,7 +51,7 @@ export const fixtureERC20 = async (signer: JsonRpcSigner) => { }; }; -export const fixtureERC721 = async (signer: JsonRpcSigner) => { +export const fixtureERC721 = async (signer: JsonRpcSigner | Wallet) => { const testERC721: TestERC721 = await deployContract("TestERC721", signer); const set721ApprovalForAll = ( @@ -61,13 +65,13 @@ export const fixtureERC721 = async (signer: JsonRpcSigner) => { .withArgs(signer.address, spender, approved); }; - const mint721 = async (signer: Wallet, id?: BigNumberish) => { + const mint721 = async (signer: Wallet | Contract, id?: BigNumberish) => { const nftId = id ? toBN(id) : randomBN(); await testERC721.mint(signer.address, nftId); return nftId; }; - const mint721s = async (signer: Wallet, count: number) => { + const mint721s = async (signer: Wallet | Contract, count: number) => { const arr = []; for (let i = 0; i < count; i++) arr.push(await mint721(signer)); return arr; @@ -124,7 +128,7 @@ export const fixtureERC721 = async (signer: JsonRpcSigner) => { }; }; -export const fixtureERC1155 = async (signer: JsonRpcSigner) => { +export const fixtureERC1155 = async (signer: JsonRpcSigner | Wallet) => { const testERC1155: TestERC1155 = await deployContract("TestERC1155", signer); const set1155ApprovalForAll = ( @@ -212,14 +216,14 @@ export const fixtureERC1155 = async (signer: JsonRpcSigner) => { const minRandom = (min: number) => randomBN(10).add(min); -export const tokensFixture = async (signer: JsonRpcSigner) => { +export const tokensFixture = async (signer: JsonRpcSigner | Wallet) => { const erc20 = await fixtureERC20(signer); const erc721 = await fixtureERC721(signer); const erc1155 = await fixtureERC1155(signer); const { testERC1155: testERC1155Two } = await fixtureERC1155(signer); const tokenByType = [ { - address: constants.AddressZero, + address: ethers.constants.AddressZero, } as any, // ETH erc20.testERC20, erc721.testERC721, diff --git a/test/utils/helpers.ts b/test/utils/helpers.ts new file mode 100644 index 000000000..758798b99 --- /dev/null +++ b/test/utils/helpers.ts @@ -0,0 +1,46 @@ +import { ethers } from "ethers"; + +import { randomBN } from "./encoding"; + +import type { + AdvancedOrder, + CriteriaResolver, + Fulfillment, + Order, +} from "./types"; + +export const VERSION = `1.1${process.env.REFERENCE ? "-reference" : ""}`; + +export const minRandom = (min: ethers.BigNumberish) => randomBN(10).add(min); + +export const getCustomRevertSelector = (customErrorString: string) => + ethers.utils + .keccak256(ethers.utils.toUtf8Bytes(customErrorString)) + .slice(0, 10); + +export const simulateMatchOrders = async ( + marketplaceContract: ethers.Contract, + orders: Order[], + fulfillments: Fulfillment[], + caller: ethers.Wallet, + value: ethers.BigNumberish +) => + marketplaceContract + .connect(caller) + .callStatic.matchOrders(orders, fulfillments, { + value, + }); + +export const simulateAdvancedMatchOrders = async ( + marketplaceContract: ethers.Contract, + orders: AdvancedOrder[], + criteriaResolvers: CriteriaResolver[], + fulfillments: Fulfillment[], + caller: ethers.Wallet, + value: ethers.BigNumberish +) => + marketplaceContract + .connect(caller) + .callStatic.matchAdvancedOrders(orders, criteriaResolvers, fulfillments, { + value, + }); diff --git a/test/utils/impersonate.ts b/test/utils/impersonate.ts index bd1ccb283..21056f380 100644 --- a/test/utils/impersonate.ts +++ b/test/utils/impersonate.ts @@ -1,8 +1,10 @@ -import { JsonRpcProvider } from "@ethersproject/providers"; import { parseEther } from "@ethersproject/units"; import { ethers } from "hardhat"; + import { randomHex } from "./encoding"; +import type { JsonRpcProvider } from "@ethersproject/providers"; + const TEN_THOUSAND_ETH = parseEther("10000").toHexString().replace("0x0", "0x"); export const impersonate = async ( diff --git a/test/utils/sign.ts b/test/utils/sign.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/utils/types.ts b/test/utils/types.ts index 898286ae5..999824086 100644 --- a/test/utils/types.ts +++ b/test/utils/types.ts @@ -1,6 +1,4 @@ -import { BigNumber } from "ethers"; - -export type BigNumberish = string | BigNumber | number | boolean; +import type { BigNumber } from "ethers"; export type AdditionalRecipient = { amount: BigNumber; @@ -12,6 +10,11 @@ export type FulfillmentComponent = { itemIndex: number; }; +export type Fulfillment = { + offerComponents: FulfillmentComponent[]; + considerationComponents: FulfillmentComponent[]; +}; + export type CriteriaResolver = { orderIndex: number; side: 0 | 1; diff --git a/tsconfig.json b/tsconfig.json index 831e4358e..1ca3df43d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,9 @@ "strict": true, "esModuleInterop": true, "outDir": "dist", - "declaration": true + "declaration": true, + "resolveJsonModule": true }, - "include": ["./scripts", "./test", "./typechain-types"], + "include": ["./scripts", "./test", "./typechain-types", "./eip-712-types", "./*.config.ts"], "files": ["./hardhat.config.ts"] } diff --git a/yarn.lock b/yarn.lock index c8722159b..174c90147 100644 --- a/yarn.lock +++ b/yarn.lock @@ -268,6 +268,17 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/rlp" "^5.6.0" +"@ethersproject/address@^5.0.2": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" + integrity sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/rlp" "^5.6.1" + "@ethersproject/base64@5.6.0", "@ethersproject/base64@^5.6.0": version "5.6.0" resolved "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.0.tgz" @@ -292,7 +303,16 @@ "@ethersproject/logger" "^5.6.0" bn.js "^4.11.9" -"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.6.0": +"@ethersproject/bignumber@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66" + integrity sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.6.0", "@ethersproject/bytes@^5.6.1": version "5.6.1" resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz" integrity sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g== @@ -381,6 +401,14 @@ "@ethersproject/bytes" "^5.6.0" js-sha3 "0.8.0" +"@ethersproject/keccak256@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc" + integrity sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA== + dependencies: + "@ethersproject/bytes" "^5.6.1" + js-sha3 "0.8.0" + "@ethersproject/logger@5.6.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.6.0": version "5.6.0" resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz" @@ -449,6 +477,14 @@ "@ethersproject/bytes" "^5.6.0" "@ethersproject/logger" "^5.6.0" +"@ethersproject/rlp@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.1.tgz#df8311e6f9f24dcb03d59a2bac457a28a4fe2bd8" + integrity sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/sha2@5.6.0", "@ethersproject/sha2@^5.6.0": version "5.6.0" resolved "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.0.tgz" @@ -614,10 +650,26 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomiclabs/hardhat-ethers@^2.0.4": - version "2.0.5" - resolved "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz" - integrity sha512-A2gZAGB6kUvLx+kzM92HKuUF33F1FSe90L0TmkXkT2Hh0OKRpvWZURUSU2nghD2yC4DzfEZ3DftfeHGvZ2JTUw== +"@nomiclabs/hardhat-ethers@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.6.tgz#1c695263d5b46a375dcda48c248c4fba9dfe2fc2" + integrity sha512-q2Cjp20IB48rEn2NPjR1qxsIQBvFVYW9rFRCFq+bC4RUrn1Ljz3g4wM8uSlgIBZYBi2JMXxmOzFqHraczxq4Ng== + +"@nomiclabs/hardhat-etherscan@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.0.tgz#7137554862b3b1c914f1b1bf110f0529fd2dec53" + integrity sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^5.0.2" + chalk "^2.4.2" + debug "^4.1.1" + fs-extra "^7.0.1" + lodash "^4.17.11" + semver "^6.3.0" + table "^6.8.0" + undici "^5.4.0" "@nomiclabs/hardhat-waffle@^2.0.1": version "2.0.3" @@ -1258,6 +1310,16 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.6.1, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" @@ -2116,9 +2178,9 @@ bech32@1.1.4: resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.0: +bignumber.js@^9.0.0, bignumber.js@^9.0.1: version "9.0.2" - resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw== binary-extensions@^2.0.0: @@ -2162,6 +2224,11 @@ bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.1.3, bn.js@^5.2.0: resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + body-parser@1.20.0, body-parser@^1.16.0: version "1.20.0" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz" @@ -2446,6 +2513,14 @@ caseless@^0.12.0, caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cbor@^5.0.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c" + integrity sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A== + dependencies: + bignumber.js "^9.0.1" + nofilter "^1.0.4" + chai@^4.3.4: version "4.3.6" resolved "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz" @@ -6028,6 +6103,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" @@ -6468,6 +6548,11 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + lodash@4.17.20, lodash@>=4.17.21, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -7096,6 +7181,11 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz" integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== +nofilter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e" + integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA== + nopt@3.x: version "3.0.6" resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" @@ -8131,7 +8221,7 @@ require-from-string@^1.1.0: resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz" integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg= -require-from-string@^2.0.0: +require-from-string@^2.0.0, require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== @@ -9176,6 +9266,17 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" +table@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tape@^4.6.3: version "4.15.1" resolved "https://registry.yarnpkg.com/tape/-/tape-4.15.1.tgz#88fb662965a11f9be1bddb04c11662d7eceb129e" @@ -9615,6 +9716,11 @@ undici@^4.14.1: resolved "https://registry.npmjs.org/undici/-/undici-4.16.0.tgz" integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw== +undici@^5.4.0: + version "5.5.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.5.1.tgz#baaf25844a99eaa0b22e1ef8d205bffe587c8f43" + integrity sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz" From 28c59465896acae73f20ad40d116756510571465 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 29 Jun 2022 09:29:13 -0400 Subject: [PATCH 040/126] fix forge test --- test/foundry/TransferHelperTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 45381c508..5123e5430 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -856,7 +856,7 @@ contract TransferHelperTest is BaseOrderTest { bob, address(mockReceiver), true, - abi.encodePacked('ConduitErrorString("WRONG_FROM")') + abi.encodeWithSignature("ConduitErrorString(string)", "WRONG_FROM") ); } } From 7acd1788c577b06c6043bb5bdfbbbd46afdf8411 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 29 Jun 2022 10:01:34 -0400 Subject: [PATCH 041/126] add panic error forge test --- contracts/test/TestERC20Panic.sol | 5 +++ test/foundry/TransferHelperTest.sol | 48 ++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/contracts/test/TestERC20Panic.sol b/contracts/test/TestERC20Panic.sol index 84abb3b3d..3cb885402 100644 --- a/contracts/test/TestERC20Panic.sol +++ b/contracts/test/TestERC20Panic.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; contract TestERC20Panic is ERC20("TestPanic", "PANIC", 18) { + function mint(address to, uint256 amount) external returns (bool) { + _mint(to, amount); + return true; + } + function transferFrom( address, /* from */ address, /* to */ diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 5123e5430..14b898b7b 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -25,6 +25,8 @@ import { TransferHelperInterface } from "../../contracts/interfaces/TransferHelp import { ERC721ReceiverMock } from "../../contracts/test/ERC721ReceiverMock.sol"; +import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; + contract TransferHelperTest is BaseOrderTest { TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. @@ -838,12 +840,6 @@ contract TransferHelperTest is BaseOrderTest { fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne ); - // Deploy invalid mock ERC721 receiver - ERC721ReceiverMock mockReceiver = new ERC721ReceiverMock( - 0xabcd0000, - ERC721ReceiverMock.Error.RevertWithMessage - ); - TransferHelperItem memory item = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), @@ -851,12 +847,50 @@ contract TransferHelperTest is BaseOrderTest { 1 ); + // Attempt to transfer ERC721 tokens from bob to alice + // Expect revert since alice owns the tokens _performSingleItemTransferAndCheckBalances( item, bob, - address(mockReceiver), + alice, true, abi.encodeWithSignature("ConduitErrorString(string)", "WRONG_FROM") ); } + + function testRevertPanicErrorWithConduit( + FuzzInputsCommon memory inputs, + bytes32 fuzzConduitKey + ) public { + // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". + vm.assume( + fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne + ); + + // Create ERC20 token that reverts with a panic when calling transferFrom. + TestERC20Panic panicERC20 = new TestERC20Panic(); + + // Mint ERC20 tokens to alice. + panicERC20.mint(alice, 10); + + // Approve the ERC20 tokens + panicERC20.approve(alice, 10); + + TransferHelperItem memory item = TransferHelperItem( + ConduitItemType.ERC20, + address(panicERC20), + 0, + 10 + ); + + // Revert with panic error when calling execute via conduit + _performSingleItemTransferAndCheckBalances( + item, + alice, + bob, + true, + abi.encodeWithSignature("ConduitErrorPanic(uint256)", 18) + + ); + } } From 02b263a27f07fd3e5d1fbb8fb48aa1ad07c71bc6 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 29 Jun 2022 10:10:04 -0400 Subject: [PATCH 042/126] edit inline comments --- contracts/helpers/TransferHelper.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 6be5a4296..1fca2b83d 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -37,7 +37,7 @@ interface IERC721Receiver { /** * @title TransferHelper - * @author stuckinaboot, stephankmin + * @author stuckinaboot, stephankmin, ryanio * @notice TransferHelper is a utility contract for transferring * ERC20/ERC721/ERC1155 items in bulk to a specific recipient. */ @@ -247,16 +247,18 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { try ConduitInterface(conduit).execute(conduitTransfers) {} catch ( bytes memory data ) { - // Bubble up the conduit's revert reason if present. + // "Bubble up" the conduit's revert reason if present. if (data.length != 0) { assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } - // Revert if the error provides a reason string. + // Revert with the error reason string if present. } catch Error(string memory reason) { revert ConduitErrorString(reason); + // Revert with the panic error code if the error was caused + // by a panic. } catch Panic(uint256 errorCode) { revert ConduitErrorPanic(errorCode); } From 68014a406aee9735cfc3b07d624bcbf9f557272f Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 29 Jun 2022 12:21:34 -0400 Subject: [PATCH 043/126] add internal fns --- contracts/helpers/TransferHelper.sol | 321 ++++++++++++++------------- 1 file changed, 165 insertions(+), 156 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 1fca2b83d..653aacb5e 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -76,8 +76,8 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { * * @param items The items to transfer. * @param recipient The address the items should be transferred to. - * @param conduitKey The key of the conduit through which the bulk transfer - * should occur. + * @param conduitKey An optional conduit key referring to a conduit through + * which the bulk transfer should occur. * * @return magicValue A value indicating that the transfers were successful. */ @@ -86,185 +86,194 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { address recipient, bytes32 conduitKey ) external override returns (bytes4 magicValue) { + // If no conduitKey is given, use TokenTransferrer to perform transfers. + if (conduitKey == bytes32(0)) { + _performTransfersWithoutConduit(items, recipient); + } + // Otherwise, a conduitKey was provided. + else { + _performTransfersWithConduit(items, recipient, conduitKey); + } + + // Return a magic value indicating that the transfers were performed. + magicValue = this.bulkTransfer.selector; + } + + function _performTransfersWithoutConduit( + TransferHelperItem[] calldata items, + address recipient + ) internal { // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; - // If no conduitKey is given, use TokenTransferrer to perform transfers. - if (conduitKey == bytes32(0)) { - // Create a boolean that reflects whether recipient is a contract. - bool recipientIsContract; + // Create a boolean that reflects whether recipient is a contract. + bool recipientIsContract; - // Check if recipient is a contract. - if (recipient.code.length != 0) { - recipientIsContract = true; - } + // Check if recipient is a contract. + if (recipient.code.length != 0) { + recipientIsContract = true; + } - // Skip overflow checks: all for loops are indexed starting at zero. - unchecked { - // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { - // Retrieve the transfer in question. - TransferHelperItem calldata item = items[i]; + // Skip overflow checks: all for loops are indexed starting at zero. + unchecked { + // Iterate over each transfer. + for (uint256 i = 0; i < totalTransfers; ++i) { + // Retrieve the transfer in question. + TransferHelperItem calldata item = items[i]; - // Perform a transfer based on the transfer's item type. - // Revert if item being transferred is a native token. - if (item.itemType == ConduitItemType.NATIVE) { - revert InvalidItemType(); - } else if (item.itemType == ConduitItemType.ERC20) { - // Ensure that the identifier for an ERC20 token is 0. - if (item.identifier != 0) { - revert InvalidERC20Identifier(); - } + // Perform a transfer based on the transfer's item type. + // Revert if item being transferred is a native token. + if (item.itemType == ConduitItemType.NATIVE) { + revert InvalidItemType(); + } else if (item.itemType == ConduitItemType.ERC20) { + // Ensure that the identifier for an ERC20 token is 0. + if (item.identifier != 0) { + revert InvalidERC20Identifier(); + } - // Transfer ERC20 token. - _performERC20Transfer( - item.token, - msg.sender, - recipient, - item.amount - ); - } else if (item.itemType == ConduitItemType.ERC721) { - // If recipient is a contract, ensure it can receive - // ERC721 tokens. - if (recipientIsContract) { - // Check if recipient can receive ERC721 tokens. - try - IERC721Receiver(recipient).onERC721Received( - address(this), - msg.sender, - item.identifier, - "" - ) - returns (bytes4 selector) { - // Check if onERC721Received selector is valid. - if ( - selector != - IERC721Receiver.onERC721Received.selector - ) { - // Revert if recipient cannot accept - // ERC721 tokens. - revert InvalidERC721Recipient(); - } - } catch (bytes memory data) { - // Bubble up recipient's revert reason - // if present. - if (data.length != 0) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } else { - revert InvalidERC721Recipient(); + // Transfer ERC20 token. + _performERC20Transfer( + item.token, + msg.sender, + recipient, + item.amount + ); + } else if (item.itemType == ConduitItemType.ERC721) { + // If recipient is a contract, ensure it can receive + // ERC721 tokens. + if (recipientIsContract) { + // Check if recipient can receive ERC721 tokens. + try + IERC721Receiver(recipient).onERC721Received( + address(this), + msg.sender, + item.identifier, + "" + ) + returns (bytes4 selector) { + // Check if onERC721Received selector is valid. + if ( + selector != + IERC721Receiver.onERC721Received.selector + ) { + // Revert if recipient cannot accept + // ERC721 tokens. + revert InvalidERC721Recipient(); + } + } catch (bytes memory data) { + // Bubble up recipient's revert reason + // if present. + if (data.length != 0) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) } + } else { + revert InvalidERC721Recipient(); } } - // Ensure that the amount for an ERC721 transfer is 1. - if (item.amount != 1) { - revert InvalidERC721TransferAmount(); - } - - // Transfer ERC721 token. - _performERC721Transfer( - item.token, - msg.sender, - recipient, - item.identifier - ); - } else if (item.itemType == ConduitItemType.ERC1155) { - // Transfer ERC1155 token. - _performERC1155Transfer( - item.token, - msg.sender, - recipient, - item.identifier, - item.amount - ); } + // Ensure that the amount for an ERC721 transfer is 1. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } + + // Transfer ERC721 token. + _performERC721Transfer( + item.token, + msg.sender, + recipient, + item.identifier + ); + } else if (item.itemType == ConduitItemType.ERC1155) { + // Transfer ERC1155 token. + _performERC1155Transfer( + item.token, + msg.sender, + recipient, + item.identifier, + item.amount + ); } } } - // Otherwise, a conduitKey was provided. - else { - // Derive the conduit address from the deployer, conduit key - // and creation code hash. - address conduit = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - address(_CONDUIT_CONTROLLER), - conduitKey, - _CONDUIT_CREATION_CODE_HASH - ) + } + + function _performTransfersWithConduit( + TransferHelperItem[] calldata items, + address recipient, + bytes32 conduitKey + ) internal { + // Retrieve total number of transfers and place on stack. + uint256 totalTransfers = items.length; + + // Derive the conduit address from the deployer, conduit key + // and creation code hash. + address conduit = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(_CONDUIT_CONTROLLER), + conduitKey, + _CONDUIT_CREATION_CODE_HASH ) ) ) - ); - - // Create a variable to store the codehash of the conduit. - bytes32 codeHash; - - // Retrieve the codehash of the conduit and assign it to codeHash. - assembly { - codeHash := extcodehash(conduit) - } - - // Ensure codeHash equals the immutable conduit runtime codehash - // to ensure the conduit implements `execute` for the subsequent - // external call. - if (codeHash != _CONDUIT_RUNTIME_CODE_HASH) { - revert InvalidConduit(); - } + ) + ); - // Declare a new array to populate with each token transfer. - ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( - totalTransfers - ); + // Declare a new array to populate with each token transfer. + ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( + totalTransfers + ); - // Skip overflow checks: all for loops are indexed starting at zero. - unchecked { - // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { - // Retrieve the transfer in question. - TransferHelperItem calldata item = items[i]; + // Skip overflow checks: all for loops are indexed starting at zero. + unchecked { + // Iterate over each transfer. + for (uint256 i = 0; i < totalTransfers; ++i) { + // Retrieve the transfer in question. + TransferHelperItem calldata item = items[i]; - // Create a ConduitTransfer corresponding to each - // TransferHelperItem. - conduitTransfers[i] = ConduitTransfer( - item.itemType, - item.token, - msg.sender, - recipient, - item.identifier, - item.amount - ); - } + // Create a ConduitTransfer corresponding to each + // TransferHelperItem. + conduitTransfers[i] = ConduitTransfer( + item.itemType, + item.token, + msg.sender, + recipient, + item.identifier, + item.amount + ); } + } - // If the external call fails, revert with the conduit's - // custom error. - try ConduitInterface(conduit).execute(conduitTransfers) {} catch ( - bytes memory data + // If the external call fails, revert with the conduit's + // custom error. + try ConduitInterface(conduit).execute(conduitTransfers) returns ( + bytes4 conduitMagicValue + ) { + if ( + conduitMagicValue != ConduitInterface(conduit).execute.selector ) { - // "Bubble up" the conduit's revert reason if present. - if (data.length != 0) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } + revert InvalidConduit(); + } + } catch (bytes memory data) { + // "Bubble up" the conduit's revert reason if present. + if (data.length != 0) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) } - // Revert with the error reason string if present. - } catch Error(string memory reason) { - revert ConduitErrorString(reason); - // Revert with the panic error code if the error was caused - // by a panic. - } catch Panic(uint256 errorCode) { - revert ConduitErrorPanic(errorCode); } + // Revert with the error reason string if present. + } catch Error(string memory reason) { + revert ConduitErrorString(reason); + // Revert with the panic error code if the error was caused + // by a panic. + } catch Panic(uint256 errorCode) { + revert ConduitErrorPanic(errorCode); } - - // Return a magic value indicating that the transfers were performed. - magicValue = this.bulkTransfer.selector; } } From 5cdb1ff803d4b41a43ff5c7c045109cb512c000b Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 29 Jun 2022 16:33:18 -0400 Subject: [PATCH 044/126] add mock contracts --- contracts/helpers/TransferHelper.sol | 2 +- .../interfaces/TransferHelperInterface.sol | 2 +- contracts/test/ConduitControllerMock.sol | 83 +++++++++++++++++++ contracts/test/ConduitMock.sol | 40 +++++++++ test/transferhelper.spec.ts | 43 ++++++++++ 5 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 contracts/test/ConduitControllerMock.sol create mode 100644 contracts/test/ConduitMock.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 653aacb5e..9cfb8598e 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -257,7 +257,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { if ( conduitMagicValue != ConduitInterface(conduit).execute.selector ) { - revert InvalidConduit(); + revert InvalidMagicValue(); } } catch (bytes memory data) { // "Bubble up" the conduit's revert reason if present. diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 7f4a16c80..a8f0de7e4 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -25,7 +25,7 @@ interface TransferHelperInterface { * @dev Revert with an error when a call to a conduit returns an invalid * magic value. */ - error InvalidConduit(); + error InvalidMagicValue(); /** * @dev Revert with an error when a call to a conduit reverts with a diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol new file mode 100644 index 000000000..1d826b5d5 --- /dev/null +++ b/contracts/test/ConduitControllerMock.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +// prettier-ignore +import { + ConduitControllerInterface +} from "../interfaces/ConduitControllerInterface.sol"; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { ConduitController } from "../conduit/ConduitController.sol"; + +import { ConduitMock } from "../test/ConduitMock.sol"; + +contract ConduitControllerMock is ConduitController { + constructor() { + // Derive the conduit creation code hash and set it as an immutable. + _CONDUIT_CREATION_CODE_HASH = keccak256(type(ConduitMock).creationCode); + + // Deploy a conduit with the zero hash as the salt. + ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }(); + + // Retrieve the conduit runtime code hash and set it as an immutable. + _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; + } + + function createConduit(bytes32 conduitKey, address initialOwner) + external + override + returns (address conduit) + { + // Ensure that an initial owner has been supplied. + if (initialOwner == address(0)) { + revert InvalidInitialOwner(); + } + + // If the first 20 bytes of the conduit key do not match the caller... + if (address(uint160(bytes20(conduitKey))) != msg.sender) { + // Revert with an error indicating that the creator is invalid. + revert InvalidCreator(); + } + + // Derive address from deployer, conduit key and creation code hash. + conduit = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + conduitKey, + _CONDUIT_CREATION_CODE_HASH + ) + ) + ) + ) + ); + + // If derived conduit exists, as evidenced by comparing runtime code... + if (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH) { + // Revert with an error indicating that the conduit already exists. + revert ConduitAlreadyExists(conduit); + } + + // Deploy the conduit via CREATE2 using the conduit key as the salt. + new ConduitMock{ salt: conduitKey }(); + + // Initialize storage variable referencing conduit properties. + ConduitProperties storage conduitProperties = _conduits[conduit]; + + // Set the supplied initial owner as the owner of the conduit. + conduitProperties.owner = initialOwner; + + // Set conduit key used to deploy the conduit to enable reverse lookup. + conduitProperties.key = conduitKey; + + // Emit an event indicating that the conduit has been deployed. + emit NewConduit(conduit, conduitKey); + + // Emit an event indicating that conduit ownership has been assigned. + emit OwnershipTransferred(conduit, address(0), initialOwner); + } +} diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol new file mode 100644 index 000000000..60fb2371e --- /dev/null +++ b/contracts/test/ConduitMock.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; + +import { Conduit } from "../conduit/Conduit.sol"; + +// prettier-ignore +import { + ConduitTransfer, + ConduitBatch1155Transfer +} from "../conduit/lib/ConduitStructs.sol"; + +contract ConduitMock is Conduit { + function execute(ConduitTransfer[] calldata transfers) + external + override + onlyOpenChannel + returns (bytes4 magicValue) + { + // Retrieve the total number of transfers and place on the stack. + uint256 totalStandardTransfers = transfers.length; + + // Iterate over each transfer. + for (uint256 i = 0; i < totalStandardTransfers; ) { + // Retrieve the transfer in question and perform the transfer. + _transfer(transfers[i]); + + // Skip overflow check as for loop is indexed starting at zero. + unchecked { + ++i; + } + } + + // Return a magic value indicating that the transfers were performed. + magicValue = 0xabc42069; + } +} diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index bdc65332a..b2cbd59b4 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; +import { deployMockContract } from "@ethereum-waffle/mock-contract"; import { randomHex } from "./utils/encoding"; import { @@ -691,4 +692,46 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ).to.be.reverted; } }); + + // it("Reverts with invalid magic value returned by call to conduit", async () => { + // // Deploy ERC20 Contract + // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // const mockConduitControllerFactory = await ethers.getContractFactory( + // "ConduitControllerMock" + // ); + // const mockConduitController = await mockConduitControllerFactory.deploy(); + + // const mockConduitImplementation = await ethers.getContractFactory( + // "ConduitMock" + // ); + // const mockConduitKey = owner.address + randomHex(12).slice(2); + + // // Deploy the mock conduit through the mock conduit controller + // const mockConduitAddress = await mockConduitController + // .connect(owner) + // .createConduit(mockConduitKey, owner); + // const mockConduit = mockConduitImplementation.attach(mockConduitAddress); + + // const transferHelperItems = [ + // { + // itemType: 1, + // token: tempERC20Contract.address, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: tempERC20Contract.address, + // identifier: 0, + // amount: 20, + // }, + // ]; + + // await expect( + // tempTransferHelper + // .connect(sender) + // .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + // ).to.be.revertedWith("InvalidMagicValue"); + // }); }); From 11f30dcf80cf68a4959c7fa2dfa5f2f905cdafc0 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 30 Jun 2022 13:40:36 -0400 Subject: [PATCH 045/126] add progress --- contracts/test/ConduitControllerMock.sol | 17 ++-- contracts/test/ConduitMock.sol | 38 ++++---- test/foundry/TransferHelperTest.sol | 113 ++++++++++++++++------- test/transferhelper.spec.ts | 84 +++++++++-------- 4 files changed, 153 insertions(+), 99 deletions(-) diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index 1d826b5d5..b18d6f0f4 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -13,20 +13,25 @@ import { ConduitController } from "../conduit/ConduitController.sol"; import { ConduitMock } from "../test/ConduitMock.sol"; contract ConduitControllerMock is ConduitController { + // Set conduit creation code and runtime code hashes as immutable arguments. + bytes32 internal immutable _MOCK_CONDUIT_CREATION_CODE_HASH; + bytes32 internal immutable _MOCK_CONDUIT_RUNTIME_CODE_HASH; + constructor() { // Derive the conduit creation code hash and set it as an immutable. - _CONDUIT_CREATION_CODE_HASH = keccak256(type(ConduitMock).creationCode); + _MOCK_CONDUIT_CREATION_CODE_HASH = keccak256( + type(ConduitMock).creationCode + ); // Deploy a conduit with the zero hash as the salt. ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }(); // Retrieve the conduit runtime code hash and set it as an immutable. - _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; + _MOCK_CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; } - function createConduit(bytes32 conduitKey, address initialOwner) + function createMockConduit(bytes32 conduitKey, address initialOwner) external - override returns (address conduit) { // Ensure that an initial owner has been supplied. @@ -49,7 +54,7 @@ contract ConduitControllerMock is ConduitController { bytes1(0xff), address(this), conduitKey, - _CONDUIT_CREATION_CODE_HASH + _MOCK_CONDUIT_CREATION_CODE_HASH ) ) ) @@ -57,7 +62,7 @@ contract ConduitControllerMock is ConduitController { ); // If derived conduit exists, as evidenced by comparing runtime code... - if (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH) { + if (conduit.codehash == _MOCK_CONDUIT_RUNTIME_CODE_HASH) { // Revert with an error indicating that the conduit already exists. revert ConduitAlreadyExists(conduit); } diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 60fb2371e..9d775b0c7 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -13,28 +13,28 @@ import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; -contract ConduitMock is Conduit { +contract ConduitMock is ConduitInterface { + constructor() {} + function execute(ConduitTransfer[] calldata transfers) external - override - onlyOpenChannel returns (bytes4 magicValue) { - // Retrieve the total number of transfers and place on the stack. - uint256 totalStandardTransfers = transfers.length; - - // Iterate over each transfer. - for (uint256 i = 0; i < totalStandardTransfers; ) { - // Retrieve the transfer in question and perform the transfer. - _transfer(transfers[i]); - - // Skip overflow check as for loop is indexed starting at zero. - unchecked { - ++i; - } - } - - // Return a magic value indicating that the transfers were performed. - magicValue = 0xabc42069; + return 0xabc42069; + } + + function executeBatch1155( + ConduitBatch1155Transfer[] calldata batch1155Transfers + ) external returns (bytes4 magicValue) { + return 0xabc69420; } + + function executeWithBatch1155( + ConduitTransfer[] calldata standardTransfers, + ConduitBatch1155Transfer[] calldata batch1155Transfers + ) external returns (bytes4 magicValue) { + return 0x42069420; + } + + function updateChannel(address channel, bool isOpen) external {} } diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 14b898b7b..5687e672c 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -17,6 +17,10 @@ import { TestERC20 } from "../../contracts/test/TestERC20.sol"; import { TestERC721 } from "../../contracts/test/TestERC721.sol"; import { TestERC1155 } from "../../contracts/test/TestERC1155.sol"; +import { ConduitMock } from "../../contracts/test/ConduitMock.sol"; + +import { ConduitControllerMock } from "../../contracts/test/ConduitControllerMock.sol"; + import { InvalidERC721Recipient } from "../../contracts/test/InvalidERC721Recipient.sol"; import { TokenTransferrerErrors } from "../../contracts/interfaces/TokenTransferrerErrors.sol"; @@ -767,21 +771,28 @@ contract TransferHelperTest is BaseOrderTest { vm.assume( fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne ); - TransferHelperItem memory item = _getFuzzedTransferItem( + + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = _getFuzzedTransferItem( ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], inputs.identifiers[0] ); + // Reassign the conduit key that gets passed into TransferHelper to fuzzConduitKey. conduitKeyOne = fuzzConduitKey; - _performSingleItemTransferAndCheckBalances( - item, - alice, - bob, - true, - abi.encodePacked(TransferHelperInterface.InvalidConduit.selector) + + (address unknownConduitAddress, ) = conduitController.getConduit( + conduitKeyOne ); + vm.label(unknownConduitAddress, "unknown conduit"); + + vm.expectRevert( + abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) + ); + vm.prank(alice); + transferHelper.bulkTransfer(items, bob, conduitKeyOne); } function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) @@ -808,14 +819,9 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertInvalidItemWithConduit( - FuzzInputsCommon memory inputs, - bytes32 fuzzConduitKey - ) public { - // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". - vm.assume( - fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne - ); + function testRevertInvalidItemWithConduit(FuzzInputsCommon memory inputs) + public + { TransferHelperItem memory invalidItem = _getFuzzedTransferItem( ConduitItemType.NATIVE, inputs.amounts[0], @@ -831,15 +837,9 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertStringErrorWithConduit( - FuzzInputsCommon memory inputs, - bytes32 fuzzConduitKey - ) public { - // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". - vm.assume( - fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne - ); - + function testRevertStringErrorWithConduit(FuzzInputsCommon memory inputs) + public + { TransferHelperItem memory item = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), @@ -858,15 +858,9 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertPanicErrorWithConduit( - FuzzInputsCommon memory inputs, - bytes32 fuzzConduitKey - ) public { - // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". - vm.assume( - fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne - ); - + function testRevertPanicErrorWithConduit(FuzzInputsCommon memory inputs) + public + { // Create ERC20 token that reverts with a panic when calling transferFrom. TestERC20Panic panicERC20 = new TestERC20Panic(); @@ -890,7 +884,60 @@ contract TransferHelperTest is BaseOrderTest { bob, true, abi.encodeWithSignature("ConduitErrorPanic(uint256)", 18) + ); + } + + function testRevertInvalidConduitMagicValue(FuzzInputsCommon memory inputs) + public + { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock(); + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMock mockConduit = ConduitMock( + mockConduitController.createMockConduit( + conduitKeyAlice, + address(alice) + ) + ); + vm.label(address(mockConduit), "mock conduit"); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + assertEq(conduitKeyAlice, mockConduitKey); + + // Create item to transfer + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 + ); + + vm.expectRevert( + abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) + ); + mockTransferHelper.bulkTransfer(items, bob, mockConduitKey); + vm.stopPrank(); } } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index b2cbd59b4..411a881f0 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -693,45 +693,47 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } }); - // it("Reverts with invalid magic value returned by call to conduit", async () => { - // // Deploy ERC20 Contract - // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - // const mockConduitControllerFactory = await ethers.getContractFactory( - // "ConduitControllerMock" - // ); - // const mockConduitController = await mockConduitControllerFactory.deploy(); - - // const mockConduitImplementation = await ethers.getContractFactory( - // "ConduitMock" - // ); - // const mockConduitKey = owner.address + randomHex(12).slice(2); - - // // Deploy the mock conduit through the mock conduit controller - // const mockConduitAddress = await mockConduitController - // .connect(owner) - // .createConduit(mockConduitKey, owner); - // const mockConduit = mockConduitImplementation.attach(mockConduitAddress); - - // const transferHelperItems = [ - // { - // itemType: 1, - // token: tempERC20Contract.address, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: tempERC20Contract.address, - // identifier: 0, - // amount: 20, - // }, - // ]; - - // await expect( - // tempTransferHelper - // .connect(sender) - // .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - // ).to.be.revertedWith("InvalidMagicValue"); - // }); + it("Reverts with invalid magic value returned by call to conduit", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy(); + + const mockConduitImplementation = await ethers.getContractFactory( + "ConduitMock" + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + const { conduit: mockConduitAddress } = + await mockConduitController.getConduit(mockConduitKey); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createMockConduit(mockConduitKey, owner.address); + const mockConduit = mockConduitImplementation.attach(mockConduitAddress); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith("InvalidMagicValue"); + }); }); From 4bc233673e5fcc32a923ebf3f2e3574f82f33bce Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 30 Jun 2022 13:52:45 -0400 Subject: [PATCH 046/126] lint and skip coverage for test files --- config/.solcover.js | 2 ++ test/transferhelper.spec.ts | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/config/.solcover.js b/config/.solcover.js index 55a647a22..a69c4322a 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -34,6 +34,8 @@ module.exports = { "test/TestERC20Revert.sol", "test/InvalidERC721Recipient.sol", "test/ERC721ReceiverMock.sol", + "test/ConduitControllerMock.sol", + "test/ConduitMock.sol", ], configureYulOptimizer: true, solcOptimizerDetails: { diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 411a881f0..482f00929 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -1,7 +1,6 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import { deployMockContract } from "@ethereum-waffle/mock-contract"; import { randomHex } from "./utils/encoding"; import { @@ -702,18 +701,12 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); const mockConduitController = await mockConduitControllerFactory.deploy(); - const mockConduitImplementation = await ethers.getContractFactory( - "ConduitMock" - ); const mockConduitKey = owner.address + randomHex(12).slice(2); - const { conduit: mockConduitAddress } = - await mockConduitController.getConduit(mockConduitKey); // Deploy the mock conduit through the mock conduit controller await mockConduitController .connect(owner) .createMockConduit(mockConduitKey, owner.address); - const mockConduit = mockConduitImplementation.attach(mockConduitAddress); const transferHelperItems = [ { From 370f889a8a1c76e6c304a2b10902960b38b8d5ad Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 30 Jun 2022 19:05:56 -0400 Subject: [PATCH 047/126] more progress --- contracts/helpers/TransferHelper.sol | 21 +- .../interfaces/TransferHelperInterface.sol | 2 + contracts/test/ConduitControllerMock.sol | 455 +++++++++++++++++- test/foundry/TransferHelperTest.sol | 8 +- test/transferhelper.spec.ts | 100 ++-- test/utils/fixtures/conduit.ts | 18 +- test/utils/fixtures/tokens.ts | 22 +- 7 files changed, 540 insertions(+), 86 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 9cfb8598e..ca28e517f 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -94,7 +94,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { else { _performTransfersWithConduit(items, recipient, conduitKey); } - // Return a magic value indicating that the transfers were performed. magicValue = this.bulkTransfer.selector; } @@ -249,16 +248,14 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } + bytes4 magicValue = 0x0; + // If the external call fails, revert with the conduit's // custom error. try ConduitInterface(conduit).execute(conduitTransfers) returns ( bytes4 conduitMagicValue ) { - if ( - conduitMagicValue != ConduitInterface(conduit).execute.selector - ) { - revert InvalidMagicValue(); - } + magicValue = conduitMagicValue; } catch (bytes memory data) { // "Bubble up" the conduit's revert reason if present. if (data.length != 0) { @@ -266,6 +263,8 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + } else { + revert InvalidConduit(); } // Revert with the error reason string if present. } catch Error(string memory reason) { @@ -275,5 +274,15 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } catch Panic(uint256 errorCode) { revert ConduitErrorPanic(errorCode); } + // if (magicValue == 0x0) { + + // } + } + + function isContract(address account) internal view returns (bool) { + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + bytes32 codeHash = account.codehash; + + return (codeHash != accountHash && codeHash != 0x0); } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index a8f0de7e4..71c907efc 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -27,6 +27,8 @@ interface TransferHelperInterface { */ error InvalidMagicValue(); + error InvalidConduit(); + /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index b18d6f0f4..89fe5fd0f 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -12,26 +12,46 @@ import { ConduitController } from "../conduit/ConduitController.sol"; import { ConduitMock } from "../test/ConduitMock.sol"; -contract ConduitControllerMock is ConduitController { +contract ConduitControllerMock is ConduitControllerInterface { + // Register keys, owners, new potential owners, and channels by conduit. + mapping(address => ConduitProperties) internal _conduits; + // Set conduit creation code and runtime code hashes as immutable arguments. - bytes32 internal immutable _MOCK_CONDUIT_CREATION_CODE_HASH; - bytes32 internal immutable _MOCK_CONDUIT_RUNTIME_CODE_HASH; + bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; + bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; + /** + * @dev Initialize contract by deploying a conduit and setting the creation + * code and runtime code hashes as immutable arguments. + */ constructor() { // Derive the conduit creation code hash and set it as an immutable. - _MOCK_CONDUIT_CREATION_CODE_HASH = keccak256( - type(ConduitMock).creationCode - ); + _CONDUIT_CREATION_CODE_HASH = keccak256(type(ConduitMock).creationCode); // Deploy a conduit with the zero hash as the salt. ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }(); // Retrieve the conduit runtime code hash and set it as an immutable. - _MOCK_CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; + _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; } - function createMockConduit(bytes32 conduitKey, address initialOwner) + /** + * @notice Deploy a new conduit using a supplied conduit key and assigning + * an initial owner for the deployed conduit. Note that the first + * twenty bytes of the supplied conduit key must match the caller + * and that a new conduit cannot be created if one has already been + * deployed using the same conduit key. + * + * @param conduitKey The conduit key used to deploy the conduit. Note that + * the first twenty bytes of the conduit key must match + * the caller of this contract. + * @param initialOwner The initial owner to set for the new conduit. + * + * @return conduit The address of the newly deployed conduit. + */ + function createConduit(bytes32 conduitKey, address initialOwner) external + override returns (address conduit) { // Ensure that an initial owner has been supplied. @@ -54,7 +74,7 @@ contract ConduitControllerMock is ConduitController { bytes1(0xff), address(this), conduitKey, - _MOCK_CONDUIT_CREATION_CODE_HASH + _CONDUIT_CREATION_CODE_HASH ) ) ) @@ -62,7 +82,7 @@ contract ConduitControllerMock is ConduitController { ); // If derived conduit exists, as evidenced by comparing runtime code... - if (conduit.codehash == _MOCK_CONDUIT_RUNTIME_CODE_HASH) { + if (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH) { // Revert with an error indicating that the conduit already exists. revert ConduitAlreadyExists(conduit); } @@ -85,4 +105,419 @@ contract ConduitControllerMock is ConduitController { // Emit an event indicating that conduit ownership has been assigned. emit OwnershipTransferred(conduit, address(0), initialOwner); } + + /** + * @notice Open or close a channel on a given conduit, thereby allowing the + * specified account to execute transfers against that conduit. + * Extreme care must be taken when updating channels, as malicious + * or vulnerable channels can transfer any ERC20, ERC721 and ERC1155 + * tokens where the token holder has granted the conduit approval. + * Only the owner of the conduit in question may call this function. + * + * @param conduit The conduit for which to open or close the channel. + * @param channel The channel to open or close on the conduit. + * @param isOpen A boolean indicating whether to open or close the channel. + */ + function updateChannel( + address conduit, + address channel, + bool isOpen + ) external override { + // Ensure the caller is the current owner of the conduit in question. + _assertCallerIsConduitOwner(conduit); + + // Call the conduit, updating the channel. + ConduitInterface(conduit).updateChannel(channel, isOpen); + + // Retrieve storage region where channels for the conduit are tracked. + ConduitProperties storage conduitProperties = _conduits[conduit]; + + // Retrieve the index, if one currently exists, for the updated channel. + uint256 channelIndexPlusOne = ( + conduitProperties.channelIndexesPlusOne[channel] + ); + + // Determine whether the updated channel is already tracked as open. + bool channelPreviouslyOpen = channelIndexPlusOne != 0; + + // If the channel has been set to open and was previously closed... + if (isOpen && !channelPreviouslyOpen) { + // Add the channel to the channels array for the conduit. + conduitProperties.channels.push(channel); + + // Add new open channel length to associated mapping as index + 1. + conduitProperties.channelIndexesPlusOne[channel] = ( + conduitProperties.channels.length + ); + } else if (!isOpen && channelPreviouslyOpen) { + // Set a previously open channel as closed via "swap & pop" method. + // Decrement located index to get the index of the closed channel. + uint256 removedChannelIndex; + + // Skip underflow check as channelPreviouslyOpen being true ensures + // that channelIndexPlusOne is nonzero. + unchecked { + removedChannelIndex = channelIndexPlusOne - 1; + } + + // Use length of channels array to determine index of last channel. + uint256 finalChannelIndex = conduitProperties.channels.length - 1; + + // If closed channel is not last channel in the channels array... + if (finalChannelIndex != removedChannelIndex) { + // Retrieve the final channel and place the value on the stack. + address finalChannel = ( + conduitProperties.channels[finalChannelIndex] + ); + + // Overwrite the removed channel using the final channel value. + conduitProperties.channels[removedChannelIndex] = finalChannel; + + // Update final index in associated mapping to removed index. + conduitProperties.channelIndexesPlusOne[finalChannel] = ( + channelIndexPlusOne + ); + } + + // Remove the last channel from the channels array for the conduit. + conduitProperties.channels.pop(); + + // Remove the closed channel from associated mapping of indexes. + delete conduitProperties.channelIndexesPlusOne[channel]; + } + } + + /** + * @notice Initiate conduit ownership transfer by assigning a new potential + * owner for the given conduit. Once set, the new potential owner + * may call `acceptOwnership` to claim ownership of the conduit. + * Only the owner of the conduit in question may call this function. + * + * @param conduit The conduit for which to initiate ownership transfer. + * @param newPotentialOwner The new potential owner of the conduit. + */ + function transferOwnership(address conduit, address newPotentialOwner) + external + override + { + // Ensure the caller is the current owner of the conduit in question. + _assertCallerIsConduitOwner(conduit); + + // Ensure the new potential owner is not an invalid address. + if (newPotentialOwner == address(0)) { + revert NewPotentialOwnerIsZeroAddress(conduit); + } + + // Ensure the new potential owner is not already set. + if (newPotentialOwner == _conduits[conduit].potentialOwner) { + revert NewPotentialOwnerAlreadySet(conduit, newPotentialOwner); + } + + // Emit an event indicating that the potential owner has been updated. + emit PotentialOwnerUpdated(newPotentialOwner); + + // Set the new potential owner as the potential owner of the conduit. + _conduits[conduit].potentialOwner = newPotentialOwner; + } + + /** + * @notice Clear the currently set potential owner, if any, from a conduit. + * Only the owner of the conduit in question may call this function. + * + * @param conduit The conduit for which to cancel ownership transfer. + */ + function cancelOwnershipTransfer(address conduit) external override { + // Ensure the caller is the current owner of the conduit in question. + _assertCallerIsConduitOwner(conduit); + + // Ensure that ownership transfer is currently possible. + if (_conduits[conduit].potentialOwner == address(0)) { + revert NoPotentialOwnerCurrentlySet(conduit); + } + + // Emit an event indicating that the potential owner has been cleared. + emit PotentialOwnerUpdated(address(0)); + + // Clear the current new potential owner from the conduit. + _conduits[conduit].potentialOwner = address(0); + } + + /** + * @notice Accept ownership of a supplied conduit. Only accounts that the + * current owner has set as the new potential owner may call this + * function. + * + * @param conduit The conduit for which to accept ownership. + */ + function acceptOwnership(address conduit) external override { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // If caller does not match current potential owner of the conduit... + if (msg.sender != _conduits[conduit].potentialOwner) { + // Revert, indicating that caller is not current potential owner. + revert CallerIsNotNewPotentialOwner(conduit); + } + + // Emit an event indicating that the potential owner has been cleared. + emit PotentialOwnerUpdated(address(0)); + + // Clear the current new potential owner from the conduit. + _conduits[conduit].potentialOwner = address(0); + + // Emit an event indicating conduit ownership has been transferred. + emit OwnershipTransferred( + conduit, + _conduits[conduit].owner, + msg.sender + ); + + // Set the caller as the owner of the conduit. + _conduits[conduit].owner = msg.sender; + } + + /** + * @notice Retrieve the current owner of a deployed conduit. + * + * @param conduit The conduit for which to retrieve the associated owner. + * + * @return owner The owner of the supplied conduit. + */ + function ownerOf(address conduit) + external + view + override + returns (address owner) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve the current owner of the conduit in question. + owner = _conduits[conduit].owner; + } + + /** + * @notice Retrieve the conduit key for a deployed conduit via reverse + * lookup. + * + * @param conduit The conduit for which to retrieve the associated conduit + * key. + * + * @return conduitKey The conduit key used to deploy the supplied conduit. + */ + function getKey(address conduit) + external + view + override + returns (bytes32 conduitKey) + { + // Attempt to retrieve a conduit key for the conduit in question. + conduitKey = _conduits[conduit].key; + + // Revert if no conduit key was located. + if (conduitKey == bytes32(0)) { + revert NoConduit(); + } + } + + /** + * @notice Derive the conduit associated with a given conduit key and + * determine whether that conduit exists (i.e. whether it has been + * deployed). + * + * @param conduitKey The conduit key used to derive the conduit. + * + * @return conduit The derived address of the conduit. + * @return exists A boolean indicating whether the derived conduit has been + * deployed or not. + */ + function getConduit(bytes32 conduitKey) + external + view + override + returns (address conduit, bool exists) + { + // Derive address from deployer, conduit key and creation code hash. + conduit = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + conduitKey, + _CONDUIT_CREATION_CODE_HASH + ) + ) + ) + ) + ); + + // Determine whether conduit exists by retrieving its runtime code. + exists = (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH); + } + + /** + * @notice Retrieve the potential owner, if any, for a given conduit. The + * current owner may set a new potential owner via + * `transferOwnership` and that owner may then accept ownership of + * the conduit in question via `acceptOwnership`. + * + * @param conduit The conduit for which to retrieve the potential owner. + * + * @return potentialOwner The potential owner, if any, for the conduit. + */ + function getPotentialOwner(address conduit) + external + view + override + returns (address potentialOwner) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve the current potential owner of the conduit in question. + potentialOwner = _conduits[conduit].potentialOwner; + } + + /** + * @notice Retrieve the status (either open or closed) of a given channel on + * a conduit. + * + * @param conduit The conduit for which to retrieve the channel status. + * @param channel The channel for which to retrieve the status. + * + * @return isOpen The status of the channel on the given conduit. + */ + function getChannelStatus(address conduit, address channel) + external + view + override + returns (bool isOpen) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve the current channel status for the conduit in question. + isOpen = _conduits[conduit].channelIndexesPlusOne[channel] != 0; + } + + /** + * @notice Retrieve the total number of open channels for a given conduit. + * + * @param conduit The conduit for which to retrieve the total channel count. + * + * @return totalChannels The total number of open channels for the conduit. + */ + function getTotalChannels(address conduit) + external + view + override + returns (uint256 totalChannels) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve the total open channel count for the conduit in question. + totalChannels = _conduits[conduit].channels.length; + } + + /** + * @notice Retrieve an open channel at a specific index for a given conduit. + * Note that the index of a channel can change as a result of other + * channels being closed on the conduit. + * + * @param conduit The conduit for which to retrieve the open channel. + * @param channelIndex The index of the channel in question. + * + * @return channel The open channel, if any, at the specified channel index. + */ + function getChannel(address conduit, uint256 channelIndex) + external + view + override + returns (address channel) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve the total open channel count for the conduit in question. + uint256 totalChannels = _conduits[conduit].channels.length; + + // Ensure that the supplied index is within range. + if (channelIndex >= totalChannels) { + revert ChannelOutOfRange(conduit); + } + + // Retrieve the channel at the given index. + channel = _conduits[conduit].channels[channelIndex]; + } + + /** + * @notice Retrieve all open channels for a given conduit. Note that calling + * this function for a conduit with many channels will revert with + * an out-of-gas error. + * + * @param conduit The conduit for which to retrieve open channels. + * + * @return channels An array of open channels on the given conduit. + */ + function getChannels(address conduit) + external + view + override + returns (address[] memory channels) + { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // Retrieve all of the open channels on the conduit in question. + channels = _conduits[conduit].channels; + } + + /** + * @dev Retrieve the conduit creation code and runtime code hashes. + */ + function getConduitCodeHashes() + external + view + override + returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash) + { + // Retrieve the conduit creation code hash from runtime. + creationCodeHash = _CONDUIT_CREATION_CODE_HASH; + + // Retrieve the conduit runtime code hash from runtime. + runtimeCodeHash = _CONDUIT_RUNTIME_CODE_HASH; + } + + /** + * @dev Private view function to revert if the caller is not the owner of a + * given conduit. + * + * @param conduit The conduit for which to assert ownership. + */ + function _assertCallerIsConduitOwner(address conduit) private view { + // Ensure that the conduit in question exists. + _assertConduitExists(conduit); + + // If the caller does not match the current owner of the conduit... + if (msg.sender != _conduits[conduit].owner) { + // Revert, indicating that the caller is not the owner. + revert CallerIsNotOwner(conduit); + } + } + + /** + * @dev Private view function to revert if a given conduit does not exist. + * + * @param conduit The conduit for which to assert existence. + */ + function _assertConduitExists(address conduit) private view { + // Attempt to retrieve a conduit key for the conduit in question. + if (_conduits[conduit].key == bytes32(0)) { + // Revert if no conduit key was located. + revert NoConduit(); + } + } } diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 5687e672c..ba7690dab 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -788,6 +788,9 @@ contract TransferHelperTest is BaseOrderTest { ); vm.label(unknownConduitAddress, "unknown conduit"); + emit log_bytes32( + bytes32(ConduitInterface(unknownConduitAddress).execute.selector) + ); vm.expectRevert( abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) ); @@ -911,10 +914,7 @@ contract TransferHelperTest is BaseOrderTest { // Create the mock conduit by calling the mock conduit controller ConduitMock mockConduit = ConduitMock( - mockConduitController.createMockConduit( - conduitKeyAlice, - address(alice) - ) + mockConduitController.createConduit(conduitKeyAlice, address(alice)) ); vm.label(address(mockConduit), "mock conduit"); diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 482f00929..8f0cdcff6 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -90,11 +90,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { conduitController.address ); - await whileImpersonating(owner.address, provider, async () => { - await conduitController - .connect(owner) - .updateChannel(tempConduit.address, tempTransferHelper.address, true); - }); + await conduitController + .connect(owner) + .updateChannel(tempConduit.address, tempTransferHelper.address, true); }); it("Executes transfers (many token types) with a conduit", async () => { @@ -518,7 +516,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { recipient.address, ethers.utils.formatBytes32String("0xabc") ) - ).to.be.revertedWith("InvalidConduit"); + ).to.be.revertedWith("InvalidMagicValue"); }); it("Reverts on error in ERC721 receiver", async () => { @@ -692,41 +690,57 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } }); - it("Reverts with invalid magic value returned by call to conduit", async () => { - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const mockConduitControllerFactory = await ethers.getContractFactory( - "ConduitControllerMock" - ); - const mockConduitController = await mockConduitControllerFactory.deploy(); - - const mockConduitKey = owner.address + randomHex(12).slice(2); - - // Deploy the mock conduit through the mock conduit controller - await mockConduitController - .connect(owner) - .createMockConduit(mockConduitKey, owner.address); - - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith("InvalidMagicValue"); - }); + // it("Reverts with invalid magic value returned by call to conduit", async () => { + // // Deploy ERC20 Contract + // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // await tempERC20Contract.connect(owner).mint(sender.address, 100); + + // const mockConduitControllerFactory = await ethers.getContractFactory( + // "ConduitControllerMock" + // ); + // const mockConduitController = await mockConduitControllerFactory.deploy(); + + // const mockTransferHelperFactory = await ethers.getContractFactory( + // "TransferHelper" + // ); + // const mockTransferHelper = await mockTransferHelperFactory.deploy( + // mockConduitController.address + // ); + // const mockConduitKey = owner.address + randomHex(12).slice(2); + + // // Deploy the mock conduit through the mock conduit controller + // await mockConduitController + // .connect(owner) + // .createConduit(mockConduitKey, owner.address); + + // const mockConduitAddress = ( + // await mockConduitController.getConduit(mockConduitKey) + // )[0]; + + // await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + // console.log("mock conduit key: ", mockConduitKey); + // console.log("mock conduit address: ", mockConduitAddress); + // const transferHelperItems = [ + // { + // itemType: 1, + // token: tempERC20Contract.address, + // identifier: 0, + // amount: 10, + // }, + // { + // itemType: 1, + // token: tempERC20Contract.address, + // identifier: 0, + // amount: 20, + // }, + // ]; + + // await expect( + // mockTransferHelper + // .connect(sender) + // .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + // ).to.be.revertedWith("InvalidMagicValue"); + // }); }); diff --git a/test/utils/fixtures/conduit.ts b/test/utils/fixtures/conduit.ts index 6c6c0fa3a..b267230df 100644 --- a/test/utils/fixtures/conduit.ts +++ b/test/utils/fixtures/conduit.ts @@ -93,17 +93,15 @@ export const conduitFixture = async ( assignedConduitKey ); - await whileImpersonating(owner.address, ethers.provider, async () => { - await expect( - conduitController - .connect(owner) - .createConduit(assignedConduitKey, constants.AddressZero) - ).to.be.revertedWith("InvalidInitialOwner"); - - await conduitController + await expect( + conduitController .connect(owner) - .createConduit(assignedConduitKey, owner.address); - }); + .createConduit(assignedConduitKey, constants.AddressZero) + ).to.be.revertedWith("InvalidInitialOwner"); + + await conduitController + .connect(owner) + .createConduit(assignedConduitKey, owner.address); const tempConduit = conduitImplementation.attach(tempConduitAddress); return tempConduit; diff --git a/test/utils/fixtures/tokens.ts b/test/utils/fixtures/tokens.ts index d634d4c4f..06067f4f7 100644 --- a/test/utils/fixtures/tokens.ts +++ b/test/utils/fixtures/tokens.ts @@ -249,19 +249,15 @@ export const tokensFixture = async (signer: JsonRpcSigner | Wallet) => { await (contract as TestERC20).mint(receiver.address, amount); // Receiver approves contract to transfer tokens - await whileImpersonating( - receiver.address, - ethers.provider, - async () => { - await expect( - (contract as TestERC20) - .connect(receiver) - .approve(approvalAddress, amount) - ) - .to.emit(contract, "Approval") - .withArgs(receiver.address, approvalAddress, amount); - } - ); + async () => { + await expect( + (contract as TestERC20) + .connect(receiver) + .approve(approvalAddress, amount) + ) + .to.emit(contract, "Approval") + .withArgs(receiver.address, approvalAddress, amount); + }; break; case 2: // ERC721 case 4: // ERC721_WITH_CRITERIA From 7ac682990888b3213226ad426a3b0cb4706277ad Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 30 Jun 2022 17:03:28 -0700 Subject: [PATCH 048/126] progress --- contracts/helpers/TransferHelper.sol | 45 ++++--- .../interfaces/TransferHelperInterface.sol | 20 ++- test/foundry/TransferHelperTest.sol | 3 - test/transferhelper.spec.ts | 120 +++++++++--------- test/utils/fixtures/conduit.ts | 1 - test/utils/fixtures/tokens.ts | 17 +-- 6 files changed, 110 insertions(+), 96 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index ca28e517f..963f9f5c4 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -94,6 +94,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { else { _performTransfersWithConduit(items, recipient, conduitKey); } + // Return a magic value indicating that the transfers were performed. magicValue = this.bulkTransfer.selector; } @@ -105,14 +106,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; - // Create a boolean that reflects whether recipient is a contract. - bool recipientIsContract; - - // Check if recipient is a contract. - if (recipient.code.length != 0) { - recipientIsContract = true; - } - // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. @@ -140,7 +133,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } else if (item.itemType == ConduitItemType.ERC721) { // If recipient is a contract, ensure it can receive // ERC721 tokens. - if (recipientIsContract) { + if (_isContract(recipient)) { // Check if recipient can receive ERC721 tokens. try IERC721Receiver(recipient).onERC721Received( @@ -248,14 +241,20 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } - bytes4 magicValue = 0x0; + if (!_isContract(conduit)) { + revert InvalidConduit(conduitKey, conduit); + } // If the external call fails, revert with the conduit's // custom error. try ConduitInterface(conduit).execute(conduitTransfers) returns ( bytes4 conduitMagicValue ) { - magicValue = conduitMagicValue; + if ( + conduitMagicValue != ConduitInterface(conduit).execute.selector + ) { + revert InvalidMagicValue(); + } } catch (bytes memory data) { // "Bubble up" the conduit's revert reason if present. if (data.length != 0) { @@ -264,25 +263,29 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert(0, returndatasize()) } } else { - revert InvalidConduit(); + revert InvalidConduit(conduitKey, conduit); } - // Revert with the error reason string if present. } catch Error(string memory reason) { - revert ConduitErrorString(reason); + // Revert with the error reason string if present. + revert ConduitErrorString(reason, conduitKey, conduit); + } catch Panic(uint256 errorCode) { // Revert with the panic error code if the error was caused // by a panic. - } catch Panic(uint256 errorCode) { - revert ConduitErrorPanic(errorCode); + revert ConduitErrorPanic(errorCode, conduitKey, conduit); } - // if (magicValue == 0x0) { - - // } } - function isContract(address account) internal view returns (bool) { - bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + function _isContract(address account) internal view returns (bool) { + // This is the default codeHash for non-contract addresses. + // prettier-ignore + bytes32 accountHash = + 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + + // Get the account codeHash. bytes32 codeHash = account.codehash; + // Account is a contract if codeHash is not equal to accountHash + // and is not equal to 0 meaning it does not exist or is empty. return (codeHash != accountHash && codeHash != 0x0); } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 71c907efc..f0901d716 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -21,25 +21,37 @@ interface TransferHelperInterface { */ error InvalidERC20Identifier(); + /** + * @dev Revert with an error when attempting to fill an order referencing an + * invalid conduit (i.e. one that has not been deployed). + */ + error InvalidConduit(bytes32 conduitKey, address conduit); + /** * @dev Revert with an error when a call to a conduit returns an invalid * magic value. */ error InvalidMagicValue(); - error InvalidConduit(); - /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. */ - error ConduitErrorString(string reason); + error ConduitErrorString( + string reason, + bytes32 conduitKey, + address conduit + ); /** * @dev Revert with an error when a call to a conduit reverts with a * panic error. */ - error ConduitErrorPanic(uint256 errorCode); + error ConduitErrorPanic( + uint256 errorCode, + bytes32 conduitKey, + address conduit + ); /** * @notice Transfer multiple items to a single recipient. diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index ba7690dab..9c7026e84 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -788,9 +788,6 @@ contract TransferHelperTest is BaseOrderTest { ); vm.label(unknownConduitAddress, "unknown conduit"); - emit log_bytes32( - bytes32(ConduitInterface(unknownConduitAddress).execute.selector) - ); vm.expectRevert( abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) ); diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 8f0cdcff6..a03106d71 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -10,7 +10,7 @@ import { seaportFixture, } from "./utils/fixtures"; import { VERSION } from "./utils/helpers"; -import { faucet, whileImpersonating } from "./utils/impersonate"; +import { faucet } from "./utils/impersonate"; import type { ConduitControllerInterface, @@ -516,7 +516,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { recipient.address, ethers.utils.formatBytes32String("0xabc") ) - ).to.be.revertedWith("InvalidMagicValue"); + ).to.be.revertedWith("InvalidConduit"); }); it("Reverts on error in ERC721 receiver", async () => { @@ -650,7 +650,11 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith('ConduitErrorString("WRONG_FROM")'); + ).to.be.revertedWith( + `ConduitErrorString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); }); it("Reverts with bubbled up panic error from call to conduit", async () => { @@ -680,7 +684,11 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("ConduitErrorPanic(18)"); + ).to.be.revertedWith( + `ConduitErrorPanic(18, "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); } else { await expect( tempTransferHelper @@ -690,57 +698,55 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } }); - // it("Reverts with invalid magic value returned by call to conduit", async () => { - // // Deploy ERC20 Contract - // const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - // await tempERC20Contract.connect(owner).mint(sender.address, 100); - - // const mockConduitControllerFactory = await ethers.getContractFactory( - // "ConduitControllerMock" - // ); - // const mockConduitController = await mockConduitControllerFactory.deploy(); - - // const mockTransferHelperFactory = await ethers.getContractFactory( - // "TransferHelper" - // ); - // const mockTransferHelper = await mockTransferHelperFactory.deploy( - // mockConduitController.address - // ); - // const mockConduitKey = owner.address + randomHex(12).slice(2); - - // // Deploy the mock conduit through the mock conduit controller - // await mockConduitController - // .connect(owner) - // .createConduit(mockConduitKey, owner.address); - - // const mockConduitAddress = ( - // await mockConduitController.getConduit(mockConduitKey) - // )[0]; - - // await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - - // console.log("mock conduit key: ", mockConduitKey); - // console.log("mock conduit address: ", mockConduitAddress); - // const transferHelperItems = [ - // { - // itemType: 1, - // token: tempERC20Contract.address, - // identifier: 0, - // amount: 10, - // }, - // { - // itemType: 1, - // token: tempERC20Contract.address, - // identifier: 0, - // amount: 20, - // }, - // ]; - - // await expect( - // mockTransferHelper - // .connect(sender) - // .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - // ).to.be.revertedWith("InvalidMagicValue"); - // }); + it("Reverts with invalid magic value returned by call to conduit", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + await tempERC20Contract.connect(owner).mint(sender.address, 100); + + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy(); + + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith("InvalidMagicValue"); + }); }); diff --git a/test/utils/fixtures/conduit.ts b/test/utils/fixtures/conduit.ts index b267230df..8d71384c5 100644 --- a/test/utils/fixtures/conduit.ts +++ b/test/utils/fixtures/conduit.ts @@ -5,7 +5,6 @@ import hre, { ethers } from "hardhat"; import { deployContract } from "../contracts"; import { randomHex } from "../encoding"; -import { whileImpersonating } from "../impersonate"; import type { ConduitControllerInterface, diff --git a/test/utils/fixtures/tokens.ts b/test/utils/fixtures/tokens.ts index 06067f4f7..84da3a942 100644 --- a/test/utils/fixtures/tokens.ts +++ b/test/utils/fixtures/tokens.ts @@ -8,7 +8,6 @@ import { randomBN, toBN, } from "../encoding"; -import { whileImpersonating } from "../impersonate"; import type { TestERC1155, @@ -249,15 +248,13 @@ export const tokensFixture = async (signer: JsonRpcSigner | Wallet) => { await (contract as TestERC20).mint(receiver.address, amount); // Receiver approves contract to transfer tokens - async () => { - await expect( - (contract as TestERC20) - .connect(receiver) - .approve(approvalAddress, amount) - ) - .to.emit(contract, "Approval") - .withArgs(receiver.address, approvalAddress, amount); - }; + await expect( + (contract as TestERC20) + .connect(receiver) + .approve(approvalAddress, amount) + ) + .to.emit(contract, "Approval") + .withArgs(receiver.address, approvalAddress, amount); break; case 2: // ERC721 case 4: // ERC721_WITH_CRITERIA From fac65b255759c3dc92c4ce9088cd2e9c8bf4611e Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 30 Jun 2022 17:25:48 -0700 Subject: [PATCH 049/126] add conduitKey and conduit to error InvalidMagicValue --- config/.solcover-reference.js | 2 ++ contracts/helpers/TransferHelper.sol | 2 +- contracts/interfaces/TransferHelperInterface.sol | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/.solcover-reference.js b/config/.solcover-reference.js index 80b256384..8f2aa5bae 100644 --- a/config/.solcover-reference.js +++ b/config/.solcover-reference.js @@ -32,5 +32,7 @@ module.exports = { "test/TestERC20Revert.sol", "test/InvalidERC721Recipient.sol", "test/ERC721ReceiverMock.sol", + "test/ConduitControllerMock.sol", + "test/ConduitMock.sol", ], }; diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 963f9f5c4..13bc7bb52 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -253,7 +253,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { if ( conduitMagicValue != ConduitInterface(conduit).execute.selector ) { - revert InvalidMagicValue(); + revert InvalidMagicValue(conduitKey, conduit); } } catch (bytes memory data) { // "Bubble up" the conduit's revert reason if present. diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index f0901d716..7d3cad8f2 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -31,7 +31,7 @@ interface TransferHelperInterface { * @dev Revert with an error when a call to a conduit returns an invalid * magic value. */ - error InvalidMagicValue(); + error InvalidMagicValue(bytes32 conduitKey, address conduit); /** * @dev Revert with an error when a call to a conduit reverts with a From 5ccad99b57f860aa22c4f040df9ca574a5633bc0 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 30 Jun 2022 17:35:43 -0700 Subject: [PATCH 050/126] fix ref test run --- contracts/test/ConduitMock.sol | 16 ++++++++-------- reference/shim/Shim.sol | 2 ++ test/utils/fixtures/conduit.ts | 18 +++++++++++++----- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 9d775b0c7..76f531977 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -16,25 +16,25 @@ import { contract ConduitMock is ConduitInterface { constructor() {} - function execute(ConduitTransfer[] calldata transfers) - external + function execute(ConduitTransfer[] calldata /* transfers */) + external pure override returns (bytes4 magicValue) { return 0xabc42069; } function executeBatch1155( - ConduitBatch1155Transfer[] calldata batch1155Transfers - ) external returns (bytes4 magicValue) { + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external pure override returns (bytes4 magicValue) { return 0xabc69420; } function executeWithBatch1155( - ConduitTransfer[] calldata standardTransfers, - ConduitBatch1155Transfer[] calldata batch1155Transfers - ) external returns (bytes4 magicValue) { + ConduitTransfer[] calldata /* standardTransfers */, + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external pure override returns (bytes4 magicValue) { return 0x42069420; } - function updateChannel(address channel, bool isOpen) external {} + function updateChannel(address channel, bool isOpen) external override {} } diff --git a/reference/shim/Shim.sol b/reference/shim/Shim.sol index 4c0b59abf..7b017de06 100644 --- a/reference/shim/Shim.sol +++ b/reference/shim/Shim.sol @@ -16,6 +16,8 @@ import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; import { InvalidERC721Recipient } from "contracts/test/InvalidERC721Recipient.sol"; import { ERC721ReceiverMock } from "contracts/test/ERC721ReceiverMock.sol"; import { TestERC20Panic } from "contracts/test/TestERC20Panic.sol"; +import { ConduitControllerMock } from "contracts/test/ConduitControllerMock.sol"; +import { ConduitMock } from "contracts/test/ConduitMock.sol"; // prettier-ignore import { diff --git a/test/utils/fixtures/conduit.ts b/test/utils/fixtures/conduit.ts index 8d71384c5..b359c9d07 100644 --- a/test/utils/fixtures/conduit.ts +++ b/test/utils/fixtures/conduit.ts @@ -92,11 +92,19 @@ export const conduitFixture = async ( assignedConduitKey ); - await expect( - conduitController - .connect(owner) - .createConduit(assignedConduitKey, constants.AddressZero) - ).to.be.revertedWith("InvalidInitialOwner"); + if (!process.env.REFERENCE) { + await expect( + conduitController + .connect(owner) + .createConduit(assignedConduitKey, constants.AddressZero) + ).to.be.revertedWith("InvalidInitialOwner"); + } else { + await expect( + conduitController + .connect(owner) + .createConduit(assignedConduitKey, constants.AddressZero) + ).to.be.reverted; + } await conduitController .connect(owner) From b6cbafc8295e5fe6a501510bf212388f676751ca Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 30 Jun 2022 18:16:51 -0700 Subject: [PATCH 051/126] add ConduitErrorGenericRevert when revert with no reason data --- contracts/helpers/TransferHelper.sol | 2 +- .../interfaces/TransferHelperInterface.sol | 6 +++ contracts/test/ConduitMock.sol | 16 ++++-- contracts/test/TestERC20Revert.sol | 2 +- test/transferhelper.spec.ts | 50 +++++++++++++++++++ 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 13bc7bb52..06de2eced 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -263,7 +263,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert(0, returndatasize()) } } else { - revert InvalidConduit(conduitKey, conduit); + revert ConduitErrorGenericRevert(conduitKey, conduit); } } catch Error(string memory reason) { // Revert with the error reason string if present. diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 7d3cad8f2..46129d968 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -33,6 +33,12 @@ interface TransferHelperInterface { */ error InvalidMagicValue(bytes32 conduitKey, address conduit); + /** + * @dev Revert with a generic error when a call to a conduit reverts with + * no data about the reason. + */ + error ConduitErrorGenericRevert(bytes32 conduitKey, address conduit); + /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 76f531977..0e3f40d4d 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -5,8 +5,6 @@ import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; -import { Conduit } from "../conduit/Conduit.sol"; - // prettier-ignore import { ConduitTransfer, @@ -16,10 +14,18 @@ import { contract ConduitMock is ConduitInterface { constructor() {} - function execute(ConduitTransfer[] calldata /* transfers */) - external pure override + function execute(ConduitTransfer[] calldata transfers) + external + pure + override returns (bytes4 magicValue) { + // To test for more coverage paths, if transfers.length > 10, + // then revert with empty reason. + if (transfers.length > 10) { + revert(); + } + // Otherwise, we will return an invalid magic value. return 0xabc42069; } @@ -30,7 +36,7 @@ contract ConduitMock is ConduitInterface { } function executeWithBatch1155( - ConduitTransfer[] calldata /* standardTransfers */, + ConduitTransfer[] calldata, /* standardTransfers */ ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ ) external pure override returns (bytes4 magicValue) { return 0x42069420; diff --git a/contracts/test/TestERC20Revert.sol b/contracts/test/TestERC20Revert.sol index b7dddfa1d..ef6019010 100644 --- a/contracts/test/TestERC20Revert.sol +++ b/contracts/test/TestERC20Revert.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.7; import "@rari-capital/solmate/src/tokens/ERC20.sol"; -contract TestERC20Revert is ERC20("TestPanic", "PANIC", 18) { +contract TestERC20Revert is ERC20("TestRevert", "REVERT", 18) { function mint(address to, uint256 amount) external { _mint(to, amount); } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index a03106d71..1f8154c6e 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -657,6 +657,56 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); }); + it("Reverts when no revert string is returned from call to conduit", async () => { + // Deploy ERC1155 Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + + await tempERC1155Contract.connect(owner).mint(sender.address, 0, 100); + + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy(); + + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + + await tempERC1155Contract + .connect(sender) + .setApprovalForAll(mockConduitAddress, true); + + // Transfer 11 items to hit the special branch logic in ConduitMock + // for empty revert when transfers.length > 10. + const transferHelperItems = Array.from(Array(11)).map(() => ({ + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + })); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorGenericRevert("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); + it("Reverts with bubbled up panic error from call to conduit", async () => { // Deploy mock ERC20 const mockERC20PanicFactory = await ethers.getContractFactory( From e67458cbb3197cc5a318bb54e1e9f894f8b805f1 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 30 Jun 2022 18:24:43 -0700 Subject: [PATCH 052/126] add back recipientIsContract for cached value use throughout for loop --- contracts/helpers/TransferHelper.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 06de2eced..a3204735f 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -106,6 +106,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; + // Create a boolean that reflects whether recipient is a contract. + bool recipientIsContract = _isContract(recipient); + // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. @@ -133,7 +136,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } else if (item.itemType == ConduitItemType.ERC721) { // If recipient is a contract, ensure it can receive // ERC721 tokens. - if (_isContract(recipient)) { + if (recipientIsContract) { // Check if recipient can receive ERC721 tokens. try IERC721Receiver(recipient).onERC721Received( From 8a1f0c9ebdfa06deeea6b963e4c33fd69313dd5c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 30 Jun 2022 22:04:47 -0400 Subject: [PATCH 053/126] fix forge tests to include new errors --- test/foundry/TransferHelperTest.sol | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 9c7026e84..c32906a50 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -789,7 +789,11 @@ contract TransferHelperTest is BaseOrderTest { vm.label(unknownConduitAddress, "unknown conduit"); vm.expectRevert( - abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) + abi.encodeWithSignature( + "InvalidConduit(bytes32,address)", + fuzzConduitKey, + unknownConduitAddress + ) ); vm.prank(alice); transferHelper.bulkTransfer(items, bob, conduitKeyOne); @@ -847,6 +851,7 @@ contract TransferHelperTest is BaseOrderTest { 1 ); + (address conduit, ) = conduitController.getConduit(conduitKeyOne); // Attempt to transfer ERC721 tokens from bob to alice // Expect revert since alice owns the tokens _performSingleItemTransferAndCheckBalances( @@ -854,7 +859,12 @@ contract TransferHelperTest is BaseOrderTest { bob, alice, true, - abi.encodeWithSignature("ConduitErrorString(string)", "WRONG_FROM") + abi.encodeWithSignature( + "ConduitErrorString(string,bytes32,address)", + "WRONG_FROM", + conduitKeyOne, + conduit + ) ); } @@ -877,13 +887,19 @@ contract TransferHelperTest is BaseOrderTest { 10 ); + (address conduit, ) = conduitController.getConduit(conduitKeyOne); // Revert with panic error when calling execute via conduit _performSingleItemTransferAndCheckBalances( item, alice, bob, true, - abi.encodeWithSignature("ConduitErrorPanic(uint256)", 18) + abi.encodeWithSignature( + "ConduitErrorPanic(uint256,bytes32,address)", + 18, + conduitKeyOne, + conduit + ) ); } @@ -931,8 +947,13 @@ contract TransferHelperTest is BaseOrderTest { 1 ); + (address conduit, ) = conduitController.getConduit(conduitKeyOne); vm.expectRevert( - abi.encodePacked(TransferHelperInterface.InvalidMagicValue.selector) + abi.encodeWithSignature( + "InvalidMagicValue(bytes32,address)", + mockConduitKey, + mockConduit + ) ); mockTransferHelper.bulkTransfer(items, bob, mockConduitKey); vm.stopPrank(); From 322e4263ebd1de09e90a7fb09817eac3f072d5f8 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 10:41:48 -0400 Subject: [PATCH 054/126] add inline comments --- contracts/helpers/TransferHelper.sol | 52 ++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index a3204735f..cc424992c 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -72,7 +72,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } /** - * @notice Transfer multiple items to a single recipient. + * @notice Transfer multiple items to a single recipient by calling one of + * two internal functions, depending on whether a conduit key is + * passed into the function. * * @param items The items to transfer. * @param recipient The address the items should be transferred to. @@ -99,6 +101,13 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { magicValue = this.bulkTransfer.selector; } + /** + * @notice Perform multiple transfers to a single recipient via + * TokenTransferrer. + * + * @param items The items to transfer. + * @param recipient The address the items should be transferred to. + */ function _performTransfersWithoutConduit( TransferHelperItem[] calldata items, address recipient @@ -156,13 +165,15 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert InvalidERC721Recipient(); } } catch (bytes memory data) { - // Bubble up recipient's revert reason + // "Bubble up" recipient's revert reason // if present. if (data.length != 0) { assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + // Revert with a generic error if no + // revert reason is given by the recipient. } else { revert InvalidERC721Recipient(); } @@ -194,6 +205,15 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } + /** + * @notice Perform multiple transfers to a single recipient via + * the conduit derived from the provided conduit key. + * + * @param items The items to transfer. + * @param recipient The address the items should be transferred to. + * @param conduitKey The conduit key referring to the conduit through + * which the bulk transfer should occur. + */ function _performTransfersWithConduit( TransferHelperItem[] calldata items, address recipient, @@ -244,20 +264,27 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } + // Check if the derived conduit is a contract. if (!_isContract(conduit)) { + // If conduit is not a contract, revert with the passed in + // conduit key and derived conduit address. revert InvalidConduit(conduitKey, conduit); } - // If the external call fails, revert with the conduit's - // custom error. + // Attempt the external call to transfer tokens via the derived conduit. try ConduitInterface(conduit).execute(conduitTransfers) returns ( bytes4 conduitMagicValue ) { + // Check if the value returned from the external call matches + // the conduit `execute` selector. if ( conduitMagicValue != ConduitInterface(conduit).execute.selector ) { + // If the external call fails, revert with the conduit key + // and conduit address. revert InvalidMagicValue(conduitKey, conduit); } + // Catch reverts from the external call to the conduit. } catch (bytes memory data) { // "Bubble up" the conduit's revert reason if present. if (data.length != 0) { @@ -265,19 +292,30 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + // If no revert reason is present, revert with a generic error + // with the conduit key and conduit address. } else { revert ConduitErrorGenericRevert(conduitKey, conduit); } + // Catch reverts with a provided reason string. } catch Error(string memory reason) { - // Revert with the error reason string if present. + // Revert with the error reason string, conduit key and + // conduit address. revert ConduitErrorString(reason, conduitKey, conduit); + // Catch reverts caused by a panic. } catch Panic(uint256 errorCode) { - // Revert with the panic error code if the error was caused - // by a panic. + // Revert with the panic error code, conduit key and + // conduit address. revert ConduitErrorPanic(errorCode, conduitKey, conduit); } } + /** + * @notice Check if the passed-in address refers to a contract by comparing + * its codeHash to the default codeHash for non-contract addresses. + * + * @param account The account to be checked. + */ function _isContract(address account) internal view returns (bool) { // This is the default codeHash for non-contract addresses. // prettier-ignore From d91bbc44bed2eef43709f12248b2ba85fc3cd638 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 11:27:16 -0400 Subject: [PATCH 055/126] move inline comments --- contracts/helpers/TransferHelper.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index cc424992c..d59c4f958 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -172,9 +172,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + } else { // Revert with a generic error if no // revert reason is given by the recipient. - } else { revert InvalidERC721Recipient(); } } @@ -292,9 +292,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + } else { // If no revert reason is present, revert with a generic error // with the conduit key and conduit address. - } else { revert ConduitErrorGenericRevert(conduitKey, conduit); } // Catch reverts with a provided reason string. From 5b08806382c1dfdd15bf9a121adaf8b0ee2129dc Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 11:37:35 -0400 Subject: [PATCH 056/126] move comments --- contracts/helpers/TransferHelper.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index d59c4f958..0271c1fe8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -91,9 +91,8 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // If no conduitKey is given, use TokenTransferrer to perform transfers. if (conduitKey == bytes32(0)) { _performTransfersWithoutConduit(items, recipient); - } - // Otherwise, a conduitKey was provided. - else { + } else { + // Otherwise, a conduitKey was provided. _performTransfersWithConduit(items, recipient, conduitKey); } From a8975e71284f91aeb39b8e8ebf99b5a89d1db95c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 16:09:05 -0400 Subject: [PATCH 057/126] move comments and add branch coverage tests --- contracts/helpers/TransferHelper.sol | 22 ++++++------ test/foundry/TransferHelperTest.sol | 40 +++++++++++++++++++++ test/transferhelper.spec.ts | 53 +++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 0271c1fe8..261f6ded7 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -125,10 +125,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { TransferHelperItem calldata item = items[i]; // Perform a transfer based on the transfer's item type. - // Revert if item being transferred is a native token. - if (item.itemType == ConduitItemType.NATIVE) { - revert InvalidItemType(); - } else if (item.itemType == ConduitItemType.ERC20) { + if (item.itemType == ConduitItemType.ERC20) { // Ensure that the identifier for an ERC20 token is 0. if (item.identifier != 0) { revert InvalidERC20Identifier(); @@ -199,6 +196,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { item.identifier, item.amount ); + } else { + // Revert if the item being transferred is a native token. + revert InvalidItemType(); } } } @@ -283,9 +283,9 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // and conduit address. revert InvalidMagicValue(conduitKey, conduit); } - // Catch reverts from the external call to the conduit. } catch (bytes memory data) { - // "Bubble up" the conduit's revert reason if present. + // Catch reverts from the external call to the conduit and + // "bubble up" the conduit's revert reason if present. if (data.length != 0) { assembly { returndatacopy(0, 0, returndatasize()) @@ -296,15 +296,13 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // with the conduit key and conduit address. revert ConduitErrorGenericRevert(conduitKey, conduit); } - // Catch reverts with a provided reason string. } catch Error(string memory reason) { - // Revert with the error reason string, conduit key and - // conduit address. + // Catch reverts with a provided reason string and + // revert with the reason, conduit key and conduit address. revert ConduitErrorString(reason, conduitKey, conduit); - // Catch reverts caused by a panic. } catch Panic(uint256 errorCode) { - // Revert with the panic error code, conduit key and - // conduit address. + // Catch reverts caused by a panic and revert with the + // panic error code, conduit key and conduit address. revert ConduitErrorPanic(errorCode, conduitKey, conduit); } } diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index c32906a50..6c91cba08 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -14,7 +14,9 @@ import { TransferHelper } from "../../contracts/helpers/TransferHelper.sol"; import { TransferHelperItem } from "../../contracts/helpers/TransferHelperStructs.sol"; import { TestERC20 } from "../../contracts/test/TestERC20.sol"; + import { TestERC721 } from "../../contracts/test/TestERC721.sol"; + import { TestERC1155 } from "../../contracts/test/TestERC1155.sol"; import { ConduitMock } from "../../contracts/test/ConduitMock.sol"; @@ -31,6 +33,15 @@ import { ERC721ReceiverMock } from "../../contracts/test/ERC721ReceiverMock.sol" import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; +interface IERC721Receiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} + contract TransferHelperTest is BaseOrderTest { TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. @@ -573,6 +584,35 @@ contract TransferHelperTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances(item, alice, bob, false, ""); } + function testBulkTransferERC721ToContractRecipientNotUsingConduit( + FuzzInputsCommon memory inputs + ) public { + ERC721ReceiverMock erc721Receiver = new ERC721ReceiverMock( + IERC721Receiver.onERC721Received.selector, + ERC721ReceiverMock.Error.None + ); + + uint256 numItems = 6; + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[i], + i + ); + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + address(erc721Receiver), + false, + "" + ); + } + function testBulkTransferERC721AndERC20NotUsingConduit( FuzzInputsCommon memory inputs ) public { diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 1f8154c6e..369b4b994 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -245,7 +245,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { erc20Transfers[i] = erc20Transfer; } - // Create numEC721s amount of ERC20 objects + // Create numEC721s amount of ERC721 objects for (let i = 0; i < numEC721s; i++) { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); @@ -333,6 +333,57 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } }); + it("Executes ERC721 transfers to a contract recipient without a conduit", async () => { + // Deploy recipient contract + const erc721RecipientFactory = await ethers.getContractFactory( + "ERC721ReceiverMock.sol" + ); + const erc721Recipient = await erc721RecipientFactory.deploy( + Buffer.from("abcd0000", "hex"), + 0 + ); + + const erc721Contracts = []; + const erc721Transfers = []; + + // Create 5 ERC721 objects + for (let i = 0; i < 5; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721Transfers, + erc721Recipient.address, + ethers.utils.formatBytes32String("") + ); + + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < 5; i++) { + // Get identifier and ERC721 token contract + const { identifier } = erc721Transfers[i]; + const token = erc721Contracts[i]; + + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(erc721Recipient.address); + } + }); + it("Reverts on native token transfers", async () => { const ethTransferHelperItems = [ { From 3691cd8aa401610c24f4c559430b876ed0dcef55 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 16:14:23 -0400 Subject: [PATCH 058/126] fix typo --- test/transferhelper.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 369b4b994..68f461685 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -336,7 +336,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { it("Executes ERC721 transfers to a contract recipient without a conduit", async () => { // Deploy recipient contract const erc721RecipientFactory = await ethers.getContractFactory( - "ERC721ReceiverMock.sol" + "ERC721ReceiverMock" ); const erc721Recipient = await erc721RecipientFactory.deploy( Buffer.from("abcd0000", "hex"), From a2afe34578b8041d305788cb143c731221c42bd7 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 1 Jul 2022 16:31:05 -0400 Subject: [PATCH 059/126] fix typo --- test/transferhelper.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 68f461685..3862d4488 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -339,7 +339,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { "ERC721ReceiverMock" ); const erc721Recipient = await erc721RecipientFactory.deploy( - Buffer.from("abcd0000", "hex"), + Buffer.from("150b7a02", "hex"), 0 ); From 045c46f8dba77c209be1bddc0f532ba602d5405b Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 5 Jul 2022 18:43:44 -0700 Subject: [PATCH 060/126] remove now-unneeded prettier-ignore --- contracts/helpers/TransferHelper.sol | 1 - contracts/test/ConduitControllerMock.sol | 3 +-- contracts/test/ConduitMock.sol | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 103a7d8a0..1c6b3221a 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -5,7 +5,6 @@ import "./TransferHelperStructs.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; -// prettier-ignore import { TokenTransferrerErrors } from "../interfaces/TokenTransferrerErrors.sol"; diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index 89fe5fd0f..b696816c6 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; -// prettier-ignore import { - ConduitControllerInterface + ConduitControllerInterface } from "../interfaces/ConduitControllerInterface.sol"; import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 0e3f40d4d..60badc0fb 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -5,7 +5,6 @@ import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; -// prettier-ignore import { ConduitTransfer, ConduitBatch1155Transfer From bf39a50162fa04f95389195ffd524e813baea492 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 7 Jul 2022 11:42:33 -0400 Subject: [PATCH 061/126] remove _isContract check on conduit, add IERC721Receiver --- contracts/helpers/TransferHelper.sol | 48 +++--------------------- contracts/interfaces/IERC721Receiver.sol | 11 ++++++ test/foundry/TransferHelperTest.sol | 2 +- 3 files changed, 18 insertions(+), 43 deletions(-) create mode 100644 contracts/interfaces/IERC721Receiver.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 1c6b3221a..707d36394 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; +import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol"; + import "./TransferHelperStructs.sol"; import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; @@ -23,15 +25,6 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; -interface IERC721Receiver { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external returns (bytes4); -} - /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio @@ -112,7 +105,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { uint256 totalTransfers = items.length; // Create a boolean that reflects whether recipient is a contract. - bool recipientIsContract = _isContract(recipient); + bool recipientIsContract = (recipient.code.length != 0); // Skip overflow checks: all for loops are indexed starting at zero. unchecked { @@ -260,13 +253,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } - // Check if the derived conduit is a contract. - if (!_isContract(conduit)) { - // If conduit is not a contract, revert with the passed in - // conduit key and derived conduit address. - revert InvalidConduit(conduitKey, conduit); - } - // Attempt the external call to transfer tokens via the derived conduit. try ConduitInterface(conduit).execute(conduitTransfers) returns ( bytes4 conduitMagicValue @@ -278,7 +264,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) { // If the external call fails, revert with the conduit key // and conduit address. - revert InvalidMagicValue(conduitKey, conduit); + revert InvalidConduit(conduitKey, conduit); } } catch (bytes memory data) { // Catch reverts from the external call to the conduit and @@ -288,6 +274,8 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } + } else if (data.length > 256) { + revert InvalidConduit(conduitKey, conduit); } else { // If no revert reason is present, revert with a generic error // with the conduit key and conduit address. @@ -297,30 +285,6 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { // Catch reverts with a provided reason string and // revert with the reason, conduit key and conduit address. revert ConduitErrorString(reason, conduitKey, conduit); - } catch Panic(uint256 errorCode) { - // Catch reverts caused by a panic and revert with the - // panic error code, conduit key and conduit address. - revert ConduitErrorPanic(errorCode, conduitKey, conduit); } } - - /** - * @notice Check if the passed-in address refers to a contract by comparing - * its codeHash to the default codeHash for non-contract addresses. - * - * @param account The account to be checked. - */ - function _isContract(address account) internal view returns (bool) { - // This is the default codeHash for non-contract addresses. - // prettier-ignore - bytes32 accountHash = - 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - - // Get the account codeHash. - bytes32 codeHash = account.codehash; - - // Account is a contract if codeHash is not equal to accountHash - // and is not equal to 0 meaning it does not exist or is empty. - return (codeHash != accountHash && codeHash != 0x0); - } } diff --git a/contracts/interfaces/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol new file mode 100644 index 000000000..d21578c43 --- /dev/null +++ b/contracts/interfaces/IERC721Receiver.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +interface IERC721Receiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4); +} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 5c25c2122..08f89eab7 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -1004,7 +1004,7 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, ) = conduitController.getConduit(conduitKeyOne); vm.expectRevert( abi.encodeWithSignature( - "InvalidMagicValue(bytes32,address)", + "InvalidConduit(bytes32,address)", mockConduitKey, mockConduit ) From f4c3c7580122b8d474aee26b162a38cf99e8d80f Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 7 Jul 2022 12:21:48 -0400 Subject: [PATCH 062/126] modify tests --- contracts/helpers/TransferHelper.sol | 2 +- test/transferhelper.spec.ts | 28 +++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 707d36394..2ce062a5c 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -275,7 +275,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { revert(0, returndatasize()) } } else if (data.length > 256) { - revert InvalidConduit(conduitKey, conduit); + revert ConduitErrorGenericRevert(conduitKey, conduit); } else { // If no revert reason is present, revert with a generic error // with the conduit key and conduit address. diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 3862d4488..068167531 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -567,7 +567,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { recipient.address, ethers.utils.formatBytes32String("0xabc") ) - ).to.be.revertedWith("InvalidConduit"); + ).to.be.reverted; }); it("Reverts on error in ERC721 receiver", async () => { @@ -780,23 +780,11 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { }, ]; - if (!process.env.REFERENCE) { - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith( - `ConduitErrorPanic(18, "${tempConduitKey.toLowerCase()}", "${ - tempConduit.address - }")` - ); - } else { - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.reverted; - } + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.reverted; }); it("Reverts with invalid magic value returned by call to conduit", async () => { @@ -848,6 +836,8 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { mockTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith("InvalidMagicValue"); + ).to.be.revertedWith( + `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); }); }); From b0a0402d09f73a3fba381e094cfbf1ef186ee71c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 8 Jul 2022 10:39:27 -0400 Subject: [PATCH 063/126] progress on tests --- contracts/helpers/TransferHelper.sol | 21 ++++-- .../interfaces/TransferHelperInterface.sol | 11 +++ contracts/test/ConduitControllerMock.sol | 7 +- contracts/test/ConduitMock.sol | 63 +++++++++++----- test/foundry/TransferHelperTest.sol | 42 ++++++----- test/transferhelper.spec.ts | 71 +++++++++++++++++-- 6 files changed, 167 insertions(+), 48 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 2ce062a5c..2d36f6e9f 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -101,11 +101,16 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { TransferHelperItem[] calldata items, address recipient ) internal { + // Ensure tokens aren't transferred to the zero address. + if (recipient == address(0x0)) { + revert RecipientCannotBeZero(); + } + // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; // Create a boolean that reflects whether recipient is a contract. - bool recipientIsContract = (recipient.code.length != 0); + bool recipientIsContract = recipient.code.length != 0; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { @@ -208,6 +213,11 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { address recipient, bytes32 conduitKey ) internal { + // Ensure tokens aren't transferred to the zero address. + if (recipient == address(0x0)) { + revert RecipientCannotBeZero(); + } + // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; @@ -269,16 +279,15 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } catch (bytes memory data) { // Catch reverts from the external call to the conduit and // "bubble up" the conduit's revert reason if present. - if (data.length != 0) { + if (data.length < 256) { assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } - } else if (data.length > 256) { - revert ConduitErrorGenericRevert(conduitKey, conduit); } else { - // If no revert reason is present, revert with a generic error - // with the conduit key and conduit address. + // If no revert reason is present or data length is too large, + // revert with a generic error with the conduit key and + // conduit address. revert ConduitErrorGenericRevert(conduitKey, conduit); } } catch Error(string memory reason) { diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 46129d968..2cfea53ff 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -16,11 +16,22 @@ interface TransferHelperInterface { */ error InvalidERC721Recipient(); + /** + * @dev Revert with an error when attempting to execute an ERC1155 transfer + to an invalid recipient. + */ + error InvalidERC1155Recipient(); + /** * @dev Revert with an error when an ERC20 token has an invalid identifier. */ error InvalidERC20Identifier(); + /** + * @dev Revert with an error if the recipient is the zero address. + */ + error RecipientCannotBeZero(); + /** * @dev Revert with an error when attempting to fill an order referencing an * invalid conduit (i.e. one that has not been deployed). diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index b696816c6..a9fedbe0a 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -28,7 +28,10 @@ contract ConduitControllerMock is ConduitControllerInterface { _CONDUIT_CREATION_CODE_HASH = keccak256(type(ConduitMock).creationCode); // Deploy a conduit with the zero hash as the salt. - ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }(); + ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }( + 0x4ce34aa2, + ConduitMock.Error(0) + ); // Retrieve the conduit runtime code hash and set it as an immutable. _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; @@ -87,7 +90,7 @@ contract ConduitControllerMock is ConduitControllerInterface { } // Deploy the conduit via CREATE2 using the conduit key as the salt. - new ConduitMock{ salt: conduitKey }(); + new ConduitMock{ salt: conduitKey }(0x4ce34aa2, ConduitMock.Error(0)); // Initialize storage variable referencing conduit properties. ConduitProperties storage conduitProperties = _conduits[conduit]; diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 60badc0fb..3d3398338 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -11,34 +11,61 @@ import { } from "../conduit/lib/ConduitStructs.sol"; contract ConduitMock is ConduitInterface { - constructor() {} - - function execute(ConduitTransfer[] calldata transfers) - external - pure - override - returns (bytes4 magicValue) - { - // To test for more coverage paths, if transfers.length > 10, - // then revert with empty reason. - if (transfers.length > 10) { - revert(); + enum Error { + None, + RevertWithNoErrorString, + RevertWithDataLengthOver256, + InvalidMagicValue + } + + bytes4 private immutable _retval; + Error private immutable _error; + + constructor(bytes4 retval, Error error) { + _retval = retval; + _error = error; + } + + function execute( + ConduitTransfer[] calldata /* transfers */ + ) external view override returns (bytes4) { + if (_error == Error.RevertWithNoErrorString) { + revert InvalidController(); + } else if (_error == Error.RevertWithDataLengthOver256) { + revert InvalidController(); + } else if (_error == Error.InvalidMagicValue) { + return 0xabcd0000; } - // Otherwise, we will return an invalid magic value. - return 0xabc42069; + + // Otherwise, we will return the valid magic value. + return _retval; } function executeBatch1155( ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external pure override returns (bytes4 magicValue) { - return 0xabc69420; + ) external view override returns (bytes4 magicValue) { + if (_error == Error.RevertWithNoErrorString) { + revert(); + } else if (_error == Error.RevertWithDataLengthOver256) { + revert( + "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" + ); + } + return _retval; } function executeWithBatch1155( ConduitTransfer[] calldata, /* standardTransfers */ ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external pure override returns (bytes4 magicValue) { - return 0x42069420; + ) external view override returns (bytes4 magicValue) { + if (_error == Error.RevertWithNoErrorString) { + revert(); + } else if (_error == Error.RevertWithDataLengthOver256) { + revert( + "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" + ); + } + return _retval; } function updateChannel(address channel, bool isOpen) external override {} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 08f89eab7..b1b5349b2 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -842,13 +842,7 @@ contract TransferHelperTest is BaseOrderTest { ); vm.label(unknownConduitAddress, "unknown conduit"); - vm.expectRevert( - abi.encodeWithSignature( - "InvalidConduit(bytes32,address)", - fuzzConduitKey, - unknownConduitAddress - ) - ); + vm.expectRevert(); vm.prank(alice); transferHelper.bulkTransfer(items, bob, conduitKeyOne); } @@ -948,12 +942,7 @@ contract TransferHelperTest is BaseOrderTest { alice, bob, true, - abi.encodeWithSignature( - "ConduitErrorPanic(uint256,bytes32,address)", - 18, - conduitKeyOne, - conduit - ) + abi.encodePacked("Division or modulo by 0") ); } @@ -985,12 +974,29 @@ contract TransferHelperTest is BaseOrderTest { ); vm.label(address(mockConduit), "mock conduit"); + ConduitMock aliceConduit = new ConduitMock{ salt: conduitKeyAlice }( + 0x4ce34aa2, + ConduitMock.Error(0) + ); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + bytes32 aliceConduitCodeHash = address(aliceConduit).codehash; + emit log_named_bytes32("alice conduit code hash", aliceConduitCodeHash); + + ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }( + 0x4ce34aa2, + ConduitMock.Error(0) + ); + bytes32 zeroConduitCodeHash = address(zeroConduit).codehash; + emit log_named_bytes32("zero conduit code hash", zeroConduitCodeHash); + // Assert the conduit key derived from the conduit address // matches alice's conduit key bytes32 mockConduitKey = mockConduitController.getKey( address(mockConduit) ); - assertEq(conduitKeyAlice, mockConduitKey); // Create item to transfer TransferHelperItem[] memory items = new TransferHelperItem[](1); @@ -1001,15 +1007,17 @@ contract TransferHelperTest is BaseOrderTest { 1 ); - (address conduit, ) = conduitController.getConduit(conduitKeyOne); + (address conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); vm.expectRevert( abi.encodeWithSignature( "InvalidConduit(bytes32,address)", - mockConduitKey, + conduitKeyAlice, mockConduit ) ); - mockTransferHelper.bulkTransfer(items, bob, mockConduitKey); + mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); vm.stopPrank(); } } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 068167531..a5a2b203b 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import { randomHex } from "./utils/encoding"; +import { randomHex, toBN } from "./utils/encoding"; import { fixtureERC1155, fixtureERC20, @@ -21,6 +21,7 @@ import type { } from "../typechain-types"; import type { SeaportFixtures } from "./utils/fixtures"; import type { Wallet } from "ethers"; +import { V4MAPPED } from "dns"; describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const { provider } = ethers; @@ -717,7 +718,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const mockConduitControllerFactory = await ethers.getContractFactory( "ConduitControllerMock" ); - const mockConduitController = await mockConduitControllerFactory.deploy(); + const mockConduitController = await mockConduitControllerFactory.deploy( + toBN(1) + ); const mockTransferHelperFactory = await ethers.getContractFactory( "TransferHelper" @@ -740,8 +743,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .setApprovalForAll(mockConduitAddress, true); - // Transfer 11 items to hit the special branch logic in ConduitMock - // for empty revert when transfers.length > 10. const transferHelperItems = Array.from(Array(11)).map(() => ({ itemType: 3, token: tempERC1155Contract.address, @@ -796,7 +797,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const mockConduitControllerFactory = await ethers.getContractFactory( "ConduitControllerMock" ); - const mockConduitController = await mockConduitControllerFactory.deploy(); + const mockConduitController = await mockConduitControllerFactory.deploy( + toBN(3) + ); const mockTransferHelperFactory = await ethers.getContractFactory( "TransferHelper" @@ -840,4 +843,62 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); + + it("Reverts with generic revert when revert data length > 256", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + await tempERC20Contract.connect(owner).mint(sender.address, 100); + + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + toBN(2) + ); + + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + console.log("conduit key: ", mockConduitKey); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + console.log("mock conduit address: ", mockConduitAddress); + + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorGenericRevert("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); }); From 300f95eec4e2a0331b8ed05aac4cb9c03a526688 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 8 Jul 2022 18:31:53 -0400 Subject: [PATCH 064/126] add mock conduits, modify try catch, add tests --- contracts/helpers/TransferHelper.sol | 17 +- .../interfaces/TransferHelperInterface.sol | 12 +- contracts/test/ConduitControllerMock.sol | 84 ++++++-- contracts/test/ConduitMock.sol | 49 +---- contracts/test/ConduitMockErrors.sol | 6 + contracts/test/ConduitMockInvalidMagic.sol | 32 +++ contracts/test/ConduitMockRevertBytes.sol | 52 +++++ .../ConduitMockRevertDataLengthTooLong.sol | 40 ++++ contracts/test/ConduitMockRevertNoReason.sol | 33 +++ test/foundry/TransferHelperTest.sol | 198 ++++++++++++++++-- test/transferhelper.spec.ts | 61 +++++- 11 files changed, 502 insertions(+), 82 deletions(-) create mode 100644 contracts/test/ConduitMockErrors.sol create mode 100644 contracts/test/ConduitMockInvalidMagic.sol create mode 100644 contracts/test/ConduitMockRevertBytes.sol create mode 100644 contracts/test/ConduitMockRevertDataLengthTooLong.sol create mode 100644 contracts/test/ConduitMockRevertNoReason.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 2d36f6e9f..3bf76c4e5 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -25,6 +25,8 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; +import "hardhat/console.sol"; + /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio @@ -279,21 +281,22 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } catch (bytes memory data) { // Catch reverts from the external call to the conduit and // "bubble up" the conduit's revert reason if present. - if (data.length < 256) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } + if (data.length != 0 && data.length < 256) { + revert ConduitErrorRevertBytes(data, conduitKey, conduit); } else { // If no revert reason is present or data length is too large, // revert with a generic error with the conduit key and // conduit address. - revert ConduitErrorGenericRevert(conduitKey, conduit); + revert ConduitErrorRevertGeneric(conduitKey, conduit); } } catch Error(string memory reason) { // Catch reverts with a provided reason string and // revert with the reason, conduit key and conduit address. - revert ConduitErrorString(reason, conduitKey, conduit); + if (bytes(reason).length != 0 && bytes(reason).length < 256) { + revert ConduitErrorRevertString(reason, conduitKey, conduit); + } else { + revert ConduitErrorRevertGeneric(conduitKey, conduit); + } } } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 2cfea53ff..d8d9d8c21 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -48,23 +48,29 @@ interface TransferHelperInterface { * @dev Revert with a generic error when a call to a conduit reverts with * no data about the reason. */ - error ConduitErrorGenericRevert(bytes32 conduitKey, address conduit); + error ConduitErrorRevertGeneric(bytes32 conduitKey, address conduit); /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. */ - error ConduitErrorString( + error ConduitErrorRevertString( string reason, bytes32 conduitKey, address conduit ); + error ConduitErrorRevertBytes( + bytes reason, + bytes32 conduitKey, + address conduit + ); + /** * @dev Revert with an error when a call to a conduit reverts with a * panic error. */ - error ConduitErrorPanic( + error ConduitErrorRevertPanic( uint256 errorCode, bytes32 conduitKey, address conduit diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index a9fedbe0a..93d362f81 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -11,6 +11,18 @@ import { ConduitController } from "../conduit/ConduitController.sol"; import { ConduitMock } from "../test/ConduitMock.sol"; +import { ConduitMockInvalidMagic } from "../test/ConduitMockInvalidMagic.sol"; + +import { + ConduitMockRevertDataLengthTooLong +} from "../test/ConduitMockRevertDataLengthTooLong.sol"; + +import { + ConduitMockRevertNoReason +} from "../test/ConduitMockRevertNoReason.sol"; + +import { ConduitMockRevertBytes } from "../test/ConduitMockRevertBytes.sol"; + contract ConduitControllerMock is ConduitControllerInterface { // Register keys, owners, new potential owners, and channels by conduit. mapping(address => ConduitProperties) internal _conduits; @@ -19,22 +31,57 @@ contract ConduitControllerMock is ConduitControllerInterface { bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; + uint256 private immutable conduitNum; + /** * @dev Initialize contract by deploying a conduit and setting the creation * code and runtime code hashes as immutable arguments. */ - constructor() { - // Derive the conduit creation code hash and set it as an immutable. - _CONDUIT_CREATION_CODE_HASH = keccak256(type(ConduitMock).creationCode); - - // Deploy a conduit with the zero hash as the salt. - ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }( - 0x4ce34aa2, - ConduitMock.Error(0) - ); - - // Retrieve the conduit runtime code hash and set it as an immutable. - _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; + constructor(uint256 _conduitNum) { + conduitNum = _conduitNum; + + bytes32 creationCodeHash; + bytes32 runtimeCodeHash; + + if (conduitNum == 0) { + creationCodeHash = keccak256(type(ConduitMock).creationCode); + ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }(); + runtimeCodeHash = address(zeroConduit).codehash; + } else if (conduitNum == 1) { + creationCodeHash = keccak256( + type(ConduitMockRevertNoReason).creationCode + ); + ConduitMockRevertNoReason zeroConduit = new ConduitMockRevertNoReason{ + salt: bytes32(0) + }(); + runtimeCodeHash = address(zeroConduit).codehash; + } else if (conduitNum == 2) { + creationCodeHash = keccak256( + type(ConduitMockRevertDataLengthTooLong).creationCode + ); + ConduitMockRevertDataLengthTooLong zeroConduit = new ConduitMockRevertDataLengthTooLong{ + salt: bytes32(0) + }(); + runtimeCodeHash = address(zeroConduit).codehash; + } else if (conduitNum == 3) { + creationCodeHash = keccak256( + type(ConduitMockInvalidMagic).creationCode + ); + ConduitMockInvalidMagic zeroConduit = new ConduitMockInvalidMagic{ + salt: bytes32(0) + }(); + runtimeCodeHash = address(zeroConduit).codehash; + } else if (conduitNum == 4) { + creationCodeHash = keccak256( + type(ConduitMockRevertBytes).creationCode + ); + ConduitMockRevertBytes zeroConduit = new ConduitMockRevertBytes{ + salt: bytes32(0) + }(); + runtimeCodeHash = address(zeroConduit).codehash; + } + _CONDUIT_CREATION_CODE_HASH = creationCodeHash; + _CONDUIT_RUNTIME_CODE_HASH = runtimeCodeHash; } /** @@ -90,8 +137,17 @@ contract ConduitControllerMock is ConduitControllerInterface { } // Deploy the conduit via CREATE2 using the conduit key as the salt. - new ConduitMock{ salt: conduitKey }(0x4ce34aa2, ConduitMock.Error(0)); - + if (conduitNum == 0) { + new ConduitMock{ salt: conduitKey }(); + } else if (conduitNum == 1) { + new ConduitMockRevertNoReason{ salt: conduitKey }(); + } else if (conduitNum == 2) { + new ConduitMockRevertDataLengthTooLong{ salt: conduitKey }(); + } else if (conduitNum == 3) { + new ConduitMockInvalidMagic{ salt: conduitKey }(); + } else if (conduitNum == 4) { + new ConduitMockRevertBytes{ salt: conduitKey }(); + } // Initialize storage variable referencing conduit properties. ConduitProperties storage conduitProperties = _conduits[conduit]; diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index 3d3398338..a9c1e4247 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -11,62 +11,23 @@ import { } from "../conduit/lib/ConduitStructs.sol"; contract ConduitMock is ConduitInterface { - enum Error { - None, - RevertWithNoErrorString, - RevertWithDataLengthOver256, - InvalidMagicValue - } - - bytes4 private immutable _retval; - Error private immutable _error; - - constructor(bytes4 retval, Error error) { - _retval = retval; - _error = error; - } + constructor() {} function execute( ConduitTransfer[] calldata /* transfers */ ) external view override returns (bytes4) { - if (_error == Error.RevertWithNoErrorString) { - revert InvalidController(); - } else if (_error == Error.RevertWithDataLengthOver256) { - revert InvalidController(); - } else if (_error == Error.InvalidMagicValue) { - return 0xabcd0000; - } - - // Otherwise, we will return the valid magic value. - return _retval; + // Return the valid magic value. + return 0x4ce34aa2; } function executeBatch1155( ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external view override returns (bytes4 magicValue) { - if (_error == Error.RevertWithNoErrorString) { - revert(); - } else if (_error == Error.RevertWithDataLengthOver256) { - revert( - "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" - ); - } - return _retval; - } + ) external view override returns (bytes4 magicValue) {} function executeWithBatch1155( ConduitTransfer[] calldata, /* standardTransfers */ ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external view override returns (bytes4 magicValue) { - if (_error == Error.RevertWithNoErrorString) { - revert(); - } else if (_error == Error.RevertWithDataLengthOver256) { - revert( - "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" - ); - } - return _retval; - } + ) external view override returns (bytes4 magicValue) {} function updateChannel(address channel, bool isOpen) external override {} } diff --git a/contracts/test/ConduitMockErrors.sol b/contracts/test/ConduitMockErrors.sol new file mode 100644 index 000000000..49c84e6b8 --- /dev/null +++ b/contracts/test/ConduitMockErrors.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +interface ConduitMockErrors { + error RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert(); +} diff --git a/contracts/test/ConduitMockInvalidMagic.sol b/contracts/test/ConduitMockInvalidMagic.sol new file mode 100644 index 000000000..e806f86f7 --- /dev/null +++ b/contracts/test/ConduitMockInvalidMagic.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; + +import { + ConduitTransfer, + ConduitBatch1155Transfer +} from "../conduit/lib/ConduitStructs.sol"; + +contract ConduitMockInvalidMagic is ConduitInterface { + constructor() {} + + function execute( + ConduitTransfer[] calldata /* transfers */ + ) external view override returns (bytes4) { + return 0xabcd0000; + } + + function executeBatch1155( + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function executeWithBatch1155( + ConduitTransfer[] calldata, /* standardTransfers */ + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function updateChannel(address channel, bool isOpen) external override {} +} diff --git a/contracts/test/ConduitMockRevertBytes.sol b/contracts/test/ConduitMockRevertBytes.sol new file mode 100644 index 000000000..47584dfd6 --- /dev/null +++ b/contracts/test/ConduitMockRevertBytes.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; + +import { + ConduitTransfer, + ConduitBatch1155Transfer +} from "../conduit/lib/ConduitStructs.sol"; + +import "hardhat/console.sol"; + +contract ConduitMockRevertBytes is ConduitInterface { + constructor() {} + + error CustomError(); + + function execute( + ConduitTransfer[] calldata /* transfers */ + ) external view override returns (bytes4) { + // Revert with data.length != 0 && data.length < 256. + // bytes memory revertData = "36e5236fcd4c61044949678014f0d085"; + // if (revertData.length != 32) { + // revert("Incorrect length"); + // } + // bytes memory revertDataStringBytes = abi.encode(string(revertData)); + // uint256 stringLength = revertDataStringBytes.length; + + // assembly { + // revert(add(0x20, revertDataStringBytes), stringLength) + // } + // assembly { + // let pointer := mload(0x40) + // mstore(pointer, "36e5236fcd4c61044949678014f0d085") + // revert(pointer, 32) + // } + revert CustomError(); + } + + function executeBatch1155( + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function executeWithBatch1155( + ConduitTransfer[] calldata, /* standardTransfers */ + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function updateChannel(address channel, bool isOpen) external override {} +} diff --git a/contracts/test/ConduitMockRevertDataLengthTooLong.sol b/contracts/test/ConduitMockRevertDataLengthTooLong.sol new file mode 100644 index 000000000..c0fbb2166 --- /dev/null +++ b/contracts/test/ConduitMockRevertDataLengthTooLong.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; + +import { + ConduitTransfer, + ConduitBatch1155Transfer +} from "../conduit/lib/ConduitStructs.sol"; + +import { ConduitMockErrors } from "./ConduitMockErrors.sol"; + +contract ConduitMockRevertDataLengthTooLong is + ConduitMockErrors, + ConduitInterface +{ + constructor() {} + + function execute( + ConduitTransfer[] calldata /* transfers */ + ) external view override returns (bytes4) { + // Revert with data length > 256. + revert( + "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" + ); + } + + function executeBatch1155( + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function executeWithBatch1155( + ConduitTransfer[] calldata, /* standardTransfers */ + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function updateChannel(address channel, bool isOpen) external override {} +} diff --git a/contracts/test/ConduitMockRevertNoReason.sol b/contracts/test/ConduitMockRevertNoReason.sol new file mode 100644 index 000000000..c797ba2ba --- /dev/null +++ b/contracts/test/ConduitMockRevertNoReason.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; + +import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; + +import { + ConduitTransfer, + ConduitBatch1155Transfer +} from "../conduit/lib/ConduitStructs.sol"; + +contract ConduitMockRevertNoReason is ConduitInterface { + constructor() {} + + function execute( + ConduitTransfer[] calldata /* transfers */ + ) external view override returns (bytes4) { + // Revert without reason string. + revert(); + } + + function executeBatch1155( + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function executeWithBatch1155( + ConduitTransfer[] calldata, /* standardTransfers */ + ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ + ) external view override returns (bytes4 magicValue) {} + + function updateChannel(address channel, bool isOpen) external override {} +} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index b1b5349b2..4160e1e9f 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -25,6 +25,17 @@ import { TestERC1155 } from "../../contracts/test/TestERC1155.sol"; import { ConduitMock } from "../../contracts/test/ConduitMock.sol"; +import { + ConduitMockInvalidMagic +} from "../../contracts/test/ConduitMockInvalidMagic.sol"; + +import { + ConduitMockRevertDataLengthTooLong +} from "../../contracts/test/ConduitMockRevertDataLengthTooLong.sol"; + +import { + ConduitMockRevertNoReason +} from "../../contracts/test/ConduitMockRevertNoReason.sol"; import { ConduitControllerMock } from "../../contracts/test/ConduitControllerMock.sol"; @@ -950,7 +961,9 @@ contract TransferHelperTest is BaseOrderTest { public { // Deploy mock conduit controller - ConduitControllerMock mockConduitController = new ConduitControllerMock(); + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 3 + ); // Create conduit key using alice's address bytes32 conduitKeyAlice = bytes32( @@ -969,28 +982,133 @@ contract TransferHelperTest is BaseOrderTest { vm.startPrank(alice); // Create the mock conduit by calling the mock conduit controller - ConduitMock mockConduit = ConduitMock( + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( mockConduitController.createConduit(conduitKeyAlice, address(alice)) ); vm.label(address(mockConduit), "mock conduit"); - ConduitMock aliceConduit = new ConduitMock{ salt: conduitKeyAlice }( - 0x4ce34aa2, - ConduitMock.Error(0) + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + + // Create item to transfer + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 + ); + + (address conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + vm.expectRevert( + abi.encodeWithSignature( + "InvalidConduit(bytes32,address)", + conduitKeyAlice, + mockConduit + ) + ); + mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + vm.stopPrank(); + } + + function testRevertNoErrorString() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 1 + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 ); + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + bytes32 conduitCodeHash = address(mockConduit).codehash; emit log_named_bytes32("conduit code hash", conduitCodeHash); - bytes32 aliceConduitCodeHash = address(aliceConduit).codehash; - emit log_named_bytes32("alice conduit code hash", aliceConduitCodeHash); + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); - ConduitMock zeroConduit = new ConduitMock{ salt: bytes32(0) }( - 0x4ce34aa2, - ConduitMock.Error(0) + // Create item to transfer + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 ); - bytes32 zeroConduitCodeHash = address(zeroConduit).codehash; - emit log_named_bytes32("zero conduit code hash", zeroConduitCodeHash); + + (address conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + vm.expectRevert( + abi.encodeWithSignature( + "ConduitErrorGenericRevert(bytes32,address)", + conduitKeyAlice, + mockConduit + ) + ); + mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + vm.stopPrank(); + } + + function testRevertDataLengthTooLong() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 2 + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 + ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); // Assert the conduit key derived from the conduit address // matches alice's conduit key @@ -1012,7 +1130,7 @@ contract TransferHelperTest is BaseOrderTest { ); vm.expectRevert( abi.encodeWithSignature( - "InvalidConduit(bytes32,address)", + "ConduitErrorGenericRevert(bytes32,address)", conduitKeyAlice, mockConduit ) @@ -1020,4 +1138,58 @@ contract TransferHelperTest is BaseOrderTest { mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); vm.stopPrank(); } + + function testRevertWithData() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 4 + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 + ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + + // Create item to transfer + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 + ); + + (address conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + vm.expectRevert(abi.encode("CustomError()")); + mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + vm.stopPrank(); + } } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index a5a2b203b..fb589e4eb 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -21,7 +21,6 @@ import type { } from "../typechain-types"; import type { SeaportFixtures } from "./utils/fixtures"; import type { Wallet } from "ethers"; -import { V4MAPPED } from "dns"; describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const { provider } = ethers; @@ -901,4 +900,64 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { `ConduitErrorGenericRevert("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); + + it("Reverts when data length is greater than 0 and less than 256", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + await tempERC20Contract.connect(owner).mint(sender.address, 100); + + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + toBN(4) + ); + + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + console.log("conduit key: ", mockConduitKey); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + console.log("mock conduit address: ", mockConduitAddress); + + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + const customErrorSelector = ethers.utils.id("CustomError()").slice(0, 10); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); }); From 43513fec461081997d6c522070681161520900c2 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 09:20:09 -0700 Subject: [PATCH 065/126] fix HH tests --- contracts/helpers/TransferHelper.sol | 2 -- test/foundry/TransferHelperTest.sol | 2 +- test/transferhelper.spec.ts | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 3bf76c4e5..0cae40df8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -25,8 +25,6 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; -import "hardhat/console.sol"; - /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 4160e1e9f..1b2ca0c45 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -1188,7 +1188,7 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, bool exists) = mockConduitController.getConduit( conduitKeyAlice ); - vm.expectRevert(abi.encode("CustomError()")); + vm.expectRevert(abi.encodeWithSignature("CustomError()")); mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); vm.stopPrank(); } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index fb589e4eb..906452ded 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -702,7 +702,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) ).to.be.revertedWith( - `ConduitErrorString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ + `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address }")` ); @@ -754,7 +754,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) ).to.be.revertedWith( - `ConduitErrorGenericRevert("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + `ConduitErrorRevertGeneric("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); @@ -897,7 +897,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) ).to.be.revertedWith( - `ConduitErrorGenericRevert("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + `ConduitErrorRevertGeneric("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); From 7602d9be199ba31972ebc36ade1b24f760ac2a38 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 12:06:28 -0700 Subject: [PATCH 066/126] update forge tests with new error handling --- test/foundry/TransferHelperTest.sol | 242 +++++++++++++++++++++++----- test/transferhelper.spec.ts | 86 ++++++++++ 2 files changed, 290 insertions(+), 38 deletions(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 1b2ca0c45..dccdf8a0c 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -306,6 +306,99 @@ contract TransferHelperTest is BaseOrderTest { vm.stopPrank(); } + function _performMultiItemTransferAndCheckBalances( + TransferHelperItem[] memory items, + address from, + address to, + bool useConduit, + bytes memory expectRevertDataWithConduit, + bytes memory expectRevertDataWithoutConduit + ) public { + vm.startPrank(from); + + // Get balances before transfer + FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from, + to + ); + } + + // Register expected revert if present. + if ( + // Compare hashes as we cannot directly compare bytes memory with bytes storage. + (keccak256(expectRevertDataWithConduit) == + keccak256(REVERT_DATA_NO_MSG) && + useConduit) || + (keccak256(expectRevertDataWithoutConduit) == + keccak256(REVERT_DATA_NO_MSG) && + !useConduit) + ) { + vm.expectRevert(); + } else if (expectRevertDataWithConduit.length > 0 && useConduit) { + vm.expectRevert(expectRevertDataWithConduit); + } else if (expectRevertDataWithoutConduit.length > 0 && !useConduit) { + vm.expectRevert(expectRevertDataWithoutConduit); + } + // Perform transfer. + transferHelper.bulkTransfer( + items, + to, + useConduit ? conduitKeyOne : bytes32(0) + ); + + // Get balances after transfer + FromToBalance[] memory afterTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + afterTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from, + to + ); + } + + if ( + (expectRevertDataWithConduit.length > 0) || + (expectRevertDataWithoutConduit.length > 0) + ) { + // If revert is expected, balances should not have changed. + for (uint256 i = 0; i < items.length; i++) { + assert( + beforeTransferBalances[i].from == + afterTransferBalances[i].from + ); + assert( + beforeTransferBalances[i].to == afterTransferBalances[i].to + ); + } + return; + } + + // Check after transfer balances are as expected by calculating difference against before transfer balances. + for (uint256 i = 0; i < items.length; i++) { + // ERC721 balance should only ever change by amount 1. + uint256 amount = items[i].itemType == ConduitItemType.ERC721 + ? 1 + : items[i].amount; + assertEq( + afterTransferBalances[i].from, + beforeTransferBalances[i].from - amount + ); + assertEq( + afterTransferBalances[i].to, + beforeTransferBalances[i].to + amount + ); + } + + vm.stopPrank(); + } + function _getFuzzedTransferItem( ConduitItemType itemType, uint256 fuzzAmount, @@ -364,6 +457,13 @@ contract TransferHelperTest is BaseOrderTest { return item; } + function getSelector(bytes calldata returnData) + public + returns (bytes memory) + { + return returnData[0x84:0x88]; + } + // Test successful transfers function testBulkTransferERC20(FuzzInputsCommon memory inputs) public { @@ -710,18 +810,32 @@ contract TransferHelperTest is BaseOrderTest { function testRevertBulkTransferETHonly(FuzzInputsCommon memory inputs) public { - TransferHelperItem memory item = _getFuzzedTransferItem( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = _getFuzzedTransferItem( ConduitItemType.NATIVE, inputs.amounts[0], inputs.tokenIndex[0], inputs.identifiers[0] ); - _performSingleItemTransferAndCheckBalances( - item, + bytes memory returnedData; + try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + bytes4 magicValue + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + + _performMultiItemTransferAndCheckBalances( + items, alice, bob, inputs.useConduit, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ), abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) ); } @@ -743,11 +857,24 @@ contract TransferHelperTest is BaseOrderTest { inputs.identifiers[1] ); + bytes memory returnedData; + try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + bytes4 magicValue + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + _performMultiItemTransferAndCheckBalances( items, alice, bob, inputs.useConduit, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ), abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) ); } @@ -758,6 +885,7 @@ contract TransferHelperTest is BaseOrderTest { ) public { vm.assume(invalidAmount > 1); + TransferHelperItem[] memory items = new TransferHelperItem[](1); TransferHelperItem memory item = _getFuzzedERC721TransferItemWithAmountGreaterThan1( invalidAmount, @@ -765,13 +893,23 @@ contract TransferHelperTest is BaseOrderTest { inputs.identifiers[0] ); + items[0] = item; + bytes memory returnedData; + try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + bytes4 magicValue + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } _performSingleItemTransferAndCheckBalances( item, alice, bob, true, - abi.encodePacked( - TokenTransferrerErrors.InvalidERC721TransferAmount.selector + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit ) ); } @@ -795,13 +933,23 @@ contract TransferHelperTest is BaseOrderTest { inputs.identifiers[1] ); + bytes memory returnedData; + try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + bytes4 magicValue + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + _performMultiItemTransferAndCheckBalances( items, alice, bob, true, - abi.encodePacked( - TokenTransferrerErrors.InvalidERC721TransferAmount.selector + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit ) ); } @@ -809,21 +957,36 @@ contract TransferHelperTest is BaseOrderTest { function testRevertBulkTransferNotOpenConduitChannel( FuzzInputsCommon memory inputs ) public { - TransferHelperItem memory item = _getFuzzedTransferItem( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = _getFuzzedTransferItem( ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], inputs.identifiers[0] ); + _updateConduitChannel(false); + + // try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + // bytes4 magicValue + // ) {} catch (bytes memory reason) { + // returnedData = this.getSelector(reason); + // } + bytes memory returnedData = abi.encodeWithSelector( + 0x93daadf2, + 0x6b8E18793B5630b0d439F957f610B01219110940 + ); + _performSingleItemTransferAndCheckBalances( - item, + items[0], alice, bob, true, - abi.encodeWithSelector( - ConduitInterface.ChannelClosed.selector, - address(transferHelper) + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit ) ); } @@ -882,24 +1045,6 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertInvalidItemWithConduit(FuzzInputsCommon memory inputs) - public - { - TransferHelperItem memory invalidItem = _getFuzzedTransferItem( - ConduitItemType.NATIVE, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0] - ); - _performSingleItemTransferAndCheckBalances( - invalidItem, - alice, - bob, - true, - abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) - ); - } - function testRevertStringErrorWithConduit(FuzzInputsCommon memory inputs) public { @@ -919,7 +1064,7 @@ contract TransferHelperTest is BaseOrderTest { alice, true, abi.encodeWithSignature( - "ConduitErrorString(string,bytes32,address)", + "ConduitErrorRevertString(string,bytes32,address)", "WRONG_FROM", conduitKeyOne, conduit @@ -939,7 +1084,8 @@ contract TransferHelperTest is BaseOrderTest { // Approve the ERC20 tokens panicERC20.approve(alice, 10); - TransferHelperItem memory item = TransferHelperItem( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( ConduitItemType.ERC20, address(panicERC20), 0, @@ -947,13 +1093,20 @@ contract TransferHelperTest is BaseOrderTest { ); (address conduit, ) = conduitController.getConduit(conduitKeyOne); + bytes memory panicError = abi.encodeWithSelector(0x4e487b71, 18); + // Revert with panic error when calling execute via conduit - _performSingleItemTransferAndCheckBalances( - item, + _performMultiItemTransferAndCheckBalances( + items, alice, bob, true, - abi.encodePacked("Division or modulo by 0") + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + panicError, + conduitKeyOne, + conduit + ) ); } @@ -1070,7 +1223,7 @@ contract TransferHelperTest is BaseOrderTest { ); vm.expectRevert( abi.encodeWithSignature( - "ConduitErrorGenericRevert(bytes32,address)", + "ConduitErrorRevertGeneric(bytes32,address)", conduitKeyAlice, mockConduit ) @@ -1130,7 +1283,7 @@ contract TransferHelperTest is BaseOrderTest { ); vm.expectRevert( abi.encodeWithSignature( - "ConduitErrorGenericRevert(bytes32,address)", + "ConduitErrorRevertGeneric(bytes32,address)", conduitKeyAlice, mockConduit ) @@ -1188,7 +1341,20 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, bool exists) = mockConduitController.getConduit( conduitKeyAlice ); - vm.expectRevert(abi.encodeWithSignature("CustomError()")); + bytes memory returnedData; + try + mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice) + returns (bytes4 magicValue) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + vm.expectRevert( + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyAlice, + mockConduit + ) + ); mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); vm.stopPrank(); } diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 906452ded..8e65d9dd3 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -960,4 +960,90 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); + + it("Reverts when recipient is the null address (with conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + ethers.constants.AddressZero, + tempConduitKey + ) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); + + it("Reverts when recipient is the null address (without conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + ethers.constants.AddressZero, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); }); From 6a3d07657c617e6e5457b69c8b038f1729d14f61 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 12:08:59 -0700 Subject: [PATCH 067/126] remove console import --- contracts/test/ConduitMockRevertBytes.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/test/ConduitMockRevertBytes.sol b/contracts/test/ConduitMockRevertBytes.sol index 47584dfd6..d5b64c906 100644 --- a/contracts/test/ConduitMockRevertBytes.sol +++ b/contracts/test/ConduitMockRevertBytes.sol @@ -10,8 +10,6 @@ import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol"; -import "hardhat/console.sol"; - contract ConduitMockRevertBytes is ConduitInterface { constructor() {} From df117c96a43d28c929130b3565ae6320c559409e Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 12:11:24 -0700 Subject: [PATCH 068/126] remove state var immutability --- contracts/test/ConduitControllerMock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index 93d362f81..9b18e9b03 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -31,7 +31,7 @@ contract ConduitControllerMock is ConduitControllerInterface { bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; - uint256 private immutable conduitNum; + uint256 private conduitNum; /** * @dev Initialize contract by deploying a conduit and setting the creation From 65d1c30fde74673a7800c3fdeb3a134737933f3c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 12:25:56 -0700 Subject: [PATCH 069/126] fix hh test --- test/transferhelper.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 8e65d9dd3..d47318288 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -656,11 +656,19 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { }, ]; + const invalidItemTypeErrorSelector = ethers.utils + .id("InvalidItemType()") + .slice(0, 10); + await expect( tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith("InvalidItemType"); + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); }); it("Reverts with bubbled up string error from call to conduit", async () => { From af5ba7c49086c2de9c25b8b549990edd7294251c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 13:32:35 -0700 Subject: [PATCH 070/126] add console import --- contracts/helpers/TransferHelper.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 0cae40df8..26815fdc8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -25,6 +25,8 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; +import "hardhat/console.sol"; + /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio @@ -158,7 +160,7 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } catch (bytes memory data) { // "Bubble up" recipient's revert reason // if present. - if (data.length != 0) { + if (data.length != 0 && data.length < 256) { assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) From 4dfb23c6c95e50f7fb0f89b2f157b8c70bf4761e Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 14:37:14 -0700 Subject: [PATCH 071/126] update interface, remove print lines from tests --- contracts/helpers/TransferHelper.sol | 2 -- contracts/interfaces/TransferHelperInterface.sol | 12 ------------ test/transferhelper.spec.ts | 5 ----- 3 files changed, 19 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 26815fdc8..6a04a6a5e 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -25,8 +25,6 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; -import "hardhat/console.sol"; - /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index d8d9d8c21..d5205a0cd 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -16,12 +16,6 @@ interface TransferHelperInterface { */ error InvalidERC721Recipient(); - /** - * @dev Revert with an error when attempting to execute an ERC1155 transfer - to an invalid recipient. - */ - error InvalidERC1155Recipient(); - /** * @dev Revert with an error when an ERC20 token has an invalid identifier. */ @@ -38,12 +32,6 @@ interface TransferHelperInterface { */ error InvalidConduit(bytes32 conduitKey, address conduit); - /** - * @dev Revert with an error when a call to a conduit returns an invalid - * magic value. - */ - error InvalidMagicValue(bytes32 conduitKey, address conduit); - /** * @dev Revert with a generic error when a call to a conduit reverts with * no data about the reason. diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index d47318288..2a6f02fe2 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -871,7 +871,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { mockConduitController.address ); const mockConduitKey = owner.address + randomHex(12).slice(2); - console.log("conduit key: ", mockConduitKey); // Deploy the mock conduit through the mock conduit controller await mockConduitController @@ -881,7 +880,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const mockConduitAddress = ( await mockConduitController.getConduit(mockConduitKey) )[0]; - console.log("mock conduit address: ", mockConduitAddress); await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); @@ -929,7 +927,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { mockConduitController.address ); const mockConduitKey = owner.address + randomHex(12).slice(2); - console.log("conduit key: ", mockConduitKey); // Deploy the mock conduit through the mock conduit controller await mockConduitController @@ -939,8 +936,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const mockConduitAddress = ( await mockConduitController.getConduit(mockConduitKey) )[0]; - console.log("mock conduit address: ", mockConduitAddress); - await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); const transferHelperItems = [ From d730b15e126caa7f321a7894c80bfb97a20cd0df Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:45:48 -0400 Subject: [PATCH 072/126] update `.solcover.js` --- config/.solcover.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/.solcover.js b/config/.solcover.js index a69c4322a..ab2b194ee 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -36,6 +36,11 @@ module.exports = { "test/ERC721ReceiverMock.sol", "test/ConduitControllerMock.sol", "test/ConduitMock.sol", + "test/ConduitMockErrors.sol", + "test/ConduitMockInvalidMagic.sol", + "test/ConduitMockRevertBytes.sol", + "test/ConduitMockRevertDataLengthTooLong.sol", + "test/ConduitMockRevertNoReason.sol", ], configureYulOptimizer: true, solcOptimizerDetails: { From 6b1d9aae817a24851d1122b5760f648421836842 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 11 Jul 2022 17:32:48 -0700 Subject: [PATCH 073/126] add transfers to multiple recipients, internal fns, update interface --- contracts/helpers/TransferHelper.sol | 213 ++++++++++++++++++ contracts/helpers/TransferHelperStructs.sol | 8 + .../interfaces/TransferHelperInterface.sol | 10 +- 3 files changed, 230 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 6a04a6a5e..89d8252bc 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -90,6 +90,15 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { magicValue = this.bulkTransfer.selector; } + function bulkTransferToMultipleRecipients( + TransferHelperItemWithRecipient[] calldata items, + bytes32 conduitKey + ) external override returns (bytes4 magicValue) { + if (conduitKey == bytes32(0)) { + _performTransfersWithoutConduit(items); + } + } + /** * @notice Perform multiple transfers to a single recipient via * TokenTransferrer. @@ -199,6 +208,113 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } + /** + * @notice Perform multiple transfers to individually-specified recipients + * via TokenTransferrer. + * + * @param items The items to transfer. + */ + function _performTransfersWithoutConduit( + TransferHelperItemWithRecipient[] calldata items + ) internal { + // Retrieve total number of transfers and place on stack. + uint256 totalTransfers = items.length; + + // Skip overflow checks: all for loops are indexed starting at zero. + unchecked { + // Iterate over each transfer. + for (uint256 i = 0; i < totalTransfers; ++i) { + // Retrieve the transfer in question. + TransferHelperItemWithRecipient calldata item = items[i]; + + // Ensure tokens aren't transferred to the zero address. + if (item.recipient == address(0x0)) { + revert RecipientCannotBeZero(); + } + + // Create a boolean that reflects whether recipient is a contract. + bool recipientIsContract = item.recipient.code.length != 0; + + // Perform a transfer based on the transfer's item type. + if (item.itemType == ConduitItemType.ERC20) { + // Ensure that the identifier for an ERC20 token is 0. + if (item.identifier != 0) { + revert InvalidERC20Identifier(); + } + + // Transfer ERC20 token. + _performERC20Transfer( + item.token, + msg.sender, + item.recipient, + item.amount + ); + } else if (item.itemType == ConduitItemType.ERC721) { + // If recipient is a contract, ensure it can receive + // ERC721 tokens. + if (recipientIsContract) { + // Check if recipient can receive ERC721 tokens. + try + IERC721Receiver(item.recipient).onERC721Received( + address(this), + msg.sender, + item.identifier, + "" + ) + returns (bytes4 selector) { + // Check if onERC721Received selector is valid. + if ( + selector != + IERC721Receiver.onERC721Received.selector + ) { + // Revert if recipient cannot accept + // ERC721 tokens. + revert InvalidERC721Recipient(); + } + } catch (bytes memory data) { + // "Bubble up" recipient's revert reason + // if present. + if (data.length != 0 && data.length < 256) { + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } else { + // Revert with a generic error if no + // revert reason is given by the recipient. + revert InvalidERC721Recipient(); + } + } + } + // Ensure that the amount for an ERC721 transfer is 1. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } + + // Transfer ERC721 token. + _performERC721Transfer( + item.token, + msg.sender, + item.recipient, + item.identifier + ); + } else if (item.itemType == ConduitItemType.ERC1155) { + // Transfer ERC1155 token. + _performERC1155Transfer( + item.token, + msg.sender, + item.recipient, + item.identifier, + item.amount + ); + } else { + // Revert if the item being transferred is a native token. + revert InvalidItemType(); + } + } + } + } + /** * @notice Perform multiple transfers to a single recipient via * the conduit derived from the provided conduit key. @@ -297,4 +413,101 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } } + + /** + * @notice Perform multiple transfers to individually-specified recipients + * via the conduit derived from the provided conduit key. + * + * @param items The items to transfer. + * @param conduitKey The conduit key referring to the conduit through + * which the bulk transfer should occur. + */ + function _performTransfersWithConduit( + TransferHelperItemWithRecipient[] calldata items, + bytes32 conduitKey + ) internal { + // Retrieve total number of transfers and place on stack. + uint256 totalTransfers = items.length; + + // Derive the conduit address from the deployer, conduit key + // and creation code hash. + address conduit = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(_CONDUIT_CONTROLLER), + conduitKey, + _CONDUIT_CREATION_CODE_HASH + ) + ) + ) + ) + ); + + // Declare a new array to populate with each token transfer. + ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( + totalTransfers + ); + + // Skip overflow checks: all for loops are indexed starting at zero. + unchecked { + // Iterate over each transfer. + for (uint256 i = 0; i < totalTransfers; ++i) { + // Retrieve the transfer in question. + TransferHelperItemWithRecipient calldata item = items[i]; + + // Ensure tokens aren't transferred to the zero address. + if (item.recipient == address(0x0)) { + revert RecipientCannotBeZero(); + } + + // Create a ConduitTransfer corresponding to each + // TransferHelperItem. + conduitTransfers[i] = ConduitTransfer( + item.itemType, + item.token, + msg.sender, + item.recipient, + item.identifier, + item.amount + ); + } + } + + // Attempt the external call to transfer tokens via the derived conduit. + try ConduitInterface(conduit).execute(conduitTransfers) returns ( + bytes4 conduitMagicValue + ) { + // Check if the value returned from the external call matches + // the conduit `execute` selector. + if ( + conduitMagicValue != ConduitInterface(conduit).execute.selector + ) { + // If the external call fails, revert with the conduit key + // and conduit address. + revert InvalidConduit(conduitKey, conduit); + } + } catch (bytes memory data) { + // Catch reverts from the external call to the conduit and + // "bubble up" the conduit's revert reason if present. + if (data.length != 0 && data.length < 256) { + revert ConduitErrorRevertBytes(data, conduitKey, conduit); + } else { + // If no revert reason is present or data length is too large, + // revert with a generic error with the conduit key and + // conduit address. + revert ConduitErrorRevertGeneric(conduitKey, conduit); + } + } catch Error(string memory reason) { + // Catch reverts with a provided reason string and + // revert with the reason, conduit key and conduit address. + if (bytes(reason).length != 0 && bytes(reason).length < 256) { + revert ConduitErrorRevertString(reason, conduitKey, conduit); + } else { + revert ConduitErrorRevertGeneric(conduitKey, conduit); + } + } + } } diff --git a/contracts/helpers/TransferHelperStructs.sol b/contracts/helpers/TransferHelperStructs.sol index aa4dda0e2..1dd46ecfd 100644 --- a/contracts/helpers/TransferHelperStructs.sol +++ b/contracts/helpers/TransferHelperStructs.sol @@ -10,6 +10,14 @@ struct TransferHelperItem { uint256 amount; } +struct TransferHelperItemWithRecipient { + ConduitItemType itemType; + address token; + uint256 identifier; + uint256 amount; + address recipient; +} + enum Error { None, RevertWithMessage, diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index d5205a0cd..7db4a8951 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; -import { TransferHelperItem } from "../helpers/TransferHelperStructs.sol"; +import { + TransferHelperItem, + TransferHelperItemWithRecipient +} from "../helpers/TransferHelperStructs.sol"; interface TransferHelperInterface { /** @@ -76,4 +79,9 @@ interface TransferHelperInterface { address recipient, bytes32 conduitKey ) external returns (bytes4); + + function bulkTransferToMultipleRecipients( + TransferHelperItemWithRecipient[] calldata items, + bytes32 conduitKey + ) external returns (bytes4); } From 5dacaec20446f019be9ecb3e4497d91f5c1cf4af Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 11 Jul 2022 20:43:17 -0700 Subject: [PATCH 074/126] add ERC721Receiver revert errors, remove data length check in catch clause since the reason will already be in memory, remove generic revert errors (will just have empty reason) --- config/.solcover-reference.js | 4 + config/.solcover.js | 1 - contracts/helpers/TransferHelper.sol | 92 ++++++++----------- .../interfaces/TransferHelperInterface.sol | 52 +++++++---- contracts/test/ConduitControllerMock.sol | 18 +--- contracts/test/ConduitMock.sol | 2 +- contracts/test/ConduitMockInvalidMagic.sol | 2 +- contracts/test/ConduitMockRevertBytes.sol | 18 +--- .../ConduitMockRevertDataLengthTooLong.sol | 40 -------- contracts/test/ConduitMockRevertNoReason.sol | 2 +- test/foundry/TransferHelperTest.sol | 73 +-------------- test/transferhelper.spec.ts | 74 +++------------ 12 files changed, 98 insertions(+), 280 deletions(-) delete mode 100644 contracts/test/ConduitMockRevertDataLengthTooLong.sol diff --git a/config/.solcover-reference.js b/config/.solcover-reference.js index 8f2aa5bae..e44a4a010 100644 --- a/config/.solcover-reference.js +++ b/config/.solcover-reference.js @@ -34,5 +34,9 @@ module.exports = { "test/ERC721ReceiverMock.sol", "test/ConduitControllerMock.sol", "test/ConduitMock.sol", + "test/ConduitMockErrors.sol", + "test/ConduitMockInvalidMagic.sol", + "test/ConduitMockRevertBytes.sol", + "test/ConduitMockRevertNoReason.sol", ], }; diff --git a/config/.solcover.js b/config/.solcover.js index ab2b194ee..f5fc9242b 100644 --- a/config/.solcover.js +++ b/config/.solcover.js @@ -39,7 +39,6 @@ module.exports = { "test/ConduitMockErrors.sol", "test/ConduitMockInvalidMagic.sol", "test/ConduitMockRevertBytes.sol", - "test/ConduitMockRevertDataLengthTooLong.sol", "test/ConduitMockRevertNoReason.sol", ], configureYulOptimizer: true, diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 89d8252bc..db5d3e42a 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -162,21 +162,24 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) { // Revert if recipient cannot accept // ERC721 tokens. - revert InvalidERC721Recipient(); + revert InvalidERC721Recipient(recipient); } } catch (bytes memory data) { - // "Bubble up" recipient's revert reason - // if present. - if (data.length != 0 && data.length < 256) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } else { - // Revert with a generic error if no - // revert reason is given by the recipient. - revert InvalidERC721Recipient(); - } + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertBytes( + data, + recipient, + msg.sender, + item.identifier + ); + } catch Error(string memory reason) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertString( + reason, + recipient, + msg.sender, + item.identifier + ); } } // Ensure that the amount for an ERC721 transfer is 1. @@ -269,21 +272,24 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { ) { // Revert if recipient cannot accept // ERC721 tokens. - revert InvalidERC721Recipient(); + revert InvalidERC721Recipient(item.recipient); } } catch (bytes memory data) { - // "Bubble up" recipient's revert reason - // if present. - if (data.length != 0 && data.length < 256) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } else { - // Revert with a generic error if no - // revert reason is given by the recipient. - revert InvalidERC721Recipient(); - } + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertBytes( + data, + item.recipient, + msg.sender, + item.identifier + ); + } catch Error(string memory reason) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertString( + reason, + item.recipient, + msg.sender, + item.identifier + ); } } // Ensure that the amount for an ERC721 transfer is 1. @@ -394,23 +400,12 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } catch (bytes memory data) { // Catch reverts from the external call to the conduit and - // "bubble up" the conduit's revert reason if present. - if (data.length != 0 && data.length < 256) { - revert ConduitErrorRevertBytes(data, conduitKey, conduit); - } else { - // If no revert reason is present or data length is too large, - // revert with a generic error with the conduit key and - // conduit address. - revert ConduitErrorRevertGeneric(conduitKey, conduit); - } + // "bubble up" the conduit's revert reason. + revert ConduitErrorRevertBytes(data, conduitKey, conduit); } catch Error(string memory reason) { // Catch reverts with a provided reason string and // revert with the reason, conduit key and conduit address. - if (bytes(reason).length != 0 && bytes(reason).length < 256) { - revert ConduitErrorRevertString(reason, conduitKey, conduit); - } else { - revert ConduitErrorRevertGeneric(conduitKey, conduit); - } + revert ConduitErrorRevertString(reason, conduitKey, conduit); } } @@ -491,23 +486,12 @@ contract TransferHelper is TransferHelperInterface, TokenTransferrer { } } catch (bytes memory data) { // Catch reverts from the external call to the conduit and - // "bubble up" the conduit's revert reason if present. - if (data.length != 0 && data.length < 256) { - revert ConduitErrorRevertBytes(data, conduitKey, conduit); - } else { - // If no revert reason is present or data length is too large, - // revert with a generic error with the conduit key and - // conduit address. - revert ConduitErrorRevertGeneric(conduitKey, conduit); - } + // "bubble up" the conduit's revert reason. + revert ConduitErrorRevertBytes(data, conduitKey, conduit); } catch Error(string memory reason) { // Catch reverts with a provided reason string and // revert with the reason, conduit key and conduit address. - if (bytes(reason).length != 0 && bytes(reason).length < 256) { - revert ConduitErrorRevertString(reason, conduitKey, conduit); - } else { - revert ConduitErrorRevertGeneric(conduitKey, conduit); - } + revert ConduitErrorRevertString(reason, conduitKey, conduit); } } } diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 7db4a8951..43f7cfc7e 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -15,9 +15,31 @@ interface TransferHelperInterface { /** * @dev Revert with an error when attempting to execute an ERC721 transfer - to an invalid recipient. + * to an invalid recipient. */ - error InvalidERC721Recipient(); + error InvalidERC721Recipient(address recipient); + + /** + * @dev Revert with an error when a call to a ERC721 receiver reverts with + * bytes data. + */ + error ERC721ReceiverErrorRevertBytes( + bytes reason, + address receiver, + address sender, + uint256 identifier + ); + + /** + * @dev Revert with an error when a call to a ERC721 receiver reverts with + * string reason. + */ + error ERC721ReceiverErrorRevertString( + string reason, + address receiver, + address sender, + uint256 identifier + ); /** * @dev Revert with an error when an ERC20 token has an invalid identifier. @@ -35,12 +57,6 @@ interface TransferHelperInterface { */ error InvalidConduit(bytes32 conduitKey, address conduit); - /** - * @dev Revert with a generic error when a call to a conduit reverts with - * no data about the reason. - */ - error ConduitErrorRevertGeneric(bytes32 conduitKey, address conduit); - /** * @dev Revert with an error when a call to a conduit reverts with a * reason string. @@ -51,18 +67,12 @@ interface TransferHelperInterface { address conduit ); - error ConduitErrorRevertBytes( - bytes reason, - bytes32 conduitKey, - address conduit - ); - /** - * @dev Revert with an error when a call to a conduit reverts with a - * panic error. + * @dev Revert with an error when a call to a conduit reverts with bytes + * data. */ - error ConduitErrorRevertPanic( - uint256 errorCode, + error ConduitErrorRevertBytes( + bytes reason, bytes32 conduitKey, address conduit ); @@ -80,6 +90,12 @@ interface TransferHelperInterface { bytes32 conduitKey ) external returns (bytes4); + /** + * @notice Transfer multiple items to multiple recipients. + * + * @param items The items to transfer. + * @param conduitKey The key of the conduit performing the bulk transfer. + */ function bulkTransferToMultipleRecipients( TransferHelperItemWithRecipient[] calldata items, bytes32 conduitKey diff --git a/contracts/test/ConduitControllerMock.sol b/contracts/test/ConduitControllerMock.sol index 9b18e9b03..dd37c0e7c 100644 --- a/contracts/test/ConduitControllerMock.sol +++ b/contracts/test/ConduitControllerMock.sol @@ -13,10 +13,6 @@ import { ConduitMock } from "../test/ConduitMock.sol"; import { ConduitMockInvalidMagic } from "../test/ConduitMockInvalidMagic.sol"; -import { - ConduitMockRevertDataLengthTooLong -} from "../test/ConduitMockRevertDataLengthTooLong.sol"; - import { ConduitMockRevertNoReason } from "../test/ConduitMockRevertNoReason.sol"; @@ -56,14 +52,6 @@ contract ConduitControllerMock is ConduitControllerInterface { }(); runtimeCodeHash = address(zeroConduit).codehash; } else if (conduitNum == 2) { - creationCodeHash = keccak256( - type(ConduitMockRevertDataLengthTooLong).creationCode - ); - ConduitMockRevertDataLengthTooLong zeroConduit = new ConduitMockRevertDataLengthTooLong{ - salt: bytes32(0) - }(); - runtimeCodeHash = address(zeroConduit).codehash; - } else if (conduitNum == 3) { creationCodeHash = keccak256( type(ConduitMockInvalidMagic).creationCode ); @@ -71,7 +59,7 @@ contract ConduitControllerMock is ConduitControllerInterface { salt: bytes32(0) }(); runtimeCodeHash = address(zeroConduit).codehash; - } else if (conduitNum == 4) { + } else if (conduitNum == 3) { creationCodeHash = keccak256( type(ConduitMockRevertBytes).creationCode ); @@ -142,10 +130,8 @@ contract ConduitControllerMock is ConduitControllerInterface { } else if (conduitNum == 1) { new ConduitMockRevertNoReason{ salt: conduitKey }(); } else if (conduitNum == 2) { - new ConduitMockRevertDataLengthTooLong{ salt: conduitKey }(); - } else if (conduitNum == 3) { new ConduitMockInvalidMagic{ salt: conduitKey }(); - } else if (conduitNum == 4) { + } else if (conduitNum == 3) { new ConduitMockRevertBytes{ salt: conduitKey }(); } // Initialize storage variable referencing conduit properties. diff --git a/contracts/test/ConduitMock.sol b/contracts/test/ConduitMock.sol index a9c1e4247..67b7d0cb0 100644 --- a/contracts/test/ConduitMock.sol +++ b/contracts/test/ConduitMock.sol @@ -15,7 +15,7 @@ contract ConduitMock is ConduitInterface { function execute( ConduitTransfer[] calldata /* transfers */ - ) external view override returns (bytes4) { + ) external pure override returns (bytes4) { // Return the valid magic value. return 0x4ce34aa2; } diff --git a/contracts/test/ConduitMockInvalidMagic.sol b/contracts/test/ConduitMockInvalidMagic.sol index e806f86f7..3352fbcdc 100644 --- a/contracts/test/ConduitMockInvalidMagic.sol +++ b/contracts/test/ConduitMockInvalidMagic.sol @@ -15,7 +15,7 @@ contract ConduitMockInvalidMagic is ConduitInterface { function execute( ConduitTransfer[] calldata /* transfers */ - ) external view override returns (bytes4) { + ) external pure override returns (bytes4) { return 0xabcd0000; } diff --git a/contracts/test/ConduitMockRevertBytes.sol b/contracts/test/ConduitMockRevertBytes.sol index d5b64c906..d9f48d0bb 100644 --- a/contracts/test/ConduitMockRevertBytes.sol +++ b/contracts/test/ConduitMockRevertBytes.sol @@ -17,23 +17,7 @@ contract ConduitMockRevertBytes is ConduitInterface { function execute( ConduitTransfer[] calldata /* transfers */ - ) external view override returns (bytes4) { - // Revert with data.length != 0 && data.length < 256. - // bytes memory revertData = "36e5236fcd4c61044949678014f0d085"; - // if (revertData.length != 32) { - // revert("Incorrect length"); - // } - // bytes memory revertDataStringBytes = abi.encode(string(revertData)); - // uint256 stringLength = revertDataStringBytes.length; - - // assembly { - // revert(add(0x20, revertDataStringBytes), stringLength) - // } - // assembly { - // let pointer := mload(0x40) - // mstore(pointer, "36e5236fcd4c61044949678014f0d085") - // revert(pointer, 32) - // } + ) external pure override returns (bytes4) { revert CustomError(); } diff --git a/contracts/test/ConduitMockRevertDataLengthTooLong.sol b/contracts/test/ConduitMockRevertDataLengthTooLong.sol deleted file mode 100644 index c0fbb2166..000000000 --- a/contracts/test/ConduitMockRevertDataLengthTooLong.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.7; - -import { ConduitInterface } from "../interfaces/ConduitInterface.sol"; - -import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; - -import { - ConduitTransfer, - ConduitBatch1155Transfer -} from "../conduit/lib/ConduitStructs.sol"; - -import { ConduitMockErrors } from "./ConduitMockErrors.sol"; - -contract ConduitMockRevertDataLengthTooLong is - ConduitMockErrors, - ConduitInterface -{ - constructor() {} - - function execute( - ConduitTransfer[] calldata /* transfers */ - ) external view override returns (bytes4) { - // Revert with data length > 256. - revert( - "RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert" - ); - } - - function executeBatch1155( - ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external view override returns (bytes4 magicValue) {} - - function executeWithBatch1155( - ConduitTransfer[] calldata, /* standardTransfers */ - ConduitBatch1155Transfer[] calldata /* batch1155Transfers */ - ) external view override returns (bytes4 magicValue) {} - - function updateChannel(address channel, bool isOpen) external override {} -} diff --git a/contracts/test/ConduitMockRevertNoReason.sol b/contracts/test/ConduitMockRevertNoReason.sol index c797ba2ba..72ded1b52 100644 --- a/contracts/test/ConduitMockRevertNoReason.sol +++ b/contracts/test/ConduitMockRevertNoReason.sol @@ -15,7 +15,7 @@ contract ConduitMockRevertNoReason is ConduitInterface { function execute( ConduitTransfer[] calldata /* transfers */ - ) external view override returns (bytes4) { + ) external pure override returns (bytes4) { // Revert without reason string. revert(); } diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index dccdf8a0c..d7ad2a0b4 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -29,10 +29,6 @@ import { ConduitMockInvalidMagic } from "../../contracts/test/ConduitMockInvalidMagic.sol"; -import { - ConduitMockRevertDataLengthTooLong -} from "../../contracts/test/ConduitMockRevertDataLengthTooLong.sol"; - import { ConduitMockRevertNoReason } from "../../contracts/test/ConduitMockRevertNoReason.sol"; @@ -1115,7 +1111,7 @@ contract TransferHelperTest is BaseOrderTest { { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( - 3 + 2 // ConduitMockInvalidMagic ); // Create conduit key using alice's address @@ -1175,7 +1171,7 @@ contract TransferHelperTest is BaseOrderTest { function testRevertNoErrorString() public { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( - 1 + 1 // ConduitMockRevertNoReason ); // Create conduit key using alice's address @@ -1223,67 +1219,8 @@ contract TransferHelperTest is BaseOrderTest { ); vm.expectRevert( abi.encodeWithSignature( - "ConduitErrorRevertGeneric(bytes32,address)", - conduitKeyAlice, - mockConduit - ) - ); - mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); - vm.stopPrank(); - } - - function testRevertDataLengthTooLong() public { - // Deploy mock conduit controller - ConduitControllerMock mockConduitController = new ConduitControllerMock( - 2 - ); - - // Create conduit key using alice's address - bytes32 conduitKeyAlice = bytes32( - uint256(uint160(address(alice))) << 96 - ); - - // Deploy mock transfer helper that takes in the mock conduit controller - TransferHelper mockTransferHelper = TransferHelper( - deployCode( - "optimized-out/TransferHelper.sol/TransferHelper.json", - abi.encode(address(mockConduitController)) - ) - ); - vm.label(address(mockTransferHelper), "mock transfer helper"); - - vm.startPrank(alice); - - // Create the mock conduit by calling the mock conduit controller - ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( - mockConduitController.createConduit(conduitKeyAlice, address(alice)) - ); - vm.label(address(mockConduit), "mock conduit"); - - bytes32 conduitCodeHash = address(mockConduit).codehash; - emit log_named_bytes32("conduit code hash", conduitCodeHash); - - // Assert the conduit key derived from the conduit address - // matches alice's conduit key - bytes32 mockConduitKey = mockConduitController.getKey( - address(mockConduit) - ); - - // Create item to transfer - TransferHelperItem[] memory items = new TransferHelperItem[](1); - items[0] = TransferHelperItem( - ConduitItemType.ERC721, - address(erc721s[0]), - 5, - 1 - ); - - (address conduit, bool exists) = mockConduitController.getConduit( - conduitKeyAlice - ); - vm.expectRevert( - abi.encodeWithSignature( - "ConduitErrorRevertGeneric(bytes32,address)", + "ConduitErrorRevertBytes(bytes,bytes32,address)", + "0x", conduitKeyAlice, mockConduit ) @@ -1295,7 +1232,7 @@ contract TransferHelperTest is BaseOrderTest { function testRevertWithData() public { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( - 4 + 3 // ConduitMockRevertBytes ); // Create conduit key using alice's address diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 2a6f02fe2..bc9098695 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import { randomHex, toBN } from "./utils/encoding"; +import { randomHex } from "./utils/encoding"; import { fixtureERC1155, fixtureERC20, @@ -491,7 +491,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { tempERC721Contract.address, ethers.utils.formatBytes32String("") ) - ).to.be.revertedWith("InvalidERC721Recipient"); + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertBytes("0x", "${tempERC721Contract.address}", "${sender.address}", 1)` + ); }); it("Reverts on invalid function selector", async () => { @@ -524,7 +526,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { invalidRecipient.address, ethers.utils.formatBytes32String("") ) - ).to.be.revertedWith("InvalidERC721Recipient"); + ).to.be.revertedWith( + `InvalidERC721Recipient("${invalidRecipient.address}")` + ); }); it("Reverts on nonexistent conduit", async () => { @@ -726,7 +730,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { "ConduitControllerMock" ); const mockConduitController = await mockConduitControllerFactory.deploy( - toBN(1) + 1 // ConduitMockRevertNoReason ); const mockTransferHelperFactory = await ethers.getContractFactory( @@ -762,7 +766,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) ).to.be.revertedWith( - `ConduitErrorRevertGeneric("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); }); @@ -805,7 +809,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { "ConduitControllerMock" ); const mockConduitController = await mockConduitControllerFactory.deploy( - toBN(3) + 2 // ConduitMockInvalidMagic ); const mockTransferHelperFactory = await ethers.getContractFactory( @@ -851,62 +855,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); }); - it("Reverts with generic revert when revert data length > 256", async () => { - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - await tempERC20Contract.connect(owner).mint(sender.address, 100); - - const mockConduitControllerFactory = await ethers.getContractFactory( - "ConduitControllerMock" - ); - const mockConduitController = await mockConduitControllerFactory.deploy( - toBN(2) - ); - - const mockTransferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - const mockTransferHelper = await mockTransferHelperFactory.deploy( - mockConduitController.address - ); - const mockConduitKey = owner.address + randomHex(12).slice(2); - - // Deploy the mock conduit through the mock conduit controller - await mockConduitController - .connect(owner) - .createConduit(mockConduitKey, owner.address); - - const mockConduitAddress = ( - await mockConduitController.getConduit(mockConduitKey) - )[0]; - - await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - - await expect( - mockTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertGeneric("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` - ); - }); - it("Reverts when data length is greater than 0 and less than 256", async () => { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); @@ -917,7 +865,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { "ConduitControllerMock" ); const mockConduitController = await mockConduitControllerFactory.deploy( - toBN(4) + 3 // ConduitMockRevertBytes ); const mockTransferHelperFactory = await ethers.getContractFactory( From 5958c30ca2262166b3b89cf1493112eb3c0deafb Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 11 Jul 2022 21:33:14 -0700 Subject: [PATCH 075/126] separate TransferHelperErrors to own file --- contracts/helpers/TransferHelper.sol | 8 +- contracts/interfaces/TransferHelperErrors.sol | 77 +++++++++++++++++++ .../interfaces/TransferHelperInterface.sol | 70 ----------------- 3 files changed, 84 insertions(+), 71 deletions(-) create mode 100644 contracts/interfaces/TransferHelperErrors.sol diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index db5d3e42a..9a8e8c95c 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -25,13 +25,19 @@ import { TransferHelperInterface } from "../interfaces/TransferHelperInterface.sol"; +import { TransferHelperErrors } from "../interfaces/TransferHelperErrors.sol"; + /** * @title TransferHelper * @author stuckinaboot, stephankmin, ryanio * @notice TransferHelper is a utility contract for transferring * ERC20/ERC721/ERC1155 items in bulk to a specific recipient. */ -contract TransferHelper is TransferHelperInterface, TokenTransferrer { +contract TransferHelper is + TransferHelperInterface, + TransferHelperErrors, + TokenTransferrer +{ // Allow for interaction with the conduit controller. ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER; diff --git a/contracts/interfaces/TransferHelperErrors.sol b/contracts/interfaces/TransferHelperErrors.sol new file mode 100644 index 000000000..ecf820313 --- /dev/null +++ b/contracts/interfaces/TransferHelperErrors.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +/** + * @title TransferHelperErrors + */ +interface TransferHelperErrors { + /** + * @dev Revert with an error when attempting to execute transfers with a + * NATIVE itemType. + */ + error InvalidItemType(); + + /** + * @dev Revert with an error when attempting to execute an ERC721 transfer + * to an invalid recipient. + */ + error InvalidERC721Recipient(address recipient); + + /** + * @dev Revert with an error when a call to a ERC721 receiver reverts with + * bytes data. + */ + error ERC721ReceiverErrorRevertBytes( + bytes reason, + address receiver, + address sender, + uint256 identifier + ); + + /** + * @dev Revert with an error when a call to a ERC721 receiver reverts with + * string reason. + */ + error ERC721ReceiverErrorRevertString( + string reason, + address receiver, + address sender, + uint256 identifier + ); + + /** + * @dev Revert with an error when an ERC20 token has an invalid identifier. + */ + error InvalidERC20Identifier(); + + /** + * @dev Revert with an error if the recipient is the zero address. + */ + error RecipientCannotBeZero(); + + /** + * @dev Revert with an error when attempting to fill an order referencing an + * invalid conduit (i.e. one that has not been deployed). + */ + error InvalidConduit(bytes32 conduitKey, address conduit); + + /** + * @dev Revert with an error when a call to a conduit reverts with a + * reason string. + */ + error ConduitErrorRevertString( + string reason, + bytes32 conduitKey, + address conduit + ); + + /** + * @dev Revert with an error when a call to a conduit reverts with bytes + * data. + */ + error ConduitErrorRevertBytes( + bytes reason, + bytes32 conduitKey, + address conduit + ); +} diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 43f7cfc7e..7366f092e 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -7,76 +7,6 @@ import { } from "../helpers/TransferHelperStructs.sol"; interface TransferHelperInterface { - /** - * @dev Revert with an error when attempting to execute transfers with a - * NATIVE itemType. - */ - error InvalidItemType(); - - /** - * @dev Revert with an error when attempting to execute an ERC721 transfer - * to an invalid recipient. - */ - error InvalidERC721Recipient(address recipient); - - /** - * @dev Revert with an error when a call to a ERC721 receiver reverts with - * bytes data. - */ - error ERC721ReceiverErrorRevertBytes( - bytes reason, - address receiver, - address sender, - uint256 identifier - ); - - /** - * @dev Revert with an error when a call to a ERC721 receiver reverts with - * string reason. - */ - error ERC721ReceiverErrorRevertString( - string reason, - address receiver, - address sender, - uint256 identifier - ); - - /** - * @dev Revert with an error when an ERC20 token has an invalid identifier. - */ - error InvalidERC20Identifier(); - - /** - * @dev Revert with an error if the recipient is the zero address. - */ - error RecipientCannotBeZero(); - - /** - * @dev Revert with an error when attempting to fill an order referencing an - * invalid conduit (i.e. one that has not been deployed). - */ - error InvalidConduit(bytes32 conduitKey, address conduit); - - /** - * @dev Revert with an error when a call to a conduit reverts with a - * reason string. - */ - error ConduitErrorRevertString( - string reason, - bytes32 conduitKey, - address conduit - ); - - /** - * @dev Revert with an error when a call to a conduit reverts with bytes - * data. - */ - error ConduitErrorRevertBytes( - bytes reason, - bytes32 conduitKey, - address conduit - ); - /** * @notice Transfer multiple items to a single recipient. * From 30b3f528d63aa6485e95ec8434d3267e5071fd3d Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 11 Jul 2022 21:37:11 -0700 Subject: [PATCH 076/126] update foundry to TransferHelperErrors --- test/foundry/TransferHelperTest.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index d7ad2a0b4..d83ab0289 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -48,6 +48,10 @@ import { TransferHelperInterface } from "../../contracts/interfaces/TransferHelperInterface.sol"; +import { + TransferHelperErrors +} from "../../contracts/interfaces/TransferHelperErrors.sol"; + import { ERC721ReceiverMock } from "../../contracts/test/ERC721ReceiverMock.sol"; @@ -775,7 +779,7 @@ contract TransferHelperTest is BaseOrderTest { bob, false, abi.encodePacked( - TransferHelperInterface.InvalidERC20Identifier.selector + TransferHelperErrors.InvalidERC20Identifier.selector ) ); } @@ -798,7 +802,7 @@ contract TransferHelperTest is BaseOrderTest { address(invalidRecipient), false, abi.encodePacked( - TransferHelperInterface.InvalidERC721Recipient.selector + TransferHelperErrors.InvalidERC721Recipient.selector ) ); } @@ -832,7 +836,7 @@ contract TransferHelperTest is BaseOrderTest { conduitKeyOne, conduit ), - abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) + abi.encodePacked(TransferHelperErrors.InvalidItemType.selector) ); } @@ -871,7 +875,7 @@ contract TransferHelperTest is BaseOrderTest { conduitKeyOne, conduit ), - abi.encodePacked(TransferHelperInterface.InvalidItemType.selector) + abi.encodePacked(TransferHelperErrors.InvalidItemType.selector) ); } From b2d7c7d744a3185823bb023364e6818e7693d82f Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 10:22:18 -0700 Subject: [PATCH 077/126] update multi recipient function --- contracts/helpers/TransferHelper.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 9a8e8c95c..ecf49f459 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -102,7 +102,13 @@ contract TransferHelper is ) external override returns (bytes4 magicValue) { if (conduitKey == bytes32(0)) { _performTransfersWithoutConduit(items); + } else { + // Otherwise, a conduitKey was provided. + _performTransfersWithConduit(items, conduitKey); } + + // Return a magic value indicating that the transfers were performed. + magicValue = this.bulkTransfer.selector; } /** From 1a45427e61cb1e571c78fa582d731f21d9b24ad9 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 11:13:22 -0700 Subject: [PATCH 078/126] update forge test so recipient isn't create2deployer --- test/foundry/FulfillAdvancedOrder.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/foundry/FulfillAdvancedOrder.t.sol b/test/foundry/FulfillAdvancedOrder.t.sol index c233a55ce..62e9beef6 100644 --- a/test/foundry/FulfillAdvancedOrder.t.sol +++ b/test/foundry/FulfillAdvancedOrder.t.sol @@ -390,6 +390,7 @@ contract FulfillAdvancedOrder is BaseOrderTest { only1155Receiver(inputs.recipient) { vm.assume(tokenAmount > 0); + vm.assume(inputs.recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1); test( this.singleAdvanced1155, From a1b9b02c4ccb858bdac753de620395b44c92b677 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 12 Jul 2022 11:48:02 -0700 Subject: [PATCH 079/126] fixup transfer helper foundry tests, address solidity warnings --- test/foundry/GetterTests.t.sol | 2 +- test/foundry/TransferHelperTest.sol | 62 +++++++++++++++++++---------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/test/foundry/GetterTests.t.sol b/test/foundry/GetterTests.t.sol index b8f85c222..f480cd56d 100644 --- a/test/foundry/GetterTests.t.sol +++ b/test/foundry/GetterTests.t.sol @@ -36,7 +36,7 @@ contract TestGetters is BaseConsiderationTest { // Length of "Consideration" assertEq(length, 13); // Check if there are dirty bits - assertEq(value, bytes32("Consideration")); + assertEq(value, bytes32(abi.encodePacked(name))); } function testGetsCorrectVersion() public { diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index d83ab0289..80e892a09 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -52,21 +52,14 @@ import { TransferHelperErrors } from "../../contracts/interfaces/TransferHelperErrors.sol"; +import { IERC721Receiver } from '../../contracts/interfaces/IERC721Receiver.sol'; + import { ERC721ReceiverMock } from "../../contracts/test/ERC721ReceiverMock.sol"; import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; -interface IERC721Receiver { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external returns (bytes4); -} - contract TransferHelperTest is BaseOrderTest { TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. @@ -458,7 +451,7 @@ contract TransferHelperTest is BaseOrderTest { } function getSelector(bytes calldata returnData) - public + public pure returns (bytes memory) { return returnData[0x84:0x88]; @@ -801,8 +794,9 @@ contract TransferHelperTest is BaseOrderTest { alice, address(invalidRecipient), false, - abi.encodePacked( - TransferHelperErrors.InvalidERC721Recipient.selector + abi.encodeWithSignature( + "InvalidERC721Recipient(address)", + invalidRecipient ) ); } @@ -820,7 +814,7 @@ contract TransferHelperTest is BaseOrderTest { bytes memory returnedData; try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( - bytes4 magicValue + bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } @@ -859,7 +853,7 @@ contract TransferHelperTest is BaseOrderTest { bytes memory returnedData; try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( - bytes4 magicValue + bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } @@ -896,7 +890,7 @@ contract TransferHelperTest is BaseOrderTest { items[0] = item; bytes memory returnedData; try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( - bytes4 magicValue + bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } @@ -935,7 +929,7 @@ contract TransferHelperTest is BaseOrderTest { bytes memory returnedData; try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( - bytes4 magicValue + bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } @@ -1041,11 +1035,17 @@ contract TransferHelperTest is BaseOrderTest { alice, address(mockReceiver), false, - abi.encodePacked("ERC721ReceiverMock: reverting") + abi.encodeWithSignature( + "ERC721ReceiverErrorRevertString(string,address,address,uint256)", + "ERC721ReceiverMock: reverting", + mockReceiver, + alice, + inputs.identifiers[0] + ) ); } - function testRevertStringErrorWithConduit(FuzzInputsCommon memory inputs) + function testRevertStringErrorWithConduit() public { TransferHelperItem memory item = TransferHelperItem( @@ -1072,7 +1072,7 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertPanicErrorWithConduit(FuzzInputsCommon memory inputs) + function testRevertPanicErrorWithConduit() public { // Create ERC20 token that reverts with a panic when calling transferFrom. @@ -1110,7 +1110,7 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertInvalidConduitMagicValue(FuzzInputsCommon memory inputs) + function testRevertInvalidConduitMagicValue() public { // Deploy mock conduit controller @@ -1149,6 +1149,8 @@ contract TransferHelperTest is BaseOrderTest { address(mockConduit) ); + assertEq(mockConduitKey, conduitKeyAlice); + // Create item to transfer TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = TransferHelperItem( @@ -1161,6 +1163,10 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, bool exists) = mockConduitController.getConduit( conduitKeyAlice ); + + assertEq(address(mockConduit), conduit); + assertEq(exists, true); + vm.expectRevert( abi.encodeWithSignature( "InvalidConduit(bytes32,address)", @@ -1195,7 +1201,7 @@ contract TransferHelperTest is BaseOrderTest { vm.startPrank(alice); // Create the mock conduit by calling the mock conduit controller - ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + ConduitMockRevertNoReason mockConduit = ConduitMockRevertNoReason( mockConduitController.createConduit(conduitKeyAlice, address(alice)) ); vm.label(address(mockConduit), "mock conduit"); @@ -1209,6 +1215,8 @@ contract TransferHelperTest is BaseOrderTest { address(mockConduit) ); + assertEq(mockConduitKey, conduitKeyAlice); + // Create item to transfer TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = TransferHelperItem( @@ -1221,6 +1229,10 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, bool exists) = mockConduitController.getConduit( conduitKeyAlice ); + + assertEq(address(mockConduit), conduit); + assertEq(exists, true); + vm.expectRevert( abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1270,6 +1282,8 @@ contract TransferHelperTest is BaseOrderTest { address(mockConduit) ); + assertEq(mockConduitKey, conduitKeyAlice); + // Create item to transfer TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = TransferHelperItem( @@ -1282,10 +1296,14 @@ contract TransferHelperTest is BaseOrderTest { (address conduit, bool exists) = mockConduitController.getConduit( conduitKeyAlice ); + + assertEq(address(mockConduit), conduit); + assertEq(exists, true); + bytes memory returnedData; try mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice) - returns (bytes4 magicValue) {} catch (bytes memory reason) { + returns (bytes4 /* magicValue */) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } vm.expectRevert( From c7636c923b09783d4ebcab56b8203f6b1cc917bc Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 12 Jul 2022 11:48:45 -0700 Subject: [PATCH 080/126] lint:fix --- test/foundry/FulfillAdvancedOrder.t.sol | 4 +++- test/foundry/TransferHelperTest.sol | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/test/foundry/FulfillAdvancedOrder.t.sol b/test/foundry/FulfillAdvancedOrder.t.sol index 62e9beef6..7ca86d989 100644 --- a/test/foundry/FulfillAdvancedOrder.t.sol +++ b/test/foundry/FulfillAdvancedOrder.t.sol @@ -390,7 +390,9 @@ contract FulfillAdvancedOrder is BaseOrderTest { only1155Receiver(inputs.recipient) { vm.assume(tokenAmount > 0); - vm.assume(inputs.recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1); + vm.assume( + inputs.recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1 + ); test( this.singleAdvanced1155, diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 80e892a09..75b41c7db 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -52,7 +52,9 @@ import { TransferHelperErrors } from "../../contracts/interfaces/TransferHelperErrors.sol"; -import { IERC721Receiver } from '../../contracts/interfaces/IERC721Receiver.sol'; +import { + IERC721Receiver +} from "../../contracts/interfaces/IERC721Receiver.sol"; import { ERC721ReceiverMock @@ -451,7 +453,8 @@ contract TransferHelperTest is BaseOrderTest { } function getSelector(bytes calldata returnData) - public pure + public + pure returns (bytes memory) { return returnData[0x84:0x88]; @@ -1045,9 +1048,7 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertStringErrorWithConduit() - public - { + function testRevertStringErrorWithConduit() public { TransferHelperItem memory item = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), @@ -1072,9 +1073,7 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertPanicErrorWithConduit() - public - { + function testRevertPanicErrorWithConduit() public { // Create ERC20 token that reverts with a panic when calling transferFrom. TestERC20Panic panicERC20 = new TestERC20Panic(); @@ -1110,9 +1109,7 @@ contract TransferHelperTest is BaseOrderTest { ); } - function testRevertInvalidConduitMagicValue() - public - { + function testRevertInvalidConduitMagicValue() public { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( 2 // ConduitMockInvalidMagic @@ -1303,7 +1300,9 @@ contract TransferHelperTest is BaseOrderTest { bytes memory returnedData; try mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice) - returns (bytes4 /* magicValue */) {} catch (bytes memory reason) { + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); } vm.expectRevert( From ba320ffead5f885155d99ad71adc09a9af6cfcf8 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Tue, 12 Jul 2022 13:09:35 -0700 Subject: [PATCH 081/126] fix test --- test/foundry/TransferHelperTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 75b41c7db..60ea9bdd3 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -1233,7 +1233,7 @@ contract TransferHelperTest is BaseOrderTest { vm.expectRevert( abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", - "0x", + "", conduitKeyAlice, mockConduit ) From 467497195ad06d488dfbea2b3aebf77d54de8c4e Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Tue, 12 Jul 2022 13:18:46 -0700 Subject: [PATCH 082/126] fix testRevertInvalidERC721Receiver --- test/foundry/TransferHelperTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperTest.sol index 60ea9bdd3..e9459d0ca 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperTest.sol @@ -1043,7 +1043,7 @@ contract TransferHelperTest is BaseOrderTest { "ERC721ReceiverMock: reverting", mockReceiver, alice, - inputs.identifiers[0] + item.identifier ) ); } From 55e4809d28b3ed935fc08c91b2ec9771769f4f2c Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 13:21:45 -0700 Subject: [PATCH 083/126] add hh test multi recipient with conduit, progress on multi recipient without conduit --- test/transferhelper.spec.ts | 313 +++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index bc9098695..41755b5d0 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -20,7 +20,7 @@ import type { TransferHelper, } from "../typechain-types"; import type { SeaportFixtures } from "./utils/fixtures"; -import type { Wallet } from "ethers"; +import type { BigNumber, Wallet } from "ethers"; describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const { provider } = ethers; @@ -53,6 +53,10 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { let recipient: Wallet; let zone: Wallet; + let alice: Wallet; + let bob: Wallet; + let cal: Wallet; + let senderContract: EIP1271Wallet; let recipientContract: EIP1271Wallet; @@ -60,12 +64,45 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { let tempConduitKey: string; let tempTransferHelper: TransferHelper; + interface Transfer { + itemType: 0 | 1 | 2 | 3 | 4 | 5; + token: string; + from: string; + to: string; + identifier: BigNumber; + amount: BigNumber; + } + + interface TransferWithRecipient { + itemType: 0 | 1 | 2 | 3 | 4 | 5; + token: string; + identifier: BigNumber; + amount: BigNumber; + recipient: string; + } + + function createTransferWithRecipient( + transfer: Transfer + ): TransferWithRecipient { + return { + itemType: transfer.itemType, + token: transfer.token, + identifier: transfer.identifier, + amount: transfer.amount, + recipient: transfer.to, + }; + } + beforeEach(async () => { // Setup basic buyer/seller wallets with ETH sender = new ethers.Wallet(randomHex(32), provider); recipient = new ethers.Wallet(randomHex(32), provider); zone = new ethers.Wallet(randomHex(32), provider); + alice = new ethers.Wallet(randomHex(32), provider); + bob = new ethers.Wallet(randomHex(32), provider); + cal = new ethers.Wallet(randomHex(32), provider); + senderContract = await EIP1271WalletFactory.deploy(sender.address); recipientContract = await EIP1271WalletFactory.deploy(recipient.address); @@ -333,6 +370,280 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } }); + it("Executes transfers with multiple recipients (many token types) with a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + const recipients = [ + recipient.address, + alice.address, + bob.address, + cal.address, + ]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + + const transfersWithRecipients = []; + + for (let i = 0; i < transfers.length; i++) { + transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + } + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transfersWithRecipients, + tempConduitKey + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfersWithRecipients.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf(sender.address) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipients[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipients[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal(0); + expect( + await token.balanceOf( + transfersWithRecipients[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + } + }); + + it("Executes transfers with multiple recipients (many token types) without a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + const recipients = [ + recipient.address, + alice.address, + bob.address, + cal.address, + ]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + + const transfersWithRecipients = []; + + for (let i = 0; i < transfers.length; i++) { + transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + } + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transfersWithRecipients, + ethers.utils.formatBytes32String("") + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfersWithRecipients.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf(sender.address) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipients[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipients[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal(0); + expect( + await token.balanceOf( + transfersWithRecipients[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + } + }); + it("Executes ERC721 transfers to a contract recipient without a conduit", async () => { // Deploy recipient contract const erc721RecipientFactory = await ethers.getContractFactory( From fc0d3217f2e6011f9022166c1c64ca171a0d9f04 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 14:56:33 -0700 Subject: [PATCH 084/126] update multi recipient hh test without conduit --- test/transferhelper.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 41755b5d0..4f1e1ce15 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -534,12 +534,12 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { for (let i = 0; i < numERC20s; i++) { // Deploy Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s + // Create/Approve X amount of ERC20s const erc20Transfer = await createTransferWithApproval( tempERC20Contract, sender, 1, - tempConduit.address, + tempTransferHelper.address, sender.address, recipients[i % 4] ); @@ -547,7 +547,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { erc20Transfers[i] = erc20Transfer; } - // Create numEC721s amount of ERC20 objects + // Create numEC721s amount of ERC721 objects for (let i = 0; i < numEC721s; i++) { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); @@ -556,7 +556,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { tempERC721Contract, sender, 2, - tempConduit.address, + tempTransferHelper.address, sender.address, recipients[i % 4] ); @@ -573,7 +573,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { tempERC1155Contract, sender, 3, - tempConduit.address, + tempTransferHelper.address, sender.address, recipients[i % 4] ); @@ -598,6 +598,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { for (let i = 0; i < transfers.length; i++) { transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); } + // Send the bulk transfers await tempTransferHelper .connect(sender) From 360cfcf76c9b6a9c7ba707671f967ca91c5708a1 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Tue, 12 Jul 2022 15:10:07 -0700 Subject: [PATCH 085/126] add tests for bulkTransferToMultipleRecipients --- .../TransferHelperMultipleRecipientsTest.sol | 1488 +++++++++++++++++ ... => TransferHelperSingleRecipientTest.sol} | 4 +- 2 files changed, 1490 insertions(+), 2 deletions(-) create mode 100644 test/foundry/TransferHelperMultipleRecipientsTest.sol rename test/foundry/{TransferHelperTest.sol => TransferHelperSingleRecipientTest.sol} (99%) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol new file mode 100644 index 000000000..2c3d0d969 --- /dev/null +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -0,0 +1,1488 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { BaseConsiderationTest } from "./utils/BaseConsiderationTest.sol"; + +import { BaseOrderTest } from "./utils/BaseOrderTest.sol"; + +import { + ConduitInterface +} from "../../contracts/interfaces/ConduitInterface.sol"; + +import { ConduitItemType } from "../../contracts/conduit/lib/ConduitEnums.sol"; + +import { TransferHelper } from "../../contracts/helpers/TransferHelper.sol"; + +import { + TransferHelperItemWithRecipient +} from "../../contracts/helpers/TransferHelperStructs.sol"; + +import { TestERC20 } from "../../contracts/test/TestERC20.sol"; + +import { TestERC721 } from "../../contracts/test/TestERC721.sol"; + +import { TestERC1155 } from "../../contracts/test/TestERC1155.sol"; + +import { ConduitMock } from "../../contracts/test/ConduitMock.sol"; + +import { + ConduitMockInvalidMagic +} from "../../contracts/test/ConduitMockInvalidMagic.sol"; + +import { + ConduitMockRevertNoReason +} from "../../contracts/test/ConduitMockRevertNoReason.sol"; +import { + ConduitControllerMock +} from "../../contracts/test/ConduitControllerMock.sol"; + +import { + InvalidERC721Recipient +} from "../../contracts/test/InvalidERC721Recipient.sol"; + +import { + TokenTransferrerErrors +} from "../../contracts/interfaces/TokenTransferrerErrors.sol"; + +import { + TransferHelperInterface +} from "../../contracts/interfaces/TransferHelperInterface.sol"; + +import { + TransferHelperErrors +} from "../../contracts/interfaces/TransferHelperErrors.sol"; + +import { + IERC721Receiver +} from "../../contracts/interfaces/IERC721Receiver.sol"; + +import { + ERC721ReceiverMock +} from "../../contracts/test/ERC721ReceiverMock.sol"; + +import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; + +contract TransferHelperMultipleRecipientsTest is BaseOrderTest { + TransferHelper transferHelper; + // Total supply of fungible tokens to be used in tests for all fungible tokens. + uint256 constant TOTAL_FUNGIBLE_TOKENS = 1e6; + // Total number of token identifiers to mint tokens for for ERC721s and ERC1155s. + uint256 constant TOTAL_TOKEN_IDENTIFERS = 10; + // Constant bytes used for expecting revert with no message. + bytes constant REVERT_DATA_NO_MSG = "revert no message"; + ERC721ReceiverMock validERC721Receiver; + ERC721ReceiverMock invalidERC721Receiver; + InvalidERC721Recipient invalidRecipient; + + struct FromToBalance { + // Balance of from address. + uint256 from; + // Balance of to address. + uint256 to; + } + + struct FuzzInputsCommon { + // Indicates if a conduit should be used for the transfer + bool useConduit; + // Amounts that can be used for the amount field on TransferHelperItemWithRecipient + uint256[10] amounts; + // Identifiers that can be used for the identifier field on TransferHelperItemWithRecipient + uint256[10] identifiers; + // Indexes that can be used to select tokens from the arrays erc20s/erc721s/erc1155s + uint256[10] tokenIndex; + address[10] recipients; + } + + function setUp() public override { + super.setUp(); + _deployAndConfigurePrecompiledTransferHelper(); + vm.label(address(transferHelper), "transferHelper"); + + // Mint initial tokens to alice for tests. + for (uint256 tokenIdx = 0; tokenIdx < erc20s.length; tokenIdx++) { + erc20s[tokenIdx].mint(alice, TOTAL_FUNGIBLE_TOKENS); + } + + // Mint ERC721 and ERC1155 with token IDs 0 to TOTAL_TOKEN_IDENTIFERS - 1 to alice + for ( + uint256 identifier = 0; + identifier < TOTAL_TOKEN_IDENTIFERS; + identifier++ + ) { + for (uint256 tokenIdx = 0; tokenIdx < erc721s.length; tokenIdx++) { + erc721s[tokenIdx].mint(alice, identifier); + } + for (uint256 tokenIdx = 0; tokenIdx < erc1155s.length; tokenIdx++) { + erc1155s[tokenIdx].mint( + alice, + identifier, + TOTAL_FUNGIBLE_TOKENS + ); + } + } + + // Allow transfer helper to perform transfers for these addresses. + _setApprovals(alice); + _setApprovals(bob); + _setApprovals(cal); + + // Open a channel for transfer helper on the conduit + _updateConduitChannel(true); + + validERC721Receiver = new ERC721ReceiverMock( + IERC721Receiver.onERC721Received.selector, + ERC721ReceiverMock.Error.None + ); + vm.label(address(validERC721Receiver), "valid ERC721 receiver"); + invalidERC721Receiver = new ERC721ReceiverMock( + 0xabcd0000, + ERC721ReceiverMock.Error.RevertWithMessage + ); + vm.label( + address(invalidERC721Receiver), + "invalid (error) ERC721 receiver" + ); + + invalidRecipient = new InvalidERC721Recipient(); + vm.label( + address(invalidRecipient), + "invalid ERC721 receiver (bad selector)" + ); + } + + /** + * @dev TransferHelper depends on precomputed Conduit creation code hash, which will differ + * if tests are run with different compiler settings (which they are by default) + */ + function _deployAndConfigurePrecompiledTransferHelper() public { + transferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(conduitController)) + ) + ); + } + + // Helper functions + + function _setApprovals(address _owner) internal override { + super._setApprovals(_owner); + vm.startPrank(_owner); + for (uint256 i = 0; i < erc20s.length; i++) { + erc20s[i].approve(address(transferHelper), MAX_INT); + } + for (uint256 i = 0; i < erc1155s.length; i++) { + erc1155s[i].setApprovalForAll(address(transferHelper), true); + } + for (uint256 i = 0; i < erc721s.length; i++) { + erc721s[i].setApprovalForAll(address(transferHelper), true); + } + vm.stopPrank(); + // emit log_named_address( + // "Owner proxy approved for all tokens from", + // _owner + // ); + // emit log_named_address( + // "Consideration approved for all tokens from", + // _owner + // ); + } + + function _updateConduitChannel(bool open) internal { + (address _conduit, ) = conduitController.getConduit(conduitKeyOne); + vm.prank(address(conduitController)); + ConduitInterface(_conduit).updateChannel(address(transferHelper), open); + } + + function _balanceOfTransferItemForAddress( + TransferHelperItemWithRecipient memory item, + address addr + ) internal view returns (uint256) { + if (item.itemType == ConduitItemType.ERC20) { + return TestERC20(item.token).balanceOf(addr); + } else if (item.itemType == ConduitItemType.ERC721) { + return + TestERC721(item.token).ownerOf(item.identifier) == addr ? 1 : 0; + } else if (item.itemType == ConduitItemType.ERC1155) { + return TestERC1155(item.token).balanceOf(addr, item.identifier); + } else if (item.itemType == ConduitItemType.NATIVE) { + // Balance for native does not matter as don't support native transfers so just return dummy value. + return 0; + } + // Revert on unsupported ConduitItemType. + revert(); + } + + function _balanceOfTransferItemForFromTo( + TransferHelperItemWithRecipient memory item, + address from + ) internal view returns (FromToBalance memory) { + return + FromToBalance( + _balanceOfTransferItemForAddress(item, from), + _balanceOfTransferItemForAddress(item, item.recipient) + ); + } + + function _performSingleItemTransferAndCheckBalances( + TransferHelperItemWithRecipient memory item, + address from, + bool useConduit, + bytes memory expectRevertData + ) public { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = item; + _performMultiItemTransferAndCheckBalances( + items, + from, + useConduit, + expectRevertData + ); + } + + function _performMultiItemTransferAndCheckBalances( + TransferHelperItemWithRecipient[] memory items, + address from, + bool useConduit, + bytes memory expectRevertData + ) public { + vm.startPrank(from); + + // Get balances before transfer + FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from + ); + } + + // Register expected revert if present. + if ( + // Compare hashes as we cannot directly compare bytes memory with bytes storage. + keccak256(expectRevertData) == keccak256(REVERT_DATA_NO_MSG) + ) { + vm.expectRevert(); + } else if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + // Perform transfer. + transferHelper.bulkTransferToMultipleRecipients( + items, + useConduit ? conduitKeyOne : bytes32(0) + ); + + // Get balances after transfer + FromToBalance[] memory afterTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + afterTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from + ); + } + + if (expectRevertData.length > 0) { + // If revert is expected, balances should not have changed. + for (uint256 i = 0; i < items.length; i++) { + assert( + beforeTransferBalances[i].from == + afterTransferBalances[i].from + ); + assert( + beforeTransferBalances[i].to == afterTransferBalances[i].to + ); + } + return; + } + + // Check after transfer balances are as expected by calculating difference against before transfer balances. + for (uint256 i = 0; i < items.length; i++) { + // ERC721 balance should only ever change by amount 1. + uint256 amount = items[i].itemType == ConduitItemType.ERC721 + ? 1 + : items[i].amount; + assertEq( + afterTransferBalances[i].from, + beforeTransferBalances[i].from - amount + ); + assertEq( + afterTransferBalances[i].to, + beforeTransferBalances[i].to + amount + ); + } + + vm.stopPrank(); + } + + function _performMultiItemTransferAndCheckBalances( + TransferHelperItemWithRecipient[] memory items, + address from, + bool useConduit, + bytes memory expectRevertDataWithConduit, + bytes memory expectRevertDataWithoutConduit + ) public { + vm.startPrank(from); + + // Get balances before transfer + FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from + ); + } + + // Register expected revert if present. + if ( + // Compare hashes as we cannot directly compare bytes memory with bytes storage. + (keccak256(expectRevertDataWithConduit) == + keccak256(REVERT_DATA_NO_MSG) && + useConduit) || + (keccak256(expectRevertDataWithoutConduit) == + keccak256(REVERT_DATA_NO_MSG) && + !useConduit) + ) { + vm.expectRevert(); + } else if (expectRevertDataWithConduit.length > 0 && useConduit) { + vm.expectRevert(expectRevertDataWithConduit); + } else if (expectRevertDataWithoutConduit.length > 0 && !useConduit) { + vm.expectRevert(expectRevertDataWithoutConduit); + } + // Perform transfer. + transferHelper.bulkTransferToMultipleRecipients( + items, + useConduit ? conduitKeyOne : bytes32(0) + ); + + // Get balances after transfer + FromToBalance[] memory afterTransferBalances = new FromToBalance[]( + items.length + ); + for (uint256 i = 0; i < items.length; i++) { + afterTransferBalances[i] = _balanceOfTransferItemForFromTo( + items[i], + from + ); + } + + if ( + (expectRevertDataWithConduit.length > 0) || + (expectRevertDataWithoutConduit.length > 0) + ) { + // If revert is expected, balances should not have changed. + for (uint256 i = 0; i < items.length; i++) { + assert( + beforeTransferBalances[i].from == + afterTransferBalances[i].from + ); + assert( + beforeTransferBalances[i].to == afterTransferBalances[i].to + ); + } + return; + } + + // Check after transfer balances are as expected by calculating difference against before transfer balances. + for (uint256 i = 0; i < items.length; i++) { + // ERC721 balance should only ever change by amount 1. + uint256 amount = items[i].itemType == ConduitItemType.ERC721 + ? 1 + : items[i].amount; + assertEq( + afterTransferBalances[i].from, + beforeTransferBalances[i].from - amount + ); + assertEq( + afterTransferBalances[i].to, + beforeTransferBalances[i].to + amount + ); + } + + vm.stopPrank(); + } + + function _makeSafeRecipient(address from, address fuzzRecipient) + internal + view + returns (address) + { + return _makeSafeRecipient(from, fuzzRecipient, false); + } + + function _makeSafeRecipient( + address from, + address fuzzRecipient, + bool reverting + ) internal view returns (address) { + if ( + fuzzRecipient == address(validERC721Receiver) || + (reverting && + (fuzzRecipient == address(invalidERC721Receiver) || + fuzzRecipient == address(invalidRecipient))) + ) { + return fuzzRecipient; + } else if ( + fuzzRecipient == address(0) || + fuzzRecipient.code.length > 0 || + from == fuzzRecipient + ) { + return address(uint160(fuzzRecipient) + 1); + } + return fuzzRecipient; + } + + function _getFuzzedTransferItem( + address from, + ConduitItemType itemType, + uint256 fuzzAmount, + uint256 fuzzIndex, + uint256 fuzzIdentifier, + address fuzzRecipient + ) internal view returns (TransferHelperItemWithRecipient memory) { + return + _getFuzzedTransferItem( + from, + itemType, + fuzzAmount, + fuzzIndex, + fuzzIdentifier, + fuzzRecipient, + false + ); + } + + function _getFuzzedTransferItem( + address from, + ConduitItemType itemType, + uint256 fuzzAmount, + uint256 fuzzIndex, + uint256 fuzzIdentifier, + address fuzzRecipient, + bool reverting + ) internal view returns (TransferHelperItemWithRecipient memory) { + uint256 amount = fuzzAmount % TOTAL_FUNGIBLE_TOKENS; + uint256 identifier = fuzzIdentifier % TOTAL_TOKEN_IDENTIFERS; + address recipient = _makeSafeRecipient(from, fuzzRecipient, reverting); + if (itemType == ConduitItemType.ERC20) { + uint256 index = fuzzIndex % erc20s.length; + TestERC20 erc20 = erc20s[index]; + return + TransferHelperItemWithRecipient( + itemType, + address(erc20), + identifier, + amount, + recipient + ); + } else if (itemType == ConduitItemType.ERC1155) { + uint256 index = fuzzIndex % erc1155s.length; + TestERC1155 erc1155 = erc1155s[index]; + return + TransferHelperItemWithRecipient( + itemType, + address(erc1155), + identifier, + amount, + recipient + ); + } else if (itemType == ConduitItemType.NATIVE) { + return + TransferHelperItemWithRecipient( + itemType, + address(0), + identifier, + amount, + recipient + ); + } else if (itemType == ConduitItemType.ERC721) { + uint256 index = fuzzIndex % erc721s.length; + return + TransferHelperItemWithRecipient( + itemType, + address(erc721s[index]), + identifier, + 1, + recipient + ); + } + revert(); + } + + function _getFuzzedERC721TransferItemWithAmountGreaterThan1( + address from, + uint256 fuzzAmount, + uint256 fuzzIndex, + uint256 fuzzIdentifier, + address fuzzRecipient + ) internal view returns (TransferHelperItemWithRecipient memory) { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + from, + ConduitItemType.ERC721, + fuzzAmount, + fuzzIndex, + fuzzIdentifier, + fuzzRecipient + ); + item.amount = 2 + (fuzzAmount % TOTAL_FUNGIBLE_TOKENS); + return item; + } + + function getSelector(bytes calldata returnData) + public + pure + returns (bytes memory) + { + return returnData[0x84:0x88]; + } + + // Test successful transfers + + function testBulkTransferERC20(FuzzInputsCommon memory inputs) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[0], + inputs.tokenIndex[0], + 0, + inputs.recipients[0] + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC721(FuzzInputsCommon memory inputs) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC721toBobThenCal(FuzzInputsCommon memory inputs) + public + { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + TransferHelperItemWithRecipient memory item2 = _getFuzzedTransferItem( + bob, + ConduitItemType.ERC721, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + cal + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + inputs.useConduit, + "" + ); + _performSingleItemTransferAndCheckBalances( + item2, + bob, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC1155(FuzzInputsCommon memory inputs) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC1155andERC721(FuzzInputsCommon memory inputs) + public + { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](2); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[1], + inputs.tokenIndex[1], + inputs.identifiers[1], + inputs.recipients[1] + ); + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC1155andERC721andERC20( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](3); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[1], + inputs.identifiers[1], + inputs.recipients[1] + ); + items[2] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[2], + inputs.tokenIndex[2], + 0, + inputs.recipients[2] + ); + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferMultipleERC721SameContract( + FuzzInputsCommon memory inputs + ) public { + uint256 numItems = 3; + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](numItems); + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[i], + // Same token index for all items since this is testing from same contract + inputs.tokenIndex[0], + // Each item has a different token identifier as alice only owns one ERC721 token + // for each identifier for this particular contract + i, + inputs.recipients[0] + ); + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferMultipleERC721DifferentContracts( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](3); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[0], + // Different token index for all items since this is testing from different contracts + 0, + inputs.identifiers[0], + inputs.recipients[0] + ); + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[1], + 1, + inputs.identifiers[1], + inputs.recipients[1] + ); + items[2] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[2], + 2, + inputs.identifiers[2], + inputs.recipients[2] + ); + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferMultipleERC721andMultipleERC1155( + FuzzInputsCommon memory inputs + ) public { + uint256 numItems = 6; + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](numItems); + + // Fill items such that the first floor(numItems / 2) items are ERC1155 and the remaining + // items are ERC721 + for (uint256 i = 0; i < numItems; i++) { + if (i < numItems / 2) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[i], + // Ensure each item is from a different contract + i, + inputs.identifiers[i], + inputs.recipients[i] + ); + } else { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[i], + i, + inputs.identifiers[i], + inputs.recipients[i] + ); + } + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + "" + ); + } + + function testBulkTransferERC7211NotUsingConduit( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + + _performSingleItemTransferAndCheckBalances(item, alice, false, ""); + } + + function testBulkTransferERC721ToContractRecipientNotUsingConduit( + FuzzInputsCommon memory inputs + ) public { + // ERC721ReceiverMock erc721Receiver = new ERC721ReceiverMock( + // IERC721Receiver.onERC721Received.selector, + // ERC721ReceiverMock.Error.None + // ); + + uint256 numItems = 6; + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[i], + i, + address(validERC721Receiver) + ); + } + + _performMultiItemTransferAndCheckBalances(items, alice, false, ""); + } + + function testBulkTransferERC721AndERC20NotUsingConduit( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](2); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0], + inputs.recipients[0] + ); + + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[1], + inputs.tokenIndex[1], + 0, + inputs.recipients[1] + ); + + _performMultiItemTransferAndCheckBalances(items, alice, false, ""); + } + + // Test reverts + + function testRevertBulkTransferERC20InvalidIdentifier( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[0], + inputs.tokenIndex[0], + 5, + inputs.recipients[0] + ); + // Ensure ERC20 identifier is at least 1 + item.identifier += 1; + + _performSingleItemTransferAndCheckBalances( + item, + alice, + false, + abi.encodePacked( + TransferHelperErrors.InvalidERC20Identifier.selector + ) + ); + } + + function testRevertBulkTransferERC721InvalidRecipient( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0], + address(invalidRecipient), + true + ); + + _performSingleItemTransferAndCheckBalances( + item, + alice, + false, + abi.encodeWithSignature( + "InvalidERC721Recipient(address)", + invalidRecipient + ) + ); + } + + function testRevertBulkTransferETHonly(FuzzInputsCommon memory inputs) + public + { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.NATIVE, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + bytes memory returnedData; + try + transferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyOne + ) + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ), + abi.encodePacked(TransferHelperErrors.InvalidItemType.selector) + ); + } + + function testRevertBulkTransferETHandERC721(FuzzInputsCommon memory inputs) + public + { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](2); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.NATIVE, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[1], + inputs.identifiers[1], + bob + ); + + bytes memory returnedData; + try + transferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyOne + ) + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.useConduit, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ), + abi.encodePacked(TransferHelperErrors.InvalidItemType.selector) + ); + } + + function testRevertBulkTransferERC721AmountMoreThan1UsingConduit( + FuzzInputsCommon memory inputs, + uint256 invalidAmount + ) public { + vm.assume(invalidAmount > 1); + + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + TransferHelperItemWithRecipient + memory item = _getFuzzedERC721TransferItemWithAmountGreaterThan1( + alice, + invalidAmount, + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + items[0] = item; + bytes memory returnedData; + try + transferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyOne + ) + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + _performSingleItemTransferAndCheckBalances( + item, + alice, + true, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ) + ); + } + + function testRevertBulkTransferERC721AmountMoreThan1AndERC20UsingConduit( + FuzzInputsCommon memory inputs + ) public { + vm.assume(inputs.amounts[0] > 0); + + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](2); + items[0] = _getFuzzedERC721TransferItemWithAmountGreaterThan1( + alice, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + items[1] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[1], + inputs.tokenIndex[1], + inputs.identifiers[1], + bob + ); + + bytes memory returnedData; + try + transferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyOne + ) + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + + _performMultiItemTransferAndCheckBalances( + items, + alice, + true, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ) + ); + } + + function testRevertBulkTransferNotOpenConduitChannel( + FuzzInputsCommon memory inputs + ) public { + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + _updateConduitChannel(false); + + bytes memory returnedData = abi.encodeWithSelector( + 0x93daadf2, + address(transferHelper) + ); + + _performSingleItemTransferAndCheckBalances( + items[0], + alice, + true, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyOne, + conduit + ) + ); + } + + function testRevertBulkTransferUnknownConduit( + FuzzInputsCommon memory inputs, + bytes32 fuzzConduitKey + ) public { + // Assume fuzzConduitKey is not equal to TransferHelper's value for "no conduit". + vm.assume( + fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne + ); + + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[0], + inputs.tokenIndex[0], + inputs.identifiers[0], + bob + ); + + // Reassign the conduit key that gets passed into TransferHelper to fuzzConduitKey. + conduitKeyOne = fuzzConduitKey; + + (address unknownConduitAddress, ) = conduitController.getConduit( + conduitKeyOne + ); + vm.label(unknownConduitAddress, "unknown conduit"); + + vm.expectRevert(); + vm.prank(alice); + transferHelper.bulkTransferToMultipleRecipients(items, conduitKeyOne); + } + + function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) + public + { + TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[0], + inputs.identifiers[0], + address(invalidERC721Receiver), + true + ); + _performSingleItemTransferAndCheckBalances( + item, + alice, + false, + abi.encodeWithSignature( + "ERC721ReceiverErrorRevertString(string,address,address,uint256)", + "ERC721ReceiverMock: reverting", + invalidERC721Receiver, + alice, + item.identifier + ) + ); + } + + function testRevertStringErrorWithConduit() public { + TransferHelperItemWithRecipient + memory item = TransferHelperItemWithRecipient( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1, + alice + ); + + (address _conduit, ) = conduitController.getConduit(conduitKeyOne); + // Attempt to transfer ERC721 tokens from bob to alice + // Expect revert since alice owns the tokens + _performSingleItemTransferAndCheckBalances( + item, + bob, + true, + abi.encodeWithSignature( + "ConduitErrorRevertString(string,bytes32,address)", + "WRONG_FROM", + conduitKeyOne, + _conduit + ) + ); + } + + function testRevertPanicErrorWithConduit() public { + // Create ERC20 token that reverts with a panic when calling transferFrom. + TestERC20Panic panicERC20 = new TestERC20Panic(); + + // Mint ERC20 tokens to alice. + panicERC20.mint(alice, 10); + + // Approve the ERC20 tokens + panicERC20.approve(alice, 10); + + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = TransferHelperItemWithRecipient( + ConduitItemType.ERC20, + address(panicERC20), + 0, + 10, + bob + ); + + (address _conduit, ) = conduitController.getConduit(conduitKeyOne); + bytes memory panicError = abi.encodeWithSelector(0x4e487b71, 18); + + // Revert with panic error when calling execute via conduit + _performMultiItemTransferAndCheckBalances( + items, + alice, + true, + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + panicError, + conduitKeyOne, + _conduit + ) + ); + } + + function testRevertInvalidConduitMagicValue() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 2 // ConduitMockInvalidMagic + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 + ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + + assertEq(mockConduitKey, conduitKeyAlice); + + // Create item to transfer + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = TransferHelperItemWithRecipient( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1, + bob + ); + + (address _conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + + assertEq(address(mockConduit), _conduit); + assertEq(exists, true); + + vm.expectRevert( + abi.encodeWithSignature( + "InvalidConduit(bytes32,address)", + conduitKeyAlice, + mockConduit + ) + ); + mockTransferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyAlice + ); + vm.stopPrank(); + } + + function testRevertNoErrorString() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 1 // ConduitMockRevertNoReason + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 + ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockRevertNoReason mockConduit = ConduitMockRevertNoReason( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + + assertEq(mockConduitKey, conduitKeyAlice); + + // Create item to transfer + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = TransferHelperItemWithRecipient( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1, + bob + ); + + (address _conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + + assertEq(address(mockConduit), _conduit); + assertEq(exists, true); + + vm.expectRevert( + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + "", + conduitKeyAlice, + mockConduit + ) + ); + mockTransferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyAlice + ); + vm.stopPrank(); + } + + function testRevertWithData() public { + // Deploy mock conduit controller + ConduitControllerMock mockConduitController = new ConduitControllerMock( + 3 // ConduitMockRevertBytes + ); + + // Create conduit key using alice's address + bytes32 conduitKeyAlice = bytes32( + uint256(uint160(address(alice))) << 96 + ); + + // Deploy mock transfer helper that takes in the mock conduit controller + TransferHelper mockTransferHelper = TransferHelper( + deployCode( + "optimized-out/TransferHelper.sol/TransferHelper.json", + abi.encode(address(mockConduitController)) + ) + ); + vm.label(address(mockTransferHelper), "mock transfer helper"); + + vm.startPrank(alice); + + // Create the mock conduit by calling the mock conduit controller + ConduitMockInvalidMagic mockConduit = ConduitMockInvalidMagic( + mockConduitController.createConduit(conduitKeyAlice, address(alice)) + ); + vm.label(address(mockConduit), "mock conduit"); + + bytes32 conduitCodeHash = address(mockConduit).codehash; + emit log_named_bytes32("conduit code hash", conduitCodeHash); + + // Assert the conduit key derived from the conduit address + // matches alice's conduit key + bytes32 mockConduitKey = mockConduitController.getKey( + address(mockConduit) + ); + + assertEq(mockConduitKey, conduitKeyAlice); + + // Create item to transfer + TransferHelperItemWithRecipient[] + memory items = new TransferHelperItemWithRecipient[](1); + items[0] = TransferHelperItemWithRecipient( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1, + bob + ); + + (address _conduit, bool exists) = mockConduitController.getConduit( + conduitKeyAlice + ); + + assertEq(address(mockConduit), _conduit); + assertEq(exists, true); + + bytes memory returnedData; + try + mockTransferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyAlice + ) + returns ( + bytes4 /* magicValue */ + ) {} catch (bytes memory reason) { + returnedData = this.getSelector(reason); + } + vm.expectRevert( + abi.encodeWithSignature( + "ConduitErrorRevertBytes(bytes,bytes32,address)", + returnedData, + conduitKeyAlice, + mockConduit + ) + ); + mockTransferHelper.bulkTransferToMultipleRecipients( + items, + conduitKeyAlice + ); + vm.stopPrank(); + } +} diff --git a/test/foundry/TransferHelperTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol similarity index 99% rename from test/foundry/TransferHelperTest.sol rename to test/foundry/TransferHelperSingleRecipientTest.sol index e9459d0ca..81b1e7603 100644 --- a/test/foundry/TransferHelperTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -62,7 +62,7 @@ import { import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; -contract TransferHelperTest is BaseOrderTest { +contract TransferHelperSingleRecipientTest is BaseOrderTest { TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. uint256 constant TOTAL_FUNGIBLE_TOKENS = 1e6; @@ -971,7 +971,7 @@ contract TransferHelperTest is BaseOrderTest { // } bytes memory returnedData = abi.encodeWithSelector( 0x93daadf2, - 0x6b8E18793B5630b0d439F957f610B01219110940 + address(transferHelper) ); _performSingleItemTransferAndCheckBalances( From df00e5d453d1da19ddb9d87e84e7900c2bbd9295 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 16:31:29 -0700 Subject: [PATCH 086/126] add remaining hh tests --- test/transferhelper.spec.ts | 2887 ++++++++++++++++++++++------------- 1 file changed, 1826 insertions(+), 1061 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 4f1e1ce15..3501473a8 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -132,1181 +132,1946 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .updateChannel(tempConduit.address, tempTransferHelper.address, true); }); - it("Executes transfers (many token types) with a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + describe("bulkTransfer tests", async () => { + it("Executes transfers (many token types) with a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + recipient.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } - const erc20Contracts = []; - const erc20Transfers = []; + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } - const erc721Contracts = []; - const erc721Transfers = []; + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + recipient.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } - const erc1155Contracts = []; - const erc1155Transfers = []; + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer(transfers, recipient.address, tempConduitKey); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + recipient.address + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf(recipient.address, identifier) + ).to.equal(amount); + break; + } + } + }); - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempConduit.address, - sender.address, - recipient.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } + it("Executes transfers (many token types) without a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempConduit.address, - sender.address, - recipient.address - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + // Create numEC721s amount of ERC721 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempConduit.address, - sender.address, - recipient.address + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer( + transfers, + recipient.address, + ethers.utils.formatBytes32String("") + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfers.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + recipient.address + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(recipient.address); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf(recipient.address, identifier) + ).to.equal(amount); + break; + } + } + }); + + it("Executes ERC721 transfers to a contract recipient without a conduit", async () => { + // Deploy recipient contract + const erc721RecipientFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const erc721Recipient = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer(transfers, recipient.address, tempConduitKey); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf(sender.address) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - recipient.address - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal(0); - expect(await token.balanceOf(recipient.address, identifier)).to.equal( - amount - ); - break; + const erc721Contracts = []; + const erc721Transfers = []; + + // Create 5 ERC721 objects + for (let i = 0; i < 5; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + recipient.address + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; } - } - }); - it("Executes transfers (many token types) without a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + // Send the bulk transfers + await tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721Transfers, + erc721Recipient.address, + ethers.utils.formatBytes32String("") + ); - const erc20Contracts = []; - const erc20Transfers = []; + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < 5; i++) { + // Get identifier and ERC721 token contract + const { identifier } = erc721Transfers[i]; + const token = erc721Contracts[i]; - const erc721Contracts = []; - const erc721Transfers = []; + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(erc721Recipient.address); + } + }); + + it("Reverts on native token transfers", async () => { + const ethTransferHelperItems = [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + ethTransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidItemType"); + }); - const erc1155Contracts = []; - const erc1155Transfers = []; + it("Reverts on invalid ERC20 identifier", async () => { + const erc20TransferHelperItems = [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc20TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { + it("Reverts on invalid ERC721 transfer amount", async () => { // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address, - sender.address, - recipient.address - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create numEC721s amount of ERC721 objects - for (let i = 0; i < numEC721s; i++) { + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + + it("Reverts on invalid ERC721 recipient", async () => { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address, - sender.address, - recipient.address + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + tempERC721Contract.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertBytes("0x", "${tempERC721Contract.address}", "${sender.address}", 1)` ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + }); - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address, - sender.address, - recipient.address + it("Reverts on invalid function selector", async () => { + const invalidRecipientFactory = await ethers.getContractFactory( + "InvalidERC721Recipient" ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } + const invalidRecipient = await invalidRecipientFactory.deploy(); - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer( - transfers, - recipient.address, - ethers.utils.formatBytes32String("") + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + erc721TransferHelperItems, + invalidRecipient.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith( + `InvalidERC721Recipient("${invalidRecipient.address}")` ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf(sender.address) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - recipient.address - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(recipient.address); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal(0); - expect(await token.balanceOf(recipient.address, identifier)).to.equal( - amount - ); - break; - } - } - }); + }); - it("Executes transfers with multiple recipients (many token types) with a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + it("Reverts on nonexistent conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const erc20Contracts = []; - const erc20Transfers = []; + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + ethers.utils.formatBytes32String("0xabc") + ) + ).to.be.reverted; + }); - const erc721Contracts = []; - const erc721Transfers = []; + it("Reverts on error in ERC721 receiver", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const erc1155Contracts = []; - const erc1155Transfers = []; + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + Buffer.from("abcd0000", "hex"), + 1 + ); - const recipients = [ - recipient.address, - alice.address, - bob.address, - cal.address, - ]; + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + mockERC721Receiver.address, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + }); - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract + it("Reverts with custom error in conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempConduit.address, - sender.address, - recipients[i % 4] + + const transferHelperItems = [ + // Invalid item type + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + const invalidItemTypeErrorSelector = ethers.utils + .id("InvalidItemType()") + .slice(0, 10); + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } + }); - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract + it("Reverts with bubbled up string error from call to conduit", async () => { + // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempConduit.address, - sender.address, - recipients[i % 4] + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // Call will revert since ERC721 tokens have not been minted + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + }); - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract + it("Reverts when no revert string is returned from call to conduit", async () => { + // Deploy ERC1155 Contract const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempConduit.address, - sender.address, - recipients[i % 4] - ); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } + await tempERC1155Contract.connect(owner).mint(sender.address, 0, 100); - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; - - const transfersWithRecipients = []; - - for (let i = 0; i < transfers.length; i++) { - transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); - } - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransferToMultipleRecipients( - transfersWithRecipients, - tempConduitKey + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 1 // ConduitMockRevertNoReason ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfersWithRecipients.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf(sender.address) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - transfersWithRecipients[i].recipient - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(transfersWithRecipients[i].recipient); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal(0); - expect( - await token.balanceOf( - transfersWithRecipients[i].recipient, - identifier - ) - ).to.equal(amount); - break; - } - } - }); - - it("Executes transfers with multiple recipients (many token types) without a conduit", async () => { - // Get 3 Numbers that's value adds to Item Amount and minimum 1. - const itemsToCreate = 10; - const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); - const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); - const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - const erc20Contracts = []; - const erc20Transfers = []; + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); - const erc721Contracts = []; - const erc721Transfers = []; + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); - const erc1155Contracts = []; - const erc1155Transfers = []; + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; - const recipients = [ - recipient.address, - alice.address, - bob.address, - cal.address, - ]; + await tempERC1155Contract + .connect(sender) + .setApprovalForAll(mockConduitAddress, true); - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address, - sender.address, - recipients[i % 4] + const transferHelperItems = Array.from(Array(11)).map(() => ({ + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + })); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } + }); - // Create numEC721s amount of ERC721 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address, - sender.address, - recipients[i % 4] + it("Reverts with bubbled up panic error from call to conduit", async () => { + // Deploy mock ERC20 + const mockERC20PanicFactory = await ethers.getContractFactory( + "TestERC20Panic" ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + const mockERC20Panic = await mockERC20PanicFactory.deploy(); + + const transferHelperItems = [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + ).to.be.reverted; + }); - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address, - sender.address, - recipients[i % 4] - ); + it("Reverts with invalid magic value returned by call to conduit", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } + await tempERC20Contract.connect(owner).mint(sender.address, 100); - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; - - const transfersWithRecipients = []; - - for (let i = 0; i < transfers.length; i++) { - transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); - } + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 2 // ConduitMockInvalidMagic + ); - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransferToMultipleRecipients( - transfersWithRecipients, - ethers.utils.formatBytes32String("") + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfersWithRecipients.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf(sender.address) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - transfersWithRecipients[i].recipient - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(transfersWithRecipients[i].recipient); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal(0); - expect( - await token.balanceOf( - transfersWithRecipients[i].recipient, - identifier - ) - ).to.equal(amount); - break; - } - } - }); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); - it("Executes ERC721 transfers to a contract recipient without a conduit", async () => { - // Deploy recipient contract - const erc721RecipientFactory = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - const erc721Recipient = await erc721RecipientFactory.deploy( - Buffer.from("150b7a02", "hex"), - 0 - ); + it("Reverts with conduit revert data", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const erc721Contracts = []; - const erc721Transfers = []; + await tempERC20Contract.connect(owner).mint(sender.address, 100); - // Create 5 ERC721 objects - for (let i = 0; i < 5; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address, - sender.address, - recipient.address + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 3 // ConduitMockRevertBytes ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } - // Send the bulk transfers - await tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721Transfers, - erc721Recipient.address, - ethers.utils.formatBytes32String("") + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + + const customErrorSelector = ethers.utils.id("CustomError()").slice(0, 10); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < 5; i++) { - // Get identifier and ERC721 token contract - const { identifier } = erc721Transfers[i]; - const token = erc721Contracts[i]; + it("Reverts when recipient is the null address (with conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(erc721Recipient.address); - } - }); + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + ethers.constants.AddressZero, + tempConduitKey + ) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); - it("Reverts on native token transfers", async () => { - const ethTransferHelperItems = [ - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - ethTransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidItemType"); - }); + it("Reverts when recipient is the null address (without conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - it("Reverts on invalid ERC20 identifier", async () => { - const erc20TransferHelperItems = [ - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 5, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 4, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc20TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC20Identifier"); + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + ethers.constants.AddressZero, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); }); - it("Reverts on invalid ERC721 transfer amount", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + describe("bulkTransferToMultipleRecipients tests", async () => { + it("Executes transfers with multiple recipients (many token types) with a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 10, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("InvalidERC721TransferAmount"); - }); + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + const recipients = [ + recipient.address, + alice.address, + bob.address, + cal.address, + ]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } - it("Reverts on invalid ERC721 recipient", async () => { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper + // Create numEC721s amount of ERC20 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + recipients[i % 4] + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + + const transfersWithRecipients = []; + + for (let i = 0; i < transfers.length; i++) { + transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + } + // Send the bulk transfers + await tempTransferHelper .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - tempERC721Contract.address, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith( - `ERC721ReceiverErrorRevertBytes("0x", "${tempERC721Contract.address}", "${sender.address}", 1)` - ); - }); + .bulkTransferToMultipleRecipients( + transfersWithRecipients, + tempConduitKey + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfersWithRecipients.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipients[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipients[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf( + transfersWithRecipients[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + } + }); - it("Reverts on invalid function selector", async () => { - const invalidRecipientFactory = await ethers.getContractFactory( - "InvalidERC721Recipient" - ); - const invalidRecipient = await invalidRecipientFactory.deploy(); - - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - ]; - await expect( - tempTransferHelper + it("Executes transfers with multiple recipients (many token types) without a conduit", async () => { + // Get 3 Numbers that's value adds to Item Amount and minimum 1. + const itemsToCreate = 10; + const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); + const numEC721s = Math.max(1, randomInt(itemsToCreate - numERC20s - 1)); + const numERC1155s = Math.max(1, itemsToCreate - numERC20s - numEC721s); + + const erc20Contracts = []; + const erc20Transfers = []; + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc1155Contracts = []; + const erc1155Transfers = []; + + const recipients = [ + recipient.address, + alice.address, + bob.address, + cal.address, + ]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address, + sender.address, + recipients[i % 4] + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } + + // Create numEC721s amount of ERC721 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + recipients[i % 4] + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } + + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address, + sender.address, + recipients[i % 4] + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } + + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; + const contracts = [ + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts, + ]; + + const transfersWithRecipients = []; + + for (let i = 0; i < transfers.length; i++) { + transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + } + + // Send the bulk transfers + await tempTransferHelper .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - invalidRecipient.address, + .bulkTransferToMultipleRecipients( + transfersWithRecipients, ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith( - `InvalidERC721Recipient("${invalidRecipient.address}")` - ); - }); + ); + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfersWithRecipients.length; i++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = transfers[i]; + const token = contracts[i]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipients[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipients[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect(await token.balanceOf(sender.address, identifier)).to.equal( + 0 + ); + expect( + await token.balanceOf( + transfersWithRecipients[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + } + }); - it("Reverts on nonexistent conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("0xabc") - ) - ).to.be.reverted; - }); + it("Executes ERC721 transfers to multiple contract recipients without a conduit", async () => { + // Deploy recipient contract + const erc721RecipientFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const erc721RecipientOne = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 + ); - it("Reverts on error in ERC721 receiver", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + const erc721RecipientTwo = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 + ); - // Deploy mock ERC721 receiver - const mockERC721ReceiverFactory = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - const mockERC721Receiver = await mockERC721ReceiverFactory.deploy( - Buffer.from("abcd0000", "hex"), - 1 - ); + const erc721RecipientThree = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 + ); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper + const erc721RecipientFour = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 + ); + + const erc721RecipientFive = await erc721RecipientFactory.deploy( + Buffer.from("150b7a02", "hex"), + 0 + ); + + const erc721Contracts = []; + const erc721Transfers = []; + + const erc721Recipients = [ + erc721RecipientOne, + erc721RecipientTwo, + erc721RecipientThree, + erc721RecipientFour, + erc721RecipientFive, + ]; + + // Create 5 ERC721 objects + for (let i = 0; i < 5; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + erc721Recipients[i].address + ); + + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = createTransferWithRecipient(erc721Transfer); + } + + // Send the bulk transfers + await tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - mockERC721Receiver.address, + .bulkTransferToMultipleRecipients( + erc721Transfers, ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("ERC721ReceiverMock: reverting"); - }); + ); + + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < 5; i++) { + // Get identifier and ERC721 token contract + const { identifier } = erc721Transfers[i]; + const { recipient } = erc721Transfers[i]; + const token = erc721Contracts[i]; + + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(recipient); + } + }); - it("Reverts with custom error in conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - // Invalid item type - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; + it("Reverts on native token transfers", async () => { + const ethTransferHelperItems = [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + recipient: alice.address, + }, + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + recipient: bob.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + ethTransferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidItemType"); + }); - const invalidItemTypeErrorSelector = ethers.utils - .id("InvalidItemType()") - .slice(0, 10); + it("Reverts on invalid ERC20 identifier", async () => { + const erc20TransferHelperItems = [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + recipient: alice.address, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + recipient: bob.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + erc20TransferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ - tempConduit.address - }")` - ); - }); + it("Reverts on invalid ERC721 transfer amount", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - it("Reverts with bubbled up string error from call to conduit", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - // Call will revert since ERC721 tokens have not been minted - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + recipient: alice.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + recipient: bob.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + erc721TransferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ - tempConduit.address - }")` - ); - }); + it("Reverts on invalid ERC721 recipient", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + const { testERC721: tempERC721ContractTwo } = await fixtureERC721(owner); + + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: tempERC721Contract.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: tempERC721ContractTwo.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + erc721TransferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertBytes("0x", "${tempERC721Contract.address}", "${sender.address}", 1)` + ); + }); - it("Reverts when no revert string is returned from call to conduit", async () => { - // Deploy ERC1155 Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); + it("Reverts on invalid function selector", async () => { + const invalidRecipientFactory = await ethers.getContractFactory( + "InvalidERC721Recipient" + ); + const invalidRecipient = await invalidRecipientFactory.deploy(); + const invalidRecipientTwo = await invalidRecipientFactory.deploy(); - await tempERC1155Contract.connect(owner).mint(sender.address, 0, 100); + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - const mockConduitControllerFactory = await ethers.getContractFactory( - "ConduitControllerMock" - ); - const mockConduitController = await mockConduitControllerFactory.deploy( - 1 // ConduitMockRevertNoReason - ); + const erc721TransferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: invalidRecipient.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: invalidRecipientTwo.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + erc721TransferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith( + `InvalidERC721Recipient("${invalidRecipient.address}")` + ); + }); - const mockTransferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - const mockTransferHelper = await mockTransferHelperFactory.deploy( - mockConduitController.address - ); - const mockConduitKey = owner.address + randomHex(12).slice(2); + it("Reverts on nonexistent conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Deploy the mock conduit through the mock conduit controller - await mockConduitController - .connect(owner) - .createConduit(mockConduitKey, owner.address); + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: alice.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: bob.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: cal.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: alice.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transferHelperItems, + ethers.utils.formatBytes32String("0xabc") + ) + ).to.be.reverted; + }); - const mockConduitAddress = ( - await mockConduitController.getConduit(mockConduitKey) - )[0]; + it("Reverts on error in ERC721 receiver", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - await tempERC1155Contract - .connect(sender) - .setApprovalForAll(mockConduitAddress, true); + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const mockERC721ReceiverOne = await mockERC721ReceiverFactory.deploy( + Buffer.from("abcd0000", "hex"), + 1 + ); + const mockERC721ReceiverTwo = await mockERC721ReceiverFactory.deploy( + Buffer.from("abcd6969", "hex"), + 1 + ); + const mockERC721ReceiverThree = await mockERC721ReceiverFactory.deploy( + Buffer.from("42069abc", "hex"), + 1 + ); + const mockERC721ReceiverFour = await mockERC721ReceiverFactory.deploy( + Buffer.from("abc42069", "hex"), + 1 + ); - const transferHelperItems = Array.from(Array(11)).map(() => ({ - itemType: 3, - token: tempERC1155Contract.address, - identifier: 0, - amount: 10, - })); + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: mockERC721ReceiverOne.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: mockERC721ReceiverTwo.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: mockERC721ReceiverThree.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: mockERC721ReceiverFour.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + }); - await expect( - mockTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` - ); - }); + it("Reverts with custom error in conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - it("Reverts with bubbled up panic error from call to conduit", async () => { - // Deploy mock ERC20 - const mockERC20PanicFactory = await ethers.getContractFactory( - "TestERC20Panic" - ); - const mockERC20Panic = await mockERC20PanicFactory.deploy(); + const transferHelperItems = [ + // Invalid item type + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + recipient: alice.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: bob.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: cal.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: alice.address, + }, + ]; + + const invalidItemTypeErrorSelector = ethers.utils + .id("InvalidItemType()") + .slice(0, 10); + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); + }); - const transferHelperItems = [ - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 20, - }, - ]; + it("Reverts with bubbled up string error from call to conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.reverted; - }); + // Call will revert since ERC721 tokens have not been minted + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: alice.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: bob.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: cal.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: alice.address, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); + }); - it("Reverts with invalid magic value returned by call to conduit", async () => { - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + it("Reverts when no revert string is returned from call to conduit", async () => { + // Deploy ERC1155 Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155(owner); - await tempERC20Contract.connect(owner).mint(sender.address, 100); + await tempERC1155Contract.connect(owner).mint(sender.address, 0, 100); - const mockConduitControllerFactory = await ethers.getContractFactory( - "ConduitControllerMock" - ); - const mockConduitController = await mockConduitControllerFactory.deploy( - 2 // ConduitMockInvalidMagic - ); + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 1 // ConduitMockRevertNoReason + ); - const mockTransferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - const mockTransferHelper = await mockTransferHelperFactory.deploy( - mockConduitController.address - ); - const mockConduitKey = owner.address + randomHex(12).slice(2); + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); - // Deploy the mock conduit through the mock conduit controller - await mockConduitController - .connect(owner) - .createConduit(mockConduitKey, owner.address); + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); - const mockConduitAddress = ( - await mockConduitController.getConduit(mockConduitKey) - )[0]; + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; - await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + await tempERC1155Contract + .connect(sender) + .setApprovalForAll(mockConduitAddress, true); - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, + const transferHelperItems = Array.from(Array(11)).map(() => ({ + itemType: 3, + token: tempERC1155Contract.address, identifier: 0, amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; + recipient: alice.address, + })); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); - await expect( - mockTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith( - `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` - ); - }); + it("Reverts with bubbled up panic error from call to conduit", async () => { + // Deploy mock ERC20 + const mockERC20PanicFactory = await ethers.getContractFactory( + "TestERC20Panic" + ); + const mockERC20Panic = await mockERC20PanicFactory.deploy(); + + const transferHelperItems = [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + recipient: alice.address, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + recipient: bob.address, + }, + ]; + + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + ).to.be.reverted; + }); - it("Reverts when data length is greater than 0 and less than 256", async () => { - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + it("Reverts with invalid magic value returned by call to conduit", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - await tempERC20Contract.connect(owner).mint(sender.address, 100); + await tempERC20Contract.connect(owner).mint(sender.address, 100); - const mockConduitControllerFactory = await ethers.getContractFactory( - "ConduitControllerMock" - ); - const mockConduitController = await mockConduitControllerFactory.deploy( - 3 // ConduitMockRevertBytes - ); + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 2 // ConduitMockInvalidMagic + ); - const mockTransferHelperFactory = await ethers.getContractFactory( - "TransferHelper" - ); - const mockTransferHelper = await mockTransferHelperFactory.deploy( - mockConduitController.address - ); - const mockConduitKey = owner.address + randomHex(12).slice(2); + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: alice.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: bob.address, + }, + ]; + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + ).to.be.revertedWith( + `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); - // Deploy the mock conduit through the mock conduit controller - await mockConduitController - .connect(owner) - .createConduit(mockConduitKey, owner.address); + it("Reverts with conduit revert data", async () => { + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const mockConduitAddress = ( - await mockConduitController.getConduit(mockConduitKey) - )[0]; - await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + await tempERC20Contract.connect(owner).mint(sender.address, 100); - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; + const mockConduitControllerFactory = await ethers.getContractFactory( + "ConduitControllerMock" + ); + const mockConduitController = await mockConduitControllerFactory.deploy( + 3 // ConduitMockRevertBytes + ); - const customErrorSelector = ethers.utils.id("CustomError()").slice(0, 10); + const mockTransferHelperFactory = await ethers.getContractFactory( + "TransferHelper" + ); + const mockTransferHelper = await mockTransferHelperFactory.deploy( + mockConduitController.address + ); + const mockConduitKey = owner.address + randomHex(12).slice(2); + + // Deploy the mock conduit through the mock conduit controller + await mockConduitController + .connect(owner) + .createConduit(mockConduitKey, owner.address); + + const mockConduitAddress = ( + await mockConduitController.getConduit(mockConduitKey) + )[0]; + await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); + + const transferHelperItems = [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: alice.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: bob.address, + }, + ]; + + const customErrorSelector = ethers.utils.id("CustomError()").slice(0, 10); + + await expect( + mockTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` + ); + }); - await expect( - mockTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` - ); - }); + it("Reverts when recipient is the null address (with conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - it("Reverts when recipient is the null address (with conduit)", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - ethers.constants.AddressZero, - tempConduitKey - ) - ).to.be.revertedWith("RecipientCannotBeZero()"); - }); + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: alice.address, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: ethers.constants.AddressZero, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: cal.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: alice.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); - it("Reverts when recipient is the null address (without conduit)", async () => { - // Deploy ERC721 Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Deploy ERC20 Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - }, - ]; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer( - transferHelperItems, - ethers.constants.AddressZero, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("RecipientCannotBeZero()"); + it("Reverts when recipient is the null address (without conduit)", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + const transferHelperItems = [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + recipient: ethers.constants.AddressZero, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + recipient: bob.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + recipient: cal.address, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + recipient: alice.address, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transferHelperItems, + ethers.utils.formatBytes32String("") + ) + ).to.be.revertedWith("RecipientCannotBeZero()"); + }); }); }); From bf63b67440e8be3914ce66fdbf66c09c19e95a56 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 17:38:03 -0700 Subject: [PATCH 087/126] update reverts in hh tests --- test/transferhelper.spec.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 3501473a8..97f376874 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -673,7 +673,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { mockERC721Receiver.address, ethers.utils.formatBytes32String("") ) - ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertString("ERC721ReceiverMock: reverting", "${mockERC721Receiver.address}", "${sender.address}", 1` + ); }); it("Reverts with custom error in conduit", async () => { @@ -842,11 +844,18 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { }, ]; + const panicError = + "0x4e487b710000000000000000000000000000000000000000000000000000000000000012"; + await expect( tempTransferHelper .connect(sender) .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.reverted; + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); }); it("Reverts with invalid magic value returned by call to conduit", async () => { @@ -1681,7 +1690,9 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { transferHelperItems, ethers.utils.formatBytes32String("") ) - ).to.be.revertedWith("ERC721ReceiverMock: reverting"); + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertString("ERC721ReceiverMock: reverting", "${mockERC721ReceiverOne.address}", "${sender.address}", 1` + ); }); it("Reverts with custom error in conduit", async () => { @@ -1861,11 +1872,18 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { }, ]; + const panicError = + "0x4e487b710000000000000000000000000000000000000000000000000000000000000012"; + await expect( tempTransferHelper .connect(sender) .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) - ).to.be.reverted; + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); }); it("Reverts with invalid magic value returned by call to conduit", async () => { From 12730c20250133a818e21869e2aae7e0d4828574 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 17:48:10 -0700 Subject: [PATCH 088/126] modify hh tests for reference contracts --- test/transferhelper.spec.ts | 67 ++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 97f376874..836f89e91 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -846,16 +846,31 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const panicError = "0x4e487b710000000000000000000000000000000000000000000000000000000000000012"; - - await expect( - tempTransferHelper - .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ - tempConduit.address - }")` - ); + if (!process.env.REFERENCE) { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + tempConduitKey + ) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); + } else { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer( + transferHelperItems, + recipient.address, + tempConduitKey + ) + ).to.be.reverted; + } }); it("Reverts with invalid magic value returned by call to conduit", async () => { @@ -1875,15 +1890,29 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const panicError = "0x4e487b710000000000000000000000000000000000000000000000000000000000000012"; - await expect( - tempTransferHelper - .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) - ).to.be.revertedWith( - `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ - tempConduit.address - }")` - ); + if (!process.env.REFERNCE) { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transferHelperItems, + tempConduitKey + ) + ).to.be.revertedWith( + `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ + tempConduit.address + }")` + ); + } else { + await expect( + tempTransferHelper + .connect(sender) + .bulkTransferToMultipleRecipients( + transferHelperItems, + tempConduitKey + ) + ).to.be.reverted; + } }); it("Reverts with invalid magic value returned by call to conduit", async () => { From 1d2d332e389f52e326c3a17cfc7b7318a5d5740d Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 18:05:30 -0700 Subject: [PATCH 089/126] typo --- test/transferhelper.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 836f89e91..acb99607f 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -1890,7 +1890,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const panicError = "0x4e487b710000000000000000000000000000000000000000000000000000000000000012"; - if (!process.env.REFERNCE) { + if (!process.env.REFERENCE) { await expect( tempTransferHelper .connect(sender) From c0e25e1f6e49b38b29f00b64f747215be5320e3a Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 18:20:26 -0700 Subject: [PATCH 090/126] remove unused mock conduit --- contracts/test/ConduitMockErrors.sol | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 contracts/test/ConduitMockErrors.sol diff --git a/contracts/test/ConduitMockErrors.sol b/contracts/test/ConduitMockErrors.sol deleted file mode 100644 index 49c84e6b8..000000000 --- a/contracts/test/ConduitMockErrors.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.7; - -interface ConduitMockErrors { - error RevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevertRevert(); -} From 2b66d15439e3edc78ae0da7c63ab3e0fe4f322ed Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 12 Jul 2022 18:42:00 -0700 Subject: [PATCH 091/126] only check recipientIsContract for erc721 transfers to multi recipients, change magic for bulkTransferToMultipleRecipients --- contracts/helpers/TransferHelper.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index ecf49f459..931602ff0 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -100,6 +100,7 @@ contract TransferHelper is TransferHelperItemWithRecipient[] calldata items, bytes32 conduitKey ) external override returns (bytes4 magicValue) { + // If no conduitKey is given, use TokenTransferrer to perform transfers. if (conduitKey == bytes32(0)) { _performTransfersWithoutConduit(items); } else { @@ -108,7 +109,7 @@ contract TransferHelper is } // Return a magic value indicating that the transfers were performed. - magicValue = this.bulkTransfer.selector; + magicValue = this.bulkTransferToMultipleRecipients.selector; } /** @@ -130,7 +131,7 @@ contract TransferHelper is // Retrieve total number of transfers and place on stack. uint256 totalTransfers = items.length; - // Create a boolean that reflects whether recipient is a contract. + // Create a boolean indicating whether recipient is a contract. bool recipientIsContract = recipient.code.length != 0; // Skip overflow checks: all for loops are indexed starting at zero. @@ -247,9 +248,6 @@ contract TransferHelper is revert RecipientCannotBeZero(); } - // Create a boolean that reflects whether recipient is a contract. - bool recipientIsContract = item.recipient.code.length != 0; - // Perform a transfer based on the transfer's item type. if (item.itemType == ConduitItemType.ERC20) { // Ensure that the identifier for an ERC20 token is 0. @@ -265,6 +263,10 @@ contract TransferHelper is item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { + // Create a boolean indicating whether recipient is + // a contract. + bool recipientIsContract = item.recipient.code.length != 0; + // If recipient is a contract, ensure it can receive // ERC721 tokens. if (recipientIsContract) { From a9c1119d9ee9c9f8120f582f2cc08dafd5df489c Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 13 Jul 2022 11:19:44 -0700 Subject: [PATCH 092/126] lint:fix --- test/utils/fixtures/conduit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/fixtures/conduit.ts b/test/utils/fixtures/conduit.ts index 100da1b94..b359c9d07 100644 --- a/test/utils/fixtures/conduit.ts +++ b/test/utils/fixtures/conduit.ts @@ -91,7 +91,7 @@ export const conduitFixture = async ( const { conduit: tempConduitAddress } = await conduitController.getConduit( assignedConduitKey ); - + if (!process.env.REFERENCE) { await expect( conduitController From 7788302b234e5c0406fc883dacd7841717bc20ba Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 15 Jul 2022 14:53:18 -0700 Subject: [PATCH 093/126] update internal fn to iterate through transfers and increment totalItems by number of items per transfer --- contracts/helpers/TransferHelper.sol | 457 ++++++------------ contracts/helpers/TransferHelperStructs.sol | 16 +- contracts/interfaces/TransferHelperErrors.sol | 2 +- .../interfaces/TransferHelperInterface.sol | 17 +- contracts/test/ERC721ReceiverMock.sol | 10 +- 5 files changed, 144 insertions(+), 358 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 931602ff0..42f21132c 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -29,7 +29,7 @@ import { TransferHelperErrors } from "../interfaces/TransferHelperErrors.sol"; /** * @title TransferHelper - * @author stuckinaboot, stephankmin, ryanio + * @author stephankmin, stuckinaboot, ryanio * @notice TransferHelper is a utility contract for transferring * ERC20/ERC721/ERC1155 items in bulk to a specific recipient. */ @@ -68,36 +68,16 @@ contract TransferHelper is } /** - * @notice Transfer multiple items to a single recipient by calling one of - * two internal functions, depending on whether a conduit key is - * passed into the function. + * @notice Transfer multiple items to recipients. * - * @param items The items to transfer. - * @param recipient The address the items should be transferred to. + * @param items The items to transfer with the intended recipient. * @param conduitKey An optional conduit key referring to a conduit through * which the bulk transfer should occur. * * @return magicValue A value indicating that the transfers were successful. */ function bulkTransfer( - TransferHelperItem[] calldata items, - address recipient, - bytes32 conduitKey - ) external override returns (bytes4 magicValue) { - // If no conduitKey is given, use TokenTransferrer to perform transfers. - if (conduitKey == bytes32(0)) { - _performTransfersWithoutConduit(items, recipient); - } else { - // Otherwise, a conduitKey was provided. - _performTransfersWithConduit(items, recipient, conduitKey); - } - - // Return a magic value indicating that the transfers were performed. - magicValue = this.bulkTransfer.selector; - } - - function bulkTransferToMultipleRecipients( - TransferHelperItemWithRecipient[] calldata items, + TransferHelperItemsWithRecipient[] calldata items, bytes32 conduitKey ) external override returns (bytes4 magicValue) { // If no conduitKey is given, use TokenTransferrer to perform transfers. @@ -109,253 +89,146 @@ contract TransferHelper is } // Return a magic value indicating that the transfers were performed. - magicValue = this.bulkTransferToMultipleRecipients.selector; - } - - /** - * @notice Perform multiple transfers to a single recipient via - * TokenTransferrer. - * - * @param items The items to transfer. - * @param recipient The address the items should be transferred to. - */ - function _performTransfersWithoutConduit( - TransferHelperItem[] calldata items, - address recipient - ) internal { - // Ensure tokens aren't transferred to the zero address. - if (recipient == address(0x0)) { - revert RecipientCannotBeZero(); - } - - // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = items.length; - - // Create a boolean indicating whether recipient is a contract. - bool recipientIsContract = recipient.code.length != 0; - - // Skip overflow checks: all for loops are indexed starting at zero. - unchecked { - // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { - // Retrieve the transfer in question. - TransferHelperItem calldata item = items[i]; - - // Perform a transfer based on the transfer's item type. - if (item.itemType == ConduitItemType.ERC20) { - // Ensure that the identifier for an ERC20 token is 0. - if (item.identifier != 0) { - revert InvalidERC20Identifier(); - } - - // Transfer ERC20 token. - _performERC20Transfer( - item.token, - msg.sender, - recipient, - item.amount - ); - } else if (item.itemType == ConduitItemType.ERC721) { - // If recipient is a contract, ensure it can receive - // ERC721 tokens. - if (recipientIsContract) { - // Check if recipient can receive ERC721 tokens. - try - IERC721Receiver(recipient).onERC721Received( - address(this), - msg.sender, - item.identifier, - "" - ) - returns (bytes4 selector) { - // Check if onERC721Received selector is valid. - if ( - selector != - IERC721Receiver.onERC721Received.selector - ) { - // Revert if recipient cannot accept - // ERC721 tokens. - revert InvalidERC721Recipient(recipient); - } - } catch (bytes memory data) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertBytes( - data, - recipient, - msg.sender, - item.identifier - ); - } catch Error(string memory reason) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertString( - reason, - recipient, - msg.sender, - item.identifier - ); - } - } - // Ensure that the amount for an ERC721 transfer is 1. - if (item.amount != 1) { - revert InvalidERC721TransferAmount(); - } - - // Transfer ERC721 token. - _performERC721Transfer( - item.token, - msg.sender, - recipient, - item.identifier - ); - } else if (item.itemType == ConduitItemType.ERC1155) { - // Transfer ERC1155 token. - _performERC1155Transfer( - item.token, - msg.sender, - recipient, - item.identifier, - item.amount - ); - } else { - // Revert if the item being transferred is a native token. - revert InvalidItemType(); - } - } - } + magicValue = this.bulkTransfer.selector; } /** * @notice Perform multiple transfers to individually-specified recipients * via TokenTransferrer. * - * @param items The items to transfer. + * @param transfers The transfers to perform. */ function _performTransfersWithoutConduit( - TransferHelperItemWithRecipient[] calldata items + TransferHelperItemsWithRecipient[] calldata transfers ) internal { // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = items.length; + uint256 totalTransfers = transfers.length; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. for (uint256 i = 0; i < totalTransfers; ++i) { // Retrieve the transfer in question. - TransferHelperItemWithRecipient calldata item = items[i]; + TransferHelperItemsWithRecipient calldata transfer = transfers[ + i + ]; + TransferHelperItem[] calldata transferItems = transfer.items; + address recipient = transfer.recipient; // Ensure tokens aren't transferred to the zero address. - if (item.recipient == address(0x0)) { - revert RecipientCannotBeZero(); + if (recipient == address(0x0)) { + revert RecipientCannotBeZeroAddress(); } - // Perform a transfer based on the transfer's item type. - if (item.itemType == ConduitItemType.ERC20) { - // Ensure that the identifier for an ERC20 token is 0. - if (item.identifier != 0) { - revert InvalidERC20Identifier(); - } + // Create a boolean indicating whether recipient is + // a contract. + bool callERC721Receiver = transfer.validateERC721Receiver && + recipient.code.length != 0; - // Transfer ERC20 token. - _performERC20Transfer( - item.token, - msg.sender, - item.recipient, - item.amount - ); - } else if (item.itemType == ConduitItemType.ERC721) { - // Create a boolean indicating whether recipient is - // a contract. - bool recipientIsContract = item.recipient.code.length != 0; - - // If recipient is a contract, ensure it can receive - // ERC721 tokens. - if (recipientIsContract) { - // Check if recipient can receive ERC721 tokens. - try - IERC721Receiver(item.recipient).onERC721Received( - address(this), - msg.sender, - item.identifier, - "" - ) - returns (bytes4 selector) { - // Check if onERC721Received selector is valid. - if ( - selector != - IERC721Receiver.onERC721Received.selector - ) { - // Revert if recipient cannot accept - // ERC721 tokens. - revert InvalidERC721Recipient(item.recipient); + // Retrieve total number of transfers and place on stack. + uint256 totalItemTransfers = transferItems.length; + + for (uint256 j = 0; j < totalItemTransfers; ++j) { + TransferHelperItem calldata item = transferItems[j]; + + // Perform a transfer based on the transfer's item type. + if (item.itemType == ConduitItemType.ERC20) { + // Ensure that the identifier for an ERC20 token is 0. + if (item.identifier != 0) { + revert InvalidERC20Identifier(); + } + + // Transfer ERC20 token. + _performERC20Transfer( + item.token, + msg.sender, + recipient, + item.amount + ); + } else if (item.itemType == ConduitItemType.ERC721) { + // If recipient is a contract, ensure it can receive + // ERC721 tokens. + if (callERC721Receiver) { + // Check if recipient can receive ERC721 tokens. + try + IERC721Receiver(recipient).onERC721Received( + address(this), + msg.sender, + item.identifier, + "" + ) + returns (bytes4 selector) { + // Check if onERC721Received selector is valid. + if ( + selector != + IERC721Receiver.onERC721Received.selector + ) { + // Revert if recipient cannot accept + // ERC721 tokens. + revert InvalidERC721Recipient(recipient); + } + } catch (bytes memory data) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertBytes( + data, + recipient, + msg.sender, + item.identifier + ); + } catch Error(string memory reason) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertString( + reason, + recipient, + msg.sender, + item.identifier + ); } - } catch (bytes memory data) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertBytes( - data, - item.recipient, - msg.sender, - item.identifier - ); - } catch Error(string memory reason) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertString( - reason, - item.recipient, - msg.sender, - item.identifier - ); } - } - // Ensure that the amount for an ERC721 transfer is 1. - if (item.amount != 1) { - revert InvalidERC721TransferAmount(); - } + // Ensure that the amount for an ERC721 transfer is 1. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } - // Transfer ERC721 token. - _performERC721Transfer( - item.token, - msg.sender, - item.recipient, - item.identifier - ); - } else if (item.itemType == ConduitItemType.ERC1155) { - // Transfer ERC1155 token. - _performERC1155Transfer( - item.token, - msg.sender, - item.recipient, - item.identifier, - item.amount - ); - } else { - // Revert if the item being transferred is a native token. - revert InvalidItemType(); + // Transfer ERC721 token. + _performERC721Transfer( + item.token, + msg.sender, + recipient, + item.identifier + ); + } else if (item.itemType == ConduitItemType.ERC1155) { + // Transfer ERC1155 token. + _performERC1155Transfer( + item.token, + msg.sender, + recipient, + item.identifier, + item.amount + ); + } else { + // Revert if the item being transferred is a native token. + revert InvalidItemType(); + } } } } } /** - * @notice Perform multiple transfers to a single recipient via - * the conduit derived from the provided conduit key. + * @notice Perform multiple transfers to individually-specified recipients + * via the conduit derived from the provided conduit key. * - * @param items The items to transfer. - * @param recipient The address the items should be transferred to. + * @param transfers The items to transfer. * @param conduitKey The conduit key referring to the conduit through * which the bulk transfer should occur. */ function _performTransfersWithConduit( - TransferHelperItem[] calldata items, - address recipient, + TransferHelperItemsWithRecipient[] calldata transfers, bytes32 conduitKey ) internal { - // Ensure tokens aren't transferred to the zero address. - if (recipient == address(0x0)) { - revert RecipientCannotBeZero(); - } - // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = items.length; + uint256 totalTransfers = transfers.length; // Derive the conduit address from the deployer, conduit key // and creation code hash. @@ -374,90 +247,22 @@ contract TransferHelper is ) ); - // Declare a new array to populate with each token transfer. - ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( - totalTransfers - ); + // Declare a variable to store the sum of all items across all transfers. + uint256 totalItems; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. for (uint256 i = 0; i < totalTransfers; ++i) { - // Retrieve the transfer in question. - TransferHelperItem calldata item = items[i]; - - // Create a ConduitTransfer corresponding to each - // TransferHelperItem. - conduitTransfers[i] = ConduitTransfer( - item.itemType, - item.token, - msg.sender, - recipient, - item.identifier, - item.amount - ); - } - } - - // Attempt the external call to transfer tokens via the derived conduit. - try ConduitInterface(conduit).execute(conduitTransfers) returns ( - bytes4 conduitMagicValue - ) { - // Check if the value returned from the external call matches - // the conduit `execute` selector. - if ( - conduitMagicValue != ConduitInterface(conduit).execute.selector - ) { - // If the external call fails, revert with the conduit key - // and conduit address. - revert InvalidConduit(conduitKey, conduit); + // Increment totalItems by the number of items in the transfer. + totalItems += transfers[i].items.length; } - } catch (bytes memory data) { - // Catch reverts from the external call to the conduit and - // "bubble up" the conduit's revert reason. - revert ConduitErrorRevertBytes(data, conduitKey, conduit); - } catch Error(string memory reason) { - // Catch reverts with a provided reason string and - // revert with the reason, conduit key and conduit address. - revert ConduitErrorRevertString(reason, conduitKey, conduit); } - } - - /** - * @notice Perform multiple transfers to individually-specified recipients - * via the conduit derived from the provided conduit key. - * - * @param items The items to transfer. - * @param conduitKey The conduit key referring to the conduit through - * which the bulk transfer should occur. - */ - function _performTransfersWithConduit( - TransferHelperItemWithRecipient[] calldata items, - bytes32 conduitKey - ) internal { - // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = items.length; - - // Derive the conduit address from the deployer, conduit key - // and creation code hash. - address conduit = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - address(_CONDUIT_CONTROLLER), - conduitKey, - _CONDUIT_CREATION_CODE_HASH - ) - ) - ) - ) - ); - // Declare a new array to populate with each token transfer. + // Declare a new array in memory with length totalItems to populate with + // each conduit transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( - totalTransfers + totalItems ); // Skip overflow checks: all for loops are indexed starting at zero. @@ -465,23 +270,33 @@ contract TransferHelper is // Iterate over each transfer. for (uint256 i = 0; i < totalTransfers; ++i) { // Retrieve the transfer in question. - TransferHelperItemWithRecipient calldata item = items[i]; + TransferHelperItemsWithRecipient calldata transfer = transfers[ + i + ]; + TransferHelperItem[] calldata transferItems = transfer.items; + address recipient = transfer.recipient; // Ensure tokens aren't transferred to the zero address. - if (item.recipient == address(0x0)) { - revert RecipientCannotBeZero(); + if (recipient == address(0x0)) { + revert RecipientCannotBeZeroAddress(); } - // Create a ConduitTransfer corresponding to each - // TransferHelperItem. - conduitTransfers[i] = ConduitTransfer( - item.itemType, - item.token, - msg.sender, - item.recipient, - item.identifier, - item.amount - ); + // Retrieve total number of transfers and place on stack. + uint256 totalItemTransfers = transferItems.length; + + for (uint256 j = 0; j < totalItemTransfers; ++j) { + TransferHelperItem calldata item = transferItems[j]; + // Create a ConduitTransfer corresponding to each + // TransferHelperItem. + conduitTransfers[j] = ConduitTransfer( + item.itemType, + item.token, + msg.sender, + recipient, + item.identifier, + item.amount + ); + } } } diff --git a/contracts/helpers/TransferHelperStructs.sol b/contracts/helpers/TransferHelperStructs.sol index 1dd46ecfd..d8c61509a 100644 --- a/contracts/helpers/TransferHelperStructs.sol +++ b/contracts/helpers/TransferHelperStructs.sol @@ -10,17 +10,9 @@ struct TransferHelperItem { uint256 amount; } -struct TransferHelperItemWithRecipient { - ConduitItemType itemType; - address token; - uint256 identifier; - uint256 amount; +struct TransferHelperItemsWithRecipient { + TransferHelperItem[] items; address recipient; -} - -enum Error { - None, - RevertWithMessage, - RevertWithoutMessage, - Panic + /* Pass true to call onERC721Received on a recipient contract. */ + bool validateERC721Receiver; } diff --git a/contracts/interfaces/TransferHelperErrors.sol b/contracts/interfaces/TransferHelperErrors.sol index ecf820313..2b84fd752 100644 --- a/contracts/interfaces/TransferHelperErrors.sol +++ b/contracts/interfaces/TransferHelperErrors.sol @@ -47,7 +47,7 @@ interface TransferHelperErrors { /** * @dev Revert with an error if the recipient is the zero address. */ - error RecipientCannotBeZero(); + error RecipientCannotBeZeroAddress(); /** * @dev Revert with an error when attempting to fill an order referencing an diff --git a/contracts/interfaces/TransferHelperInterface.sol b/contracts/interfaces/TransferHelperInterface.sol index 7366f092e..e6fcc705a 100644 --- a/contracts/interfaces/TransferHelperInterface.sol +++ b/contracts/interfaces/TransferHelperInterface.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.7; import { TransferHelperItem, - TransferHelperItemWithRecipient + TransferHelperItemsWithRecipient } from "../helpers/TransferHelperStructs.sol"; interface TransferHelperInterface { @@ -11,23 +11,10 @@ interface TransferHelperInterface { * @notice Transfer multiple items to a single recipient. * * @param items The items to transfer. - * @param recipient The address the items should be transferred to. * @param conduitKey The key of the conduit performing the bulk transfer. */ function bulkTransfer( - TransferHelperItem[] calldata items, - address recipient, - bytes32 conduitKey - ) external returns (bytes4); - - /** - * @notice Transfer multiple items to multiple recipients. - * - * @param items The items to transfer. - * @param conduitKey The key of the conduit performing the bulk transfer. - */ - function bulkTransferToMultipleRecipients( - TransferHelperItemWithRecipient[] calldata items, + TransferHelperItemsWithRecipient[] calldata items, bytes32 conduitKey ) external returns (bytes4); } diff --git a/contracts/test/ERC721ReceiverMock.sol b/contracts/test/ERC721ReceiverMock.sol index e69b02b1c..8d69b85d4 100644 --- a/contracts/test/ERC721ReceiverMock.sol +++ b/contracts/test/ERC721ReceiverMock.sol @@ -1,14 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; - -interface IERC721Receiver { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external returns (bytes4); -} +import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol"; contract ERC721ReceiverMock is IERC721Receiver { enum Error { From b639bf6a6dcaab8564d019cbaa4e9b2aea693483 Mon Sep 17 00:00:00 2001 From: Aspyn Palatnick Date: Fri, 15 Jul 2022 19:10:08 -0400 Subject: [PATCH 094/126] first stab --- .../TransferHelperMultipleRecipientsTest.sol | 344 +++++++++++------- .../TransferHelperSingleRecipientTest.sol | 121 +++++- 2 files changed, 319 insertions(+), 146 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 2c3d0d969..798fe9edc 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -14,7 +14,8 @@ import { ConduitItemType } from "../../contracts/conduit/lib/ConduitEnums.sol"; import { TransferHelper } from "../../contracts/helpers/TransferHelper.sol"; import { - TransferHelperItemWithRecipient + TransferHelperItem, + TransferHelperItemsWithRecipient } from "../../contracts/helpers/TransferHelperStructs.sol"; import { TestERC20 } from "../../contracts/test/TestERC20.sol"; @@ -84,9 +85,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { struct FuzzInputsCommon { // Indicates if a conduit should be used for the transfer bool useConduit; - // Amounts that can be used for the amount field on TransferHelperItemWithRecipient + // Amounts that can be used for the amount field on TransferHelperItem uint256[10] amounts; - // Identifiers that can be used for the identifier field on TransferHelperItemWithRecipient + // Identifiers that can be used for the identifier field on TransferHelperItem uint256[10] identifiers; // Indexes that can be used to select tokens from the arrays erc20s/erc721s/erc1155s uint256[10] tokenIndex; @@ -195,7 +196,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } function _balanceOfTransferItemForAddress( - TransferHelperItemWithRecipient memory item, + TransferHelperItem memory item, address addr ) internal view returns (uint256) { if (item.itemType == ConduitItemType.ERC20) { @@ -214,36 +215,56 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } function _balanceOfTransferItemForFromTo( - TransferHelperItemWithRecipient memory item, - address from + TransferHelperItem memory item, + address from, + address to ) internal view returns (FromToBalance memory) { return FromToBalance( _balanceOfTransferItemForAddress(item, from), - _balanceOfTransferItemForAddress(item, item.recipient) + _balanceOfTransferItemForAddress(item, to) + ); + } + + function _getTransferHelperWithRecipientsFromTransferHelperItems( + TransferHelperItem[] memory items, + // TODO stephen: support multiple to (recipients) + address to + ) internal view returns (TransferHelperItemsWithRecipient[] memory) { + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + items.length ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + to, + true + ); + return itemsWithRecipient; } function _performSingleItemTransferAndCheckBalances( - TransferHelperItemWithRecipient memory item, + TransferHelperItem memory item, address from, + address to, bool useConduit, bytes memory expectRevertData ) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); + TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = item; _performMultiItemTransferAndCheckBalances( items, from, + to, useConduit, expectRevertData ); } function _performMultiItemTransferAndCheckBalances( - TransferHelperItemWithRecipient[] memory items, + TransferHelperItem[] memory items, address from, + address to, bool useConduit, bytes memory expectRevertData ) public { @@ -256,7 +277,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < items.length; i++) { beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( items[i], - from + from, + to ); } @@ -270,8 +292,18 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(expectRevertData); } // Perform transfer. - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( items, + to, + true + ); + + transferHelper.bulkTransfer( + itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); @@ -282,7 +314,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < items.length; i++) { afterTransferBalances[i] = _balanceOfTransferItemForFromTo( items[i], - from + from, + to ); } @@ -320,8 +353,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } function _performMultiItemTransferAndCheckBalances( - TransferHelperItemWithRecipient[] memory items, + TransferHelperItem[] memory items, address from, + address to, bool useConduit, bytes memory expectRevertDataWithConduit, bytes memory expectRevertDataWithoutConduit @@ -335,7 +369,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < items.length; i++) { beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( items[i], - from + from, + to ); } @@ -356,8 +391,17 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(expectRevertDataWithoutConduit); } // Perform transfer. - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( items, + to, + true + ); + transferHelper.bulkTransfer( + itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); @@ -368,7 +412,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < items.length; i++) { afterTransferBalances[i] = _balanceOfTransferItemForFromTo( items[i], - from + from, + to ); } @@ -445,7 +490,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256 fuzzIndex, uint256 fuzzIdentifier, address fuzzRecipient - ) internal view returns (TransferHelperItemWithRecipient memory) { + ) internal view returns (TransferHelperItem memory) { return _getFuzzedTransferItem( from, @@ -466,7 +511,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256 fuzzIdentifier, address fuzzRecipient, bool reverting - ) internal view returns (TransferHelperItemWithRecipient memory) { + ) internal view returns (TransferHelperItem memory) { uint256 amount = fuzzAmount % TOTAL_FUNGIBLE_TOKENS; uint256 identifier = fuzzIdentifier % TOTAL_TOKEN_IDENTIFERS; address recipient = _makeSafeRecipient(from, fuzzRecipient, reverting); @@ -474,42 +519,42 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256 index = fuzzIndex % erc20s.length; TestERC20 erc20 = erc20s[index]; return - TransferHelperItemWithRecipient( + TransferHelperItem( itemType, address(erc20), identifier, - amount, - recipient + amount + // recipient ); } else if (itemType == ConduitItemType.ERC1155) { uint256 index = fuzzIndex % erc1155s.length; TestERC1155 erc1155 = erc1155s[index]; return - TransferHelperItemWithRecipient( + TransferHelperItem( itemType, address(erc1155), identifier, - amount, - recipient + amount + // recipient ); } else if (itemType == ConduitItemType.NATIVE) { return - TransferHelperItemWithRecipient( + TransferHelperItem( itemType, address(0), identifier, - amount, - recipient + amount + // recipient ); } else if (itemType == ConduitItemType.ERC721) { uint256 index = fuzzIndex % erc721s.length; return - TransferHelperItemWithRecipient( + TransferHelperItem( itemType, address(erc721s[index]), identifier, - 1, - recipient + 1 + // recipient ); } revert(); @@ -521,8 +566,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256 fuzzIndex, uint256 fuzzIdentifier, address fuzzRecipient - ) internal view returns (TransferHelperItemWithRecipient memory) { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + ) internal view returns (TransferHelperItem memory) { + TransferHelperItem memory item = _getFuzzedTransferItem( from, ConduitItemType.ERC721, fuzzAmount, @@ -545,7 +590,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // Test successful transfers function testBulkTransferERC20(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC20, inputs.amounts[0], @@ -557,13 +602,14 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + inputs.recipients[0], inputs.useConduit, "" ); } function testBulkTransferERC721(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, inputs.amounts[0], @@ -575,6 +621,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -583,7 +630,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC721toBobThenCal(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, inputs.amounts[0], @@ -592,7 +639,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bob ); - TransferHelperItemWithRecipient memory item2 = _getFuzzedTransferItem( + TransferHelperItem memory item2 = _getFuzzedTransferItem( bob, ConduitItemType.ERC721, inputs.amounts[0], @@ -604,19 +651,21 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + bob, inputs.useConduit, "" ); _performSingleItemTransferAndCheckBalances( item2, bob, + cal, inputs.useConduit, "" ); } function testBulkTransferERC1155(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC1155, inputs.amounts[0], @@ -628,6 +677,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -636,8 +686,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC1155andERC721(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](2); + TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC1155, @@ -658,6 +707,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -666,8 +716,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC1155andERC721andERC20( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](3); + TransferHelperItem[] memory items = new TransferHelperItem[](3); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC1155, @@ -696,6 +745,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -705,8 +755,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { FuzzInputsCommon memory inputs ) public { uint256 numItems = 3; - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](numItems); + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( alice, @@ -724,6 +773,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -732,8 +782,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferMultipleERC721DifferentContracts( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](3); + TransferHelperItem[] memory items = new TransferHelperItem[](3); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, @@ -763,6 +812,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -772,8 +822,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { FuzzInputsCommon memory inputs ) public { uint256 numItems = 6; - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](numItems); + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); // Fill items such that the first floor(numItems / 2) items are ERC1155 and the remaining // items are ERC721 @@ -803,6 +852,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + inputs.recipients[0], inputs.useConduit, "" ); @@ -811,7 +861,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC7211NotUsingConduit( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, 1, @@ -820,7 +870,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { inputs.recipients[0] ); - _performSingleItemTransferAndCheckBalances(item, alice, false, ""); + _performSingleItemTransferAndCheckBalances( + item, + alice, + inputs.recipients[0], + false, + "" + ); } function testBulkTransferERC721ToContractRecipientNotUsingConduit( @@ -832,8 +888,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // ); uint256 numItems = 6; - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](numItems); + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( @@ -846,14 +901,19 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - _performMultiItemTransferAndCheckBalances(items, alice, false, ""); + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.recipients[0], + false, + "" + ); } function testBulkTransferERC721AndERC20NotUsingConduit( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](2); + TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, @@ -872,7 +932,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { inputs.recipients[1] ); - _performMultiItemTransferAndCheckBalances(items, alice, false, ""); + _performMultiItemTransferAndCheckBalances( + items, + alice, + inputs.recipients[0], + false, + "" + ); } // Test reverts @@ -880,7 +946,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferERC20InvalidIdentifier( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC20, inputs.amounts[0], @@ -894,6 +960,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + inputs.recipients[0], false, abi.encodePacked( TransferHelperErrors.InvalidERC20Identifier.selector @@ -904,7 +971,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferERC721InvalidRecipient( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, 1, @@ -917,6 +984,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + inputs.recipients[0], false, abi.encodeWithSignature( "InvalidERC721Recipient(address)", @@ -928,8 +996,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferETHonly(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); + TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.NATIVE, @@ -940,11 +1007,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); bytes memory returnedData; - try - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( items, - conduitKeyOne - ) + bob + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -954,6 +1023,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + bob, inputs.useConduit, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -968,8 +1038,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferETHandERC721(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](2); + TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.NATIVE, @@ -988,11 +1057,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); bytes memory returnedData; - try - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( items, - conduitKeyOne - ) + bob + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -1002,6 +1073,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + bob, inputs.useConduit, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1019,9 +1091,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { vm.assume(invalidAmount > 1); - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); - TransferHelperItemWithRecipient + TransferHelperItem[] memory items = new TransferHelperItem[](1); + TransferHelperItem memory item = _getFuzzedERC721TransferItemWithAmountGreaterThan1( alice, invalidAmount, @@ -1032,11 +1103,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { items[0] = item; bytes memory returnedData; - try - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( items, - conduitKeyOne - ) + bob + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -1045,6 +1118,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + bob, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1060,8 +1134,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { vm.assume(inputs.amounts[0] > 0); - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](2); + TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedERC721TransferItemWithAmountGreaterThan1( alice, inputs.amounts[0], @@ -1080,11 +1153,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); bytes memory returnedData; - try - transferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( items, - conduitKeyOne - ) + bob + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -1094,6 +1169,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + bob, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1107,8 +1183,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferNotOpenConduitChannel( FuzzInputsCommon memory inputs ) public { - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); + TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC20, @@ -1128,6 +1203,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( items[0], alice, + bob, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1147,8 +1223,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { fuzzConduitKey != bytes32(0) && fuzzConduitKey != conduitKeyOne ); - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); + TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( alice, ConduitItemType.ERC20, @@ -1168,13 +1243,18 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); - transferHelper.bulkTransferToMultipleRecipients(items, conduitKeyOne); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob + ); + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne); } function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) public { - TransferHelperItemWithRecipient memory item = _getFuzzedTransferItem( + TransferHelperItem memory item = _getFuzzedTransferItem( alice, ConduitItemType.ERC721, 1, @@ -1186,6 +1266,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, + bob, false, abi.encodeWithSignature( "ERC721ReceiverErrorRevertString(string,address,address,uint256)", @@ -1198,14 +1279,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } function testRevertStringErrorWithConduit() public { - TransferHelperItemWithRecipient - memory item = TransferHelperItemWithRecipient( - ConduitItemType.ERC721, - address(erc721s[0]), - 5, - 1, - alice - ); + TransferHelperItem memory item = TransferHelperItem( + ConduitItemType.ERC721, + address(erc721s[0]), + 5, + 1 + ); (address _conduit, ) = conduitController.getConduit(conduitKeyOne); // Attempt to transfer ERC721 tokens from bob to alice @@ -1213,6 +1292,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, bob, + alice, true, abi.encodeWithSignature( "ConduitErrorRevertString(string,bytes32,address)", @@ -1233,14 +1313,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // Approve the ERC20 tokens panicERC20.approve(alice, 10); - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); - items[0] = TransferHelperItemWithRecipient( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( ConduitItemType.ERC20, address(panicERC20), 0, - 10, - bob + 10 ); (address _conduit, ) = conduitController.getConduit(conduitKeyOne); @@ -1250,6 +1328,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, + bob, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1300,14 +1379,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { assertEq(mockConduitKey, conduitKeyAlice); // Create item to transfer - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); - items[0] = TransferHelperItemWithRecipient( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), 5, - 1, - bob + 1 ); (address _conduit, bool exists) = mockConduitController.getConduit( @@ -1324,10 +1401,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransferToMultipleRecipients( - items, - conduitKeyAlice - ); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob + ); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1371,14 +1450,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { assertEq(mockConduitKey, conduitKeyAlice); // Create item to transfer - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); - items[0] = TransferHelperItemWithRecipient( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), 5, - 1, - bob + 1 ); (address _conduit, bool exists) = mockConduitController.getConduit( @@ -1396,10 +1473,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransferToMultipleRecipients( - items, - conduitKeyAlice - ); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob + ); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1443,14 +1522,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { assertEq(mockConduitKey, conduitKeyAlice); // Create item to transfer - TransferHelperItemWithRecipient[] - memory items = new TransferHelperItemWithRecipient[](1); - items[0] = TransferHelperItemWithRecipient( + TransferHelperItem[] memory items = new TransferHelperItem[](1); + items[0] = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), 5, - 1, - bob + 1 ); (address _conduit, bool exists) = mockConduitController.getConduit( @@ -1461,11 +1538,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { assertEq(exists, true); bytes memory returnedData; - try - mockTransferHelper.bulkTransferToMultipleRecipients( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( items, - conduitKeyAlice - ) + bob + ); + try + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -1479,10 +1558,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransferToMultipleRecipients( - items, - conduitKeyAlice - ); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } } diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index 81b1e7603..bfb9774cc 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -14,7 +14,8 @@ import { ConduitItemType } from "../../contracts/conduit/lib/ConduitEnums.sol"; import { TransferHelper } from "../../contracts/helpers/TransferHelper.sol"; import { - TransferHelperItem + TransferHelperItem, + TransferHelperItemsWithRecipient } from "../../contracts/helpers/TransferHelperStructs.sol"; import { TestERC20 } from "../../contracts/test/TestERC20.sol"; @@ -250,9 +251,17 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(expectRevertData); } // Perform transfer. - transferHelper.bulkTransfer( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( items, to, + true + ); + transferHelper.bulkTransfer( + itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); @@ -340,9 +349,17 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(expectRevertDataWithoutConduit); } // Perform transfer. - transferHelper.bulkTransfer( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( items, to, + true + ); + transferHelper.bulkTransfer( + itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); @@ -816,7 +833,18 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); bytes memory returnedData; - try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) + returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); @@ -855,7 +883,18 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); bytes memory returnedData; - try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) + returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); @@ -892,7 +931,18 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items[0] = item; bytes memory returnedData; - try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) + returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); @@ -931,7 +981,18 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); bytes memory returnedData; - try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + try + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) + returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { returnedData = this.getSelector(reason); @@ -1015,7 +1076,16 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); - transferHelper.bulkTransfer(items, bob, conduitKeyOne); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne); } function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) @@ -1171,7 +1241,16 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1238,7 +1317,16 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1298,8 +1386,17 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { assertEq(exists, true); bytes memory returnedData; + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + 1 + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + bob, + true + ); try - mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice) + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice) returns ( bytes4 /* magicValue */ ) {} catch (bytes memory reason) { @@ -1313,7 +1410,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { mockConduit ) ); - mockTransferHelper.bulkTransfer(items, bob, conduitKeyAlice); + mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } } From 904d4ff4573c8a0fb5334bd934ba62944183974c Mon Sep 17 00:00:00 2001 From: Aspyn Palatnick Date: Fri, 15 Jul 2022 19:19:09 -0400 Subject: [PATCH 095/126] use helper everywhere --- .../TransferHelperMultipleRecipientsTest.sol | 22 ++-- .../TransferHelperSingleRecipientTest.sol | 117 +++++++----------- 2 files changed, 54 insertions(+), 85 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 798fe9edc..d30569677 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -228,7 +228,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function _getTransferHelperWithRecipientsFromTransferHelperItems( TransferHelperItem[] memory items, - // TODO stephen: support multiple to (recipients) + // TODO stephen: support multiple to (recipients) and move to helper address to ) internal view returns (TransferHelperItemsWithRecipient[] memory) { TransferHelperItemsWithRecipient[] @@ -293,14 +293,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + to ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - to, - true - ); transferHelper.bulkTransfer( itemsWithRecipient, @@ -392,14 +388,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + to ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - to, - true - ); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index bfb9774cc..da8704ff3 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -202,6 +202,23 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); } + function _getTransferHelperWithRecipientsFromTransferHelperItems( + TransferHelperItem[] memory items, + // TODO stephen: support multiple to (recipients) and move to helper + address to + ) internal view returns (TransferHelperItemsWithRecipient[] memory) { + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + items.length + ); + itemsWithRecipient[0] = TransferHelperItemsWithRecipient( + items, + to, + true + ); + return itemsWithRecipient; + } + function _performSingleItemTransferAndCheckBalances( TransferHelperItem memory item, address from, @@ -252,14 +269,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + to ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - to, - true - ); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) @@ -350,14 +363,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + to ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - to, - true - ); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) @@ -834,14 +843,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( @@ -884,14 +889,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( @@ -932,14 +933,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items[0] = item; bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( @@ -982,14 +979,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) returns ( @@ -1077,14 +1070,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne); } @@ -1242,14 +1231,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1318,14 +1303,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } @@ -1387,14 +1368,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - 1 + memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + items, + bob ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - bob, - true - ); try mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice) returns ( From 3b1b36824b67fbe6b238e9e69f305347663839a2 Mon Sep 17 00:00:00 2001 From: Aspyn Palatnick Date: Fri, 15 Jul 2022 19:24:13 -0400 Subject: [PATCH 096/126] fix naming --- .../TransferHelperMultipleRecipientsTest.sol | 22 +++++++++---------- .../TransferHelperSingleRecipientTest.sol | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index d30569677..1dcdcc0cc 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -226,7 +226,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function _getTransferHelperWithRecipientsFromTransferHelperItems( + function _getTransferHelperItemsWithRecipientsFromTransferHelperItems( TransferHelperItem[] memory items, // TODO stephen: support multiple to (recipients) and move to helper address to @@ -293,7 +293,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, to ); @@ -388,7 +388,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, to ); @@ -1000,7 +1000,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1050,7 +1050,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1096,7 +1096,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { items[0] = item; bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1146,7 +1146,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1236,7 +1236,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1394,7 +1394,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1466,7 +1466,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1531,7 +1531,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index da8704ff3..b99b4dcce 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -202,7 +202,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); } - function _getTransferHelperWithRecipientsFromTransferHelperItems( + function _getTransferHelperItemsWithRecipientsFromTransferHelperItems( TransferHelperItem[] memory items, // TODO stephen: support multiple to (recipients) and move to helper address to @@ -269,7 +269,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, to ); @@ -363,7 +363,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, to ); @@ -843,7 +843,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -889,7 +889,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -933,7 +933,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items[0] = item; bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -979,7 +979,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1070,7 +1070,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1231,7 +1231,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1303,7 +1303,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); @@ -1368,7 +1368,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( items, bob ); From c4567882cdee2cfd32202e79bcd29d33bb54f194 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 15 Jul 2022 17:13:11 -0700 Subject: [PATCH 097/126] update comments --- contracts/helpers/TransferHelper.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 42f21132c..216f8ad87 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -216,8 +216,8 @@ contract TransferHelper is } /** - * @notice Perform multiple transfers to individually-specified recipients - * via the conduit derived from the provided conduit key. + * @notice Perform multiple transfers to specified recipients via the + * conduit derived from the provided conduit key. * * @param transfers The items to transfer. * @param conduitKey The conduit key referring to the conduit through @@ -247,7 +247,7 @@ contract TransferHelper is ) ); - // Declare a variable to store the sum of all items across all transfers. + // Declare a variable to store the sum of all items across transfers. uint256 totalItems; // Skip overflow checks: all for loops are indexed starting at zero. From 77d4a2a75bc4426458659a8f3fc8d330ac772ec1 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Fri, 15 Jul 2022 17:15:41 -0700 Subject: [PATCH 098/126] bump --- contracts/helpers/TransferHelper.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 216f8ad87..edd2ab56c 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -31,7 +31,7 @@ import { TransferHelperErrors } from "../interfaces/TransferHelperErrors.sol"; * @title TransferHelper * @author stephankmin, stuckinaboot, ryanio * @notice TransferHelper is a utility contract for transferring - * ERC20/ERC721/ERC1155 items in bulk to a specific recipient. + * ERC20/ERC721/ERC1155 items in bulk to specific recipients. */ contract TransferHelper is TransferHelperInterface, @@ -68,9 +68,10 @@ contract TransferHelper is } /** - * @notice Transfer multiple items to recipients. + * @notice Transfer multiple ERC20/ERC721/ERC1155 items to + * specified recipients. * - * @param items The items to transfer with the intended recipient. + * @param items The items to transfer to an intended recipient. * @param conduitKey An optional conduit key referring to a conduit through * which the bulk transfer should occur. * From 22294529da0b4fe80acdc7596797e6f2a894d908 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 09:57:47 -0700 Subject: [PATCH 099/126] check onERC721Received for conduit transfers, add internal fns --- contracts/helpers/TransferHelper.sol | 161 ++++++++++++++++++--------- 1 file changed, 106 insertions(+), 55 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index edd2ab56c..bc35b03c8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -113,18 +113,17 @@ contract TransferHelper is TransferHelperItemsWithRecipient calldata transfer = transfers[ i ]; + + // Retrieve the items of the transfer in question. TransferHelperItem[] calldata transferItems = transfer.items; - address recipient = transfer.recipient; - // Ensure tokens aren't transferred to the zero address. - if (recipient == address(0x0)) { - revert RecipientCannotBeZeroAddress(); - } + // Ensure recipient is not the zero address. + _checkRecipientIsNotZeroAddress(transfer.recipient); - // Create a boolean indicating whether recipient is - // a contract. + // Create a boolean indicating whether validateERC721Receiver + // is true and recipient is a contract. bool callERC721Receiver = transfer.validateERC721Receiver && - recipient.code.length != 0; + transfer.recipient.code.length != 0; // Retrieve total number of transfers and place on stack. uint256 totalItemTransfers = transferItems.length; @@ -143,48 +142,19 @@ contract TransferHelper is _performERC20Transfer( item.token, msg.sender, - recipient, + transfer.recipient, item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { - // If recipient is a contract, ensure it can receive - // ERC721 tokens. + // If recipient is a contract and validateERC721Receiver + // is true... if (callERC721Receiver) { - // Check if recipient can receive ERC721 tokens. - try - IERC721Receiver(recipient).onERC721Received( - address(this), - msg.sender, - item.identifier, - "" - ) - returns (bytes4 selector) { - // Check if onERC721Received selector is valid. - if ( - selector != - IERC721Receiver.onERC721Received.selector - ) { - // Revert if recipient cannot accept - // ERC721 tokens. - revert InvalidERC721Recipient(recipient); - } - } catch (bytes memory data) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertBytes( - data, - recipient, - msg.sender, - item.identifier - ); - } catch Error(string memory reason) { - // "Bubble up" recipient's revert reason. - revert ERC721ReceiverErrorRevertString( - reason, - recipient, - msg.sender, - item.identifier - ); - } + // Check if the recipient implements onERC721Received + // for the given tokenId. + _checkERC721Receiver( + transfer.recipient, + item.identifier + ); } // Ensure that the amount for an ERC721 transfer is 1. if (item.amount != 1) { @@ -195,7 +165,7 @@ contract TransferHelper is _performERC721Transfer( item.token, msg.sender, - recipient, + transfer.recipient, item.identifier ); } else if (item.itemType == ConduitItemType.ERC1155) { @@ -203,7 +173,7 @@ contract TransferHelper is _performERC1155Transfer( item.token, msg.sender, - recipient, + transfer.recipient, item.identifier, item.amount ); @@ -255,8 +225,13 @@ contract TransferHelper is unchecked { // Iterate over each transfer. for (uint256 i = 0; i < totalTransfers; ++i) { + // Retrieve the transfer in question. + TransferHelperItemsWithRecipient calldata transfer = transfers[ + i + ]; + // Increment totalItems by the number of items in the transfer. - totalItems += transfers[i].items.length; + totalItems += transfer.items.length; } } @@ -275,25 +250,45 @@ contract TransferHelper is i ]; TransferHelperItem[] calldata transferItems = transfer.items; - address recipient = transfer.recipient; - // Ensure tokens aren't transferred to the zero address. - if (recipient == address(0x0)) { - revert RecipientCannotBeZeroAddress(); - } + // Ensure recipient is not the zero address. + _checkRecipientIsNotZeroAddress(transfer.recipient); // Retrieve total number of transfers and place on stack. uint256 totalItemTransfers = transferItems.length; + // Iterate over each item in the transfer to create a + // corresponding ConduitTransfer. for (uint256 j = 0; j < totalItemTransfers; ++j) { + // Retrieve the item from the transfer. TransferHelperItem calldata item = transferItems[j]; + + // Create a boolean indicating whether validateERC721Receiver + // is true and recipient is a contract. + bool callERC721Receiver = transfer.validateERC721Receiver && + transfer.recipient.code.length != 0; + + // If the item is an ERC721 token and + // callERC721Receiver is true... + if ( + item.itemType == ConduitItemType.ERC721 && + callERC721Receiver + ) { + // Check if the recipient implements onERC721Received + // for the given tokenId. + _checkERC721Receiver( + transfer.recipient, + item.identifier + ); + } + // Create a ConduitTransfer corresponding to each // TransferHelperItem. conduitTransfers[j] = ConduitTransfer( item.itemType, item.token, msg.sender, - recipient, + transfer.recipient, item.identifier, item.amount ); @@ -324,4 +319,60 @@ contract TransferHelper is revert ConduitErrorRevertString(reason, conduitKey, conduit); } } + + /** + * @notice An internal function to check if a recipient address implements + * onERC721Received for a given tokenId. + * + * @param recipient The ERC721 recipient on which to call onERC721Received. + * @param tokenId The ERC721 tokenId of the token being transferred. + * + */ + function _checkERC721Receiver(address recipient, uint256 tokenId) internal { + // Check if recipient can receive ERC721 tokens. + try + IERC721Receiver(recipient).onERC721Received( + address(this), + msg.sender, + tokenId, + "" + ) + returns (bytes4 selector) { + // Check if onERC721Received selector is valid. + if (selector != IERC721Receiver.onERC721Received.selector) { + // Revert if recipient cannot accept + // ERC721 tokens. + revert InvalidERC721Recipient(recipient); + } + } catch (bytes memory data) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertBytes( + data, + recipient, + msg.sender, + tokenId + ); + } catch Error(string memory reason) { + // "Bubble up" recipient's revert reason. + revert ERC721ReceiverErrorRevertString( + reason, + recipient, + msg.sender, + tokenId + ); + } + } + + /** + * @notice An internal function that reverts if the passed-in recipient + * is the zero address. + * + * @param recipient The recipient on which to perform the check. + */ + function _checkRecipientIsNotZeroAddress(address recipient) internal pure { + // Revert if the recipient is the zero address. + if (recipient == address(0x0)) { + revert RecipientCannotBeZeroAddress(); + } + } } From a4663492427e0eb15af49af17509b9d27daaf9db Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 10:12:23 -0700 Subject: [PATCH 100/126] declare callERC721Receiver only when itemtype is 721 for conduit transfers --- contracts/helpers/TransferHelper.sol | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index bc35b03c8..4759c71f8 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -263,23 +263,23 @@ contract TransferHelper is // Retrieve the item from the transfer. TransferHelperItem calldata item = transferItems[j]; - // Create a boolean indicating whether validateERC721Receiver - // is true and recipient is a contract. - bool callERC721Receiver = transfer.validateERC721Receiver && - transfer.recipient.code.length != 0; - // If the item is an ERC721 token and // callERC721Receiver is true... - if ( - item.itemType == ConduitItemType.ERC721 && - callERC721Receiver - ) { - // Check if the recipient implements onERC721Received - // for the given tokenId. - _checkERC721Receiver( - transfer.recipient, - item.identifier - ); + if (item.itemType == ConduitItemType.ERC721) { + // Create a boolean indicating whether validateERC721Receiver + // is true and recipient is a contract. + bool callERC721Receiver = transfer + .validateERC721Receiver && + transfer.recipient.code.length != 0; + + if (callERC721Receiver) { + // Check if the recipient implements onERC721Received + // for the given tokenId. + _checkERC721Receiver( + transfer.recipient, + item.identifier + ); + } } // Create a ConduitTransfer corresponding to each From 5ce978da7b00beee9e45d0bd6aef3e84c6806497 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 10:28:47 -0700 Subject: [PATCH 101/126] lint warnings --- contracts/helpers/TransferHelper.sol | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 4759c71f8..1e55f4a12 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -149,8 +149,8 @@ contract TransferHelper is // If recipient is a contract and validateERC721Receiver // is true... if (callERC721Receiver) { - // Check if the recipient implements onERC721Received - // for the given tokenId. + // Check if the recipient implements + // onERC721Received for the given tokenId. _checkERC721Receiver( transfer.recipient, item.identifier @@ -178,7 +178,8 @@ contract TransferHelper is item.amount ); } else { - // Revert if the item being transferred is a native token. + // Revert if the item being transferred is a + // native token. revert InvalidItemType(); } } @@ -266,15 +267,16 @@ contract TransferHelper is // If the item is an ERC721 token and // callERC721Receiver is true... if (item.itemType == ConduitItemType.ERC721) { - // Create a boolean indicating whether validateERC721Receiver - // is true and recipient is a contract. + // Create a boolean indicating whether + // validateERC721Receiver is true and recipient is + // a contract. bool callERC721Receiver = transfer .validateERC721Receiver && transfer.recipient.code.length != 0; if (callERC721Receiver) { - // Check if the recipient implements onERC721Received - // for the given tokenId. + // Check if the recipient implements + // onERC721Received for the given tokenId. _checkERC721Receiver( transfer.recipient, item.identifier From d1769df66956640693d30a60a6f7f0d948c93710 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 11:16:41 -0700 Subject: [PATCH 102/126] add deploy script --- script/TransferHelperDeployer.s.sol | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 script/TransferHelperDeployer.s.sol diff --git a/script/TransferHelperDeployer.s.sol b/script/TransferHelperDeployer.s.sol new file mode 100644 index 000000000..c2cac266c --- /dev/null +++ b/script/TransferHelperDeployer.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "forge-std/Script.sol"; +import { TransferHelper } from "contracts/helpers/TransferHelper.sol"; + +interface ImmutableCreate2Factory { + function safeCreate2(bytes32, bytes memory) external; +} + +contract TransferHelperDeployer is Script { + function setUp() public {} + + function run() public { + address deployer = 0x43Eb2499553a4cb062cfeD09407F6c94C1494F86; + vm.broadcast(); + ImmutableCreate2Factory factory = ImmutableCreate2Factory( + 0x0000000000FFe8B47B3e2130213B802212439497 + ); + bytes32 salt = bytes32(0); + factory.safeCreate2( + salt, + abi.encodePacked( + type(TransferHelper).creationCode, + abi.encode(address(0x00000000F9490004C11Cef243f5400493c00Ad63)) + ) + ); + } +} From df7a0204967dec0024bc4afee8f0681e658fb9db Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 14:17:30 -0700 Subject: [PATCH 103/126] add itemIndex for conduit transfers array --- contracts/helpers/TransferHelper.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 1e55f4a12..039788424 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -242,6 +242,9 @@ contract TransferHelper is totalItems ); + // Declare an index for storing ConduitTransfers in conduitTransfers. + uint256 itemIndex; + // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. @@ -286,7 +289,7 @@ contract TransferHelper is // Create a ConduitTransfer corresponding to each // TransferHelperItem. - conduitTransfers[j] = ConduitTransfer( + conduitTransfers[itemIndex] = ConduitTransfer( item.itemType, item.token, msg.sender, @@ -294,6 +297,9 @@ contract TransferHelper is item.identifier, item.amount ); + + // Increment the index for storing ConduitTransfers. + itemIndex++; } } } From 4c7d31a9618f65175af09520182f52d72bc28dc6 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Mon, 18 Jul 2022 15:20:28 -0700 Subject: [PATCH 104/126] ++itemIndex --- contracts/helpers/TransferHelper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 039788424..8a81e1ece 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -299,7 +299,7 @@ contract TransferHelper is ); // Increment the index for storing ConduitTransfers. - itemIndex++; + ++itemIndex; } } } From 665a51b50425018c70745c24f6f7c668a2b9530b Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 19 Jul 2022 12:44:07 -0700 Subject: [PATCH 105/126] update hh tests to match new function and struct --- test/transferhelper.spec.ts | 2049 ++++++++++++++++++++++------------- 1 file changed, 1272 insertions(+), 777 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 9a906e431..fdb33b78d 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -12,9 +12,10 @@ import { } from "./utils/fixtures"; import { VERSION } from "./utils/helpers"; -import type { +import { ConduitControllerInterface, ConduitInterface, + contracts, EIP1271Wallet, EIP1271Wallet__factory, TransferHelper, @@ -73,23 +74,41 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { amount: BigNumber; } - interface TransferWithRecipient { + interface TransferHelperItem { itemType: 0 | 1 | 2 | 3 | 4 | 5; token: string; identifier: BigNumber; amount: BigNumber; + } + + interface TransferWithRecipient { + items: TransferHelperItem[]; recipient: string; + validateERC721Receiver: boolean; } - function createTransferWithRecipient( - transfer: Transfer - ): TransferWithRecipient { + function createTransferHelperItem(transfer: Transfer): TransferHelperItem { return { itemType: transfer.itemType, token: transfer.token, identifier: transfer.identifier, amount: transfer.amount, - recipient: transfer.to, + }; + } + + function createTransferWithRecipient( + transfers: Transfer[], + recipient: string, + validate: boolean + ): TransferWithRecipient { + let transferHelperItems = []; + for (let i = 0; i < transfers.length; i++) { + transferHelperItems[i] = createTransferHelperItem(transfers[i]); + } + return { + items: transferHelperItems, + recipient: recipient, + validateERC721Receiver: validate, }; } @@ -132,7 +151,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .updateChannel(tempConduit.address, tempTransferHelper.address, true); }); - describe("bulkTransfer tests", async () => { + describe("Single recipient tests", async () => { it("Executes transfers (many token types) with a conduit", async () => { // Get 3 Numbers that's value adds to Item Amount and minimum 1. const itemsToCreate = 10; @@ -212,14 +231,24 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ...erc721Contracts, ...erc1155Contracts, ]; + + const transfersWithRecipients = []; + + transfersWithRecipients[0] = createTransferWithRecipient( + transfers, + recipient.address, + true + ); + // Send the bulk transfers await tempTransferHelper .connect(sender) - .bulkTransfer(transfers, recipient.address, tempConduitKey); + .bulkTransfer(transfersWithRecipients, tempConduitKey); // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfers.length; i++) { + for (let i = 0; i < transfersWithRecipients[0].items.length; i++) { // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; + const { itemType, amount, identifier } = + transfersWithRecipients[0].items[i]; const token = contracts[i]; switch (itemType) { @@ -335,12 +364,20 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ...erc721Contracts, ...erc1155Contracts, ]; + + const transfersWithRecipients = []; + + transfersWithRecipients[0] = createTransferWithRecipient( + transfers, + recipient.address, + true + ); + // Send the bulk transfers await tempTransferHelper .connect(sender) .bulkTransfer( - transfers, - recipient.address, + transfersWithRecipients, ethers.utils.formatBytes32String("") ); // Loop through all transfer to do ownership/balance checks @@ -413,12 +450,19 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { erc721Transfers[i] = erc721Transfer; } + const transfersWithRecipients = []; + + transfersWithRecipients[0] = createTransferWithRecipient( + erc721Transfers, + erc721Recipient.address, + true + ); + // Send the bulk transfers await tempTransferHelper .connect(sender) .bulkTransfer( - erc721Transfers, - erc721Recipient.address, + transfersWithRecipients, ethers.utils.formatBytes32String("") ); @@ -435,54 +479,59 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { }); it("Reverts on native token transfers", async () => { - const ethTransferHelperItems = [ - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, - }, - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, + const ethTransfers = [ + { + items: [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - ethTransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(ethTransfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith("InvalidItemType"); }); it("Reverts on invalid ERC20 identifier", async () => { - const erc20TransferHelperItems = [ - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 5, - amount: 10, - }, - { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 4, - amount: 20, + const erc20Transfers = [ + { + items: [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - erc20TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(erc20Transfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith("InvalidERC20Identifier"); }); @@ -490,28 +539,30 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 10, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 20, + const erc721Transfers = [ + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(erc721Transfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith("InvalidERC721TransferAmount"); }); @@ -519,28 +570,30 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + const erc721Transfers = [ + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], + recipient: tempERC721Contract.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - tempERC721Contract.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(erc721Transfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith( `ERC721ReceiverErrorRevertBytes("0x", "${tempERC721Contract.address}", "${sender.address}", 1)` ); @@ -554,28 +607,30 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - const erc721TransferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + const erc721Transfers = [ + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], + recipient: invalidRecipient.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - erc721TransferHelperItems, - invalidRecipient.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(erc721Transfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith( `InvalidERC721Recipient("${invalidRecipient.address}")` ); @@ -587,40 +642,42 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - ethers.utils.formatBytes32String("0xabc") - ) + .bulkTransfer(transfers, ethers.utils.formatBytes32String("0xabc")) ).to.be.reverted; }); @@ -639,40 +696,42 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { 1 ); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: mockERC721Receiver.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - mockERC721Receiver.address, - ethers.utils.formatBytes32String("") - ) + .bulkTransfer(transfers, ethers.utils.formatBytes32String("")) ).to.be.revertedWith( `ERC721ReceiverErrorRevertString("ERC721ReceiverMock: reverting", "${mockERC721Receiver.address}", "${sender.address}", 1` ); @@ -684,31 +743,36 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ - // Invalid item type - { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; @@ -719,7 +783,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + .bulkTransfer(transfers, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -734,37 +798,43 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const { testERC20: tempERC20Contract } = await fixtureERC20(owner); // Call will revert since ERC721 tokens have not been minted - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, tempConduitKey) + .bulkTransfer(transfers, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -806,17 +876,43 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .setApprovalForAll(mockConduitAddress, true); - const transferHelperItems = Array.from(Array(11)).map(() => ({ - itemType: 3, - token: tempERC1155Contract.address, - identifier: 0, - amount: 10, - })); + const transfers = [ + { + items: [ + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, + }, + ]; await expect( mockTransferHelper .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -829,18 +925,24 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); const mockERC20Panic = await mockERC20PanicFactory.deploy(); - const transferHelperItems = [ - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; @@ -850,11 +952,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - tempConduitKey - ) + .bulkTransfer(transfers, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -864,11 +962,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - recipient.address, - tempConduitKey - ) + .bulkTransfer(transfers, recipient.address) ).to.be.reverted; } }); @@ -905,25 +999,31 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; await expect( mockTransferHelper .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -960,18 +1060,24 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { )[0]; await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - const transferHelperItems = [ - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, }, ]; @@ -980,7 +1086,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( mockTransferHelper .connect(sender) - .bulkTransfer(transferHelperItems, recipient.address, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -992,41 +1098,44 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: ethers.constants.AddressZero, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - ethers.constants.AddressZero, - tempConduitKey - ) - ).to.be.revertedWith("RecipientCannotBeZero()"); + .bulkTransfer(transfers, tempConduitKey) + ).to.be.revertedWith("RecipientCannotBeZeroAddress()"); }); it("Reverts when recipient is the null address (without conduit)", async () => { @@ -1035,46 +1144,49 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - }, + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: ethers.constants.AddressZero, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransfer( - transferHelperItems, - ethers.constants.AddressZero, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("RecipientCannotBeZero()"); + .bulkTransfer(transfers, ethers.utils.formatBytes32String("")) + ).to.be.revertedWith("RecipientCannotBeZeroAddress()"); }); }); - - describe("bulkTransferToMultipleRecipients tests", async () => { + describe("Multi-recipient tests", async () => { it("Executes transfers with multiple recipients (many token types) with a conduit", async () => { + const numTransfers = 4; + // Get 3 Numbers that's value adds to Item Amount and minimum 1. const itemsToCreate = 10; const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); @@ -1097,127 +1209,144 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { cal.address, ]; - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempConduit.address, - sender.address, - recipients[i % 4] - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC20 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempConduit.address, - sender.address, - recipients[i % 4] - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + const transfersWithRecipients = []; + const allContracts = []; + const allTransfers = []; + + // Create numTransfers amount of TransferHelperItemsWithRecipient + for (let j = 0; j < numTransfers; j++) { + const transferRecipient = recipients[j]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempConduit.address, + sender.address, + transferRecipient + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempConduit.address, - sender.address, - recipients[i % 4] - ); + // Create numEC721s amount of ERC721 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempConduit.address, + sender.address, + transferRecipient + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempConduit.address, + sender.address, + transferRecipient + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; - const transfersWithRecipients = []; + allContracts.push( + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts + ); - for (let i = 0; i < transfers.length; i++) { - transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + transfersWithRecipients[j] = createTransferWithRecipient( + transfers, + transferRecipient, + true + ); } + // Send the bulk transfers await tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - transfersWithRecipients, - tempConduitKey - ); + .bulkTransfer(transfersWithRecipients, tempConduitKey); + + let contractsStartingIndex = 0; // Loop through all transfer to do ownership/balance checks for (let i = 0; i < transfersWithRecipients.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - sender.address - ) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - transfersWithRecipients[i].recipient - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(transfersWithRecipients[i].recipient); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf( - transfersWithRecipients[i].recipient, - identifier - ) - ).to.equal(amount); - break; + const items = transfersWithRecipients[i].items; + + for (let j = 0; j < items.length; j++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = items[j]; + const token = allContracts[contractsStartingIndex]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipients[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipients[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect( + await token.balanceOf(sender.address, identifier) + ).to.equal(0); + expect( + await token.balanceOf( + transfersWithRecipients[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + contractsStartingIndex++; } } }); it("Executes transfers with multiple recipients (many token types) without a conduit", async () => { + const numTransfers = 4; + // Get 3 Numbers that's value adds to Item Amount and minimum 1. const itemsToCreate = 10; const numERC20s = Math.max(1, randomInt(itemsToCreate - 2)); @@ -1240,123 +1369,139 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { cal.address, ]; - // Create numERC20s amount of ERC20 objects - for (let i = 0; i < numERC20s; i++) { - // Deploy Contract - const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - // Create/Approve X amount of ERC20s - const erc20Transfer = await createTransferWithApproval( - tempERC20Contract, - sender, - 1, - tempTransferHelper.address, - sender.address, - recipients[i % 4] - ); - erc20Contracts[i] = tempERC20Contract; - erc20Transfers[i] = erc20Transfer; - } - - // Create numEC721s amount of ERC721 objects - for (let i = 0; i < numEC721s; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address, - sender.address, - recipients[i % 4] - ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = erc721Transfer; - } + const transfersWithRecipientsNoConduit = []; + const allContracts = []; + + // Create numTransfers amount of TransferHelperItemsWithRecipient + for (let j = 0; j < numTransfers; j++) { + const transferRecipient = recipients[j]; + + // Create numERC20s amount of ERC20 objects + for (let i = 0; i < numERC20s; i++) { + // Deploy Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + // Create/Approve X amount of ERC20s + const erc20Transfer = await createTransferWithApproval( + tempERC20Contract, + sender, + 1, + tempTransferHelper.address, + sender.address, + transferRecipient + ); + erc20Contracts[i] = tempERC20Contract; + erc20Transfers[i] = erc20Transfer; + } - // Create numERC1155s amount of ERC1155 objects - for (let i = 0; i < numERC1155s; i++) { - // Deploy Contract - const { testERC1155: tempERC1155Contract } = await fixtureERC1155( - owner - ); - // Create/Approve numERC1155s amount of ERC1155s - const erc1155Transfer = await createTransferWithApproval( - tempERC1155Contract, - sender, - 3, - tempTransferHelper.address, - sender.address, - recipients[i % 4] - ); + // Create numEC721s amount of ERC721 objects + for (let i = 0; i < numEC721s; i++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + transferRecipient + ); + erc721Contracts[i] = tempERC721Contract; + erc721Transfers[i] = erc721Transfer; + } - erc1155Contracts[i] = tempERC1155Contract; - erc1155Transfers[i] = erc1155Transfer; - } + // Create numERC1155s amount of ERC1155 objects + for (let i = 0; i < numERC1155s; i++) { + // Deploy Contract + const { testERC1155: tempERC1155Contract } = await fixtureERC1155( + owner + ); + // Create/Approve numERC1155s amount of ERC1155s + const erc1155Transfer = await createTransferWithApproval( + tempERC1155Contract, + sender, + 3, + tempTransferHelper.address, + sender.address, + transferRecipient + ); + + erc1155Contracts[i] = tempERC1155Contract; + erc1155Transfers[i] = erc1155Transfer; + } - const transfers = [ - ...erc20Transfers, - ...erc721Transfers, - ...erc1155Transfers, - ]; - const contracts = [ - ...erc20Contracts, - ...erc721Contracts, - ...erc1155Contracts, - ]; + const transfers = [ + ...erc20Transfers, + ...erc721Transfers, + ...erc1155Transfers, + ]; - const transfersWithRecipients = []; + allContracts.push( + ...erc20Contracts, + ...erc721Contracts, + ...erc1155Contracts + ); - for (let i = 0; i < transfers.length; i++) { - transfersWithRecipients[i] = createTransferWithRecipient(transfers[i]); + transfersWithRecipientsNoConduit[j] = createTransferWithRecipient( + transfers, + transferRecipient, + true + ); } // Send the bulk transfers await tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - transfersWithRecipients, + .bulkTransfer( + transfersWithRecipientsNoConduit, ethers.utils.formatBytes32String("") ); - // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < transfersWithRecipients.length; i++) { - // Get Itemtype, token, amount, identifier - const { itemType, amount, identifier } = transfers[i]; - const token = contracts[i]; - switch (itemType) { - case 1: // ERC20 - // Check balance - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - sender.address - ) - ).to.equal(0); - expect( - await (token as typeof erc20Contracts[0]).balanceOf( - transfersWithRecipients[i].recipient - ) - ).to.equal(amount); - break; - case 2: // ERC721 - case 4: // ERC721_WITH_CRITERIA - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(transfersWithRecipients[i].recipient); - break; - case 3: // ERC1155 - case 5: // ERC1155_WITH_CRITERIA - // Check balance - expect(await token.balanceOf(sender.address, identifier)).to.equal( - 0 - ); - expect( - await token.balanceOf( - transfersWithRecipients[i].recipient, - identifier - ) - ).to.equal(amount); - break; + let contractsStartingIndex = 0; + // Loop through all transfer to do ownership/balance checks + for (let i = 0; i < transfersWithRecipientsNoConduit.length; i++) { + const items = transfersWithRecipientsNoConduit[i].items; + + for (let j = 0; j < items.length; j++) { + // Get Itemtype, token, amount, identifier + const { itemType, amount, identifier } = items[j]; + const token = allContracts[contractsStartingIndex]; + + switch (itemType) { + case 1: // ERC20 + // Check balance + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + sender.address + ) + ).to.equal(0); + expect( + await (token as typeof erc20Contracts[0]).balanceOf( + transfersWithRecipientsNoConduit[i].recipient + ) + ).to.equal(amount); + break; + case 2: // ERC721 + case 4: // ERC721_WITH_CRITERIA + expect( + await (token as typeof erc721Contracts[0]).ownerOf(identifier) + ).to.equal(transfersWithRecipientsNoConduit[i].recipient); + break; + case 3: // ERC1155 + case 5: // ERC1155_WITH_CRITERIA + // Check balance + expect( + await token.balanceOf(sender.address, identifier) + ).to.equal(0); + expect( + await token.balanceOf( + transfersWithRecipientsNoConduit[i].recipient, + identifier + ) + ).to.equal(amount); + break; + } + contractsStartingIndex++; } } }); @@ -1391,9 +1536,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { 0 ); - const erc721Contracts = []; - const erc721Transfers = []; - const erc721Recipients = [ erc721RecipientOne, erc721RecipientTwo, @@ -1402,66 +1544,98 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { erc721RecipientFive, ]; - // Create 5 ERC721 objects - for (let i = 0; i < 5; i++) { - // Deploy Contract - const { testERC721: tempERC721Contract } = await fixtureERC721(owner); - // Create/Approve numEC721s amount of ERC721s - const erc721Transfer = await createTransferWithApproval( - tempERC721Contract, - sender, - 2, - tempTransferHelper.address, - sender.address, - erc721Recipients[i].address + const numTransfers = 5; + const transfersWithRecipients = []; + + const allContracts = []; + + for (let i = 0; i < numTransfers; i++) { + const erc721Items = []; + const erc721Contracts = []; + + // Create 5 ERC721 items + for (let j = 0; j < 5; j++) { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Create/Approve numEC721s amount of ERC721s + const erc721Transfer = await createTransferWithApproval( + tempERC721Contract, + sender, + 2, + tempTransferHelper.address, + sender.address, + erc721Recipients[j].address + ); + + erc721Contracts[j] = tempERC721Contract; + erc721Items[j] = erc721Transfer; + } + transfersWithRecipients[i] = createTransferWithRecipient( + erc721Items, + erc721Recipients[i].address, + true ); - erc721Contracts[i] = tempERC721Contract; - erc721Transfers[i] = createTransferWithRecipient(erc721Transfer); + allContracts.push(...erc721Contracts); } // Send the bulk transfers await tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - erc721Transfers, + .bulkTransfer( + transfersWithRecipients, ethers.utils.formatBytes32String("") ); + let contractsIndex = 0; // Loop through all transfer to do ownership/balance checks - for (let i = 0; i < 5; i++) { - // Get identifier and ERC721 token contract - const { identifier } = erc721Transfers[i]; - const { recipient } = erc721Transfers[i]; - const token = erc721Contracts[i]; - - expect( - await (token as typeof erc721Contracts[0]).ownerOf(identifier) - ).to.equal(recipient); + for (let i = 0; i < numTransfers; i++) { + for (let j = 0; j < 5; j++) { + // Get identifier and ERC721 token contract + const identifier = transfersWithRecipients[i].items[j].identifier; + const recipient = transfersWithRecipients[i].recipient; + const token = allContracts[contractsIndex]; + + expect( + await (token as typeof allContracts[0]).ownerOf(identifier) + ).to.equal(recipient); + + contractsIndex++; + } } }); it("Reverts on native token transfers", async () => { const ethTransferHelperItems = [ { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 10, + items: [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 10, + }, + ], recipient: alice.address, + validateERC721Receiver: true, }, { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 20, + items: [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 0, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( ethTransferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1471,24 +1645,35 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { it("Reverts on invalid ERC20 identifier", async () => { const erc20TransferHelperItems = [ { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 5, - amount: 10, + items: [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + ], recipient: alice.address, + validateERC721Receiver: false, }, { - itemType: 1, - token: ethers.constants.AddressZero, - identifier: 4, - amount: 20, + items: [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: false, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( erc20TransferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1501,24 +1686,35 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const erc721TransferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 10, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + ], recipient: alice.address, + validateERC721Receiver: false, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 20, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: false, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( erc721TransferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1532,24 +1728,35 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const erc721TransferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + ], recipient: tempERC721Contract.address, + validateERC721Receiver: true, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - recipient: tempERC721ContractTwo.address, + items: [ + { + itemType: 2, + token: tempERC721ContractTwo.address, + identifier: 2, + amount: 1, + }, + ], + recipient: tempERC721Contract.address, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( erc721TransferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1569,24 +1776,35 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const erc721TransferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + ], recipient: invalidRecipient.address, + validateERC721Receiver: true, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], recipient: invalidRecipientTwo.address, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( erc721TransferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1603,38 +1821,59 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const transferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + ], recipient: alice.address, + validateERC721Receiver: false, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], recipient: bob.address, + validateERC721Receiver: false, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + ], recipient: cal.address, + validateERC721Receiver: false, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, + validateERC721Receiver: false, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( transferHelperItems, ethers.utils.formatBytes32String("0xabc") ) @@ -1670,38 +1909,59 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const transferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + ], recipient: mockERC721ReceiverOne.address, + validateERC721Receiver: true, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], recipient: mockERC721ReceiverTwo.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + ], recipient: mockERC721ReceiverThree.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: mockERC721ReceiverFour.address, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( + .bulkTransfer( transferHelperItems, ethers.utils.formatBytes32String("") ) @@ -1719,32 +1979,52 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const transferHelperItems = [ // Invalid item type { - itemType: 0, - token: ethers.constants.AddressZero, - identifier: 0, - amount: 1, + items: [ + { + itemType: 0, + token: ethers.constants.AddressZero, + identifier: 1, + amount: 1, + }, + ], recipient: alice.address, + validateERC721Receiver: true, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], recipient: bob.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + ], recipient: cal.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, + validateERC721Receiver: true, }, ]; @@ -1755,7 +2035,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + .bulkTransfer(transferHelperItems, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${invalidItemTypeErrorSelector}", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -1772,39 +2052,59 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Call will revert since ERC721 tokens have not been minted const transferHelperItems = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + ], recipient: alice.address, + validateERC721Receiver: true, }, { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + ], recipient: bob.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + ], recipient: cal.address, + validateERC721Receiver: true, }, { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) + .bulkTransfer(transferHelperItems, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertString("WRONG_FROM", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -1846,18 +2146,73 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { .connect(sender) .setApprovalForAll(mockConduitAddress, true); - const transferHelperItems = Array.from(Array(11)).map(() => ({ - itemType: 3, - token: tempERC1155Contract.address, - identifier: 0, - amount: 10, - recipient: alice.address, - })); + const transfers = [ + { + items: [ + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 3, + token: tempERC1155Contract.address, + identifier: 0, + amount: 10, + }, + ], + recipient: alice.address, + validateERC721Receiver: true, + }, + ]; await expect( mockTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("0x", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -1870,20 +2225,42 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); const mockERC20Panic = await mockERC20PanicFactory.deploy(); - const transferHelperItems = [ + const transfers = [ { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, - }, - { - itemType: 1, - token: mockERC20Panic.address, - identifier: 0, - amount: 20, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: mockERC20Panic.address, + identifier: 0, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: true, }, ]; @@ -1894,10 +2271,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - transferHelperItems, - tempConduitKey - ) + .bulkTransfer(transfers, tempConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${panicError}", "${tempConduitKey.toLowerCase()}", "${ tempConduit.address @@ -1907,10 +2281,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - transferHelperItems, - tempConduitKey - ) + .bulkTransfer(transfers, tempConduitKey) ).to.be.reverted; } }); @@ -1947,27 +2318,49 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - const transferHelperItems = [ + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: true, }, ]; await expect( mockTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `InvalidConduit("${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -2004,20 +2397,60 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { )[0]; await tempERC20Contract.connect(sender).approve(mockConduitAddress, 100); - const transferHelperItems = [ + const transfers = [ { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: alice.address, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: bob.address, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: cal.address, + validateERC721Receiver: true, }, ]; @@ -2026,7 +2459,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { await expect( mockTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, mockConduitKey) + .bulkTransfer(transfers, mockConduitKey) ).to.be.revertedWith( `ConduitErrorRevertBytes("${customErrorSelector}", "${mockConduitKey.toLowerCase()}", "${mockConduitAddress}")` ); @@ -2038,41 +2471,73 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, - recipient: alice.address, - }, + const transfers = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: ethers.constants.AddressZero, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - recipient: cal.address, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - recipient: alice.address, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: ethers.constants.AddressZero, + validateERC721Receiver: true, }, ]; await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients(transferHelperItems, tempConduitKey) - ).to.be.revertedWith("RecipientCannotBeZero()"); + .bulkTransfer(transfers, tempConduitKey) + ).to.be.revertedWith("RecipientCannotBeZeroAddress()"); }); it("Reverts when recipient is the null address (without conduit)", async () => { @@ -2081,44 +2546,74 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { // Deploy ERC20 Contract const { testERC20: tempERC20Contract } = await fixtureERC20(owner); - const transferHelperItems = [ + const transfers = [ { - itemType: 2, - token: tempERC721Contract.address, - identifier: 1, - amount: 1, + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], recipient: ethers.constants.AddressZero, - }, - { - itemType: 2, - token: tempERC721Contract.address, - identifier: 2, - amount: 1, - recipient: bob.address, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 10, - recipient: cal.address, - }, - { - itemType: 1, - token: tempERC20Contract.address, - identifier: 0, - amount: 20, - recipient: alice.address, + validateERC721Receiver: true, + }, + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: ethers.constants.AddressZero, + validateERC721Receiver: true, }, ]; + await expect( tempTransferHelper .connect(sender) - .bulkTransferToMultipleRecipients( - transferHelperItems, - ethers.utils.formatBytes32String("") - ) - ).to.be.revertedWith("RecipientCannotBeZero()"); + .bulkTransfer(transfers, ethers.utils.formatBytes32String("")) + ).to.be.revertedWith("RecipientCannotBeZeroAddress()"); }); }); }); From cfb40ae1c24ac69dce84df73c4d282b5104ced25 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 19 Jul 2022 14:01:31 -0700 Subject: [PATCH 106/126] update forge single recipient tests for new function and struct --- .../TransferHelperSingleRecipientTest.sol | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index b99b4dcce..387de3bcb 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -202,14 +202,14 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ); } - function _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + function _getTransferHelperItemsWithRecipientFromTransferHelperItems( TransferHelperItem[] memory items, // TODO stephen: support multiple to (recipients) and move to helper address to ) internal view returns (TransferHelperItemsWithRecipient[] memory) { TransferHelperItemsWithRecipient[] memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - items.length + 1 ); itemsWithRecipient[0] = TransferHelperItemsWithRecipient( items, @@ -269,7 +269,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, to ); @@ -363,7 +363,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, to ); @@ -843,7 +843,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -889,7 +889,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -933,7 +933,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items[0] = item; bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -979,7 +979,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -1070,7 +1070,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -1115,7 +1115,9 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { 1 ); - (address conduit, ) = conduitController.getConduit(conduitKeyOne); + (address derivedConduit, ) = conduitController.getConduit( + conduitKeyOne + ); // Attempt to transfer ERC721 tokens from bob to alice // Expect revert since alice owns the tokens _performSingleItemTransferAndCheckBalances( @@ -1127,7 +1129,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { "ConduitErrorRevertString(string,bytes32,address)", "WRONG_FROM", conduitKeyOne, - conduit + derivedConduit ) ); } @@ -1150,7 +1152,9 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { 10 ); - (address conduit, ) = conduitController.getConduit(conduitKeyOne); + (address derivedConduit, ) = conduitController.getConduit( + conduitKeyOne + ); bytes memory panicError = abi.encodeWithSelector(0x4e487b71, 18); // Revert with panic error when calling execute via conduit @@ -1163,7 +1167,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { "ConduitErrorRevertBytes(bytes,bytes32,address)", panicError, conduitKeyOne, - conduit + derivedConduit ) ); } @@ -1216,11 +1220,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { 1 ); - (address conduit, bool exists) = mockConduitController.getConduit( - conduitKeyAlice - ); + (address derivedConduit, bool exists) = mockConduitController + .getConduit(conduitKeyAlice); - assertEq(address(mockConduit), conduit); + assertEq(address(mockConduit), derivedConduit); assertEq(exists, true); vm.expectRevert( @@ -1231,7 +1234,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -1287,11 +1290,10 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { 1 ); - (address conduit, bool exists) = mockConduitController.getConduit( - conduitKeyAlice - ); + (address derivedConduit, bool exists) = mockConduitController + .getConduit(conduitKeyAlice); - assertEq(address(mockConduit), conduit); + assertEq(address(mockConduit), derivedConduit); assertEq(exists, true); vm.expectRevert( @@ -1303,7 +1305,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); @@ -1359,16 +1361,15 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { 1 ); - (address conduit, bool exists) = mockConduitController.getConduit( - conduitKeyAlice - ); + (address derivedConduit, bool exists) = mockConduitController + .getConduit(conduitKeyAlice); - assertEq(address(mockConduit), conduit); + assertEq(address(mockConduit), derivedConduit); assertEq(exists, true); bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithRecipientFromTransferHelperItems( items, bob ); From 1393ec97443d82ef77b260e70c990de595d5382f Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 19 Jul 2022 17:22:10 -0700 Subject: [PATCH 107/126] progress on multi recipient forge tests --- .../TransferHelperMultipleRecipientsTest.sol | 534 +++++++++++------- .../TransferHelperSingleRecipientTest.sol | 1 - 2 files changed, 330 insertions(+), 205 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 1dcdcc0cc..16eec6f72 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -91,6 +91,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256[10] identifiers; // Indexes that can be used to select tokens from the arrays erc20s/erc721s/erc1155s uint256[10] tokenIndex; + // Recipients that can be used for the recipient field on TransferHelperItemsWithRecipients address[10] recipients; } @@ -226,36 +227,47 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + function _ensureFuzzAssumptions(FuzzInputsCommon memory inputs) internal { + for (uint256 i = 0; i < inputs.amounts.length; i++) { + inputs.amounts[i] = bound(inputs.amounts[i], 1, 2**128); + vm.assume(inputs.recipients[i] != address(0)); + } + } + + function _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + address from, TransferHelperItem[] memory items, - // TODO stephen: support multiple to (recipients) and move to helper - address to + address[10] memory recipients ) internal view returns (TransferHelperItemsWithRecipient[] memory) { TransferHelperItemsWithRecipient[] memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( - items.length + recipients.length ); - itemsWithRecipient[0] = TransferHelperItemsWithRecipient( - items, - to, - true - ); + for (uint256 i = 0; i < recipients.length; i++) { + itemsWithRecipient[i] = TransferHelperItemsWithRecipient( + items, + _makeSafeRecipient(from, recipients[i]), + true + ); + } + return itemsWithRecipient; } function _performSingleItemTransferAndCheckBalances( TransferHelperItem memory item, address from, - address to, + address[10] memory recipients, bool useConduit, bytes memory expectRevertData ) public { TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = item; + _performMultiItemTransferAndCheckBalances( items, from, - to, + recipients, useConduit, expectRevertData ); @@ -264,24 +276,42 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function _performMultiItemTransferAndCheckBalances( TransferHelperItem[] memory items, address from, - address to, + address[10] memory recipients, bool useConduit, bytes memory expectRevertData ) public { vm.startPrank(from); - // Get balances before transfer + // Get size of beforeTransferBalances and afterTransferBalances. + uint256 arraySize = items.length * recipients.length; + FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( - items.length + arraySize ); - for (uint256 i = 0; i < items.length; i++) { - beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( - items[i], - from, - to - ); + FromToBalance[] memory afterTransferBalances = new FromToBalance[]( + arraySize + ); + + // Declare index for storing before transfer balances. + uint256 itemIndex; + for (uint256 i = 0; i < recipients.length; i++) { + // Get balances before transfer + for (uint256 j = 0; j < items.length; j++) { + beforeTransferBalances[ + itemIndex + ] = _balanceOfTransferItemForFromTo( + items[j], + from, + recipients[i] + ); + // Increment index. + ++itemIndex; + } } + // Reset index for after transfer balances. + itemIndex = 0; + // Register expected revert if present. if ( // Compare hashes as we cannot directly compare bytes memory with bytes storage. @@ -293,9 +323,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + from, items, - to + recipients ); transferHelper.bulkTransfer( @@ -303,21 +334,25 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { useConduit ? conduitKeyOne : bytes32(0) ); - // Get balances after transfer - FromToBalance[] memory afterTransferBalances = new FromToBalance[]( - items.length - ); - for (uint256 i = 0; i < items.length; i++) { - afterTransferBalances[i] = _balanceOfTransferItemForFromTo( - items[i], - from, - to - ); + for (uint256 i = 0; i < recipients.length; i++) { + // Get balances after transfer + for (uint256 j = 0; j < items.length; j++) { + afterTransferBalances[ + itemIndex + ] = _balanceOfTransferItemForFromTo( + items[j], + from, + recipients[i] + ); + ++itemIndex; + } } + assertEq(beforeTransferBalances.length, afterTransferBalances.length); + if (expectRevertData.length > 0) { // If revert is expected, balances should not have changed. - for (uint256 i = 0; i < items.length; i++) { + for (uint256 i = 0; i < beforeTransferBalances.length; i++) { assert( beforeTransferBalances[i].from == afterTransferBalances[i].from @@ -351,25 +386,43 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function _performMultiItemTransferAndCheckBalances( TransferHelperItem[] memory items, address from, - address to, + address[10] memory recipients, bool useConduit, bytes memory expectRevertDataWithConduit, bytes memory expectRevertDataWithoutConduit ) public { vm.startPrank(from); - // Get balances before transfer + // Get size of beforeTransferBalances and afterTransferBalances. + uint256 arraySize = items.length * recipients.length; + FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( - items.length + arraySize ); - for (uint256 i = 0; i < items.length; i++) { - beforeTransferBalances[i] = _balanceOfTransferItemForFromTo( - items[i], - from, - to - ); + FromToBalance[] memory afterTransferBalances = new FromToBalance[]( + arraySize + ); + + // Declare index for storing before transfer balances. + uint256 itemIndex; + for (uint256 i = 0; i < recipients.length; i++) { + // Get balances before transfer + for (uint256 j = 0; j < items.length; j++) { + beforeTransferBalances[ + itemIndex + ] = _balanceOfTransferItemForFromTo( + items[j], + from, + recipients[i] + ); + // Increment index. + ++itemIndex; + } } + // Reset index for after transfer balances. + itemIndex = 0; + // Register expected revert if present. if ( // Compare hashes as we cannot directly compare bytes memory with bytes storage. @@ -388,33 +441,38 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } // Perform transfer. TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + from, items, - to + recipients ); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); - // Get balances after transfer - FromToBalance[] memory afterTransferBalances = new FromToBalance[]( - items.length - ); - for (uint256 i = 0; i < items.length; i++) { - afterTransferBalances[i] = _balanceOfTransferItemForFromTo( - items[i], - from, - to - ); + for (uint256 i = 0; i < recipients.length; i++) { + // Get balances after transfer + for (uint256 j = 0; j < items.length; j++) { + afterTransferBalances[ + itemIndex + ] = _balanceOfTransferItemForFromTo( + items[j], + from, + recipients[i] + ); + ++itemIndex; + } } + assertEq(beforeTransferBalances.length, afterTransferBalances.length); + if ( (expectRevertDataWithConduit.length > 0) || (expectRevertDataWithoutConduit.length > 0) ) { // If revert is expected, balances should not have changed. - for (uint256 i = 0; i < items.length; i++) { + for (uint256 i = 0; i < beforeTransferBalances.length; i++) { assert( beforeTransferBalances[i].from == afterTransferBalances[i].from @@ -425,21 +483,24 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } return; } - + uint256 balanceIndex; // Check after transfer balances are as expected by calculating difference against before transfer balances. - for (uint256 i = 0; i < items.length; i++) { - // ERC721 balance should only ever change by amount 1. - uint256 amount = items[i].itemType == ConduitItemType.ERC721 - ? 1 - : items[i].amount; - assertEq( - afterTransferBalances[i].from, - beforeTransferBalances[i].from - amount - ); - assertEq( - afterTransferBalances[i].to, - beforeTransferBalances[i].to + amount - ); + for (uint256 i = 0; i < recipients.length; i++) { + for (uint256 j = 0; j < items.length; j++) { + // ERC721 balance should only ever change by amount 1. + uint256 amount = items[j].itemType == ConduitItemType.ERC721 + ? 1 + : items[j].amount; + assertEq( + afterTransferBalances[balanceIndex].from, + beforeTransferBalances[balanceIndex].from - amount + ); + assertEq( + afterTransferBalances[balanceIndex].to, + beforeTransferBalances[balanceIndex].to + amount + ); + ++balanceIndex; + } } vm.stopPrank(); @@ -582,94 +643,131 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // Test successful transfers function testBulkTransferERC20(FuzzInputsCommon memory inputs) public { - TransferHelperItem memory item = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC20, - inputs.amounts[0], - inputs.tokenIndex[0], - 0, - inputs.recipients[0] - ); + uint256 numItems = inputs.amounts.length; - _performSingleItemTransferAndCheckBalances( - item, + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC20, + inputs.amounts[i], + inputs.tokenIndex[i], + 0, + inputs.recipients[i] + ); + } + + _performMultiItemTransferAndCheckBalances( + items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); } function testBulkTransferERC721(FuzzInputsCommon memory inputs) public { - TransferHelperItem memory item = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC721, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] - ); + _ensureFuzzAssumptions(inputs); - _performSingleItemTransferAndCheckBalances( - item, - alice, - inputs.recipients[0], - inputs.useConduit, - "" - ); - } + uint256 numItems = inputs.amounts.length; - function testBulkTransferERC721toBobThenCal(FuzzInputsCommon memory inputs) - public - { - TransferHelperItem memory item = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC721, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0], - bob - ); + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); - TransferHelperItem memory item2 = _getFuzzedTransferItem( - bob, - ConduitItemType.ERC721, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0], - cal - ); + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[i], + inputs.identifiers[i], + inputs.recipients[i] + ); + } - _performSingleItemTransferAndCheckBalances( - item, + _performMultiItemTransferAndCheckBalances( + items, alice, - bob, - inputs.useConduit, - "" - ); - _performSingleItemTransferAndCheckBalances( - item2, - bob, - cal, + inputs.recipients, inputs.useConduit, "" ); } + // function testBulkTransferERC721toBobThenCal(FuzzInputsCommon memory inputs) + // public + // { + // TransferHelperItem memory item = _getFuzzedTransferItem( + // alice, + // ConduitItemType.ERC721, + // inputs.amounts[0], + // inputs.tokenIndex[0], + // inputs.identifiers[0], + // bob + // ); + + // TransferHelperItem memory item2 = _getFuzzedTransferItem( + // bob, + // ConduitItemType.ERC721, + // inputs.amounts[0], + // inputs.tokenIndex[0], + // inputs.identifiers[0], + // cal + // ); + + // uint256 numItems = inputs.amounts.length; + + // TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + // for (uint256 i = 0; i < numItems; i++) { + // items[i] = _getFuzzedTransferItem( + // alice, + // ConduitItemType.ERC721, + // 1, + // inputs.tokenIndex[i], + // inputs.identifiers[i], + // inputs.recipients[i] + // ); + // } + + // _performSingleItemTransferAndCheckBalances( + // item, + // alice, + // inputs.recipients, + // inputs.useConduit, + // "" + // ); + // _performSingleItemTransferAndCheckBalances( + // item2, + // bob, + // inputs.recipients, + // inputs.useConduit, + // "" + // ); + // } + function testBulkTransferERC1155(FuzzInputsCommon memory inputs) public { - TransferHelperItem memory item = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC1155, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] - ); + _ensureFuzzAssumptions(inputs); - _performSingleItemTransferAndCheckBalances( - item, + uint256 numItems = inputs.amounts.length; + + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[i], + inputs.tokenIndex[i], + inputs.identifiers[i], + inputs.recipients[i] + ); + } + + _performMultiItemTransferAndCheckBalances( + items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -678,28 +776,36 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC1155andERC721(FuzzInputsCommon memory inputs) public { - TransferHelperItem[] memory items = new TransferHelperItem[](2); - items[0] = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC1155, - inputs.amounts[0], - inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] - ); - items[1] = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC721, - inputs.amounts[1], - inputs.tokenIndex[1], - inputs.identifiers[1], - inputs.recipients[1] - ); + uint256 numItems = inputs.amounts.length; + + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + if (i % 2 == 0) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC1155, + inputs.amounts[i], + inputs.tokenIndex[i], + inputs.identifiers[i], + inputs.recipients[i] + ); + } else { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + inputs.amounts[i], + inputs.tokenIndex[i], + inputs.identifiers[i], + inputs.recipients[i] + ); + } + } _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -737,7 +843,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -765,7 +871,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -804,7 +910,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -844,7 +950,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, inputs.useConduit, "" ); @@ -853,19 +959,25 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testBulkTransferERC7211NotUsingConduit( FuzzInputsCommon memory inputs ) public { - TransferHelperItem memory item = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC721, - 1, - inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] - ); + uint256 numItems = inputs.amounts.length; - _performSingleItemTransferAndCheckBalances( - item, + TransferHelperItem[] memory items = new TransferHelperItem[](numItems); + + for (uint256 i = 0; i < numItems; i++) { + items[i] = _getFuzzedTransferItem( + alice, + ConduitItemType.ERC721, + 1, + inputs.tokenIndex[i], + inputs.identifiers[i], + inputs.recipients[i] + ); + } + + _performMultiItemTransferAndCheckBalances( + items, alice, - inputs.recipients[0], + inputs.recipients, false, "" ); @@ -896,7 +1008,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, false, "" ); @@ -927,7 +1039,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - inputs.recipients[0], + inputs.recipients, false, "" ); @@ -952,7 +1064,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - inputs.recipients[0], + inputs.recipients, false, abi.encodePacked( TransferHelperErrors.InvalidERC20Identifier.selector @@ -976,7 +1088,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - inputs.recipients[0], + inputs.recipients, false, abi.encodeWithSignature( "InvalidERC721Recipient(address)", @@ -1000,9 +1112,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) @@ -1015,7 +1128,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - bob, + inputs.recipients, inputs.useConduit, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1050,9 +1163,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) @@ -1065,7 +1179,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - bob, + inputs.recipients, inputs.useConduit, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1096,9 +1210,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { items[0] = item; bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) @@ -1110,7 +1225,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - bob, + inputs.recipients, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1146,9 +1261,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); try transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) @@ -1161,7 +1277,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - bob, + inputs.recipients, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1192,10 +1308,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address(transferHelper) ); - _performSingleItemTransferAndCheckBalances( - items[0], + _performMultiItemTransferAndCheckBalances( + items, alice, - bob, + inputs.recipients, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1236,9 +1352,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(); vm.prank(alice); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne); } @@ -1258,7 +1375,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - bob, + inputs.recipients, false, abi.encodeWithSignature( "ERC721ReceiverErrorRevertString(string,address,address,uint256)", @@ -1270,7 +1387,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function testRevertStringErrorWithConduit() public { + function testRevertStringErrorWithConduit(FuzzInputsCommon memory inputs) + public + { TransferHelperItem memory item = TransferHelperItem( ConduitItemType.ERC721, address(erc721s[0]), @@ -1284,7 +1403,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, bob, - alice, + inputs.recipients, true, abi.encodeWithSignature( "ConduitErrorRevertString(string,bytes32,address)", @@ -1295,7 +1414,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function testRevertPanicErrorWithConduit() public { + function testRevertPanicErrorWithConduit(FuzzInputsCommon memory inputs) + public + { // Create ERC20 token that reverts with a panic when calling transferFrom. TestERC20Panic panicERC20 = new TestERC20Panic(); @@ -1320,7 +1441,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performMultiItemTransferAndCheckBalances( items, alice, - bob, + inputs.recipients, true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", @@ -1331,7 +1452,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function testRevertInvalidConduitMagicValue() public { + function testRevertInvalidConduitMagicValue(FuzzInputsCommon memory inputs) + public + { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( 2 // ConduitMockInvalidMagic @@ -1394,15 +1517,16 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } - function testRevertNoErrorString() public { + function testRevertNoErrorString(FuzzInputsCommon memory inputs) public { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( 1 // ConduitMockRevertNoReason @@ -1466,15 +1590,16 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) ); TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice); vm.stopPrank(); } - function testRevertWithData() public { + function testRevertWithData(FuzzInputsCommon memory inputs) public { // Deploy mock conduit controller ConduitControllerMock mockConduitController = new ConduitControllerMock( 3 // ConduitMockRevertBytes @@ -1531,9 +1656,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory returnedData; TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithRecipientsFromTransferHelperItems( + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + alice, items, - bob + inputs.recipients ); try mockTransferHelper.bulkTransfer(itemsWithRecipient, conduitKeyAlice) diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index 387de3bcb..9dee16b7e 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -204,7 +204,6 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { function _getTransferHelperItemsWithRecipientFromTransferHelperItems( TransferHelperItem[] memory items, - // TODO stephen: support multiple to (recipients) and move to helper address to ) internal view returns (TransferHelperItemsWithRecipient[] memory) { TransferHelperItemsWithRecipient[] From bb32d56a3be40b6a020ee0fa7f8b4879138a14b2 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Tue, 19 Jul 2022 19:15:34 -0700 Subject: [PATCH 108/126] add vm assumptions --- .../TransferHelperMultipleRecipientsTest.sol | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 16eec6f72..c85086207 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -227,11 +227,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function _ensureFuzzAssumptions(FuzzInputsCommon memory inputs) internal { + modifier _ensureFuzzAssumptions(FuzzInputsCommon memory inputs) { for (uint256 i = 0; i < inputs.amounts.length; i++) { - inputs.amounts[i] = bound(inputs.amounts[i], 1, 2**128); + vm.assume(inputs.amounts[i] > 0); vm.assume(inputs.recipients[i] != address(0)); } + _; } function _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( @@ -565,7 +566,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address fuzzRecipient, bool reverting ) internal view returns (TransferHelperItem memory) { - uint256 amount = fuzzAmount % TOTAL_FUNGIBLE_TOKENS; + uint256 amount = fuzzAmount % (TOTAL_FUNGIBLE_TOKENS / 10); uint256 identifier = fuzzIdentifier % TOTAL_TOKEN_IDENTIFERS; address recipient = _makeSafeRecipient(from, fuzzRecipient, reverting); if (itemType == ConduitItemType.ERC20) { @@ -667,9 +668,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - function testBulkTransferERC721(FuzzInputsCommon memory inputs) public { - _ensureFuzzAssumptions(inputs); - + function testBulkTransferERC721(FuzzInputsCommon memory inputs) + public + _ensureFuzzAssumptions(inputs) + { uint256 numItems = inputs.amounts.length; TransferHelperItem[] memory items = new TransferHelperItem[](numItems); @@ -746,9 +748,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // ); // } - function testBulkTransferERC1155(FuzzInputsCommon memory inputs) public { - _ensureFuzzAssumptions(inputs); - + function testBulkTransferERC1155(FuzzInputsCommon memory inputs) + public + _ensureFuzzAssumptions(inputs) + { uint256 numItems = inputs.amounts.length; TransferHelperItem[] memory items = new TransferHelperItem[](numItems); From 021bce6a872d4b58460f32c3a01d0cc08ae94f27 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 09:28:45 -0700 Subject: [PATCH 109/126] update _getFuzzedTransferItem --- .../TransferHelperMultipleRecipientsTest.sol | 169 +++++------------- 1 file changed, 41 insertions(+), 128 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index c85086207..c91bfa682 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -538,37 +538,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } function _getFuzzedTransferItem( - address from, - ConduitItemType itemType, - uint256 fuzzAmount, - uint256 fuzzIndex, - uint256 fuzzIdentifier, - address fuzzRecipient - ) internal view returns (TransferHelperItem memory) { - return - _getFuzzedTransferItem( - from, - itemType, - fuzzAmount, - fuzzIndex, - fuzzIdentifier, - fuzzRecipient, - false - ); - } - - function _getFuzzedTransferItem( - address from, ConduitItemType itemType, uint256 fuzzAmount, uint256 fuzzIndex, - uint256 fuzzIdentifier, - address fuzzRecipient, - bool reverting + uint256 fuzzIdentifier ) internal view returns (TransferHelperItem memory) { uint256 amount = fuzzAmount % (TOTAL_FUNGIBLE_TOKENS / 10); uint256 identifier = fuzzIdentifier % TOTAL_TOKEN_IDENTIFERS; - address recipient = _makeSafeRecipient(from, fuzzRecipient, reverting); if (itemType == ConduitItemType.ERC20) { uint256 index = fuzzIndex % erc20s.length; TestERC20 erc20 = erc20s[index]; @@ -578,7 +554,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address(erc20), identifier, amount - // recipient ); } else if (itemType == ConduitItemType.ERC1155) { uint256 index = fuzzIndex % erc1155s.length; @@ -589,17 +564,9 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address(erc1155), identifier, amount - // recipient ); } else if (itemType == ConduitItemType.NATIVE) { - return - TransferHelperItem( - itemType, - address(0), - identifier, - amount - // recipient - ); + return TransferHelperItem(itemType, address(0), identifier, amount); } else if (itemType == ConduitItemType.ERC721) { uint256 index = fuzzIndex % erc721s.length; return @@ -608,7 +575,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address(erc721s[index]), identifier, 1 - // recipient ); } revert(); @@ -622,12 +588,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address fuzzRecipient ) internal view returns (TransferHelperItem memory) { TransferHelperItem memory item = _getFuzzedTransferItem( - from, ConduitItemType.ERC721, fuzzAmount, fuzzIndex, - fuzzIdentifier, - fuzzRecipient + fuzzIdentifier ); item.amount = 2 + (fuzzAmount % TOTAL_FUNGIBLE_TOKENS); return item; @@ -650,12 +614,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[i], inputs.tokenIndex[i], - 0, - inputs.recipients[i] + 0 ); } @@ -677,14 +639,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { TransferHelperItem[] memory items = new TransferHelperItem[](numItems); for (uint256 i = 0; i < numItems; i++) { - items[i] = _getFuzzedTransferItem( - alice, - ConduitItemType.ERC721, - 1, - inputs.tokenIndex[i], - inputs.identifiers[i], - inputs.recipients[i] - ); + items[i] = _getFuzzedTransferItem(ConduitItemType.ERC721, 1, i, i); } _performMultiItemTransferAndCheckBalances( @@ -758,12 +713,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC1155, inputs.amounts[i], inputs.tokenIndex[i], - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } @@ -786,21 +739,17 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { if (i % 2 == 0) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC1155, inputs.amounts[i], inputs.tokenIndex[i], - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } else { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[i], inputs.tokenIndex[i], - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } } @@ -819,28 +768,22 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { TransferHelperItem[] memory items = new TransferHelperItem[](3); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC1155, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] + inputs.identifiers[0] ); items[1] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[1], - inputs.identifiers[1], - inputs.recipients[1] + inputs.identifiers[1] ); items[2] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[2], inputs.tokenIndex[2], - 0, - inputs.recipients[2] + 0 ); _performMultiItemTransferAndCheckBalances( @@ -859,15 +802,13 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { TransferHelperItem[] memory items = new TransferHelperItem[](numItems); for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[i], // Same token index for all items since this is testing from same contract inputs.tokenIndex[0], // Each item has a different token identifier as alice only owns one ERC721 token // for each identifier for this particular contract - i, - inputs.recipients[0] + i ); } @@ -885,29 +826,23 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { TransferHelperItem[] memory items = new TransferHelperItem[](3); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[0], // Different token index for all items since this is testing from different contracts 0, - inputs.identifiers[0], - inputs.recipients[0] + inputs.identifiers[0] ); items[1] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[1], 1, - inputs.identifiers[1], - inputs.recipients[1] + inputs.identifiers[1] ); items[2] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[2], 2, - inputs.identifiers[2], - inputs.recipients[2] + inputs.identifiers[2] ); _performMultiItemTransferAndCheckBalances( @@ -930,22 +865,18 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { if (i < numItems / 2) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC1155, inputs.amounts[i], // Ensure each item is from a different contract i, - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } else { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, inputs.amounts[i], i, - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } } @@ -968,12 +899,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[i], - inputs.identifiers[i], - inputs.recipients[i] + inputs.identifiers[i] ); } @@ -999,12 +928,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i = 0; i < numItems; i++) { items[i] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[i], - i, - address(validERC721Receiver) + i ); } @@ -1022,21 +949,17 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[0], - inputs.identifiers[0], - inputs.recipients[0] + inputs.identifiers[0] ); items[1] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[1], inputs.tokenIndex[1], - 0, - inputs.recipients[1] + 0 ); _performMultiItemTransferAndCheckBalances( @@ -1054,12 +977,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { FuzzInputsCommon memory inputs ) public { TransferHelperItem memory item = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - 5, - inputs.recipients[0] + 5 ); // Ensure ERC20 identifier is at least 1 item.identifier += 1; @@ -1079,13 +1000,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { FuzzInputsCommon memory inputs ) public { TransferHelperItem memory item = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[0], - inputs.identifiers[0], - address(invalidRecipient), - true + inputs.identifiers[0] ); _performSingleItemTransferAndCheckBalances( @@ -1105,12 +1023,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { { TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.NATIVE, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], - bob + inputs.identifiers[0] ); bytes memory returnedData; @@ -1148,20 +1064,16 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { { TransferHelperItem[] memory items = new TransferHelperItem[](2); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.NATIVE, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], - bob + inputs.identifiers[0] ); items[1] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[1], - inputs.identifiers[1], - bob + inputs.identifiers[1] ); bytes memory returnedData; @@ -1254,12 +1166,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); items[1] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[1], inputs.tokenIndex[1], - inputs.identifiers[1], - bob + inputs.identifiers[1] ); bytes memory returnedData; @@ -1296,12 +1206,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], - bob + inputs.identifiers[0] ); _updateConduitChannel(false); @@ -1336,12 +1244,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { TransferHelperItem[] memory items = new TransferHelperItem[](1); items[0] = _getFuzzedTransferItem( - alice, ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], - bob + inputs.identifiers[0] ); // Reassign the conduit key that gets passed into TransferHelper to fuzzConduitKey. @@ -1366,19 +1272,26 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertInvalidERC721Receiver(FuzzInputsCommon memory inputs) public { + address[10] memory invalidReceivers; + + for (uint256 i = 0; i < 10; i++) { + invalidReceivers[i] = address( + new ERC721ReceiverMock( + 0xabcd0000, + ERC721ReceiverMock.Error.RevertWithMessage + ) + ); + } TransferHelperItem memory item = _getFuzzedTransferItem( - alice, ConduitItemType.ERC721, 1, inputs.tokenIndex[0], - inputs.identifiers[0], - address(invalidERC721Receiver), - true + inputs.identifiers[0] ); _performSingleItemTransferAndCheckBalances( item, alice, - inputs.recipients, + invalidReceivers, false, abi.encodeWithSignature( "ERC721ReceiverErrorRevertString(string,address,address,uint256)", From a414309d04afa8cc2b419df04780fff1ad2c9d2e Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Wed, 20 Jul 2022 16:31:58 -0700 Subject: [PATCH 110/126] add stub tokens and update multipletransfer test --- .../TransferHelperMultipleRecipientsTest.sol | 433 +++++++----------- test/foundry/token/StubERC1155.sol | 21 + test/foundry/token/StubERC20.sol | 15 + test/foundry/token/StubERC721.sol | 18 + 4 files changed, 225 insertions(+), 262 deletions(-) create mode 100644 test/foundry/token/StubERC1155.sol create mode 100644 test/foundry/token/StubERC20.sol create mode 100644 test/foundry/token/StubERC721.sol diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index c91bfa682..bae995c1d 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -62,6 +62,9 @@ import { } from "../../contracts/test/ERC721ReceiverMock.sol"; import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; +import { StubERC20 } from "./token/StubERC20.sol"; +import { StubERC721 } from "./token/StubERC721.sol"; +import { StubERC1155 } from "./token/StubERC1155.sol"; contract TransferHelperMultipleRecipientsTest is BaseOrderTest { TransferHelper transferHelper; @@ -75,6 +78,10 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ERC721ReceiverMock invalidERC721Receiver; InvalidERC721Recipient invalidRecipient; + StubERC20[] stubERC20s; + StubERC721[] stubERC721s; + StubERC1155[] stubERC1155s; + struct FromToBalance { // Balance of from address. uint256 from; @@ -95,10 +102,19 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address[10] recipients; } + function _deployStubTokens() internal { + for (uint256 i; i < 3; i++) { + stubERC20s.push(new StubERC20()); + stubERC721s.push(new StubERC721()); + stubERC1155s.push(new StubERC1155()); + } + } + function setUp() public override { super.setUp(); _deployAndConfigurePrecompiledTransferHelper(); vm.label(address(transferHelper), "transferHelper"); + _deployStubTokens(); // Mint initial tokens to alice for tests. for (uint256 tokenIdx = 0; tokenIdx < erc20s.length; tokenIdx++) { @@ -180,14 +196,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { erc721s[i].setApprovalForAll(address(transferHelper), true); } vm.stopPrank(); - // emit log_named_address( - // "Owner proxy approved for all tokens from", - // _owner - // ); - // emit log_named_address( - // "Consideration approved for all tokens from", - // _owner - // ); } function _updateConduitChannel(bool open) internal { @@ -283,36 +291,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ) public { vm.startPrank(from); - // Get size of beforeTransferBalances and afterTransferBalances. - uint256 arraySize = items.length * recipients.length; - - FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( - arraySize - ); - FromToBalance[] memory afterTransferBalances = new FromToBalance[]( - arraySize - ); - - // Declare index for storing before transfer balances. - uint256 itemIndex; - for (uint256 i = 0; i < recipients.length; i++) { - // Get balances before transfer - for (uint256 j = 0; j < items.length; j++) { - beforeTransferBalances[ - itemIndex - ] = _balanceOfTransferItemForFromTo( - items[j], - from, - recipients[i] - ); - // Increment index. - ++itemIndex; - } - } - - // Reset index for after transfer balances. - itemIndex = 0; - + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + from, + items, + recipients + ); // Register expected revert if present. if ( // Compare hashes as we cannot directly compare bytes memory with bytes storage. @@ -321,66 +305,58 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(); } else if (expectRevertData.length > 0) { vm.expectRevert(expectRevertData); + } else { + // otherwise register expected emits + + address operator = useConduit + ? address(conduit) + : address(transferHelper); + + for (uint256 i; i < itemsWithRecipient.length; i++) { + TransferHelperItemsWithRecipient + memory singleItemsWithRecipient = itemsWithRecipient[i]; + for ( + uint256 j; + j < singleItemsWithRecipient.items.length; + j++ + ) { + TransferHelperItem memory item = singleItemsWithRecipient + .items[j]; + // expect all 3 indexed topics plus data since Transfer event has same signature for ERC20/ERC721, + // but tokenId is indexed for 721 and not for ERC20 (so amount is data) + // ERC1155 has three indexed topics plus data. + vm.expectEmit(true, true, true, true, item.token); + if (item.itemType == ConduitItemType.ERC20) { + emit Transfer( + from, + singleItemsWithRecipient.recipient, + item.amount + ); + } else if (item.itemType == ConduitItemType.ERC721) { + emit Transfer( + from, + singleItemsWithRecipient.recipient, + item.identifier + ); + } else { + emit TransferSingle( + operator, + from, + singleItemsWithRecipient.recipient, + item.identifier, + item.amount + ); + } + } + } } // Perform transfer. - TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( - from, - items, - recipients - ); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); - for (uint256 i = 0; i < recipients.length; i++) { - // Get balances after transfer - for (uint256 j = 0; j < items.length; j++) { - afterTransferBalances[ - itemIndex - ] = _balanceOfTransferItemForFromTo( - items[j], - from, - recipients[i] - ); - ++itemIndex; - } - } - - assertEq(beforeTransferBalances.length, afterTransferBalances.length); - - if (expectRevertData.length > 0) { - // If revert is expected, balances should not have changed. - for (uint256 i = 0; i < beforeTransferBalances.length; i++) { - assert( - beforeTransferBalances[i].from == - afterTransferBalances[i].from - ); - assert( - beforeTransferBalances[i].to == afterTransferBalances[i].to - ); - } - return; - } - - // Check after transfer balances are as expected by calculating difference against before transfer balances. - for (uint256 i = 0; i < items.length; i++) { - // ERC721 balance should only ever change by amount 1. - uint256 amount = items[i].itemType == ConduitItemType.ERC721 - ? 1 - : items[i].amount; - assertEq( - afterTransferBalances[i].from, - beforeTransferBalances[i].from - amount - ); - assertEq( - afterTransferBalances[i].to, - beforeTransferBalances[i].to + amount - ); - } - vm.stopPrank(); } @@ -393,37 +369,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory expectRevertDataWithoutConduit ) public { vm.startPrank(from); - - // Get size of beforeTransferBalances and afterTransferBalances. - uint256 arraySize = items.length * recipients.length; - - FromToBalance[] memory beforeTransferBalances = new FromToBalance[]( - arraySize - ); - FromToBalance[] memory afterTransferBalances = new FromToBalance[]( - arraySize - ); - - // Declare index for storing before transfer balances. - uint256 itemIndex; - for (uint256 i = 0; i < recipients.length; i++) { - // Get balances before transfer - for (uint256 j = 0; j < items.length; j++) { - beforeTransferBalances[ - itemIndex - ] = _balanceOfTransferItemForFromTo( - items[j], - from, - recipients[i] - ); - // Increment index. - ++itemIndex; - } - } - - // Reset index for after transfer balances. - itemIndex = 0; - + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + from, + items, + recipients + ); // Register expected revert if present. if ( // Compare hashes as we cannot directly compare bytes memory with bytes storage. @@ -439,71 +390,58 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { vm.expectRevert(expectRevertDataWithConduit); } else if (expectRevertDataWithoutConduit.length > 0 && !useConduit) { vm.expectRevert(expectRevertDataWithoutConduit); + } else { + // otherwise register expected emits + + address operator = useConduit + ? address(conduit) + : address(transferHelper); + + for (uint256 i; i < itemsWithRecipient.length; i++) { + TransferHelperItemsWithRecipient + memory singleItemsWithRecipient = itemsWithRecipient[i]; + for ( + uint256 j; + j < singleItemsWithRecipient.items.length; + j++ + ) { + TransferHelperItem memory item = singleItemsWithRecipient + .items[j]; + // expect all 3 indexed topics plus data since Transfer event has same signature for ERC20/ERC721, + // but tokenId is indexed for 721 and not for ERC20 (so amount is data) + // ERC1155 has three indexed topics plus data. + vm.expectEmit(true, true, true, true, item.token); + if (item.itemType == ConduitItemType.ERC20) { + emit Transfer( + from, + singleItemsWithRecipient.recipient, + item.amount + ); + } else if (item.itemType == ConduitItemType.ERC721) { + emit Transfer( + from, + singleItemsWithRecipient.recipient, + item.identifier + ); + } else { + emit TransferSingle( + operator, + from, + singleItemsWithRecipient.recipient, + item.identifier, + item.amount + ); + } + } + } } // Perform transfer. - TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( - from, - items, - recipients - ); + transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); - for (uint256 i = 0; i < recipients.length; i++) { - // Get balances after transfer - for (uint256 j = 0; j < items.length; j++) { - afterTransferBalances[ - itemIndex - ] = _balanceOfTransferItemForFromTo( - items[j], - from, - recipients[i] - ); - ++itemIndex; - } - } - - assertEq(beforeTransferBalances.length, afterTransferBalances.length); - - if ( - (expectRevertDataWithConduit.length > 0) || - (expectRevertDataWithoutConduit.length > 0) - ) { - // If revert is expected, balances should not have changed. - for (uint256 i = 0; i < beforeTransferBalances.length; i++) { - assert( - beforeTransferBalances[i].from == - afterTransferBalances[i].from - ); - assert( - beforeTransferBalances[i].to == afterTransferBalances[i].to - ); - } - return; - } - uint256 balanceIndex; - // Check after transfer balances are as expected by calculating difference against before transfer balances. - for (uint256 i = 0; i < recipients.length; i++) { - for (uint256 j = 0; j < items.length; j++) { - // ERC721 balance should only ever change by amount 1. - uint256 amount = items[j].itemType == ConduitItemType.ERC721 - ? 1 - : items[j].amount; - assertEq( - afterTransferBalances[balanceIndex].from, - beforeTransferBalances[balanceIndex].from - amount - ); - assertEq( - afterTransferBalances[balanceIndex].to, - beforeTransferBalances[balanceIndex].to + amount - ); - ++balanceIndex; - } - } - vm.stopPrank(); } @@ -542,40 +480,58 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { uint256 fuzzAmount, uint256 fuzzIndex, uint256 fuzzIdentifier + ) internal view returns (TransferHelperItem memory) { + return + _getFuzzedTransferItem( + itemType, + fuzzAmount, + fuzzIndex, + fuzzIdentifier, + true + ); + } + + function _getFuzzedTransferItem( + ConduitItemType itemType, + uint256 fuzzAmount, + uint256 fuzzIndex, + uint256 fuzzIdentifier, + bool useStub ) internal view returns (TransferHelperItem memory) { uint256 amount = fuzzAmount % (TOTAL_FUNGIBLE_TOKENS / 10); uint256 identifier = fuzzIdentifier % TOTAL_TOKEN_IDENTIFERS; if (itemType == ConduitItemType.ERC20) { - uint256 index = fuzzIndex % erc20s.length; - TestERC20 erc20 = erc20s[index]; - return - TransferHelperItem( - itemType, - address(erc20), - identifier, - amount - ); + address erc20; + if (useStub) { + uint256 index = fuzzIndex % stubERC20s.length; + erc20 = address(stubERC20s[index]); + } else { + uint256 index = fuzzIndex % erc20s.length; + erc20 = address(erc20s[index]); + } + return TransferHelperItem(itemType, erc20, identifier, amount); } else if (itemType == ConduitItemType.ERC1155) { - uint256 index = fuzzIndex % erc1155s.length; - TestERC1155 erc1155 = erc1155s[index]; - return - TransferHelperItem( - itemType, - address(erc1155), - identifier, - amount - ); + address erc1155; + if (useStub) { + uint256 index = fuzzIndex % stubERC1155s.length; + erc1155 = address(stubERC1155s[index]); + } else { + uint256 index = fuzzIndex % erc1155s.length; + erc1155 = address(erc1155s[index]); + } + return TransferHelperItem(itemType, erc1155, identifier, amount); } else if (itemType == ConduitItemType.NATIVE) { return TransferHelperItem(itemType, address(0), identifier, amount); } else if (itemType == ConduitItemType.ERC721) { - uint256 index = fuzzIndex % erc721s.length; - return - TransferHelperItem( - itemType, - address(erc721s[index]), - identifier, - 1 - ); + address erc721; + if (useStub) { + uint256 index = fuzzIndex % stubERC721s.length; + erc721 = address(stubERC721s[index]); + } else { + uint256 index = fuzzIndex % erc721s.length; + erc721 = address(erc721s[index]); + } + return TransferHelperItem(itemType, erc721, identifier, 1); } revert(); } @@ -651,58 +607,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ); } - // function testBulkTransferERC721toBobThenCal(FuzzInputsCommon memory inputs) - // public - // { - // TransferHelperItem memory item = _getFuzzedTransferItem( - // alice, - // ConduitItemType.ERC721, - // inputs.amounts[0], - // inputs.tokenIndex[0], - // inputs.identifiers[0], - // bob - // ); - - // TransferHelperItem memory item2 = _getFuzzedTransferItem( - // bob, - // ConduitItemType.ERC721, - // inputs.amounts[0], - // inputs.tokenIndex[0], - // inputs.identifiers[0], - // cal - // ); - - // uint256 numItems = inputs.amounts.length; - - // TransferHelperItem[] memory items = new TransferHelperItem[](numItems); - - // for (uint256 i = 0; i < numItems; i++) { - // items[i] = _getFuzzedTransferItem( - // alice, - // ConduitItemType.ERC721, - // 1, - // inputs.tokenIndex[i], - // inputs.identifiers[i], - // inputs.recipients[i] - // ); - // } - - // _performSingleItemTransferAndCheckBalances( - // item, - // alice, - // inputs.recipients, - // inputs.useConduit, - // "" - // ); - // _performSingleItemTransferAndCheckBalances( - // item2, - // bob, - // inputs.recipients, - // inputs.useConduit, - // "" - // ); - // } - function testBulkTransferERC1155(FuzzInputsCommon memory inputs) public _ensureFuzzAssumptions(inputs) @@ -1169,7 +1073,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[1], inputs.tokenIndex[1], - inputs.identifiers[1] + inputs.identifiers[1], + false ); bytes memory returnedData; @@ -1209,7 +1114,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + inputs.identifiers[0], + false ); _updateConduitChannel(false); @@ -1247,7 +1153,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + inputs.identifiers[0], + false ); // Reassign the conduit key that gets passed into TransferHelper to fuzzConduitKey. @@ -1286,7 +1193,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC721, 1, inputs.tokenIndex[0], - inputs.identifiers[0] + inputs.identifiers[0], + false ); _performSingleItemTransferAndCheckBalances( item, @@ -1310,7 +1218,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC721, address(erc721s[0]), 5, - 1 + 1, + false ); (address _conduit, ) = conduitController.getConduit(conduitKeyOne); diff --git a/test/foundry/token/StubERC1155.sol b/test/foundry/token/StubERC1155.sol new file mode 100644 index 000000000..8b62942f6 --- /dev/null +++ b/test/foundry/token/StubERC1155.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract StubERC1155 { + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 amount + ); + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + uint256 amount + ) public { + emit TransferSingle(msg.sender, from, to, tokenId, amount); + } +} diff --git a/test/foundry/token/StubERC20.sol b/test/foundry/token/StubERC20.sol new file mode 100644 index 000000000..3932c0a83 --- /dev/null +++ b/test/foundry/token/StubERC20.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract StubERC20 { + event Transfer(address indexed from, address indexed to, uint256 amount); + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + emit Transfer(from, to, amount); + return true; + } +} diff --git a/test/foundry/token/StubERC721.sol b/test/foundry/token/StubERC721.sol new file mode 100644 index 000000000..725a986ea --- /dev/null +++ b/test/foundry/token/StubERC721.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract StubERC721 { + event Transfer( + address indexed from, + address indexed to, + uint256 indexed tokenId + ); + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public { + emit Transfer(from, to, tokenId); + } +} From 82d0ad0f64a44508a7ea1f50204db7931214a207 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Wed, 20 Jul 2022 16:32:46 -0700 Subject: [PATCH 111/126] fix typo --- test/foundry/TransferHelperMultipleRecipientsTest.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index bae995c1d..afbeac718 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -1218,8 +1218,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC721, address(erc721s[0]), 5, - 1, - false + 1 ); (address _conduit, ) = conduitController.getConduit(conduitKeyOne); From 6bc8c794dc27561e78671df433b9985f7da05908 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Wed, 20 Jul 2022 16:34:50 -0700 Subject: [PATCH 112/126] fix safeTransferFrom signature --- test/foundry/token/StubERC1155.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/foundry/token/StubERC1155.sol b/test/foundry/token/StubERC1155.sol index 8b62942f6..d1a84c3ed 100644 --- a/test/foundry/token/StubERC1155.sol +++ b/test/foundry/token/StubERC1155.sol @@ -14,7 +14,8 @@ contract StubERC1155 { address from, address to, uint256 tokenId, - uint256 amount + uint256 amount, + bytes memory ) public { emit TransferSingle(msg.sender, from, to, tokenId, amount); } From 3e5336bfe3a75f8d0b93b07cc8872beb93f1b094 Mon Sep 17 00:00:00 2001 From: James Wenzel Date: Wed, 20 Jul 2022 18:47:17 -0700 Subject: [PATCH 113/126] change emit --- .gitmodules | 3 + lib/openzeppelin-contracts | 1 + .../TransferHelperMultipleRecipientsTest.sol | 66 ++++++++++++------- test/foundry/token/StubERC721.sol | 6 +- 4 files changed, 47 insertions(+), 29 deletions(-) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 9dfb90988..aeca04522 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/murky"] path = lib/murky url = https://github.com/dmfxyz/murky +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 000000000..580b7ab81 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 580b7ab8161e0938b2a3a1ac8871fc9ac30feeed diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index afbeac718..7fd8e848f 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -65,8 +65,10 @@ import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; import { StubERC20 } from "./token/StubERC20.sol"; import { StubERC721 } from "./token/StubERC721.sol"; import { StubERC1155 } from "./token/StubERC1155.sol"; +import { Strings } from "openzeppelin-contracts/utils/Strings.sol"; contract TransferHelperMultipleRecipientsTest is BaseOrderTest { + using Strings for uint256; TransferHelper transferHelper; // Total supply of fungible tokens to be used in tests for all fungible tokens. uint256 constant TOTAL_FUNGIBLE_TOKENS = 1e6; @@ -104,9 +106,24 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function _deployStubTokens() internal { for (uint256 i; i < 3; i++) { - stubERC20s.push(new StubERC20()); - stubERC721s.push(new StubERC721()); - stubERC1155s.push(new StubERC1155()); + StubERC20 stub20 = new StubERC20(); + StubERC721 stub721 = new StubERC721(); + StubERC1155 stub1155 = new StubERC1155(); + vm.label( + address(stub20), + string.concat("StubERC20 #", i.toString()) + ); + vm.label( + address(stub1155), + string.concat("StubERC1155 #", i.toString()) + ); + vm.label( + address(stub721), + string.concat("StubERC721 #", i.toString()) + ); + stubERC20s.push(stub20); + stubERC721s.push(stub721); + stubERC1155s.push(stub1155); } } @@ -289,8 +306,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bool useConduit, bytes memory expectRevertData ) public { - vm.startPrank(from); - TransferHelperItemsWithRecipient[] memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( from, @@ -314,35 +329,36 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { for (uint256 i; i < itemsWithRecipient.length; i++) { TransferHelperItemsWithRecipient - memory singleItemsWithRecipient = itemsWithRecipient[i]; - for ( - uint256 j; - j < singleItemsWithRecipient.items.length; - j++ - ) { - TransferHelperItem memory item = singleItemsWithRecipient + memory firstRecipientTransfer = itemsWithRecipient[i]; + for (uint256 j; j < firstRecipientTransfer.items.length; j++) { + TransferHelperItem memory item = firstRecipientTransfer .items[j]; // expect all 3 indexed topics plus data since Transfer event has same signature for ERC20/ERC721, // but tokenId is indexed for 721 and not for ERC20 (so amount is data) // ERC1155 has three indexed topics plus data. - vm.expectEmit(true, true, true, true, item.token); + if (item.itemType == ConduitItemType.ERC20) { + vm.expectEmit(true, true, true, true, item.token); + emit Transfer( from, - singleItemsWithRecipient.recipient, + firstRecipientTransfer.recipient, item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { + vm.expectEmit(true, true, true, true, item.token); emit Transfer( from, - singleItemsWithRecipient.recipient, + firstRecipientTransfer.recipient, item.identifier ); } else { + vm.expectEmit(true, true, true, true, item.token); + emit TransferSingle( operator, from, - singleItemsWithRecipient.recipient, + firstRecipientTransfer.recipient, item.identifier, item.amount ); @@ -350,14 +366,15 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } } } - // Perform transfer. + // Perform transfer. + vm.prank(from); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); - vm.stopPrank(); + // vm.stopPrank(); } function _performMultiItemTransferAndCheckBalances( @@ -368,7 +385,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bytes memory expectRevertDataWithConduit, bytes memory expectRevertDataWithoutConduit ) public { - vm.startPrank(from); TransferHelperItemsWithRecipient[] memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( from, @@ -410,20 +426,23 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { // expect all 3 indexed topics plus data since Transfer event has same signature for ERC20/ERC721, // but tokenId is indexed for 721 and not for ERC20 (so amount is data) // ERC1155 has three indexed topics plus data. - vm.expectEmit(true, true, true, true, item.token); if (item.itemType == ConduitItemType.ERC20) { + vm.expectEmit(true, true, true, true, item.token); + emit Transfer( from, singleItemsWithRecipient.recipient, item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { + vm.expectEmit(true, true, true, true, item.token); emit Transfer( from, singleItemsWithRecipient.recipient, item.identifier ); } else { + vm.expectEmit(true, true, true, true, item.token); emit TransferSingle( operator, from, @@ -436,13 +455,11 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { } } // Perform transfer. - + vm.prank(from); transferHelper.bulkTransfer( itemsWithRecipient, useConduit ? conduitKeyOne : bytes32(0) ); - - vm.stopPrank(); } function _makeSafeRecipient(address from, address fuzzRecipient) @@ -907,7 +924,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC721, 1, inputs.tokenIndex[0], - inputs.identifiers[0] + inputs.identifiers[0], + false ); _performSingleItemTransferAndCheckBalances( diff --git a/test/foundry/token/StubERC721.sol b/test/foundry/token/StubERC721.sol index 725a986ea..142274a6a 100644 --- a/test/foundry/token/StubERC721.sol +++ b/test/foundry/token/StubERC721.sol @@ -2,11 +2,7 @@ pragma solidity ^0.8.0; contract StubERC721 { - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); + event Transfer(address indexed from, address indexed to, uint256 tokenId); function transferFrom( address from, From 8b18726415518e1f124af10664a59ad405e45a34 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 21:20:49 -0700 Subject: [PATCH 114/126] update helper functions for unsafe recipients --- .../TransferHelperMultipleRecipientsTest.sol | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 7fd8e848f..3274db6a0 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -65,7 +65,7 @@ import { TestERC20Panic } from "../../contracts/test/TestERC20Panic.sol"; import { StubERC20 } from "./token/StubERC20.sol"; import { StubERC721 } from "./token/StubERC721.sol"; import { StubERC1155 } from "./token/StubERC1155.sol"; -import { Strings } from "openzeppelin-contracts/utils/Strings.sol"; +import { Strings } from "openzeppelin-contracts/contracts/utils/Strings.sol"; contract TransferHelperMultipleRecipientsTest is BaseOrderTest { using Strings for uint256; @@ -280,11 +280,31 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { return itemsWithRecipient; } + function _unsafeGetTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + TransferHelperItem[] memory items, + address[10] memory recipients + ) internal view returns (TransferHelperItemsWithRecipient[] memory) { + TransferHelperItemsWithRecipient[] + memory itemsWithRecipient = new TransferHelperItemsWithRecipient[]( + recipients.length + ); + for (uint256 i = 0; i < recipients.length; i++) { + itemsWithRecipient[i] = TransferHelperItemsWithRecipient( + items, + recipients[i], + true + ); + } + + return itemsWithRecipient; + } + function _performSingleItemTransferAndCheckBalances( TransferHelperItem memory item, address from, address[10] memory recipients, bool useConduit, + bool makeSafeRecipient, bytes memory expectRevertData ) public { TransferHelperItem[] memory items = new TransferHelperItem[](1); @@ -295,6 +315,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { from, recipients, useConduit, + makeSafeRecipient, expectRevertData ); } @@ -304,14 +325,24 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { address from, address[10] memory recipients, bool useConduit, + bool makeSafeRecipient, bytes memory expectRevertData ) public { - TransferHelperItemsWithRecipient[] - memory itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + TransferHelperItemsWithRecipient[] memory itemsWithRecipient; + + if (makeSafeRecipient) { + itemsWithRecipient = _getTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( from, items, recipients ); + } else { + itemsWithRecipient = _unsafeGetTransferHelperItemsWithMultipleRecipientsFromTransferHelperItems( + items, + recipients + ); + } + // Register expected revert if present. if ( // Compare hashes as we cannot directly compare bytes memory with bytes storage. @@ -599,6 +630,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -620,6 +652,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -646,6 +679,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -680,6 +714,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -712,6 +747,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -738,6 +774,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -771,6 +808,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -807,6 +845,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, inputs.useConduit, + true, "" ); } @@ -832,6 +871,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, false, + true, "" ); } @@ -861,6 +901,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, false, + true, "" ); } @@ -888,6 +929,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, false, + true, "" ); } @@ -911,6 +953,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, false, + true, abi.encodePacked( TransferHelperErrors.InvalidERC20Identifier.selector ) @@ -920,6 +963,14 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { function testRevertBulkTransferERC721InvalidRecipient( FuzzInputsCommon memory inputs ) public { + address[10] memory invalidRecipients; + + for (uint256 i = 0; i < invalidRecipients.length; i++) { + InvalidERC721Recipient invalid = new InvalidERC721Recipient(); + invalidRecipients[i] = address(invalid); + emit log_named_address("recipient: ", invalidRecipients[i]); + } + TransferHelperItem memory item = _getFuzzedTransferItem( ConduitItemType.ERC721, 1, @@ -931,11 +982,12 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { _performSingleItemTransferAndCheckBalances( item, alice, - inputs.recipients, + invalidRecipients, + false, false, abi.encodeWithSignature( "InvalidERC721Recipient(address)", - invalidRecipient + invalidRecipients[0] ) ); } @@ -1064,6 +1116,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, true, + true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", returnedData, @@ -1115,6 +1168,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, true, + true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", returnedData, @@ -1148,6 +1202,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, true, + true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", returnedData, @@ -1219,10 +1274,11 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, invalidReceivers, false, + false, abi.encodeWithSignature( "ERC721ReceiverErrorRevertString(string,address,address,uint256)", "ERC721ReceiverMock: reverting", - invalidERC721Receiver, + invalidReceivers[0], alice, item.identifier ) @@ -1247,6 +1303,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { bob, inputs.recipients, true, + true, abi.encodeWithSignature( "ConduitErrorRevertString(string,bytes32,address)", "WRONG_FROM", @@ -1285,6 +1342,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { alice, inputs.recipients, true, + true, abi.encodeWithSignature( "ConduitErrorRevertBytes(bytes,bytes32,address)", panicError, From 0699fceb1bc50d17ecf43e1ccfe1e07985dd79c0 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 21:36:29 -0700 Subject: [PATCH 115/126] lint --- test/transferhelper.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index fdb33b78d..5405bca77 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,7 +2,6 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import { randomHex } from "./utils/encoding"; import { faucet } from "./utils/faucet"; import { fixtureERC1155, @@ -15,11 +14,11 @@ import { VERSION } from "./utils/helpers"; import { ConduitControllerInterface, ConduitInterface, - contracts, EIP1271Wallet, EIP1271Wallet__factory, TransferHelper, } from "../typechain-types"; +import { randomHex } from "./utils/encoding"; import type { SeaportFixtures } from "./utils/fixtures"; import type { BigNumber, Wallet } from "ethers"; @@ -101,7 +100,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { recipient: string, validate: boolean ): TransferWithRecipient { - let transferHelperItems = []; + const transferHelperItems = []; for (let i = 0; i < transfers.length; i++) { transferHelperItems[i] = createTransferHelperItem(transfers[i]); } @@ -1211,7 +1210,6 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { const transfersWithRecipients = []; const allContracts = []; - const allTransfers = []; // Create numTransfers amount of TransferHelperItemsWithRecipient for (let j = 0; j < numTransfers; j++) { From 25a905df2747436640038844dea7d6c216116f36 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 21:45:14 -0700 Subject: [PATCH 116/126] bump --- test/transferhelper.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 5405bca77..0354b605b 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,7 +2,6 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import { faucet } from "./utils/faucet"; import { fixtureERC1155, fixtureERC20, @@ -11,14 +10,16 @@ import { } from "./utils/fixtures"; import { VERSION } from "./utils/helpers"; -import { +import type { ConduitControllerInterface, ConduitInterface, EIP1271Wallet, EIP1271Wallet__factory, TransferHelper, } from "../typechain-types"; + import { randomHex } from "./utils/encoding"; +import { faucet } from "./utils/faucet"; import type { SeaportFixtures } from "./utils/fixtures"; import type { BigNumber, Wallet } from "ethers"; @@ -106,7 +107,7 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { } return { items: transferHelperItems, - recipient: recipient, + recipient, validateERC721Receiver: validate, }; } From 75e082ae7ab45683beec193c1d7fe2a938992bc9 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 22:02:53 -0700 Subject: [PATCH 117/126] bump --- test/transferhelper.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 0354b605b..882f19b3e 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,12 +2,15 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; +import { randomHex } from "./utils/encoding"; +import { faucet } from "./utils/faucet"; import { fixtureERC1155, fixtureERC20, fixtureERC721, seaportFixture, } from "./utils/fixtures"; +import type { SeaportFixtures } from "./utils/fixtures"; import { VERSION } from "./utils/helpers"; import type { @@ -18,9 +21,6 @@ import type { TransferHelper, } from "../typechain-types"; -import { randomHex } from "./utils/encoding"; -import { faucet } from "./utils/faucet"; -import type { SeaportFixtures } from "./utils/fixtures"; import type { BigNumber, Wallet } from "ethers"; describe(`TransferHelper tests (Seaport v${VERSION})`, function () { From ddafe2438f7dc42497c2f50ad666f15d4b7697f4 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 22:07:02 -0700 Subject: [PATCH 118/126] move imports --- test/transferhelper.spec.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 882f19b3e..390140de3 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,24 +2,27 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; +import type { + ConduitControllerInterface, + ConduitInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + TransferHelper, +} from "../typechain-types"; + import { randomHex } from "./utils/encoding"; import { faucet } from "./utils/faucet"; + import { fixtureERC1155, fixtureERC20, fixtureERC721, seaportFixture, } from "./utils/fixtures"; + import type { SeaportFixtures } from "./utils/fixtures"; -import { VERSION } from "./utils/helpers"; -import type { - ConduitControllerInterface, - ConduitInterface, - EIP1271Wallet, - EIP1271Wallet__factory, - TransferHelper, -} from "../typechain-types"; +import { VERSION } from "./utils/helpers"; import type { BigNumber, Wallet } from "ethers"; From ff158d40d31aeaa40252d260f078cd497a7fa8d6 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Wed, 20 Jul 2022 22:16:30 -0700 Subject: [PATCH 119/126] lint --- test/transferhelper.spec.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index 390140de3..f0ba8f9df 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -2,28 +2,24 @@ import { expect } from "chai"; import { randomInt } from "crypto"; import { ethers, network } from "hardhat"; -import type { - ConduitControllerInterface, - ConduitInterface, - EIP1271Wallet, - EIP1271Wallet__factory, - TransferHelper, -} from "../typechain-types"; - import { randomHex } from "./utils/encoding"; import { faucet } from "./utils/faucet"; - import { fixtureERC1155, fixtureERC20, fixtureERC721, seaportFixture, } from "./utils/fixtures"; - -import type { SeaportFixtures } from "./utils/fixtures"; - import { VERSION } from "./utils/helpers"; +import type { + ConduitControllerInterface, + ConduitInterface, + EIP1271Wallet, + EIP1271Wallet__factory, + TransferHelper, +} from "../typechain-types"; +import type { SeaportFixtures } from "./utils/fixtures"; import type { BigNumber, Wallet } from "ethers"; describe(`TransferHelper tests (Seaport v${VERSION})`, function () { From e27756d5c13dbbd91b0ba9599f53cf791c18ddcf Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 11:42:31 -0700 Subject: [PATCH 120/126] add identifier and amount checks to conduit transfers, inline comments --- contracts/helpers/TransferHelper.sol | 24 +++++++++++++++------ contracts/helpers/TransferHelperStructs.sol | 13 ++++++++++- contracts/interfaces/IERC721Receiver.sol | 8 +++---- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 8a81e1ece..00b0f1387 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -133,7 +133,7 @@ contract TransferHelper is // Perform a transfer based on the transfer's item type. if (item.itemType == ConduitItemType.ERC20) { - // Ensure that the identifier for an ERC20 token is 0. + // Ensure that the identifier of an ERC20 token is 0. if (item.identifier != 0) { revert InvalidERC20Identifier(); } @@ -146,6 +146,11 @@ contract TransferHelper is item.amount ); } else if (item.itemType == ConduitItemType.ERC721) { + // Ensure that the amount of an ERC721 token is 1. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } + // If recipient is a contract and validateERC721Receiver // is true... if (callERC721Receiver) { @@ -156,10 +161,6 @@ contract TransferHelper is item.identifier ); } - // Ensure that the amount for an ERC721 transfer is 1. - if (item.amount != 1) { - revert InvalidERC721TransferAmount(); - } // Transfer ERC721 token. _performERC721Transfer( @@ -267,6 +268,18 @@ contract TransferHelper is // Retrieve the item from the transfer. TransferHelperItem calldata item = transferItems[j]; + if (item.itemType == ConduitItemType.ERC20) { + // Ensure that the identifier of an ERC20 token is 0. + if (item.identifier != 0) { + revert InvalidERC20Identifier(); + } + } else if (item.itemType == ConduitItemType.ERC721) { + // Ensure that the amount of an ERC721 token is 1. + if (item.amount != 1) { + revert InvalidERC721TransferAmount(); + } + } + // If the item is an ERC721 token and // callERC721Receiver is true... if (item.itemType == ConduitItemType.ERC721) { @@ -334,7 +347,6 @@ contract TransferHelper is * * @param recipient The ERC721 recipient on which to call onERC721Received. * @param tokenId The ERC721 tokenId of the token being transferred. - * */ function _checkERC721Receiver(address recipient, uint256 tokenId) internal { // Check if recipient can receive ERC721 tokens. diff --git a/contracts/helpers/TransferHelperStructs.sol b/contracts/helpers/TransferHelperStructs.sol index d8c61509a..26c6f1457 100644 --- a/contracts/helpers/TransferHelperStructs.sol +++ b/contracts/helpers/TransferHelperStructs.sol @@ -3,6 +3,12 @@ pragma solidity ^0.8.7; import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol"; +/** + * @dev A TransferHelperItem specifies the itemType (ERC20/ERC721/ERC1155), + * token address, token identifier, and amount of the token to be + * transferred via the TransferHelper. For ERC20 tokens, identifier + * must be 0. For ERC721 tokens, amount must be 1. + */ struct TransferHelperItem { ConduitItemType itemType; address token; @@ -10,9 +16,14 @@ struct TransferHelperItem { uint256 amount; } +/** + * @dev A TransferHelperItemsWithRecipient specifies the tokens to transfer + * via the TransferHelper, their intended recipient, and a boolean flag + * indicating whether onERC721Received should be called on a recipient + * contract. + */ struct TransferHelperItemsWithRecipient { TransferHelperItem[] items; address recipient; - /* Pass true to call onERC721Received on a recipient contract. */ bool validateERC721Receiver; } diff --git a/contracts/interfaces/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol index d21578c43..1f045c355 100644 --- a/contracts/interfaces/IERC721Receiver.sol +++ b/contracts/interfaces/IERC721Receiver.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.7; interface IERC721Receiver { function onERC721Received( - address, - address, - uint256, - bytes calldata + address operator, + address from, + uint256 tokenId, + bytes calldata data ) external returns (bytes4); } From de0d9c5e48fbab36dcfbba6964bab0e639817afa Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 12:55:43 -0700 Subject: [PATCH 121/126] add additional HH tests for coverage --- test/transferhelper.spec.ts | 115 ++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/transferhelper.spec.ts b/test/transferhelper.spec.ts index f0ba8f9df..ff9bfd6b7 100644 --- a/test/transferhelper.spec.ts +++ b/test/transferhelper.spec.ts @@ -534,6 +534,34 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ).to.be.revertedWith("InvalidERC20Identifier"); }); + it("Reverts on invalid ERC20 identifier via conduit", async () => { + const erc20Transfers = [ + { + items: [ + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 5, + amount: 10, + }, + { + itemType: 1, + token: ethers.constants.AddressZero, + identifier: 4, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(erc20Transfers, tempConduitKey) + ).to.be.revertedWith("InvalidERC20Identifier"); + }); + it("Reverts on invalid ERC721 transfer amount", async () => { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); @@ -565,6 +593,37 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ).to.be.revertedWith("InvalidERC721TransferAmount"); }); + it("Reverts on invalid ERC721 transfer amount via conduit", async () => { + // Deploy Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + + const erc721Transfers = [ + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 10, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 20, + }, + ], + recipient: recipient.address, + validateERC721Receiver: true, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(erc721Transfers, tempConduitKey) + ).to.be.revertedWith("InvalidERC721TransferAmount"); + }); + it("Reverts on invalid ERC721 recipient", async () => { // Deploy Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); @@ -736,6 +795,62 @@ describe(`TransferHelper tests (Seaport v${VERSION})`, function () { ); }); + it("Reverts on error in ERC721 receiver via conduit", async () => { + // Deploy ERC721 Contract + const { testERC721: tempERC721Contract } = await fixtureERC721(owner); + // Deploy ERC20 Contract + const { testERC20: tempERC20Contract } = await fixtureERC20(owner); + + // Deploy mock ERC721 receiver + const mockERC721ReceiverFactory = await ethers.getContractFactory( + "ERC721ReceiverMock" + ); + const mockERC721Receiver = await mockERC721ReceiverFactory.deploy( + Buffer.from("abcd0000", "hex"), + 1 + ); + + const transfers = [ + { + items: [ + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 1, + amount: 1, + }, + { + itemType: 2, + token: tempERC721Contract.address, + identifier: 2, + amount: 1, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 10, + }, + { + itemType: 1, + token: tempERC20Contract.address, + identifier: 0, + amount: 20, + }, + ], + recipient: mockERC721Receiver.address, + validateERC721Receiver: true, + }, + ]; + await expect( + tempTransferHelper + .connect(sender) + .bulkTransfer(transfers, tempConduitKey) + ).to.be.revertedWith( + `ERC721ReceiverErrorRevertString("ERC721ReceiverMock: reverting", "${mockERC721Receiver.address}", "${sender.address}", 1` + ); + }); + it("Reverts with custom error in conduit", async () => { // Deploy ERC721 Contract const { testERC721: tempERC721Contract } = await fixtureERC721(owner); From 26a56cb9b92ae8cc2f03b9bd22927fef920ef5f8 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 13:21:29 -0700 Subject: [PATCH 122/126] fix forge tests to account for new checks --- .../TransferHelperMultipleRecipientsTest.sol | 33 ++++------------ .../TransferHelperSingleRecipientTest.sol | 38 ++++--------------- 2 files changed, 14 insertions(+), 57 deletions(-) diff --git a/test/foundry/TransferHelperMultipleRecipientsTest.sol b/test/foundry/TransferHelperMultipleRecipientsTest.sol index 3274db6a0..2f82c506c 100644 --- a/test/foundry/TransferHelperMultipleRecipientsTest.sol +++ b/test/foundry/TransferHelperMultipleRecipientsTest.sol @@ -1104,24 +1104,15 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { items, inputs.recipients ); - try - transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) - returns ( - bytes4 /* magicValue */ - ) {} catch (bytes memory reason) { - returnedData = this.getSelector(reason); - } + _performSingleItemTransferAndCheckBalances( item, alice, inputs.recipients, true, true, - abi.encodeWithSignature( - "ConduitErrorRevertBytes(bytes,bytes32,address)", - returnedData, - conduitKeyOne, - conduit + abi.encodePacked( + TokenTransferrerErrors.InvalidERC721TransferAmount.selector ) ); } @@ -1155,13 +1146,6 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { items, inputs.recipients ); - try - transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) - returns ( - bytes4 /* magicValue */ - ) {} catch (bytes memory reason) { - returnedData = this.getSelector(reason); - } _performMultiItemTransferAndCheckBalances( items, @@ -1169,11 +1153,8 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { inputs.recipients, true, true, - abi.encodeWithSignature( - "ConduitErrorRevertBytes(bytes,bytes32,address)", - returnedData, - conduitKeyOne, - conduit + abi.encodePacked( + TokenTransferrerErrors.InvalidERC721TransferAmount.selector ) ); } @@ -1186,7 +1167,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], + 0, false ); @@ -1226,7 +1207,7 @@ contract TransferHelperMultipleRecipientsTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0], + 0, false ); diff --git a/test/foundry/TransferHelperSingleRecipientTest.sol b/test/foundry/TransferHelperSingleRecipientTest.sol index 9dee16b7e..f95754f5a 100644 --- a/test/foundry/TransferHelperSingleRecipientTest.sol +++ b/test/foundry/TransferHelperSingleRecipientTest.sol @@ -936,23 +936,14 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items, bob ); - try - transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) - returns ( - bytes4 /* magicValue */ - ) {} catch (bytes memory reason) { - returnedData = this.getSelector(reason); - } + _performSingleItemTransferAndCheckBalances( item, alice, bob, true, - abi.encodeWithSignature( - "ConduitErrorRevertBytes(bytes,bytes32,address)", - returnedData, - conduitKeyOne, - conduit + abi.encodePacked( + TokenTransferrerErrors.InvalidERC721TransferAmount.selector ) ); } @@ -982,24 +973,14 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { items, bob ); - try - transferHelper.bulkTransfer(itemsWithRecipient, conduitKeyOne) - returns ( - bytes4 /* magicValue */ - ) {} catch (bytes memory reason) { - returnedData = this.getSelector(reason); - } _performMultiItemTransferAndCheckBalances( items, alice, bob, true, - abi.encodeWithSignature( - "ConduitErrorRevertBytes(bytes,bytes32,address)", - returnedData, - conduitKeyOne, - conduit + abi.encodePacked( + TokenTransferrerErrors.InvalidERC721TransferAmount.selector ) ); } @@ -1012,16 +993,11 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + 0 ); _updateConduitChannel(false); - // try transferHelper.bulkTransfer(items, bob, conduitKeyOne) returns ( - // bytes4 magicValue - // ) {} catch (bytes memory reason) { - // returnedData = this.getSelector(reason); - // } bytes memory returnedData = abi.encodeWithSelector( 0x93daadf2, address(transferHelper) @@ -1055,7 +1031,7 @@ contract TransferHelperSingleRecipientTest is BaseOrderTest { ConduitItemType.ERC20, inputs.amounts[0], inputs.tokenIndex[0], - inputs.identifiers[0] + 0 ); // Reassign the conduit key that gets passed into TransferHelper to fuzzConduitKey. From f3691041e704f14bfebb52a6c49196399e016c3f Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 14:36:59 -0700 Subject: [PATCH 123/126] fix fuzz test --- test/foundry/FulfillAdvancedOrder.t.sol | 3 --- test/foundry/utils/TestTokenMinter.sol | 6 +++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/foundry/FulfillAdvancedOrder.t.sol b/test/foundry/FulfillAdvancedOrder.t.sol index 7ca86d989..c233a55ce 100644 --- a/test/foundry/FulfillAdvancedOrder.t.sol +++ b/test/foundry/FulfillAdvancedOrder.t.sol @@ -390,9 +390,6 @@ contract FulfillAdvancedOrder is BaseOrderTest { only1155Receiver(inputs.recipient) { vm.assume(tokenAmount > 0); - vm.assume( - inputs.recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1 - ); test( this.singleAdvanced1155, diff --git a/test/foundry/utils/TestTokenMinter.sol b/test/foundry/utils/TestTokenMinter.sol index 0729eb39b..a5d617f76 100644 --- a/test/foundry/utils/TestTokenMinter.sol +++ b/test/foundry/utils/TestTokenMinter.sol @@ -73,7 +73,11 @@ contract TestTokenMinter is address[] preapprovals; modifier only1155Receiver(address recipient) { - vm.assume(recipient != address(0)); + vm.assume( + recipient != address(0) && + recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1 && + recipient != 0x4e59b44847b379578588920ca78fbf26c0b4956c + ); if (recipient.code.length > 0) { try ERC1155Recipient(recipient).onERC1155Received( From 028bcc1bc6d4f8da90b21b5526b99649bc399a62 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 14:44:34 -0700 Subject: [PATCH 124/126] fix recipient address --- test/foundry/utils/TestTokenMinter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/utils/TestTokenMinter.sol b/test/foundry/utils/TestTokenMinter.sol index a5d617f76..c912305c1 100644 --- a/test/foundry/utils/TestTokenMinter.sol +++ b/test/foundry/utils/TestTokenMinter.sol @@ -76,7 +76,7 @@ contract TestTokenMinter is vm.assume( recipient != address(0) && recipient != 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1 && - recipient != 0x4e59b44847b379578588920ca78fbf26c0b4956c + recipient != 0x4e59b44847b379578588920cA78FbF26c0B4956C ); if (recipient.code.length > 0) { try From 104e088120ba506faa39c3254b0ae453aaa4c123 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 15:11:18 -0700 Subject: [PATCH 125/126] move code size check outside for loop, rename totalTransfers to numTransfers, totalItemTransfers to numItemsInTransfer, totalItems to sumOfItemsAcrossAllTransfers --- contracts/helpers/TransferHelper.sol | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index 00b0f1387..ccae84827 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -103,12 +103,12 @@ contract TransferHelper is TransferHelperItemsWithRecipient[] calldata transfers ) internal { // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = transfers.length; + uint256 numTransfers = transfers.length; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { + for (uint256 i = 0; i < numTransfers; ++i) { // Retrieve the transfer in question. TransferHelperItemsWithRecipient calldata transfer = transfers[ i @@ -201,7 +201,7 @@ contract TransferHelper is bytes32 conduitKey ) internal { // Retrieve total number of transfers and place on stack. - uint256 totalTransfers = transfers.length; + uint256 numTransfers = transfers.length; // Derive the conduit address from the deployer, conduit key // and creation code hash. @@ -221,26 +221,26 @@ contract TransferHelper is ); // Declare a variable to store the sum of all items across transfers. - uint256 totalItems; + uint256 sumOfItemsAcrossTransfers; // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { + for (uint256 i = 0; i < numTransfers; ++i) { // Retrieve the transfer in question. TransferHelperItemsWithRecipient calldata transfer = transfers[ i ]; // Increment totalItems by the number of items in the transfer. - totalItems += transfer.items.length; + sumOfItemsAcrossAllTransfers += transfer.items.length; } } // Declare a new array in memory with length totalItems to populate with // each conduit transfer. ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[]( - totalItems + sumOfItemsAcrossAllTransfers ); // Declare an index for storing ConduitTransfers in conduitTransfers. @@ -249,22 +249,30 @@ contract TransferHelper is // Skip overflow checks: all for loops are indexed starting at zero. unchecked { // Iterate over each transfer. - for (uint256 i = 0; i < totalTransfers; ++i) { + for (uint256 i = 0; i < numTransfers; ++i) { // Retrieve the transfer in question. TransferHelperItemsWithRecipient calldata transfer = transfers[ i ]; + + // Retrieve the items of the transfer in question. TransferHelperItem[] calldata transferItems = transfer.items; // Ensure recipient is not the zero address. _checkRecipientIsNotZeroAddress(transfer.recipient); - // Retrieve total number of transfers and place on stack. - uint256 totalItemTransfers = transferItems.length; + // Create a boolean indicating whether validateERC721Receiver + // is true and recipient is a contract. + bool callERC721Receiver = transfer.validateERC721Receiver && + transfer.recipient.code.length != 0; + + // Retrieve the total number of items in the transfer and + // place on stack. + uint256 numItemsInTransfer = transferItems.length; // Iterate over each item in the transfer to create a // corresponding ConduitTransfer. - for (uint256 j = 0; j < totalItemTransfers; ++j) { + for (uint256 j = 0; j < numItemsInTransfer; ++j) { // Retrieve the item from the transfer. TransferHelperItem calldata item = transferItems[j]; @@ -283,13 +291,6 @@ contract TransferHelper is // If the item is an ERC721 token and // callERC721Receiver is true... if (item.itemType == ConduitItemType.ERC721) { - // Create a boolean indicating whether - // validateERC721Receiver is true and recipient is - // a contract. - bool callERC721Receiver = transfer - .validateERC721Receiver && - transfer.recipient.code.length != 0; - if (callERC721Receiver) { // Check if the recipient implements // onERC721Received for the given tokenId. From 16f5802348977692a43de246a2b597a3d12dd8d1 Mon Sep 17 00:00:00 2001 From: stephankmin Date: Thu, 21 Jul 2022 15:12:50 -0700 Subject: [PATCH 126/126] typo --- contracts/helpers/TransferHelper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/helpers/TransferHelper.sol b/contracts/helpers/TransferHelper.sol index ccae84827..683822fab 100644 --- a/contracts/helpers/TransferHelper.sol +++ b/contracts/helpers/TransferHelper.sol @@ -221,7 +221,7 @@ contract TransferHelper is ); // Declare a variable to store the sum of all items across transfers. - uint256 sumOfItemsAcrossTransfers; + uint256 sumOfItemsAcrossAllTransfers; // Skip overflow checks: all for loops are indexed starting at zero. unchecked {