├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── configs ├── solcover-arbitrum.js └── solcover-mainnet.js ├── contracts ├── README.md ├── interfaces │ ├── ConduitControllerInterface.sol │ ├── ConsiderationInterface.sol │ ├── CreatorFeeEngineInterface.sol │ ├── SeaportValidatorInterface.sol │ └── ZoneInterface.sol ├── lib │ ├── ConsiderationConstants.sol │ ├── ConsiderationEnums.sol │ ├── ConsiderationStructs.sol │ ├── ConsiderationTypeHashes.sol │ ├── ErrorsAndWarnings.sol │ ├── Murky.sol │ ├── SafeStaticCall.sol │ ├── SeaportValidator.sol │ ├── SeaportValidatorTypes.sol │ └── SignatureVerification.sol └── test │ ├── TestERC1155.sol │ ├── TestERC1271.sol │ ├── TestERC20.sol │ ├── TestERC721.sol │ ├── TestERC721Funky.sol │ ├── TestEW.sol │ └── TestZone.sol ├── hardhat.config.ts ├── hardhatArbitrum.config.ts ├── package.json ├── src ├── index.ts └── seaportOrderValidator.ts ├── test ├── TestErrorsAndWarningsMainnet.spec.ts ├── ValidateOrderArbitrum.spec.ts ├── ValidateOrdersMainnet.spec.ts └── constants.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .eslintrc.js 3 | coverage 4 | coverage-mainnet 5 | coverage-arbitrum 6 | typechain-types 7 | dist -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint", "import"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "eslint:recommended", 13 | "plugin:import/recommended", 14 | "plugin:import/typescript", 15 | ], 16 | parser: "@typescript-eslint/parser", 17 | parserOptions: { 18 | ecmaVersion: 12, 19 | project: "./tsconfig.json", 20 | }, 21 | rules: { 22 | "@typescript-eslint/consistent-type-imports": "error", 23 | "@typescript-eslint/prefer-nullish-coalescing": "error", 24 | "import/order": [ 25 | "error", 26 | { 27 | alphabetize: { 28 | order: "asc", 29 | }, 30 | groups: [ 31 | "object", 32 | ["builtin", "external"], 33 | "parent", 34 | "sibling", 35 | "index", 36 | "type", 37 | ], 38 | "newlines-between": "always", 39 | }, 40 | ], 41 | "object-shorthand": "error", 42 | "prefer-const": "error", 43 | "sort-imports": ["error", { ignoreDeclarationSort: true }], 44 | }, 45 | overrides: [ 46 | { 47 | files: ["test/**"], 48 | rules: { 49 | "no-unused-expressions": "off", 50 | "no-unused-vars": "off", 51 | camelcase: "off", 52 | }, 53 | }, 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | ETH_RPC: ${{ secrets.ETH_RPC }} 13 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 14 | 15 | concurrency: 16 | group: ${{github.workflow}}-${{github.ref}} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | build: 21 | name: Build Artifacts 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | matrix: 26 | node-version: [16.x] 27 | 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Use Node.js 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | cache: "yarn" 35 | - run: yarn install --frozen-lockfile 36 | - run: yarn build 37 | 38 | lint: 39 | name: Run Linters 40 | runs-on: ubuntu-latest 41 | 42 | strategy: 43 | matrix: 44 | node-version: [16.x] 45 | 46 | steps: 47 | - uses: actions/checkout@v3 48 | - name: Use Node.js 49 | uses: actions/setup-node@v3 50 | with: 51 | node-version: ${{ matrix.node-version }} 52 | cache: "yarn" 53 | - run: yarn install --frozen-lockfile 54 | - run: yarn lint:check 55 | 56 | test: 57 | name: Run Tests 58 | runs-on: ubuntu-latest 59 | 60 | strategy: 61 | matrix: 62 | node-version: [16.x] 63 | 64 | steps: 65 | - uses: actions/checkout@v3 66 | - name: Use Node.js 67 | uses: actions/setup-node@v3 68 | with: 69 | node-version: ${{ matrix.node-version }} 70 | cache: "yarn" 71 | - run: yarn install --frozen-lockfile 72 | - run: yarn test 73 | 74 | coverage: 75 | name: Run Coverage 76 | runs-on: ubuntu-latest 77 | 78 | strategy: 79 | matrix: 80 | node-version: [16.x] 81 | 82 | steps: 83 | - uses: actions/checkout@v3 84 | - name: Use Node.js 85 | uses: actions/setup-node@v3 86 | with: 87 | node-version: ${{ matrix.node-version }} 88 | cache: "yarn" 89 | - run: yarn install --frozen-lockfile 90 | - run: yarn coverage 91 | - name: Coveralls 92 | uses: coverallsapp/github-action@master 93 | with: 94 | github-token: ${{ secrets.GITHUB_TOKEN }} 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | yarn-error.log 13 | .vscode/ 14 | coverage-arbitrum 15 | coverage-mainnet 16 | dist 17 | **.tgz -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | coverage-mainnet 3 | coverage-arbitrum 4 | typechain-types 5 | dist -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | files: "*.sol", 5 | options: { 6 | tabWidth: 4, 7 | printWidth: 80, 8 | bracketSpacing: true, 9 | compiler: "0.8.14", 10 | }, 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Ozone Networks, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Test CI][ci-badge]][ci-link] 2 | [![Code Coverage][coverage-badge]][coverage-link] 3 | 4 | # Seaport Order Validator 5 | Seaport Order Validator provides a solidity contract which validates orders and order components via RPC static calls. Seaport Order Validator currently supports validation of orders (not advanced orders) and provides minimal validation for criteria based items. This is an ongoing effort. The Seaport Order Validator is deployed at the address `0xF75194740067D6E4000000003b350688DD770000`. 6 | 7 | There are a variety of functions which conduct micro and macro validations on various components of the order. Each validation function returns two arrays of uint16s, the first is an array of errors, and the second is an array of warnings. For a quick lookup of issue codes, see the [issue table](contracts/README.md). 8 | 9 | ## Table of Contents 10 | - [JS Package Usage](#js-package-usage) 11 | - [Macro-Validation](#macro-validation) 12 | - [Micro-Validation](#micro-validation) 13 | - [validateTime](#validatetime---validates-the-timing-of-the-order) 14 | - [validateOrderStatus](#validateorderstatus---validates-the-order-status-from-on-chain-data) 15 | - [validateOfferItems](#validateofferitems---validates-the-offer-item-parameters-and-balancesapproval) 16 | - [validateOfferItem](#validateofferitem---validates-the-parameters-and-balancesapprovals-for-one-offer-item) 17 | - [validateOfferItemParameters](#validateofferitemparameters---validates-the-parameters-for-one-offer-item) 18 | - [validateOfferItemApprovalAndBalance](#validateofferitemapprovalandbalance---validates-the-balancesapprovals-for-one-offer-item) 19 | - [validateConsiderationItems](#validateconsiderationitems---validate-the-parameters-of-the-consideration-items) 20 | - [validateConsiderationItemParameters](#validateconsiderationitemparameters---check-the-parameters-for-a-single-consideration-item) 21 | - [isValidZone](#isvalidzone---checks-if-the-zone-accepts-the-order) 22 | - [validateStrictLogic](#validatestrictlogic---validate-strict-order-logic) 23 | - [validateSignature](#validatesignature---validates-the-signature-using-current-counter) 24 | - [validateSignatureWithCounter](#validatesignaturewithcounter---validates-the-signature-using-the-given-counter) 25 | - [getApprovalAddress](#getapprovaladdress---gets-the-approval-address-for-a-conduit-key) 26 | - [Merkle Validation](#merkle-validation) 27 | - [sortMerkleTokens](#sortmerkletokens) 28 | - [getMerkleRoot](#getmerkleroot) 29 | - [getMerkleProof](#getmerkleroot) 30 | 31 | ## JS Package Usage 32 | - Add the package via `yarn add @opensea/seaport-order-validator` or `npm i @opensea/seaport-order-validator` 33 | - Import the package to your JS/TS file via `import { SeaportOrderValidator } from "@opensea/seaport-order-validator"` 34 | - Create an instance of `SeaportOrderValidator` `const validator = new SeaportOrderValidator(new ethers.providers.JsonRpcProvider());` 35 | - All validation functions are exposed to the `SeaportOrderValidator` instance 36 | 37 | ## Macro-Validation 38 | 39 | - There are two macro-validation function, `isValidOrder` and `isValidOrderWithConfiguration`. `isValidOrder` simply calls `isValidOrderWithConfiguration` with a default configuration as follows: 40 | 41 | ```solidity 42 | { 43 | primaryFeeRecipient = address(0), 44 | primaryFeeBips = 0, 45 | checkCreatorFee = false, 46 | skipStrictValidation = false, 47 | shortOrderDuration = 30 minutes, 48 | distantOrderExpiration = 26 weeks 49 | } 50 | ``` 51 | 52 | - `isValidOrderWithConfiguration` 53 | - Calls the following micro-validations and aggregates the results: 54 | - `validateTime` - Called with variables from configuration 55 | - `validateOrderStatus` 56 | - `validateOfferItems` 57 | - `validateConsiderationItems` 58 | - `isValidZone` 59 | - `validateStrictLogic` - if skipStrictValidation is false. Called with the parameters from the configuration 60 | - `validateSignature` 61 | 62 | ## Micro-Validation 63 | - ##### `validateTime` - Validates the timing of the order 64 | - Errors: 65 | - End time must be after start time 66 | - Current time must be before or equal to end time 67 | - Warnings: 68 | - End time is in more than `distantOrderExpiration` (distant expiration) 69 | - Start time is greater than current time (order not active) 70 | - Order duration is less than `shortOrderDuration` (either endTime - startTime or endTime - currentTime) 71 | - ##### `validateOrderStatus` - Validates the order status from on-chain data 72 | - Errors: 73 | - Order is cancelled 74 | - Order is fully filled 75 | - ##### `validateOfferItems` - Validates the offer item parameters and balances/approval 76 | - Errors: 77 | - Zero Offer Items 78 | - Warnings: 79 | - More than one offer item 80 | 81 | Nested validation call to `validateOfferItem` for each `offerItem` 82 | 83 | - ##### `validateOfferItem` - Validates the parameters and balances/approvals for one offer item 84 | - Nested validation call to `validateOfferItemParameters` and if there are no errors, a subsequent call to `validateOfferItemApprovalAndBalance` 85 | - ##### `validateOfferItemParameters` - Validates the parameters for one offer item 86 | - Errors: 87 | - Either the startAmount or the endAmount must be non-zero 88 | - The contract must exist and abide by the interface required for the given `ItemType`. 89 | - Identifier must be 0 for fungible tokens 90 | - Token address must be null-address for native offer item 91 | - Amounts must be 1 for if the `ItemType` is an ERC721 92 | - Warnings: 93 | - Large steps for amount. This is due to the fact that as time passes, the change in amount will be relatively drastic at each step. Occurs if min amount is less than 1e15 and `minAmount ≠ maxAmount`. 94 | - High velocity for amount. This means that the price changes more than 5% per 30 min relative to the highest amount. 95 | - ##### `validateOfferItemApprovalAndBalance` - Validates the balances/approvals for one offer item 96 | - Errors: 97 | - Must be the owner of non-fungible items 98 | - Must have sufficient balance of fungible items 99 | - Must have sufficient allowance to the given conduit (seaport if none given) 100 | - Warnings: 101 | - Native offer item (a native offer item can not be pulled from the user wallet for fulfillment) 102 | 103 | There is also a nested validation call to `getApprovalAddress` to get the associated conduit for checking approvals. 104 | 105 | - ##### `validateConsiderationItems` - Validate the parameters of the consideration items 106 | - Warnings: 107 | - Zero consideration items 108 | 109 | For each consideration item, there is a nested validation call to `validateConsiderationItem` which is just a wrapper for `validateConsiderationItemParameters` 110 | 111 | - ##### `validateConsiderationItemParameters` - Check the parameters for a single consideration item 112 | - Errors: 113 | - Either start amount or end amount must be non-zero 114 | - The recipient can not be the null address 115 | - ERC721 type must have amounts be one 116 | - ERC721 token with identifier must exist 117 | - All token contracts must exist and implement the required interfaces 118 | - Native consideration item contract must be zero 119 | - Identifier for fungible items must be 0 120 | - ##### `isValidZone` - Checks if the zone accepts the order 121 | - Errors: 122 | - Zone Rejected Order 123 | - Note: Validation always passes if given zone is an EOA 124 | - Zone caller is msg.sender 125 | - ##### `validateStrictLogic` - Validate strict order logic 126 | 127 | The first consideration item is called the “primary consideration” for this section. 128 | 129 | - Errors: 130 | - Force 1 offer item along with at least one consideration item 131 | - Either the offer item or the primary consideration item must be fungible—the other non-fungible. 132 | - This ensures an either an offer or listing typed order 133 | - If `primaryFeeRecipient` and `primaryFeeBips` are non-zero, the second consideration item must be set correctly to the primary fee consideration. This must be omitted if the primary fee would be zero. 134 | - If `checkCreatorFee` is set to true, the creator fee engine is checked for royalties on the non-fungible item. If the creator fee is non-zero, the creator fee consideration item must be the next consideration item in the sequence. 135 | - There may be one last consideration item which signifies a private sale. This consideration item must be exactly the same as the offer item and the recipient must not be the offerer (no private sale to self). 136 | - There may not be any additional consideration items. 137 | - ##### `validateSignature` - Validates the signature using current counter 138 | 139 | Calls `validateSignatureWithCounter` using the offerers current counter 140 | 141 | - ##### `validateSignatureWithCounter` - Validates the signature using the given counter 142 | - Errors: 143 | - Counter below current counter - signature will never be valid 144 | - Signature Invalid 145 | - Warnings: 146 | - Signature Counter High - The signature counter is more than 2 greater than the current counter for the signer. 147 | - When error is invalid, original consideration Items field in order does not match the total consideration items. This may be the cause of the invalid signature. 148 | - ##### `getApprovalAddress` - Gets the approval address for a conduit key 149 | - Returns the approval address for the given conduit key. (seaport address if conduit key is 0) 150 | - Errors: 151 | - Conduit key invalid (not created). Returns zero address for the approval address. 152 | 153 | ### Merkle Validation 154 | 155 | - ##### `sortMerkleTokens` 156 | 157 | To generate a merkle root for a criteria order, the included token ids must first be sorted by their keccak256 hash. This function sorts accordingly. 158 | 159 | - ##### `getMerkleRoot` 160 | - Generates the merkle root from the given `includedTokens` 161 | - If `MerkleError` is given, elements are not sorted correctly, or there are too many elements. 162 | - ##### `getMerkleProof` 163 | - Generate the merkle proof for an element within the `includedTokens` at index `targetIndex`. 164 | 165 | --- 166 | 167 | [ci-badge]: https://github.com/ProjectOpenSea/seaport-order-validator/actions/workflows/test.yml/badge.svg 168 | [ci-link]: https://github.com/ProjectOpenSea/seaport-order-validator/actions/workflows/test.yml 169 | [coverage-badge]: https://coveralls.io/repos/github/ProjectOpenSea/seaport-order-validator/badge.svg?branch=main&t=UvcQpQ 170 | [coverage-link]: https://coveralls.io/github/ProjectOpenSea/seaport-order-validator?branch=main -------------------------------------------------------------------------------- /configs/solcover-arbitrum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: [ 3 | "test/TestERC721.sol", 4 | "test/TestERC1155.sol", 5 | "test/TestERC20.sol", 6 | "test/TestZone.sol", 7 | "test/TestERC1271.sol", 8 | "test/TestERC721Funky.sol", 9 | "test/TestEW.sol", 10 | "lib/ConsiderationTypeHashes.sol", 11 | "lib/ConsiderationStructs.sol", 12 | "lib/ConsiderationEnums.sol", 13 | "lib/SignatureVerification.sol", 14 | ], 15 | configureYulOptimizer: true, 16 | solcOptimizerDetails: { 17 | yul: true, 18 | yulDetails: { 19 | stackAllocation: true, 20 | }, 21 | }, 22 | istanbulReporter: ["lcov"], 23 | istanbulFolder: "./coverage-arbitrum", 24 | }; 25 | -------------------------------------------------------------------------------- /configs/solcover-mainnet.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: [ 3 | "test/TestERC721.sol", 4 | "test/TestERC1155.sol", 5 | "test/TestERC20.sol", 6 | "test/TestZone.sol", 7 | "test/TestERC1271.sol", 8 | "test/TestERC721Funky.sol", 9 | "test/TestEW.sol", 10 | "lib/ConsiderationTypeHashes.sol", 11 | "lib/ConsiderationStructs.sol", 12 | "lib/ConsiderationEnums.sol", 13 | "lib/SignatureVerification.sol", 14 | ], 15 | configureYulOptimizer: true, 16 | solcOptimizerDetails: { 17 | yul: true, 18 | yulDetails: { 19 | stackAllocation: true, 20 | }, 21 | }, 22 | istanbulReporter: ["lcov"], 23 | istanbulFolder: "./coverage-mainnet", 24 | }; 25 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Decode Function Results 2 | Use this file to decode validation results from `SeaportValidator`. The return value from most `SeaportValidator` functions is a `ErrorsAndWarnings` struct which contains two `uint16` arrays. First is the errors and second is the warnings. See below for the value of codes. 3 | 4 | ## Issue Codes 5 | | Code | Issue | 6 | | - | ----------- | 7 | | 100 | Invalid order format. Ensure offer/consideration follow requirements | 8 | | 200 | ERC20 identifier must be zero | 9 | | 201 | ERC20 invalid token | 10 | | 202 | ERC20 insufficient allowance to conduit | 11 | | 203 | ERC20 insufficient balance | 12 | | 300 | ERC721 amount must be one | 13 | | 301 | ERC721 token is invalid | 14 | | 302 | ERC721 token with identifier does not exist | 15 | | 303 | ERC721 not owner of token | 16 | | 304 | ERC721 conduit not approved | 17 | | 305 | ERC721 offer item using criteria and more than amount of one requires partial fills. | 18 | | 400 | ERC1155 invalid token | 19 | | 401 | ERC1155 conduit not approved | 20 | | 402 | ERC1155 insufficient balance | 21 | | 500 | Consideration amount must not be zero | 22 | | 501 | Consideration recipient must not be null address | 23 | | 502 | Consideration contains extra items | 24 | | 503 | Private sale can not be to self | 25 | | 504 | Zero consideration items | 26 | | 505 | Duplicate consideration items | 27 | | 506 | Private Sale Order. Be careful on fulfillment | 28 | | 507 | Amount velocity is too high. Amount changes over 5% per 30 min if warning and over 50% per 30 min if error | 29 | | 508 | Amount step large. The steps between each step may be more than expected. Offer items are rounded down and consideration items are rounded up. | 30 | | 600 | Zero offer items | 31 | | 601 | Offer amount must not be zero | 32 | | 602 | More than one offer item | 33 | | 603 | Native offer item | 34 | | 604 | Duplicate offer item | 35 | | 605 | Amount velocity is too high. Amount changes over 5% per 30 min if warning and over 50% per 30 min if error | 36 | | 606 | Amount step large. The steps between each step may be more than expected. Offer items are rounded down and consideration items are rounded up. | 37 | | 700 | Primary fee missing | 38 | | 701 | Primary fee item type incorrect | 39 | | 702 | Primary fee token incorrect | 40 | | 703 | Primary fee start amount too low | 41 | | 704 | Primary fee end amount too low | 42 | | 705 | Primary fee recipient incorrect | 43 | | 800 | Order cancelled | 44 | | 801 | Order fully filled | 45 | | 900 | End time is before start time | 46 | | 901 | Order expired | 47 | | 902 | Order expiration in too long (default 26 weeks) | 48 | | 903 | Order not active | 49 | | 904 | Short order duration (default 30 min) | 50 | | 1000 | Conduit key invalid | 51 | | 1100 | Signature invalid | 52 | | 1101 | Signature counter below current counter | 53 | | 1102 | Signature counter more than two greater than current counter | 54 | | 1103 | Signature may be invalid since `totalOriginalConsiderationItems` is not set correctly | 55 | | 1200 | Creator fee missing | 56 | | 1201 | Creator fee item type incorrect | 57 | | 1202 | Creator fee token incorrect | 58 | | 1203 | Creator fee start amount too low | 59 | | 1204 | Creator fee end amount too low | 60 | | 1205 | Creator fee recipient incorrect | 61 | | 1300 | Native token address must be null address | 62 | | 1301 | Native token identifier must be zero | 63 | | 1302 | Native token insufficient balance | 64 | | 1400 | Zone rejected order. This order must be fulfilled by the zone. | 65 | | 1401 | Zone not set. Order unfulfillable | 66 | | 1500 | Merkle input only has one leaf | 67 | | 1501 | Merkle input not sorted correctly | -------------------------------------------------------------------------------- /contracts/interfaces/ConduitControllerInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | /** 5 | * @title ConduitControllerInterface 6 | * @author 0age 7 | * @notice ConduitControllerInterface contains all external function interfaces, 8 | * structs, events, and errors for the conduit controller. 9 | */ 10 | interface ConduitControllerInterface { 11 | /** 12 | * @dev Track the conduit key, current owner, new potential owner, and open 13 | * channels for each deployed conduit. 14 | */ 15 | struct ConduitProperties { 16 | bytes32 key; 17 | address owner; 18 | address potentialOwner; 19 | address[] channels; 20 | mapping(address => uint256) channelIndexesPlusOne; 21 | } 22 | 23 | /** 24 | * @dev Emit an event whenever a new conduit is created. 25 | * 26 | * @param conduit The newly created conduit. 27 | * @param conduitKey The conduit key used to create the new conduit. 28 | */ 29 | event NewConduit(address conduit, bytes32 conduitKey); 30 | 31 | /** 32 | * @dev Emit an event whenever conduit ownership is transferred. 33 | * 34 | * @param conduit The conduit for which ownership has been 35 | * transferred. 36 | * @param previousOwner The previous owner of the conduit. 37 | * @param newOwner The new owner of the conduit. 38 | */ 39 | event OwnershipTransferred( 40 | address indexed conduit, 41 | address indexed previousOwner, 42 | address indexed newOwner 43 | ); 44 | 45 | /** 46 | * @dev Emit an event whenever a conduit owner registers a new potential 47 | * owner for that conduit. 48 | * 49 | * @param newPotentialOwner The new potential owner of the conduit. 50 | */ 51 | event PotentialOwnerUpdated(address indexed newPotentialOwner); 52 | 53 | /** 54 | * @dev Revert with an error when attempting to create a new conduit using a 55 | * conduit key where the first twenty bytes of the key do not match the 56 | * address of the caller. 57 | */ 58 | error InvalidCreator(); 59 | 60 | /** 61 | * @dev Revert with an error when attempting to create a new conduit when no 62 | * initial owner address is supplied. 63 | */ 64 | error InvalidInitialOwner(); 65 | 66 | /** 67 | * @dev Revert with an error when attempting to set a new potential owner 68 | * that is already set. 69 | */ 70 | error NewPotentialOwnerAlreadySet( 71 | address conduit, 72 | address newPotentialOwner 73 | ); 74 | 75 | /** 76 | * @dev Revert with an error when attempting to cancel ownership transfer 77 | * when no new potential owner is currently set. 78 | */ 79 | error NoPotentialOwnerCurrentlySet(address conduit); 80 | 81 | /** 82 | * @dev Revert with an error when attempting to interact with a conduit that 83 | * does not yet exist. 84 | */ 85 | error NoConduit(); 86 | 87 | /** 88 | * @dev Revert with an error when attempting to create a conduit that 89 | * already exists. 90 | */ 91 | error ConduitAlreadyExists(address conduit); 92 | 93 | /** 94 | * @dev Revert with an error when attempting to update channels or transfer 95 | * ownership of a conduit when the caller is not the owner of the 96 | * conduit in question. 97 | */ 98 | error CallerIsNotOwner(address conduit); 99 | 100 | /** 101 | * @dev Revert with an error when attempting to register a new potential 102 | * owner and supplying the null address. 103 | */ 104 | error NewPotentialOwnerIsZeroAddress(address conduit); 105 | 106 | /** 107 | * @dev Revert with an error when attempting to claim ownership of a conduit 108 | * with a caller that is not the current potential owner for the 109 | * conduit in question. 110 | */ 111 | error CallerIsNotNewPotentialOwner(address conduit); 112 | 113 | /** 114 | * @dev Revert with an error when attempting to retrieve a channel using an 115 | * index that is out of range. 116 | */ 117 | error ChannelOutOfRange(address conduit); 118 | 119 | /** 120 | * @notice Deploy a new conduit using a supplied conduit key and assigning 121 | * an initial owner for the deployed conduit. Note that the first 122 | * twenty bytes of the supplied conduit key must match the caller 123 | * and that a new conduit cannot be created if one has already been 124 | * deployed using the same conduit key. 125 | * 126 | * @param conduitKey The conduit key used to deploy the conduit. Note that 127 | * the first twenty bytes of the conduit key must match 128 | * the caller of this contract. 129 | * @param initialOwner The initial owner to set for the new conduit. 130 | * 131 | * @return conduit The address of the newly deployed conduit. 132 | */ 133 | function createConduit(bytes32 conduitKey, address initialOwner) 134 | external 135 | returns (address conduit); 136 | 137 | /** 138 | * @notice Open or close a channel on a given conduit, thereby allowing the 139 | * specified account to execute transfers against that conduit. 140 | * Extreme care must be taken when updating channels, as malicious 141 | * or vulnerable channels can transfer any ERC20, ERC721 and ERC1155 142 | * tokens where the token holder has granted the conduit approval. 143 | * Only the owner of the conduit in question may call this function. 144 | * 145 | * @param conduit The conduit for which to open or close the channel. 146 | * @param channel The channel to open or close on the conduit. 147 | * @param isOpen A boolean indicating whether to open or close the channel. 148 | */ 149 | function updateChannel( 150 | address conduit, 151 | address channel, 152 | bool isOpen 153 | ) external; 154 | 155 | /** 156 | * @notice Initiate conduit ownership transfer by assigning a new potential 157 | * owner for the given conduit. Once set, the new potential owner 158 | * may call `acceptOwnership` to claim ownership of the conduit. 159 | * Only the owner of the conduit in question may call this function. 160 | * 161 | * @param conduit The conduit for which to initiate ownership transfer. 162 | * @param newPotentialOwner The new potential owner of the conduit. 163 | */ 164 | function transferOwnership(address conduit, address newPotentialOwner) 165 | external; 166 | 167 | /** 168 | * @notice Clear the currently set potential owner, if any, from a conduit. 169 | * Only the owner of the conduit in question may call this function. 170 | * 171 | * @param conduit The conduit for which to cancel ownership transfer. 172 | */ 173 | function cancelOwnershipTransfer(address conduit) external; 174 | 175 | /** 176 | * @notice Accept ownership of a supplied conduit. Only accounts that the 177 | * current owner has set as the new potential owner may call this 178 | * function. 179 | * 180 | * @param conduit The conduit for which to accept ownership. 181 | */ 182 | function acceptOwnership(address conduit) external; 183 | 184 | /** 185 | * @notice Retrieve the current owner of a deployed conduit. 186 | * 187 | * @param conduit The conduit for which to retrieve the associated owner. 188 | * 189 | * @return owner The owner of the supplied conduit. 190 | */ 191 | function ownerOf(address conduit) external view returns (address owner); 192 | 193 | /** 194 | * @notice Retrieve the conduit key for a deployed conduit via reverse 195 | * lookup. 196 | * 197 | * @param conduit The conduit for which to retrieve the associated conduit 198 | * key. 199 | * 200 | * @return conduitKey The conduit key used to deploy the supplied conduit. 201 | */ 202 | function getKey(address conduit) external view returns (bytes32 conduitKey); 203 | 204 | /** 205 | * @notice Derive the conduit associated with a given conduit key and 206 | * determine whether that conduit exists (i.e. whether it has been 207 | * deployed). 208 | * 209 | * @param conduitKey The conduit key used to derive the conduit. 210 | * 211 | * @return conduit The derived address of the conduit. 212 | * @return exists A boolean indicating whether the derived conduit has been 213 | * deployed or not. 214 | */ 215 | function getConduit(bytes32 conduitKey) 216 | external 217 | view 218 | returns (address conduit, bool exists); 219 | 220 | /** 221 | * @notice Retrieve the potential owner, if any, for a given conduit. The 222 | * current owner may set a new potential owner via 223 | * `transferOwnership` and that owner may then accept ownership of 224 | * the conduit in question via `acceptOwnership`. 225 | * 226 | * @param conduit The conduit for which to retrieve the potential owner. 227 | * 228 | * @return potentialOwner The potential owner, if any, for the conduit. 229 | */ 230 | function getPotentialOwner(address conduit) 231 | external 232 | view 233 | returns (address potentialOwner); 234 | 235 | /** 236 | * @notice Retrieve the status (either open or closed) of a given channel on 237 | * a conduit. 238 | * 239 | * @param conduit The conduit for which to retrieve the channel status. 240 | * @param channel The channel for which to retrieve the status. 241 | * 242 | * @return isOpen The status of the channel on the given conduit. 243 | */ 244 | function getChannelStatus(address conduit, address channel) 245 | external 246 | view 247 | returns (bool isOpen); 248 | 249 | /** 250 | * @notice Retrieve the total number of open channels for a given conduit. 251 | * 252 | * @param conduit The conduit for which to retrieve the total channel count. 253 | * 254 | * @return totalChannels The total number of open channels for the conduit. 255 | */ 256 | function getTotalChannels(address conduit) 257 | external 258 | view 259 | returns (uint256 totalChannels); 260 | 261 | /** 262 | * @notice Retrieve an open channel at a specific index for a given conduit. 263 | * Note that the index of a channel can change as a result of other 264 | * channels being closed on the conduit. 265 | * 266 | * @param conduit The conduit for which to retrieve the open channel. 267 | * @param channelIndex The index of the channel in question. 268 | * 269 | * @return channel The open channel, if any, at the specified channel index. 270 | */ 271 | function getChannel(address conduit, uint256 channelIndex) 272 | external 273 | view 274 | returns (address channel); 275 | 276 | /** 277 | * @notice Retrieve all open channels for a given conduit. Note that calling 278 | * this function for a conduit with many channels will revert with 279 | * an out-of-gas error. 280 | * 281 | * @param conduit The conduit for which to retrieve open channels. 282 | * 283 | * @return channels An array of open channels on the given conduit. 284 | */ 285 | function getChannels(address conduit) 286 | external 287 | view 288 | returns (address[] memory channels); 289 | 290 | /** 291 | * @dev Retrieve the conduit creation code and runtime code hashes. 292 | */ 293 | function getConduitCodeHashes() 294 | external 295 | view 296 | returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash); 297 | } 298 | -------------------------------------------------------------------------------- /contracts/interfaces/ConsiderationInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | // prettier-ignore 5 | import { 6 | BasicOrderParameters, 7 | OrderComponents, 8 | Fulfillment, 9 | FulfillmentComponent, 10 | Execution, 11 | Order, 12 | AdvancedOrder, 13 | OrderStatus, 14 | CriteriaResolver 15 | } from "../lib/ConsiderationStructs.sol"; 16 | 17 | /** 18 | * @title ConsiderationInterface 19 | * @author 0age 20 | * @custom:version 1.1 21 | * @notice Consideration is a generalized ETH/ERC20/ERC721/ERC1155 marketplace. 22 | * It minimizes external calls to the greatest extent possible and 23 | * provides lightweight methods for common routes as well as more 24 | * flexible methods for composing advanced orders. 25 | * 26 | * @dev ConsiderationInterface contains all external function interfaces for 27 | * Consideration. 28 | */ 29 | interface ConsiderationInterface { 30 | /** 31 | * @notice Fulfill an order offering an ERC721 token by supplying Ether (or 32 | * the native token for the given chain) as consideration for the 33 | * order. An arbitrary number of "additional recipients" may also be 34 | * supplied which will each receive native tokens from the fulfiller 35 | * as consideration. 36 | * 37 | * @param parameters Additional information on the fulfilled order. Note 38 | * that the offerer must first approve this contract (or 39 | * their preferred conduit if indicated by the order) for 40 | * their offered ERC721 token to be transferred. 41 | * 42 | * @return fulfilled A boolean indicating whether the order has been 43 | * successfully fulfilled. 44 | */ 45 | function fulfillBasicOrder(BasicOrderParameters calldata parameters) 46 | external 47 | payable 48 | returns (bool fulfilled); 49 | 50 | /** 51 | * @notice Fulfill an order with an arbitrary number of items for offer and 52 | * consideration. Note that this function does not support 53 | * criteria-based orders or partial filling of orders (though 54 | * filling the remainder of a partially-filled order is supported). 55 | * 56 | * @param order The order to fulfill. Note that both the 57 | * offerer and the fulfiller must first approve 58 | * this contract (or the corresponding conduit if 59 | * indicated) to transfer any relevant tokens on 60 | * their behalf and that contracts must implement 61 | * `onERC1155Received` to receive ERC1155 tokens 62 | * as consideration. 63 | * @param fulfillerConduitKey A bytes32 value indicating what conduit, if 64 | * any, to source the fulfiller's token approvals 65 | * from. The zero hash signifies that no conduit 66 | * should be used, with direct approvals set on 67 | * Consideration. 68 | * 69 | * @return fulfilled A boolean indicating whether the order has been 70 | * successfully fulfilled. 71 | */ 72 | function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey) 73 | external 74 | payable 75 | returns (bool fulfilled); 76 | 77 | /** 78 | * @notice Fill an order, fully or partially, with an arbitrary number of 79 | * items for offer and consideration alongside criteria resolvers 80 | * containing specific token identifiers and associated proofs. 81 | * 82 | * @param advancedOrder The order to fulfill along with the fraction 83 | * of the order to attempt to fill. Note that 84 | * both the offerer and the fulfiller must first 85 | * approve this contract (or their preferred 86 | * conduit if indicated by the order) to transfer 87 | * any relevant tokens on their behalf and that 88 | * contracts must implement `onERC1155Received` 89 | * to receive ERC1155 tokens as consideration. 90 | * Also note that all offer and consideration 91 | * components must have no remainder after 92 | * multiplication of the respective amount with 93 | * the supplied fraction for the partial fill to 94 | * be considered valid. 95 | * @param criteriaResolvers An array where each element contains a 96 | * reference to a specific offer or 97 | * consideration, a token identifier, and a proof 98 | * that the supplied token identifier is 99 | * contained in the merkle root held by the item 100 | * in question's criteria element. Note that an 101 | * empty criteria indicates that any 102 | * (transferable) token identifier on the token 103 | * in question is valid and that no associated 104 | * proof needs to be supplied. 105 | * @param fulfillerConduitKey A bytes32 value indicating what conduit, if 106 | * any, to source the fulfiller's token approvals 107 | * from. The zero hash signifies that no conduit 108 | * should be used, with direct approvals set on 109 | * Consideration. 110 | * @param recipient The intended recipient for all received items, 111 | * with `address(0)` indicating that the caller 112 | * should receive the items. 113 | * 114 | * @return fulfilled A boolean indicating whether the order has been 115 | * successfully fulfilled. 116 | */ 117 | function fulfillAdvancedOrder( 118 | AdvancedOrder calldata advancedOrder, 119 | CriteriaResolver[] calldata criteriaResolvers, 120 | bytes32 fulfillerConduitKey, 121 | address recipient 122 | ) external payable returns (bool fulfilled); 123 | 124 | /** 125 | * @notice Attempt to fill a group of orders, each with an arbitrary number 126 | * of items for offer and consideration. Any order that is not 127 | * currently active, has already been fully filled, or has been 128 | * cancelled will be omitted. Remaining offer and consideration 129 | * items will then be aggregated where possible as indicated by the 130 | * supplied offer and consideration component arrays and aggregated 131 | * items will be transferred to the fulfiller or to each intended 132 | * recipient, respectively. Note that a failing item transfer or an 133 | * issue with order formatting will cause the entire batch to fail. 134 | * Note that this function does not support criteria-based orders or 135 | * partial filling of orders (though filling the remainder of a 136 | * partially-filled order is supported). 137 | * 138 | * @param orders The orders to fulfill. Note that both 139 | * the offerer and the fulfiller must first 140 | * approve this contract (or the 141 | * corresponding conduit if indicated) to 142 | * transfer any relevant tokens on their 143 | * behalf and that contracts must implement 144 | * `onERC1155Received` to receive ERC1155 145 | * tokens as consideration. 146 | * @param offerFulfillments An array of FulfillmentComponent arrays 147 | * indicating which offer items to attempt 148 | * to aggregate when preparing executions. 149 | * @param considerationFulfillments An array of FulfillmentComponent arrays 150 | * indicating which consideration items to 151 | * attempt to aggregate when preparing 152 | * executions. 153 | * @param fulfillerConduitKey A bytes32 value indicating what conduit, 154 | * if any, to source the fulfiller's token 155 | * approvals from. The zero hash signifies 156 | * that no conduit should be used, with 157 | * direct approvals set on this contract. 158 | * @param maximumFulfilled The maximum number of orders to fulfill. 159 | * 160 | * @return availableOrders An array of booleans indicating if each order 161 | * with an index corresponding to the index of the 162 | * returned boolean was fulfillable or not. 163 | * @return executions An array of elements indicating the sequence of 164 | * transfers performed as part of matching the given 165 | * orders. 166 | */ 167 | function fulfillAvailableOrders( 168 | Order[] calldata orders, 169 | FulfillmentComponent[][] calldata offerFulfillments, 170 | FulfillmentComponent[][] calldata considerationFulfillments, 171 | bytes32 fulfillerConduitKey, 172 | uint256 maximumFulfilled 173 | ) 174 | external 175 | payable 176 | returns (bool[] memory availableOrders, Execution[] memory executions); 177 | 178 | /** 179 | * @notice Attempt to fill a group of orders, fully or partially, with an 180 | * arbitrary number of items for offer and consideration per order 181 | * alongside criteria resolvers containing specific token 182 | * identifiers and associated proofs. Any order that is not 183 | * currently active, has already been fully filled, or has been 184 | * cancelled will be omitted. Remaining offer and consideration 185 | * items will then be aggregated where possible as indicated by the 186 | * supplied offer and consideration component arrays and aggregated 187 | * items will be transferred to the fulfiller or to each intended 188 | * recipient, respectively. Note that a failing item transfer or an 189 | * issue with order formatting will cause the entire batch to fail. 190 | * 191 | * @param advancedOrders The orders to fulfill along with the 192 | * fraction of those orders to attempt to 193 | * fill. Note that both the offerer and the 194 | * fulfiller must first approve this 195 | * contract (or their preferred conduit if 196 | * indicated by the order) to transfer any 197 | * relevant tokens on their behalf and that 198 | * contracts must implement 199 | * `onERC1155Received` to enable receipt of 200 | * ERC1155 tokens as consideration. Also 201 | * note that all offer and consideration 202 | * components must have no remainder after 203 | * multiplication of the respective amount 204 | * with the supplied fraction for an 205 | * order's partial fill amount to be 206 | * considered valid. 207 | * @param criteriaResolvers An array where each element contains a 208 | * reference to a specific offer or 209 | * consideration, a token identifier, and a 210 | * proof that the supplied token identifier 211 | * is contained in the merkle root held by 212 | * the item in question's criteria element. 213 | * Note that an empty criteria indicates 214 | * that any (transferable) token 215 | * identifier on the token in question is 216 | * valid and that no associated proof needs 217 | * to be supplied. 218 | * @param offerFulfillments An array of FulfillmentComponent arrays 219 | * indicating which offer items to attempt 220 | * to aggregate when preparing executions. 221 | * @param considerationFulfillments An array of FulfillmentComponent arrays 222 | * indicating which consideration items to 223 | * attempt to aggregate when preparing 224 | * executions. 225 | * @param fulfillerConduitKey A bytes32 value indicating what conduit, 226 | * if any, to source the fulfiller's token 227 | * approvals from. The zero hash signifies 228 | * that no conduit should be used, with 229 | * direct approvals set on this contract. 230 | * @param recipient The intended recipient for all received 231 | * items, with `address(0)` indicating that 232 | * the caller should receive the items. 233 | * @param maximumFulfilled The maximum number of orders to fulfill. 234 | * 235 | * @return availableOrders An array of booleans indicating if each order 236 | * with an index corresponding to the index of the 237 | * returned boolean was fulfillable or not. 238 | * @return executions An array of elements indicating the sequence of 239 | * transfers performed as part of matching the given 240 | * orders. 241 | */ 242 | function fulfillAvailableAdvancedOrders( 243 | AdvancedOrder[] calldata advancedOrders, 244 | CriteriaResolver[] calldata criteriaResolvers, 245 | FulfillmentComponent[][] calldata offerFulfillments, 246 | FulfillmentComponent[][] calldata considerationFulfillments, 247 | bytes32 fulfillerConduitKey, 248 | address recipient, 249 | uint256 maximumFulfilled 250 | ) 251 | external 252 | payable 253 | returns (bool[] memory availableOrders, Execution[] memory executions); 254 | 255 | /** 256 | * @notice Match an arbitrary number of orders, each with an arbitrary 257 | * number of items for offer and consideration along with as set of 258 | * fulfillments allocating offer components to consideration 259 | * components. Note that this function does not support 260 | * criteria-based or partial filling of orders (though filling the 261 | * remainder of a partially-filled order is supported). 262 | * 263 | * @param orders The orders to match. Note that both the offerer and 264 | * fulfiller on each order must first approve this 265 | * contract (or their conduit if indicated by the order) 266 | * to transfer any relevant tokens on their behalf and 267 | * each consideration recipient must implement 268 | * `onERC1155Received` to enable ERC1155 token receipt. 269 | * @param fulfillments An array of elements allocating offer components to 270 | * consideration components. Note that each 271 | * consideration component must be fully met for the 272 | * match operation to be valid. 273 | * 274 | * @return executions An array of elements indicating the sequence of 275 | * transfers performed as part of matching the given 276 | * orders. 277 | */ 278 | function matchOrders( 279 | Order[] calldata orders, 280 | Fulfillment[] calldata fulfillments 281 | ) external payable returns (Execution[] memory executions); 282 | 283 | /** 284 | * @notice Match an arbitrary number of full or partial orders, each with an 285 | * arbitrary number of items for offer and consideration, supplying 286 | * criteria resolvers containing specific token identifiers and 287 | * associated proofs as well as fulfillments allocating offer 288 | * components to consideration components. 289 | * 290 | * @param orders The advanced orders to match. Note that both the 291 | * offerer and fulfiller on each order must first 292 | * approve this contract (or a preferred conduit if 293 | * indicated by the order) to transfer any relevant 294 | * tokens on their behalf and each consideration 295 | * recipient must implement `onERC1155Received` in 296 | * order to receive ERC1155 tokens. Also note that 297 | * the offer and consideration components for each 298 | * order must have no remainder after multiplying 299 | * the respective amount with the supplied fraction 300 | * in order for the group of partial fills to be 301 | * considered valid. 302 | * @param criteriaResolvers An array where each element contains a reference 303 | * to a specific order as well as that order's 304 | * offer or consideration, a token identifier, and 305 | * a proof that the supplied token identifier is 306 | * contained in the order's merkle root. Note that 307 | * an empty root indicates that any (transferable) 308 | * token identifier is valid and that no associated 309 | * proof needs to be supplied. 310 | * @param fulfillments An array of elements allocating offer components 311 | * to consideration components. Note that each 312 | * consideration component must be fully met in 313 | * order for the match operation to be valid. 314 | * 315 | * @return executions An array of elements indicating the sequence of 316 | * transfers performed as part of matching the given 317 | * orders. 318 | */ 319 | function matchAdvancedOrders( 320 | AdvancedOrder[] calldata orders, 321 | CriteriaResolver[] calldata criteriaResolvers, 322 | Fulfillment[] calldata fulfillments 323 | ) external payable returns (Execution[] memory executions); 324 | 325 | /** 326 | * @notice Cancel an arbitrary number of orders. Note that only the offerer 327 | * or the zone of a given order may cancel it. Callers should ensure 328 | * that the intended order was cancelled by calling `getOrderStatus` 329 | * and confirming that `isCancelled` returns `true`. 330 | * 331 | * @param orders The orders to cancel. 332 | * 333 | * @return cancelled A boolean indicating whether the supplied orders have 334 | * been successfully cancelled. 335 | */ 336 | function cancel(OrderComponents[] calldata orders) 337 | external 338 | returns (bool cancelled); 339 | 340 | /** 341 | * @notice Validate an arbitrary number of orders, thereby registering their 342 | * signatures as valid and allowing the fulfiller to skip signature 343 | * verification on fulfillment. Note that validated orders may still 344 | * be unfulfillable due to invalid item amounts or other factors; 345 | * callers should determine whether validated orders are fulfillable 346 | * by simulating the fulfillment call prior to execution. Also note 347 | * that anyone can validate a signed order, but only the offerer can 348 | * validate an order without supplying a signature. 349 | * 350 | * @param orders The orders to validate. 351 | * 352 | * @return validated A boolean indicating whether the supplied orders have 353 | * been successfully validated. 354 | */ 355 | function validate(Order[] calldata orders) 356 | external 357 | returns (bool validated); 358 | 359 | /** 360 | * @notice Cancel all orders from a given offerer with a given zone in bulk 361 | * by incrementing a counter. Note that only the offerer may 362 | * increment the counter. 363 | * 364 | * @return newCounter The new counter. 365 | */ 366 | function incrementCounter() external returns (uint256 newCounter); 367 | 368 | /** 369 | * @notice Retrieve the order hash for a given order. 370 | * 371 | * @param order The components of the order. 372 | * 373 | * @return orderHash The order hash. 374 | */ 375 | function getOrderHash(OrderComponents calldata order) 376 | external 377 | view 378 | returns (bytes32 orderHash); 379 | 380 | /** 381 | * @notice Retrieve the status of a given order by hash, including whether 382 | * the order has been cancelled or validated and the fraction of the 383 | * order that has been filled. 384 | * 385 | * @param orderHash The order hash in question. 386 | * 387 | * @return isValidated A boolean indicating whether the order in question 388 | * has been validated (i.e. previously approved or 389 | * partially filled). 390 | * @return isCancelled A boolean indicating whether the order in question 391 | * has been cancelled. 392 | * @return totalFilled The total portion of the order that has been filled 393 | * (i.e. the "numerator"). 394 | * @return totalSize The total size of the order that is either filled or 395 | * unfilled (i.e. the "denominator"). 396 | */ 397 | function getOrderStatus(bytes32 orderHash) 398 | external 399 | view 400 | returns ( 401 | bool isValidated, 402 | bool isCancelled, 403 | uint256 totalFilled, 404 | uint256 totalSize 405 | ); 406 | 407 | /** 408 | * @notice Retrieve the current counter for a given offerer. 409 | * 410 | * @param offerer The offerer in question. 411 | * 412 | * @return counter The current counter. 413 | */ 414 | function getCounter(address offerer) 415 | external 416 | view 417 | returns (uint256 counter); 418 | 419 | /** 420 | * @notice Retrieve configuration information for this contract. 421 | * 422 | * @return version The contract version. 423 | * @return domainSeparator The domain separator for this contract. 424 | * @return conduitController The conduit Controller set for this contract. 425 | */ 426 | function information() 427 | external 428 | view 429 | returns ( 430 | string memory version, 431 | bytes32 domainSeparator, 432 | address conduitController 433 | ); 434 | 435 | /** 436 | * @notice Retrieve the name of this contract. 437 | * 438 | * @return contractName The name of this contract. 439 | */ 440 | function name() external view returns (string memory contractName); 441 | } 442 | -------------------------------------------------------------------------------- /contracts/interfaces/CreatorFeeEngineInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | interface CreatorFeeEngineInterface { 5 | function getRoyaltyView( 6 | address tokenAddress, 7 | uint256 tokenId, 8 | uint256 value 9 | ) 10 | external 11 | view 12 | returns (address payable[] memory recipients, uint256[] memory amounts); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/interfaces/SeaportValidatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ItemType } from "../lib/ConsiderationEnums.sol"; 5 | import { Order, OrderParameters } from "../lib/ConsiderationStructs.sol"; 6 | import { ErrorsAndWarnings } from "../lib/ErrorsAndWarnings.sol"; 7 | import { ValidationConfiguration } from "../lib/SeaportValidatorTypes.sol"; 8 | 9 | /** 10 | * @title SeaportValidator 11 | * @notice SeaportValidator validates simple orders that adhere to a set of rules defined below: 12 | * - The order is either a listing or an offer order (one NFT to buy or one NFT to sell). 13 | * - The first consideration is the primary consideration. 14 | * - The order pays up to two fees in the fungible token currency. First fee is primary fee, second is creator fee. 15 | * - In private orders, the last consideration specifies a recipient for the offer item. 16 | * - Offer items must be owned and properly approved by the offerer. 17 | * - Consideration items must exist. 18 | */ 19 | interface SeaportValidatorInterface { 20 | /** 21 | * @notice Conduct a comprehensive validation of the given order. 22 | * @param order The order to validate. 23 | * @return errorsAndWarnings The errors and warnings found in the order. 24 | */ 25 | function isValidOrder(Order calldata order) 26 | external 27 | view 28 | returns (ErrorsAndWarnings memory errorsAndWarnings); 29 | 30 | /** 31 | * @notice Same as `isValidOrder` but allows for more configuration related to fee validation. 32 | */ 33 | function isValidOrderWithConfiguration( 34 | ValidationConfiguration memory validationConfiguration, 35 | Order memory order 36 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 37 | 38 | /** 39 | * @notice Checks if a conduit key is valid. 40 | * @param conduitKey The conduit key to check. 41 | * @return errorsAndWarnings The errors and warnings 42 | */ 43 | function isValidConduit(bytes32 conduitKey) 44 | external 45 | view 46 | returns (ErrorsAndWarnings memory errorsAndWarnings); 47 | 48 | function validateSignature(Order memory order) 49 | external 50 | view 51 | returns (ErrorsAndWarnings memory errorsAndWarnings); 52 | 53 | function validateSignatureWithCounter(Order memory order, uint256 counter) 54 | external 55 | view 56 | returns (ErrorsAndWarnings memory errorsAndWarnings); 57 | 58 | /** 59 | * @notice Check the time validity of an order 60 | * @param orderParameters The parameters for the order to validate 61 | * @param shortOrderDuration The duration of which an order is considered short 62 | * @param distantOrderExpiration Distant order expiration delta in seconds. 63 | * @return errorsAndWarnings The Issues and warnings 64 | */ 65 | function validateTime( 66 | OrderParameters memory orderParameters, 67 | uint256 shortOrderDuration, 68 | uint256 distantOrderExpiration 69 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 70 | 71 | /** 72 | * @notice Validate the status of an order 73 | * @param orderParameters The parameters for the order to validate 74 | * @return errorsAndWarnings The errors and warnings 75 | */ 76 | function validateOrderStatus(OrderParameters memory orderParameters) 77 | external 78 | view 79 | returns (ErrorsAndWarnings memory errorsAndWarnings); 80 | 81 | /** 82 | * @notice Validate all offer items for an order 83 | * @param orderParameters The parameters for the order to validate 84 | * @return errorsAndWarnings The errors and warnings 85 | */ 86 | function validateOfferItems(OrderParameters memory orderParameters) 87 | external 88 | view 89 | returns (ErrorsAndWarnings memory errorsAndWarnings); 90 | 91 | /** 92 | * @notice Validate all consideration items for an order 93 | * @param orderParameters The parameters for the order to validate 94 | * @return errorsAndWarnings The errors and warnings 95 | */ 96 | function validateConsiderationItems(OrderParameters memory orderParameters) 97 | external 98 | view 99 | returns (ErrorsAndWarnings memory errorsAndWarnings); 100 | 101 | /** 102 | * @notice Strict validation operates under tight assumptions. It validates primary 103 | * fee, creator fee, private sale consideration, and overall order format. 104 | * @dev Only checks first fee recipient provided by CreatorFeeRegistry. 105 | * Order of consideration items must be as follows: 106 | * 1. Primary consideration 107 | * 2. Primary fee 108 | * 3. Creator Fee 109 | * 4. Private sale consideration 110 | * @param orderParameters The parameters for the order to validate. 111 | * @param primaryFeeRecipient The primary fee recipient. Set to null address for no primary fee. 112 | * @param primaryFeeBips The primary fee in BIPs. 113 | * @param checkCreatorFee Should check for creator fee. If true, creator fee must be present as 114 | * according to creator fee engine. If false, must not have creator fee. 115 | * @return errorsAndWarnings The errors and warnings. 116 | */ 117 | function validateStrictLogic( 118 | OrderParameters memory orderParameters, 119 | address primaryFeeRecipient, 120 | uint256 primaryFeeBips, 121 | bool checkCreatorFee 122 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 123 | 124 | /** 125 | * @notice Validate a consideration item 126 | * @param orderParameters The parameters for the order to validate 127 | * @param considerationItemIndex The index of the consideration item to validate 128 | * @return errorsAndWarnings The errors and warnings 129 | */ 130 | function validateConsiderationItem( 131 | OrderParameters memory orderParameters, 132 | uint256 considerationItemIndex 133 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 134 | 135 | /** 136 | * @notice Validates the parameters of a consideration item including contract validation 137 | * @param orderParameters The parameters for the order to validate 138 | * @param considerationItemIndex The index of the consideration item to validate 139 | * @return errorsAndWarnings The errors and warnings 140 | */ 141 | function validateConsiderationItemParameters( 142 | OrderParameters memory orderParameters, 143 | uint256 considerationItemIndex 144 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 145 | 146 | /** 147 | * @notice Validates an offer item 148 | * @param orderParameters The parameters for the order to validate 149 | * @param offerItemIndex The index of the offerItem in offer array to validate 150 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 151 | */ 152 | function validateOfferItem( 153 | OrderParameters memory orderParameters, 154 | uint256 offerItemIndex 155 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 156 | 157 | /** 158 | * @notice Validates the OfferItem parameters. This includes token contract validation 159 | * @dev OfferItems with criteria are currently not allowed 160 | * @param orderParameters The parameters for the order to validate 161 | * @param offerItemIndex The index of the offerItem in offer array to validate 162 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 163 | */ 164 | function validateOfferItemParameters( 165 | OrderParameters memory orderParameters, 166 | uint256 offerItemIndex 167 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 168 | 169 | /** 170 | * @notice Validates the OfferItem approvals and balances 171 | * @param orderParameters The parameters for the order to validate 172 | * @param offerItemIndex The index of the offerItem in offer array to validate 173 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 174 | */ 175 | function validateOfferItemApprovalAndBalance( 176 | OrderParameters memory orderParameters, 177 | uint256 offerItemIndex 178 | ) external view returns (ErrorsAndWarnings memory errorsAndWarnings); 179 | 180 | // TODO: Need to add support for order with extra data 181 | /** 182 | * @notice Validates the zone call for an order 183 | * @param orderParameters The parameters for the order to validate 184 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 185 | */ 186 | function isValidZone(OrderParameters memory orderParameters) 187 | external 188 | view 189 | returns (ErrorsAndWarnings memory errorsAndWarnings); 190 | 191 | /** 192 | * @notice Gets the approval address for the given conduit key 193 | * @param conduitKey Conduit key to get approval address for 194 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 195 | */ 196 | function getApprovalAddress(bytes32 conduitKey) 197 | external 198 | view 199 | returns (address, ErrorsAndWarnings memory errorsAndWarnings); 200 | 201 | /** 202 | * @notice Safely check that a contract implements an interface 203 | * @param token The token address to check 204 | * @param interfaceHash The interface hash to check 205 | */ 206 | function checkInterface(address token, bytes4 interfaceHash) 207 | external 208 | view 209 | returns (bool); 210 | 211 | function isPaymentToken(ItemType itemType) external pure returns (bool); 212 | 213 | /*////////////////////////////////////////////////////////////// 214 | Merkle Helpers 215 | //////////////////////////////////////////////////////////////*/ 216 | 217 | /** 218 | * @notice Sorts an array of token ids by the keccak256 hash of the id. Required ordering of ids 219 | * for other merkle operations. 220 | * @param includedTokens An array of included token ids. 221 | * @return sortedTokens The sorted `includedTokens` array. 222 | */ 223 | function sortMerkleTokens(uint256[] memory includedTokens) 224 | external 225 | view 226 | returns (uint256[] memory sortedTokens); 227 | 228 | /** 229 | * @notice Creates a merkle root for includedTokens. 230 | * @dev `includedTokens` must be sorting in strictly ascending order according to the keccak256 hash of the value. 231 | * @return merkleRoot The merkle root 232 | * @return errorsAndWarnings Errors and warnings from the operation 233 | */ 234 | function getMerkleRoot(uint256[] memory includedTokens) 235 | external 236 | view 237 | returns ( 238 | bytes32 merkleRoot, 239 | ErrorsAndWarnings memory errorsAndWarnings 240 | ); 241 | 242 | /** 243 | * @notice Creates a merkle proof for the the targetIndex contained in includedTokens. 244 | * @dev `targetIndex` is referring to the index of an element in `includedTokens`. 245 | * `includedTokens` must be sorting in ascending order according to the keccak256 hash of the value. 246 | * @return merkleProof The merkle proof 247 | * @return errorsAndWarnings Errors and warnings from the operation 248 | */ 249 | function getMerkleProof( 250 | uint256[] memory includedTokens, 251 | uint256 targetIndex 252 | ) 253 | external 254 | view 255 | returns ( 256 | bytes32[] memory merkleProof, 257 | ErrorsAndWarnings memory errorsAndWarnings 258 | ); 259 | 260 | function verifyMerkleProof( 261 | bytes32 merkleRoot, 262 | bytes32[] memory merkleProof, 263 | uint256 valueToProve 264 | ) external view returns (bool); 265 | } 266 | -------------------------------------------------------------------------------- /contracts/interfaces/ZoneInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { 5 | AdvancedOrder, 6 | CriteriaResolver 7 | } from "../lib/ConsiderationStructs.sol"; 8 | 9 | interface ZoneInterface { 10 | // Called by Consideration whenever extraData is not provided by the caller. 11 | function isValidOrder( 12 | bytes32 orderHash, 13 | address caller, 14 | address offerer, 15 | bytes32 zoneHash 16 | ) external view returns (bytes4 validOrderMagicValue); 17 | 18 | // Called by Consideration whenever any extraData is provided by the caller. 19 | function isValidOrderIncludingExtraData( 20 | bytes32 orderHash, 21 | address caller, 22 | AdvancedOrder calldata order, 23 | bytes32[] calldata priorOrderHashes, 24 | CriteriaResolver[] calldata criteriaResolvers 25 | ) external view returns (bytes4 validOrderMagicValue); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/lib/ConsiderationConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.7; 3 | 4 | /* 5 | * -------------------------- Disambiguation & Other Notes --------------------- 6 | * - The term "head" is used as it is in the documentation for ABI encoding, 7 | * but only in reference to dynamic types, i.e. it always refers to the 8 | * offset or pointer to the body of a dynamic type. In calldata, the head 9 | * is always an offset (relative to the parent object), while in memory, 10 | * the head is always the pointer to the body. More information found here: 11 | * https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding 12 | * - Note that the length of an array is separate from and precedes the 13 | * head of the array. 14 | * 15 | * - The term "body" is used in place of the term "head" used in the ABI 16 | * documentation. It refers to the start of the data for a dynamic type, 17 | * e.g. the first word of a struct or the first word of the first element 18 | * in an array. 19 | * 20 | * - The term "pointer" is used to describe the absolute position of a value 21 | * and never an offset relative to another value. 22 | * - The suffix "_ptr" refers to a memory pointer. 23 | * - The suffix "_cdPtr" refers to a calldata pointer. 24 | * 25 | * - The term "offset" is used to describe the position of a value relative 26 | * to some parent value. For example, OrderParameters_conduit_offset is the 27 | * offset to the "conduit" value in the OrderParameters struct relative to 28 | * the start of the body. 29 | * - Note: Offsets are used to derive pointers. 30 | * 31 | * - Some structs have pointers defined for all of their fields in this file. 32 | * Lines which are commented out are fields that are not used in the 33 | * codebase but have been left in for readability. 34 | */ 35 | 36 | // Declare constants for name, version, and reentrancy sentinel values. 37 | 38 | // Name is right padded, so it touches the length which is left padded. This 39 | // enables writing both values at once. Length goes at byte 95 in memory, and 40 | // name fills bytes 96-109, so both values can be written left-padded to 77. 41 | uint256 constant NameLengthPtr = 77; 42 | uint256 constant NameWithLength = 0x0d436F6E73696465726174696F6E; 43 | 44 | uint256 constant Version = 0x312e31; 45 | uint256 constant Version_length = 3; 46 | uint256 constant Version_shift = 0xe8; 47 | 48 | uint256 constant _NOT_ENTERED = 1; 49 | uint256 constant _ENTERED = 2; 50 | 51 | // Common Offsets 52 | // Offsets for identically positioned fields shared by: 53 | // OfferItem, ConsiderationItem, SpentItem, ReceivedItem 54 | 55 | uint256 constant Common_token_offset = 0x20; 56 | uint256 constant Common_identifier_offset = 0x40; 57 | uint256 constant Common_amount_offset = 0x60; 58 | 59 | uint256 constant ReceivedItem_size = 0xa0; 60 | uint256 constant ReceivedItem_amount_offset = 0x60; 61 | uint256 constant ReceivedItem_recipient_offset = 0x80; 62 | 63 | uint256 constant ReceivedItem_CommonParams_size = 0x60; 64 | 65 | uint256 constant ConsiderationItem_recipient_offset = 0xa0; 66 | // Store the same constant in an abbreviated format for a line length fix. 67 | uint256 constant ConsiderItem_recipient_offset = 0xa0; 68 | 69 | uint256 constant Execution_offerer_offset = 0x20; 70 | uint256 constant Execution_conduit_offset = 0x40; 71 | 72 | uint256 constant InvalidFulfillmentComponentData_error_signature = ( 73 | 0x7fda727900000000000000000000000000000000000000000000000000000000 74 | ); 75 | uint256 constant InvalidFulfillmentComponentData_error_len = 0x04; 76 | 77 | uint256 constant Panic_error_signature = ( 78 | 0x4e487b7100000000000000000000000000000000000000000000000000000000 79 | ); 80 | uint256 constant Panic_error_offset = 0x04; 81 | uint256 constant Panic_error_length = 0x24; 82 | uint256 constant Panic_arithmetic = 0x11; 83 | 84 | uint256 constant MissingItemAmount_error_signature = ( 85 | 0x91b3e51400000000000000000000000000000000000000000000000000000000 86 | ); 87 | uint256 constant MissingItemAmount_error_len = 0x04; 88 | 89 | uint256 constant OrderParameters_offer_head_offset = 0x40; 90 | uint256 constant OrderParameters_consideration_head_offset = 0x60; 91 | uint256 constant OrderParameters_conduit_offset = 0x120; 92 | uint256 constant OrderParameters_counter_offset = 0x140; 93 | 94 | uint256 constant Fulfillment_itemIndex_offset = 0x20; 95 | 96 | uint256 constant AdvancedOrder_numerator_offset = 0x20; 97 | 98 | uint256 constant AlmostOneWord = 0x1f; 99 | uint256 constant OneWord = 0x20; 100 | uint256 constant TwoWords = 0x40; 101 | uint256 constant ThreeWords = 0x60; 102 | uint256 constant FourWords = 0x80; 103 | uint256 constant FiveWords = 0xa0; 104 | 105 | uint256 constant FreeMemoryPointerSlot = 0x40; 106 | uint256 constant ZeroSlot = 0x60; 107 | uint256 constant DefaultFreeMemoryPointer = 0x80; 108 | 109 | uint256 constant Slot0x80 = 0x80; 110 | uint256 constant Slot0xA0 = 0xa0; 111 | 112 | uint256 constant BasicOrder_endAmount_cdPtr = 0x104; 113 | uint256 constant BasicOrder_common_params_size = 0xa0; 114 | uint256 constant BasicOrder_considerationHashesArray_ptr = 0x160; 115 | 116 | uint256 constant EIP712_Order_size = 0x180; 117 | uint256 constant EIP712_OfferItem_size = 0xc0; 118 | uint256 constant EIP712_ConsiderationItem_size = 0xe0; 119 | uint256 constant AdditionalRecipients_size = 0x40; 120 | 121 | uint256 constant EIP712_DomainSeparator_offset = 0x02; 122 | uint256 constant EIP712_OrderHash_offset = 0x22; 123 | uint256 constant EIP712_DigestPayload_size = 0x42; 124 | 125 | uint256 constant receivedItemsHash_ptr = 0x60; 126 | 127 | /* 128 | * Memory layout in _prepareBasicFulfillmentFromCalldata of 129 | * data for OrderFulfilled 130 | * 131 | * event OrderFulfilled( 132 | * bytes32 orderHash, 133 | * address indexed offerer, 134 | * address indexed zone, 135 | * address fulfiller, 136 | * SpentItem[] offer, 137 | * > (itemType, token, id, amount) 138 | * ReceivedItem[] consideration 139 | * > (itemType, token, id, amount, recipient) 140 | * ) 141 | * 142 | * - 0x00: orderHash 143 | * - 0x20: fulfiller 144 | * - 0x40: offer offset (0x80) 145 | * - 0x60: consideration offset (0x120) 146 | * - 0x80: offer.length (1) 147 | * - 0xa0: offerItemType 148 | * - 0xc0: offerToken 149 | * - 0xe0: offerIdentifier 150 | * - 0x100: offerAmount 151 | * - 0x120: consideration.length (1 + additionalRecipients.length) 152 | * - 0x140: considerationItemType 153 | * - 0x160: considerationToken 154 | * - 0x180: considerationIdentifier 155 | * - 0x1a0: considerationAmount 156 | * - 0x1c0: considerationRecipient 157 | * - ... 158 | */ 159 | 160 | // Minimum length of the OrderFulfilled event data. 161 | // Must be added to the size of the ReceivedItem array for additionalRecipients 162 | // (0xa0 * additionalRecipients.length) to calculate full size of the buffer. 163 | uint256 constant OrderFulfilled_baseSize = 0x1e0; 164 | uint256 constant OrderFulfilled_selector = ( 165 | 0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31 166 | ); 167 | 168 | // Minimum offset in memory to OrderFulfilled event data. 169 | // Must be added to the size of the EIP712 hash array for additionalRecipients 170 | // (32 * additionalRecipients.length) to calculate the pointer to event data. 171 | uint256 constant OrderFulfilled_baseOffset = 0x180; 172 | uint256 constant OrderFulfilled_consideration_length_baseOffset = 0x2a0; 173 | uint256 constant OrderFulfilled_offer_length_baseOffset = 0x200; 174 | 175 | // uint256 constant OrderFulfilled_orderHash_offset = 0x00; 176 | uint256 constant OrderFulfilled_fulfiller_offset = 0x20; 177 | uint256 constant OrderFulfilled_offer_head_offset = 0x40; 178 | uint256 constant OrderFulfilled_offer_body_offset = 0x80; 179 | uint256 constant OrderFulfilled_consideration_head_offset = 0x60; 180 | uint256 constant OrderFulfilled_consideration_body_offset = 0x120; 181 | 182 | // BasicOrderParameters 183 | uint256 constant BasicOrder_parameters_cdPtr = 0x04; 184 | uint256 constant BasicOrder_considerationToken_cdPtr = 0x24; 185 | // uint256 constant BasicOrder_considerationIdentifier_cdPtr = 0x44; 186 | uint256 constant BasicOrder_considerationAmount_cdPtr = 0x64; 187 | uint256 constant BasicOrder_offerer_cdPtr = 0x84; 188 | uint256 constant BasicOrder_zone_cdPtr = 0xa4; 189 | uint256 constant BasicOrder_offerToken_cdPtr = 0xc4; 190 | // uint256 constant BasicOrder_offerIdentifier_cdPtr = 0xe4; 191 | uint256 constant BasicOrder_offerAmount_cdPtr = 0x104; 192 | uint256 constant BasicOrder_basicOrderType_cdPtr = 0x124; 193 | uint256 constant BasicOrder_startTime_cdPtr = 0x144; 194 | // uint256 constant BasicOrder_endTime_cdPtr = 0x164; 195 | // uint256 constant BasicOrder_zoneHash_cdPtr = 0x184; 196 | // uint256 constant BasicOrder_salt_cdPtr = 0x1a4; 197 | uint256 constant BasicOrder_offererConduit_cdPtr = 0x1c4; 198 | uint256 constant BasicOrder_fulfillerConduit_cdPtr = 0x1e4; 199 | uint256 constant BasicOrder_totalOriginalAdditionalRecipients_cdPtr = 0x204; 200 | uint256 constant BasicOrder_additionalRecipients_head_cdPtr = 0x224; 201 | uint256 constant BasicOrder_signature_cdPtr = 0x244; 202 | uint256 constant BasicOrder_additionalRecipients_length_cdPtr = 0x264; 203 | uint256 constant BasicOrder_additionalRecipients_data_cdPtr = 0x284; 204 | 205 | uint256 constant BasicOrder_parameters_ptr = 0x20; 206 | 207 | uint256 constant BasicOrder_basicOrderType_range = 0x18; // 24 values 208 | 209 | /* 210 | * Memory layout in _prepareBasicFulfillmentFromCalldata of 211 | * EIP712 data for ConsiderationItem 212 | * - 0x80: ConsiderationItem EIP-712 typehash (constant) 213 | * - 0xa0: itemType 214 | * - 0xc0: token 215 | * - 0xe0: identifier 216 | * - 0x100: startAmount 217 | * - 0x120: endAmount 218 | * - 0x140: recipient 219 | */ 220 | uint256 constant BasicOrder_considerationItem_typeHash_ptr = 0x80; // memoryPtr 221 | uint256 constant BasicOrder_considerationItem_itemType_ptr = 0xa0; 222 | uint256 constant BasicOrder_considerationItem_token_ptr = 0xc0; 223 | uint256 constant BasicOrder_considerationItem_identifier_ptr = 0xe0; 224 | uint256 constant BasicOrder_considerationItem_startAmount_ptr = 0x100; 225 | uint256 constant BasicOrder_considerationItem_endAmount_ptr = 0x120; 226 | // uint256 constant BasicOrder_considerationItem_recipient_ptr = 0x140; 227 | 228 | /* 229 | * Memory layout in _prepareBasicFulfillmentFromCalldata of 230 | * EIP712 data for OfferItem 231 | * - 0x80: OfferItem EIP-712 typehash (constant) 232 | * - 0xa0: itemType 233 | * - 0xc0: token 234 | * - 0xe0: identifier (reused for offeredItemsHash) 235 | * - 0x100: startAmount 236 | * - 0x120: endAmount 237 | */ 238 | uint256 constant BasicOrder_offerItem_typeHash_ptr = DefaultFreeMemoryPointer; 239 | uint256 constant BasicOrder_offerItem_itemType_ptr = 0xa0; 240 | uint256 constant BasicOrder_offerItem_token_ptr = 0xc0; 241 | // uint256 constant BasicOrder_offerItem_identifier_ptr = 0xe0; 242 | // uint256 constant BasicOrder_offerItem_startAmount_ptr = 0x100; 243 | uint256 constant BasicOrder_offerItem_endAmount_ptr = 0x120; 244 | 245 | /* 246 | * Memory layout in _prepareBasicFulfillmentFromCalldata of 247 | * EIP712 data for Order 248 | * - 0x80: Order EIP-712 typehash (constant) 249 | * - 0xa0: orderParameters.offerer 250 | * - 0xc0: orderParameters.zone 251 | * - 0xe0: keccak256(abi.encodePacked(offerHashes)) 252 | * - 0x100: keccak256(abi.encodePacked(considerationHashes)) 253 | * - 0x120: orderType 254 | * - 0x140: startTime 255 | * - 0x160: endTime 256 | * - 0x180: zoneHash 257 | * - 0x1a0: salt 258 | * - 0x1c0: conduit 259 | * - 0x1e0: _counters[orderParameters.offerer] (from storage) 260 | */ 261 | uint256 constant BasicOrder_order_typeHash_ptr = 0x80; 262 | uint256 constant BasicOrder_order_offerer_ptr = 0xa0; 263 | // uint256 constant BasicOrder_order_zone_ptr = 0xc0; 264 | uint256 constant BasicOrder_order_offerHashes_ptr = 0xe0; 265 | uint256 constant BasicOrder_order_considerationHashes_ptr = 0x100; 266 | uint256 constant BasicOrder_order_orderType_ptr = 0x120; 267 | uint256 constant BasicOrder_order_startTime_ptr = 0x140; 268 | // uint256 constant BasicOrder_order_endTime_ptr = 0x160; 269 | // uint256 constant BasicOrder_order_zoneHash_ptr = 0x180; 270 | // uint256 constant BasicOrder_order_salt_ptr = 0x1a0; 271 | // uint256 constant BasicOrder_order_conduitKey_ptr = 0x1c0; 272 | uint256 constant BasicOrder_order_counter_ptr = 0x1e0; 273 | uint256 constant BasicOrder_additionalRecipients_head_ptr = 0x240; 274 | uint256 constant BasicOrder_signature_ptr = 0x260; 275 | 276 | // Signature-related 277 | bytes32 constant EIP2098_allButHighestBitMask = ( 278 | 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 279 | ); 280 | bytes32 constant ECDSA_twentySeventhAndTwentyEighthBytesSet = ( 281 | 0x0000000000000000000000000000000000000000000000000000000101000000 282 | ); 283 | uint256 constant ECDSA_MaxLength = 65; 284 | uint256 constant ECDSA_signature_s_offset = 0x40; 285 | uint256 constant ECDSA_signature_v_offset = 0x60; 286 | 287 | bytes32 constant EIP1271_isValidSignature_selector = ( 288 | 0x1626ba7e00000000000000000000000000000000000000000000000000000000 289 | ); 290 | uint256 constant EIP1271_isValidSignature_signatureHead_negativeOffset = 0x20; 291 | uint256 constant EIP1271_isValidSignature_digest_negativeOffset = 0x40; 292 | uint256 constant EIP1271_isValidSignature_selector_negativeOffset = 0x44; 293 | uint256 constant EIP1271_isValidSignature_calldata_baseLength = 0x64; 294 | 295 | uint256 constant EIP1271_isValidSignature_signature_head_offset = 0x40; 296 | 297 | // abi.encodeWithSignature("NoContract(address)") 298 | uint256 constant NoContract_error_signature = ( 299 | 0x5f15d67200000000000000000000000000000000000000000000000000000000 300 | ); 301 | uint256 constant NoContract_error_sig_ptr = 0x0; 302 | uint256 constant NoContract_error_token_ptr = 0x4; 303 | uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36 304 | 305 | uint256 constant EIP_712_PREFIX = ( 306 | 0x1901000000000000000000000000000000000000000000000000000000000000 307 | ); 308 | 309 | uint256 constant ExtraGasBuffer = 0x20; 310 | uint256 constant CostPerWord = 3; 311 | uint256 constant MemoryExpansionCoefficient = 0x200; // 512 312 | 313 | uint256 constant Create2AddressDerivation_ptr = 0x0b; 314 | uint256 constant Create2AddressDerivation_length = 0x55; 315 | 316 | uint256 constant MaskOverByteTwelve = ( 317 | 0x0000000000000000000000ff0000000000000000000000000000000000000000 318 | ); 319 | 320 | uint256 constant MaskOverLastTwentyBytes = ( 321 | 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 322 | ); 323 | 324 | uint256 constant MaskOverFirstFourBytes = ( 325 | 0xffffffff00000000000000000000000000000000000000000000000000000000 326 | ); 327 | 328 | uint256 constant Conduit_execute_signature = ( 329 | 0x4ce34aa200000000000000000000000000000000000000000000000000000000 330 | ); 331 | 332 | uint256 constant MaxUint8 = 0xff; 333 | uint256 constant MaxUint120 = 0xffffffffffffffffffffffffffffff; 334 | 335 | uint256 constant Conduit_execute_ConduitTransfer_ptr = 0x20; 336 | uint256 constant Conduit_execute_ConduitTransfer_length = 0x01; 337 | 338 | uint256 constant Conduit_execute_ConduitTransfer_offset_ptr = 0x04; 339 | uint256 constant Conduit_execute_ConduitTransfer_length_ptr = 0x24; 340 | uint256 constant Conduit_execute_transferItemType_ptr = 0x44; 341 | uint256 constant Conduit_execute_transferToken_ptr = 0x64; 342 | uint256 constant Conduit_execute_transferFrom_ptr = 0x84; 343 | uint256 constant Conduit_execute_transferTo_ptr = 0xa4; 344 | uint256 constant Conduit_execute_transferIdentifier_ptr = 0xc4; 345 | uint256 constant Conduit_execute_transferAmount_ptr = 0xe4; 346 | 347 | uint256 constant OneConduitExecute_size = 0x104; 348 | 349 | // Sentinel value to indicate that the conduit accumulator is not armed. 350 | uint256 constant AccumulatorDisarmed = 0x20; 351 | uint256 constant AccumulatorArmed = 0x40; 352 | uint256 constant Accumulator_conduitKey_ptr = 0x20; 353 | uint256 constant Accumulator_selector_ptr = 0x40; 354 | uint256 constant Accumulator_array_offset_ptr = 0x44; 355 | uint256 constant Accumulator_array_length_ptr = 0x64; 356 | 357 | uint256 constant Accumulator_itemSizeOffsetDifference = 0x3c; 358 | 359 | uint256 constant Accumulator_array_offset = 0x20; 360 | uint256 constant Conduit_transferItem_size = 0xc0; 361 | uint256 constant Conduit_transferItem_token_ptr = 0x20; 362 | uint256 constant Conduit_transferItem_from_ptr = 0x40; 363 | uint256 constant Conduit_transferItem_to_ptr = 0x60; 364 | uint256 constant Conduit_transferItem_identifier_ptr = 0x80; 365 | uint256 constant Conduit_transferItem_amount_ptr = 0xa0; 366 | 367 | // Declare constant for errors related to amount derivation. 368 | // error InexactFraction() @ AmountDerivationErrors.sol 369 | uint256 constant InexactFraction_error_signature = ( 370 | 0xc63cf08900000000000000000000000000000000000000000000000000000000 371 | ); 372 | uint256 constant InexactFraction_error_len = 0x04; 373 | 374 | // Declare constant for errors related to signature verification. 375 | uint256 constant Ecrecover_precompile = 1; 376 | uint256 constant Ecrecover_args_size = 0x80; 377 | uint256 constant Signature_lower_v = 27; 378 | 379 | // error BadSignatureV(uint8) @ SignatureVerificationErrors.sol 380 | uint256 constant BadSignatureV_error_signature = ( 381 | 0x1f003d0a00000000000000000000000000000000000000000000000000000000 382 | ); 383 | uint256 constant BadSignatureV_error_offset = 0x04; 384 | uint256 constant BadSignatureV_error_length = 0x24; 385 | 386 | // error InvalidSigner() @ SignatureVerificationErrors.sol 387 | uint256 constant InvalidSigner_error_signature = ( 388 | 0x815e1d6400000000000000000000000000000000000000000000000000000000 389 | ); 390 | uint256 constant InvalidSigner_error_length = 0x04; 391 | 392 | // error InvalidSignature() @ SignatureVerificationErrors.sol 393 | uint256 constant InvalidSignature_error_signature = ( 394 | 0x8baa579f00000000000000000000000000000000000000000000000000000000 395 | ); 396 | uint256 constant InvalidSignature_error_length = 0x04; 397 | 398 | // error BadContractSignature() @ SignatureVerificationErrors.sol 399 | uint256 constant BadContractSignature_error_signature = ( 400 | 0x4f7fb80d00000000000000000000000000000000000000000000000000000000 401 | ); 402 | uint256 constant BadContractSignature_error_length = 0x04; 403 | 404 | uint256 constant NumBitsAfterSelector = 0xe0; 405 | 406 | // 69 is the lowest modulus for which the remainder 407 | // of every selector other than the two match functions 408 | // is greater than those of the match functions. 409 | uint256 constant NonMatchSelector_MagicModulus = 69; 410 | // Of the two match function selectors, the highest 411 | // remainder modulo 69 is 29. 412 | uint256 constant NonMatchSelector_MagicRemainder = 0x1d; 413 | -------------------------------------------------------------------------------- /contracts/lib/ConsiderationEnums.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | // prettier-ignore 5 | enum OrderType { 6 | // 0: no partial fills, anyone can execute 7 | FULL_OPEN, 8 | 9 | // 1: partial fills supported, anyone can execute 10 | PARTIAL_OPEN, 11 | 12 | // 2: no partial fills, only offerer or zone can execute 13 | FULL_RESTRICTED, 14 | 15 | // 3: partial fills supported, only offerer or zone can execute 16 | PARTIAL_RESTRICTED 17 | } 18 | 19 | // prettier-ignore 20 | enum BasicOrderType { 21 | // 0: no partial fills, anyone can execute 22 | ETH_TO_ERC721_FULL_OPEN, 23 | 24 | // 1: partial fills supported, anyone can execute 25 | ETH_TO_ERC721_PARTIAL_OPEN, 26 | 27 | // 2: no partial fills, only offerer or zone can execute 28 | ETH_TO_ERC721_FULL_RESTRICTED, 29 | 30 | // 3: partial fills supported, only offerer or zone can execute 31 | ETH_TO_ERC721_PARTIAL_RESTRICTED, 32 | 33 | // 4: no partial fills, anyone can execute 34 | ETH_TO_ERC1155_FULL_OPEN, 35 | 36 | // 5: partial fills supported, anyone can execute 37 | ETH_TO_ERC1155_PARTIAL_OPEN, 38 | 39 | // 6: no partial fills, only offerer or zone can execute 40 | ETH_TO_ERC1155_FULL_RESTRICTED, 41 | 42 | // 7: partial fills supported, only offerer or zone can execute 43 | ETH_TO_ERC1155_PARTIAL_RESTRICTED, 44 | 45 | // 8: no partial fills, anyone can execute 46 | ERC20_TO_ERC721_FULL_OPEN, 47 | 48 | // 9: partial fills supported, anyone can execute 49 | ERC20_TO_ERC721_PARTIAL_OPEN, 50 | 51 | // 10: no partial fills, only offerer or zone can execute 52 | ERC20_TO_ERC721_FULL_RESTRICTED, 53 | 54 | // 11: partial fills supported, only offerer or zone can execute 55 | ERC20_TO_ERC721_PARTIAL_RESTRICTED, 56 | 57 | // 12: no partial fills, anyone can execute 58 | ERC20_TO_ERC1155_FULL_OPEN, 59 | 60 | // 13: partial fills supported, anyone can execute 61 | ERC20_TO_ERC1155_PARTIAL_OPEN, 62 | 63 | // 14: no partial fills, only offerer or zone can execute 64 | ERC20_TO_ERC1155_FULL_RESTRICTED, 65 | 66 | // 15: partial fills supported, only offerer or zone can execute 67 | ERC20_TO_ERC1155_PARTIAL_RESTRICTED, 68 | 69 | // 16: no partial fills, anyone can execute 70 | ERC721_TO_ERC20_FULL_OPEN, 71 | 72 | // 17: partial fills supported, anyone can execute 73 | ERC721_TO_ERC20_PARTIAL_OPEN, 74 | 75 | // 18: no partial fills, only offerer or zone can execute 76 | ERC721_TO_ERC20_FULL_RESTRICTED, 77 | 78 | // 19: partial fills supported, only offerer or zone can execute 79 | ERC721_TO_ERC20_PARTIAL_RESTRICTED, 80 | 81 | // 20: no partial fills, anyone can execute 82 | ERC1155_TO_ERC20_FULL_OPEN, 83 | 84 | // 21: partial fills supported, anyone can execute 85 | ERC1155_TO_ERC20_PARTIAL_OPEN, 86 | 87 | // 22: no partial fills, only offerer or zone can execute 88 | ERC1155_TO_ERC20_FULL_RESTRICTED, 89 | 90 | // 23: partial fills supported, only offerer or zone can execute 91 | ERC1155_TO_ERC20_PARTIAL_RESTRICTED 92 | } 93 | 94 | // prettier-ignore 95 | enum BasicOrderRouteType { 96 | // 0: provide Ether (or other native token) to receive offered ERC721 item. 97 | ETH_TO_ERC721, 98 | 99 | // 1: provide Ether (or other native token) to receive offered ERC1155 item. 100 | ETH_TO_ERC1155, 101 | 102 | // 2: provide ERC20 item to receive offered ERC721 item. 103 | ERC20_TO_ERC721, 104 | 105 | // 3: provide ERC20 item to receive offered ERC1155 item. 106 | ERC20_TO_ERC1155, 107 | 108 | // 4: provide ERC721 item to receive offered ERC20 item. 109 | ERC721_TO_ERC20, 110 | 111 | // 5: provide ERC1155 item to receive offered ERC20 item. 112 | ERC1155_TO_ERC20 113 | } 114 | 115 | // prettier-ignore 116 | enum ItemType { 117 | // 0: ETH on mainnet, MATIC on polygon, etc. 118 | NATIVE, 119 | 120 | // 1: ERC20 items (ERC777 and ERC20 analogues could also technically work) 121 | ERC20, 122 | 123 | // 2: ERC721 items 124 | ERC721, 125 | 126 | // 3: ERC1155 items 127 | ERC1155, 128 | 129 | // 4: ERC721 items where a number of tokenIds are supported 130 | ERC721_WITH_CRITERIA, 131 | 132 | // 5: ERC1155 items where a number of ids are supported 133 | ERC1155_WITH_CRITERIA 134 | } 135 | 136 | // prettier-ignore 137 | enum Side { 138 | // 0: Items that can be spent 139 | OFFER, 140 | 141 | // 1: Items that must be received 142 | CONSIDERATION 143 | } 144 | -------------------------------------------------------------------------------- /contracts/lib/ConsiderationStructs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | // prettier-ignore 5 | import { 6 | OrderType, 7 | BasicOrderType, 8 | ItemType, 9 | Side, 10 | BasicOrderRouteType 11 | } from "./ConsiderationEnums.sol"; 12 | 13 | /** 14 | * @dev An order contains eleven components: an offerer, a zone (or account that 15 | * can cancel the order or restrict who can fulfill the order depending on 16 | * the type), the order type (specifying partial fill support as well as 17 | * restricted order status), the start and end time, a hash that will be 18 | * provided to the zone when validating restricted orders, a salt, a key 19 | * corresponding to a given conduit, a counter, and an arbitrary number of 20 | * offer items that can be spent along with consideration items that must 21 | * be received by their respective recipient. 22 | */ 23 | struct OrderComponents { 24 | address offerer; 25 | address zone; 26 | OfferItem[] offer; 27 | ConsiderationItem[] consideration; 28 | OrderType orderType; 29 | uint256 startTime; 30 | uint256 endTime; 31 | bytes32 zoneHash; 32 | uint256 salt; 33 | bytes32 conduitKey; 34 | uint256 counter; 35 | } 36 | 37 | /** 38 | * @dev An offer item has five components: an item type (ETH or other native 39 | * tokens, ERC20, ERC721, and ERC1155, as well as criteria-based ERC721 and 40 | * ERC1155), a token address, a dual-purpose "identifierOrCriteria" 41 | * component that will either represent a tokenId or a merkle root 42 | * depending on the item type, and a start and end amount that support 43 | * increasing or decreasing amounts over the duration of the respective 44 | * order. 45 | */ 46 | struct OfferItem { 47 | ItemType itemType; 48 | address token; 49 | uint256 identifierOrCriteria; 50 | uint256 startAmount; 51 | uint256 endAmount; 52 | } 53 | 54 | /** 55 | * @dev A consideration item has the same five components as an offer item and 56 | * an additional sixth component designating the required recipient of the 57 | * item. 58 | */ 59 | struct ConsiderationItem { 60 | ItemType itemType; 61 | address token; 62 | uint256 identifierOrCriteria; 63 | uint256 startAmount; 64 | uint256 endAmount; 65 | address payable recipient; 66 | } 67 | 68 | /** 69 | * @dev A spent item is translated from a utilized offer item and has four 70 | * components: an item type (ETH or other native tokens, ERC20, ERC721, and 71 | * ERC1155), a token address, a tokenId, and an amount. 72 | */ 73 | struct SpentItem { 74 | ItemType itemType; 75 | address token; 76 | uint256 identifier; 77 | uint256 amount; 78 | } 79 | 80 | /** 81 | * @dev A received item is translated from a utilized consideration item and has 82 | * the same four components as a spent item, as well as an additional fifth 83 | * component designating the required recipient of the item. 84 | */ 85 | struct ReceivedItem { 86 | ItemType itemType; 87 | address token; 88 | uint256 identifier; 89 | uint256 amount; 90 | address payable recipient; 91 | } 92 | 93 | /** 94 | * @dev For basic orders involving ETH / native / ERC20 <=> ERC721 / ERC1155 95 | * matching, a group of six functions may be called that only requires a 96 | * subset of the usual order arguments. Note the use of a "basicOrderType" 97 | * enum; this represents both the usual order type as well as the "route" 98 | * of the basic order (a simple derivation function for the basic order 99 | * type is `basicOrderType = orderType + (4 * basicOrderRoute)`.) 100 | */ 101 | struct BasicOrderParameters { 102 | // calldata offset 103 | address considerationToken; // 0x24 104 | uint256 considerationIdentifier; // 0x44 105 | uint256 considerationAmount; // 0x64 106 | address payable offerer; // 0x84 107 | address zone; // 0xa4 108 | address offerToken; // 0xc4 109 | uint256 offerIdentifier; // 0xe4 110 | uint256 offerAmount; // 0x104 111 | BasicOrderType basicOrderType; // 0x124 112 | uint256 startTime; // 0x144 113 | uint256 endTime; // 0x164 114 | bytes32 zoneHash; // 0x184 115 | uint256 salt; // 0x1a4 116 | bytes32 offererConduitKey; // 0x1c4 117 | bytes32 fulfillerConduitKey; // 0x1e4 118 | uint256 totalOriginalAdditionalRecipients; // 0x204 119 | AdditionalRecipient[] additionalRecipients; // 0x224 120 | bytes signature; // 0x244 121 | // Total length, excluding dynamic array data: 0x264 (580) 122 | } 123 | 124 | /** 125 | * @dev Basic orders can supply any number of additional recipients, with the 126 | * implied assumption that they are supplied from the offered ETH (or other 127 | * native token) or ERC20 token for the order. 128 | */ 129 | struct AdditionalRecipient { 130 | uint256 amount; 131 | address payable recipient; 132 | } 133 | 134 | /** 135 | * @dev The full set of order components, with the exception of the counter, 136 | * must be supplied when fulfilling more sophisticated orders or groups of 137 | * orders. The total number of original consideration items must also be 138 | * supplied, as the caller may specify additional consideration items. 139 | */ 140 | struct OrderParameters { 141 | address offerer; // 0x00 142 | address zone; // 0x20 143 | OfferItem[] offer; // 0x40 144 | ConsiderationItem[] consideration; // 0x60 145 | OrderType orderType; // 0x80 146 | uint256 startTime; // 0xa0 147 | uint256 endTime; // 0xc0 148 | bytes32 zoneHash; // 0xe0 149 | uint256 salt; // 0x100 150 | bytes32 conduitKey; // 0x120 151 | uint256 totalOriginalConsiderationItems; // 0x140 152 | // offer.length // 0x160 153 | } 154 | 155 | /** 156 | * @dev Orders require a signature in addition to the other order parameters. 157 | */ 158 | struct Order { 159 | OrderParameters parameters; 160 | bytes signature; 161 | } 162 | 163 | /** 164 | * @dev Advanced orders include a numerator (i.e. a fraction to attempt to fill) 165 | * and a denominator (the total size of the order) in addition to the 166 | * signature and other order parameters. It also supports an optional field 167 | * for supplying extra data; this data will be included in a staticcall to 168 | * `isValidOrderIncludingExtraData` on the zone for the order if the order 169 | * type is restricted and the offerer or zone are not the caller. 170 | */ 171 | struct AdvancedOrder { 172 | OrderParameters parameters; 173 | uint120 numerator; 174 | uint120 denominator; 175 | bytes signature; 176 | bytes extraData; 177 | } 178 | 179 | /** 180 | * @dev Orders can be validated (either explicitly via `validate`, or as a 181 | * consequence of a full or partial fill), specifically cancelled (they can 182 | * also be cancelled in bulk via incrementing a per-zone counter), and 183 | * partially or fully filled (with the fraction filled represented by a 184 | * numerator and denominator). 185 | */ 186 | struct OrderStatus { 187 | bool isValidated; 188 | bool isCancelled; 189 | uint120 numerator; 190 | uint120 denominator; 191 | } 192 | 193 | /** 194 | * @dev A criteria resolver specifies an order, side (offer vs. consideration), 195 | * and item index. It then provides a chosen identifier (i.e. tokenId) 196 | * alongside a merkle proof demonstrating the identifier meets the required 197 | * criteria. 198 | */ 199 | struct CriteriaResolver { 200 | uint256 orderIndex; 201 | Side side; 202 | uint256 index; 203 | uint256 identifier; 204 | bytes32[] criteriaProof; 205 | } 206 | 207 | /** 208 | * @dev A fulfillment is applied to a group of orders. It decrements a series of 209 | * offer and consideration items, then generates a single execution 210 | * element. A given fulfillment can be applied to as many offer and 211 | * consideration items as desired, but must contain at least one offer and 212 | * at least one consideration that match. The fulfillment must also remain 213 | * consistent on all key parameters across all offer items (same offerer, 214 | * token, type, tokenId, and conduit preference) as well as across all 215 | * consideration items (token, type, tokenId, and recipient). 216 | */ 217 | struct Fulfillment { 218 | FulfillmentComponent[] offerComponents; 219 | FulfillmentComponent[] considerationComponents; 220 | } 221 | 222 | /** 223 | * @dev Each fulfillment component contains one index referencing a specific 224 | * order and another referencing a specific offer or consideration item. 225 | */ 226 | struct FulfillmentComponent { 227 | uint256 orderIndex; 228 | uint256 itemIndex; 229 | } 230 | 231 | /** 232 | * @dev An execution is triggered once all consideration items have been zeroed 233 | * out. It sends the item in question from the offerer to the item's 234 | * recipient, optionally sourcing approvals from either this contract 235 | * directly or from the offerer's chosen conduit if one is specified. An 236 | * execution is not provided as an argument, but rather is derived via 237 | * orders, criteria resolvers, and fulfillments (where the total number of 238 | * executions will be less than or equal to the total number of indicated 239 | * fulfillments) and returned as part of `matchOrders`. 240 | */ 241 | struct Execution { 242 | ReceivedItem item; 243 | address offerer; 244 | bytes32 conduitKey; 245 | } 246 | -------------------------------------------------------------------------------- /contracts/lib/ConsiderationTypeHashes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import "./ConsiderationStructs.sol"; 5 | 6 | uint256 constant EIP712_Order_size = 0x180; 7 | uint256 constant EIP712_OfferItem_size = 0xc0; 8 | uint256 constant EIP712_ConsiderationItem_size = 0xe0; 9 | uint256 constant EIP712_DomainSeparator_offset = 0x02; 10 | uint256 constant EIP712_OrderHash_offset = 0x22; 11 | uint256 constant EIP712_DigestPayload_size = 0x42; 12 | uint256 constant EIP_712_PREFIX = ( 13 | 0x1901000000000000000000000000000000000000000000000000000000000000 14 | ); 15 | 16 | contract ConsiderationTypeHashes { 17 | bytes32 internal immutable _NAME_HASH; 18 | bytes32 internal immutable _VERSION_HASH; 19 | bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH; 20 | bytes32 internal immutable _OFFER_ITEM_TYPEHASH; 21 | bytes32 internal immutable _CONSIDERATION_ITEM_TYPEHASH; 22 | bytes32 internal immutable _ORDER_TYPEHASH; 23 | bytes32 internal immutable _DOMAIN_SEPARATOR; 24 | address internal constant seaportAddress = 25 | address(0x00000000006c3852cbEf3e08E8dF289169EdE581); 26 | 27 | constructor() { 28 | // Derive hash of the name of the contract. 29 | _NAME_HASH = keccak256(bytes("Seaport")); 30 | 31 | // Derive hash of the version string of the contract. 32 | _VERSION_HASH = keccak256(bytes("1.1")); 33 | 34 | bytes memory offerItemTypeString = abi.encodePacked( 35 | "OfferItem(", 36 | "uint8 itemType,", 37 | "address token,", 38 | "uint256 identifierOrCriteria,", 39 | "uint256 startAmount,", 40 | "uint256 endAmount", 41 | ")" 42 | ); 43 | 44 | // Construct the ConsiderationItem type string. 45 | // prettier-ignore 46 | bytes memory considerationItemTypeString = abi.encodePacked( 47 | "ConsiderationItem(", 48 | "uint8 itemType,", 49 | "address token,", 50 | "uint256 identifierOrCriteria,", 51 | "uint256 startAmount,", 52 | "uint256 endAmount,", 53 | "address recipient", 54 | ")" 55 | ); 56 | 57 | // Construct the OrderComponents type string, not including the above. 58 | // prettier-ignore 59 | bytes memory orderComponentsPartialTypeString = abi.encodePacked( 60 | "OrderComponents(", 61 | "address offerer,", 62 | "address zone,", 63 | "OfferItem[] offer,", 64 | "ConsiderationItem[] consideration,", 65 | "uint8 orderType,", 66 | "uint256 startTime,", 67 | "uint256 endTime,", 68 | "bytes32 zoneHash,", 69 | "uint256 salt,", 70 | "bytes32 conduitKey,", 71 | "uint256 counter", 72 | ")" 73 | ); 74 | // Derive the OfferItem type hash using the corresponding type string. 75 | bytes32 offerItemTypehash = keccak256(offerItemTypeString); 76 | 77 | // Derive ConsiderationItem type hash using corresponding type string. 78 | bytes32 considerationItemTypehash = keccak256( 79 | considerationItemTypeString 80 | ); 81 | 82 | // Construct the primary EIP-712 domain type string. 83 | // prettier-ignore 84 | _EIP_712_DOMAIN_TYPEHASH = keccak256( 85 | abi.encodePacked( 86 | "EIP712Domain(", 87 | "string name,", 88 | "string version,", 89 | "uint256 chainId,", 90 | "address verifyingContract", 91 | ")" 92 | ) 93 | ); 94 | 95 | _OFFER_ITEM_TYPEHASH = offerItemTypehash; 96 | _CONSIDERATION_ITEM_TYPEHASH = considerationItemTypehash; 97 | 98 | // Derive OrderItem type hash via combination of relevant type strings. 99 | _ORDER_TYPEHASH = keccak256( 100 | abi.encodePacked( 101 | orderComponentsPartialTypeString, 102 | considerationItemTypeString, 103 | offerItemTypeString 104 | ) 105 | ); 106 | 107 | _DOMAIN_SEPARATOR = _deriveDomainSeparator(); 108 | } 109 | 110 | /** 111 | * @dev Internal view function to derive the EIP-712 domain separator. 112 | * 113 | * @return The derived domain separator. 114 | */ 115 | function _deriveDomainSeparator() internal view returns (bytes32) { 116 | // prettier-ignore 117 | return keccak256( 118 | abi.encode( 119 | _EIP_712_DOMAIN_TYPEHASH, 120 | _NAME_HASH, 121 | _VERSION_HASH, 122 | block.chainid, 123 | seaportAddress 124 | ) 125 | ); 126 | } 127 | 128 | /** 129 | * @dev Internal pure function to efficiently derive an digest to sign for 130 | * an order in accordance with EIP-712. 131 | * 132 | * @param orderHash The order hash. 133 | * 134 | * @return value The hash. 135 | */ 136 | function _deriveEIP712Digest(bytes32 orderHash) 137 | internal 138 | view 139 | returns (bytes32 value) 140 | { 141 | bytes32 domainSeparator = _DOMAIN_SEPARATOR; 142 | // Leverage scratch space to perform an efficient hash. 143 | assembly { 144 | // Place the EIP-712 prefix at the start of scratch space. 145 | mstore(0, EIP_712_PREFIX) 146 | 147 | // Place the domain separator in the next region of scratch space. 148 | mstore(EIP712_DomainSeparator_offset, domainSeparator) 149 | 150 | // Place the order hash in scratch space, spilling into the first 151 | // two bytes of the free memory pointer — this should never be set 152 | // as memory cannot be expanded to that size, and will be zeroed out 153 | // after the hash is performed. 154 | mstore(EIP712_OrderHash_offset, orderHash) 155 | 156 | // Hash the relevant region (65 bytes). 157 | value := keccak256(0, EIP712_DigestPayload_size) 158 | 159 | // Clear out the dirtied bits in the memory pointer. 160 | mstore(EIP712_OrderHash_offset, 0) 161 | } 162 | } 163 | 164 | /** 165 | * @dev Internal view function to derive the EIP-712 hash for an offer item. 166 | * 167 | * @param offerItem The offered item to hash. 168 | * 169 | * @return The hash. 170 | */ 171 | function _hashOfferItem(OfferItem memory offerItem) 172 | internal 173 | view 174 | returns (bytes32) 175 | { 176 | return 177 | keccak256( 178 | abi.encode( 179 | _OFFER_ITEM_TYPEHASH, 180 | offerItem.itemType, 181 | offerItem.token, 182 | offerItem.identifierOrCriteria, 183 | offerItem.startAmount, 184 | offerItem.endAmount 185 | ) 186 | ); 187 | } 188 | 189 | /** 190 | * @dev Internal view function to derive the EIP-712 hash for a consideration item. 191 | * 192 | * @param considerationItem The consideration item to hash. 193 | * 194 | * @return The hash. 195 | */ 196 | function _hashConsiderationItem(ConsiderationItem memory considerationItem) 197 | internal 198 | view 199 | returns (bytes32) 200 | { 201 | return 202 | keccak256( 203 | abi.encode( 204 | _CONSIDERATION_ITEM_TYPEHASH, 205 | considerationItem.itemType, 206 | considerationItem.token, 207 | considerationItem.identifierOrCriteria, 208 | considerationItem.startAmount, 209 | considerationItem.endAmount, 210 | considerationItem.recipient 211 | ) 212 | ); 213 | } 214 | 215 | /** 216 | * @dev Internal view function to derive the order hash for a given order. 217 | * Note that only the original consideration items are included in the 218 | * order hash, as additional consideration items may be supplied by the 219 | * caller. 220 | * 221 | * @param orderParameters The parameters of the order to hash. 222 | * @param counter The counter of the order to hash. 223 | * 224 | * @return orderHash The hash. 225 | */ 226 | function _deriveOrderHash( 227 | OrderParameters memory orderParameters, 228 | uint256 counter 229 | ) internal view returns (bytes32 orderHash) { 230 | // Designate new memory regions for offer and consideration item hashes. 231 | bytes32[] memory offerHashes = new bytes32[]( 232 | orderParameters.offer.length 233 | ); 234 | bytes32[] memory considerationHashes = new bytes32[]( 235 | orderParameters.totalOriginalConsiderationItems 236 | ); 237 | 238 | // Iterate over each offer on the order. 239 | for (uint256 i = 0; i < orderParameters.offer.length; ++i) { 240 | // Hash the offer and place the result into memory. 241 | offerHashes[i] = _hashOfferItem(orderParameters.offer[i]); 242 | } 243 | 244 | // Iterate over each consideration on the order. 245 | for ( 246 | uint256 i = 0; 247 | i < orderParameters.totalOriginalConsiderationItems; 248 | ++i 249 | ) { 250 | // Hash the consideration and place the result into memory. 251 | considerationHashes[i] = _hashConsiderationItem( 252 | orderParameters.consideration[i] 253 | ); 254 | } 255 | 256 | // Derive and return the order hash as specified by EIP-712. 257 | 258 | return 259 | keccak256( 260 | abi.encode( 261 | _ORDER_TYPEHASH, 262 | orderParameters.offerer, 263 | orderParameters.zone, 264 | keccak256(abi.encodePacked(offerHashes)), 265 | keccak256(abi.encodePacked(considerationHashes)), 266 | orderParameters.orderType, 267 | orderParameters.startTime, 268 | orderParameters.endTime, 269 | orderParameters.zoneHash, 270 | orderParameters.salt, 271 | orderParameters.conduitKey, 272 | counter 273 | ) 274 | ); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /contracts/lib/ErrorsAndWarnings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | struct ErrorsAndWarnings { 5 | uint16[] errors; 6 | uint16[] warnings; 7 | } 8 | 9 | library ErrorsAndWarningsLib { 10 | function concat(ErrorsAndWarnings memory ew1, ErrorsAndWarnings memory ew2) 11 | internal 12 | pure 13 | { 14 | ew1.errors = concatMemory(ew1.errors, ew2.errors); 15 | ew1.warnings = concatMemory(ew1.warnings, ew2.warnings); 16 | } 17 | 18 | function addError(ErrorsAndWarnings memory ew, uint16 err) internal pure { 19 | ew.errors = pushMemory(ew.errors, err); 20 | } 21 | 22 | function addWarning(ErrorsAndWarnings memory ew, uint16 warn) 23 | internal 24 | pure 25 | { 26 | ew.warnings = pushMemory(ew.warnings, warn); 27 | } 28 | 29 | function hasErrors(ErrorsAndWarnings memory ew) 30 | internal 31 | pure 32 | returns (bool) 33 | { 34 | return ew.errors.length != 0; 35 | } 36 | 37 | function hasWarnings(ErrorsAndWarnings memory ew) 38 | internal 39 | pure 40 | returns (bool) 41 | { 42 | return ew.warnings.length != 0; 43 | } 44 | 45 | // Helper Functions 46 | function concatMemory(uint16[] memory array1, uint16[] memory array2) 47 | private 48 | pure 49 | returns (uint16[] memory) 50 | { 51 | if (array1.length == 0) { 52 | return array2; 53 | } else if (array2.length == 0) { 54 | return array1; 55 | } 56 | 57 | uint16[] memory returnValue = new uint16[]( 58 | array1.length + array2.length 59 | ); 60 | 61 | for (uint256 i = 0; i < array1.length; i++) { 62 | returnValue[i] = array1[i]; 63 | } 64 | for (uint256 i = 0; i < array2.length; i++) { 65 | returnValue[i + array1.length] = array2[i]; 66 | } 67 | 68 | return returnValue; 69 | } 70 | 71 | function pushMemory(uint16[] memory uint16Array, uint16 newValue) 72 | internal 73 | pure 74 | returns (uint16[] memory) 75 | { 76 | uint16[] memory returnValue = new uint16[](uint16Array.length + 1); 77 | 78 | for (uint256 i = 0; i < uint16Array.length; i++) { 79 | returnValue[i] = uint16Array[i]; 80 | } 81 | returnValue[uint16Array.length] = newValue; 82 | 83 | return returnValue; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/lib/Murky.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { 5 | ErrorsAndWarnings, 6 | ErrorsAndWarningsLib 7 | } from "./ErrorsAndWarnings.sol"; 8 | 9 | import { IssueParser, MerkleIssue } from "./SeaportValidatorTypes.sol"; 10 | 11 | contract Murky { 12 | using ErrorsAndWarningsLib for ErrorsAndWarnings; 13 | using IssueParser for MerkleIssue; 14 | 15 | bool internal constant HASH_ODD_WITH_ZERO = false; 16 | 17 | function _verifyProof( 18 | bytes32 root, 19 | bytes32[] memory proof, 20 | bytes32 valueToProve 21 | ) internal pure returns (bool) { 22 | // proof length must be less than max array size 23 | bytes32 rollingHash = valueToProve; 24 | uint256 length = proof.length; 25 | unchecked { 26 | for (uint256 i = 0; i < length; ++i) { 27 | rollingHash = _hashLeafPairs(rollingHash, proof[i]); 28 | } 29 | } 30 | return root == rollingHash; 31 | } 32 | 33 | /******************** 34 | * HASHING FUNCTION * 35 | ********************/ 36 | 37 | /// ascending sort and concat prior to hashing 38 | function _hashLeafPairs(bytes32 left, bytes32 right) 39 | internal 40 | pure 41 | returns (bytes32 _hash) 42 | { 43 | assembly { 44 | switch lt(left, right) 45 | case 0 { 46 | mstore(0x0, right) 47 | mstore(0x20, left) 48 | } 49 | default { 50 | mstore(0x0, left) 51 | mstore(0x20, right) 52 | } 53 | _hash := keccak256(0x0, 0x40) 54 | } 55 | } 56 | 57 | /******************** 58 | * PROOF GENERATION * 59 | ********************/ 60 | 61 | function _getRoot(uint256[] memory data) 62 | internal 63 | pure 64 | returns (bytes32 result, ErrorsAndWarnings memory errorsAndWarnings) 65 | { 66 | errorsAndWarnings = ErrorsAndWarnings(new uint16[](0), new uint16[](0)); 67 | 68 | if (data.length < 2) { 69 | errorsAndWarnings.addError(MerkleIssue.SingleLeaf.parseInt()); 70 | return (0, errorsAndWarnings); 71 | } 72 | 73 | bool hashOddWithZero = HASH_ODD_WITH_ZERO; 74 | 75 | if (!_processInput(data)) { 76 | errorsAndWarnings.addError(MerkleIssue.Unsorted.parseInt()); 77 | return (0, errorsAndWarnings); 78 | } 79 | 80 | assembly { 81 | function hashLeafPairs(left, right) -> _hash { 82 | switch lt(left, right) 83 | case 0 { 84 | mstore(0x0, right) 85 | mstore(0x20, left) 86 | } 87 | default { 88 | mstore(0x0, left) 89 | mstore(0x20, right) 90 | } 91 | _hash := keccak256(0x0, 0x40) 92 | } 93 | function hashLevel(_data, length, _hashOddWithZero) -> newLength { 94 | // we will be modifying data in-place, so set result pointer to data pointer 95 | let _result := _data 96 | // get length of original data array 97 | // let length := mload(_data) 98 | // bool to track if we need to hash the last element of an odd-length array with zero 99 | let oddLength 100 | 101 | // if length is odd, we need to hash the last element with zero 102 | switch and(length, 1) 103 | case 1 { 104 | // if length is odd, add 1 so division by 2 will round up 105 | newLength := add(1, div(length, 2)) 106 | oddLength := 1 107 | } 108 | default { 109 | newLength := div(length, 2) 110 | } 111 | // todo: necessary? 112 | // mstore(_data, newLength) 113 | let resultIndexPointer := add(0x20, _data) 114 | let dataIndexPointer := resultIndexPointer 115 | 116 | // stop iterating over for loop at length-1 117 | let stopIteration := add(_data, mul(length, 0x20)) 118 | // write result array in-place over data array 119 | for { 120 | 121 | } lt(dataIndexPointer, stopIteration) { 122 | 123 | } { 124 | // get next two elements from data, hash them together 125 | let data1 := mload(dataIndexPointer) 126 | let data2 := mload(add(dataIndexPointer, 0x20)) 127 | let hashedPair := hashLeafPairs(data1, data2) 128 | // overwrite an element of data array with 129 | mstore(resultIndexPointer, hashedPair) 130 | // increment result pointer by 1 slot 131 | resultIndexPointer := add(0x20, resultIndexPointer) 132 | // increment data pointer by 2 slot 133 | dataIndexPointer := add(0x40, dataIndexPointer) 134 | } 135 | // we did not yet hash last index if odd-length 136 | if oddLength { 137 | let data1 := mload(dataIndexPointer) 138 | let nextValue 139 | switch _hashOddWithZero 140 | case 0 { 141 | nextValue := data1 142 | } 143 | default { 144 | nextValue := hashLeafPairs(data1, 0) 145 | } 146 | mstore(resultIndexPointer, nextValue) 147 | } 148 | } 149 | 150 | let dataLength := mload(data) 151 | for { 152 | 153 | } gt(dataLength, 1) { 154 | 155 | } { 156 | dataLength := hashLevel(data, dataLength, hashOddWithZero) 157 | } 158 | result := mload(add(0x20, data)) 159 | } 160 | } 161 | 162 | function _getProof(uint256[] memory data, uint256 node) 163 | internal 164 | pure 165 | returns ( 166 | bytes32[] memory result, 167 | ErrorsAndWarnings memory errorsAndWarnings 168 | ) 169 | { 170 | errorsAndWarnings = ErrorsAndWarnings(new uint16[](0), new uint16[](0)); 171 | 172 | if (data.length < 2) { 173 | errorsAndWarnings.addError(MerkleIssue.SingleLeaf.parseInt()); 174 | return (new bytes32[](0), errorsAndWarnings); 175 | } 176 | 177 | bool hashOddWithZero = HASH_ODD_WITH_ZERO; 178 | 179 | if (!_processInput(data)) { 180 | errorsAndWarnings.addError(MerkleIssue.Unsorted.parseInt()); 181 | return (new bytes32[](0), errorsAndWarnings); 182 | } 183 | 184 | // The size of the proof is equal to the ceiling of log2(numLeaves) 185 | // Two overflow risks: node, pos 186 | // node: max array size is 2**256-1. Largest index in the array will be 1 less than that. Also, 187 | // for dynamic arrays, size is limited to 2**64-1 188 | // pos: pos is bounded by log2(data.length), which should be less than type(uint256).max 189 | assembly { 190 | function hashLeafPairs(left, right) -> _hash { 191 | switch lt(left, right) 192 | case 0 { 193 | mstore(0x0, right) 194 | mstore(0x20, left) 195 | } 196 | default { 197 | mstore(0x0, left) 198 | mstore(0x20, right) 199 | } 200 | _hash := keccak256(0x0, 0x40) 201 | } 202 | function hashLevel(_data, length, _hashOddWithZero) -> newLength { 203 | // we will be modifying data in-place, so set result pointer to data pointer 204 | let _result := _data 205 | // get length of original data array 206 | // let length := mload(_data) 207 | // bool to track if we need to hash the last element of an odd-length array with zero 208 | let oddLength 209 | 210 | // if length is odd, we'll need to hash the last element with zero 211 | switch and(length, 1) 212 | case 1 { 213 | // if length is odd, add 1 so division by 2 will round up 214 | newLength := add(1, div(length, 2)) 215 | oddLength := 1 216 | } 217 | default { 218 | newLength := div(length, 2) 219 | } 220 | // todo: necessary? 221 | // mstore(_data, newLength) 222 | let resultIndexPointer := add(0x20, _data) 223 | let dataIndexPointer := resultIndexPointer 224 | 225 | // stop iterating over for loop at length-1 226 | let stopIteration := add(_data, mul(length, 0x20)) 227 | // write result array in-place over data array 228 | for { 229 | 230 | } lt(dataIndexPointer, stopIteration) { 231 | 232 | } { 233 | // get next two elements from data, hash them together 234 | let data1 := mload(dataIndexPointer) 235 | let data2 := mload(add(dataIndexPointer, 0x20)) 236 | let hashedPair := hashLeafPairs(data1, data2) 237 | // overwrite an element of data array with 238 | mstore(resultIndexPointer, hashedPair) 239 | // increment result pointer by 1 slot 240 | resultIndexPointer := add(0x20, resultIndexPointer) 241 | // increment data pointer by 2 slot 242 | dataIndexPointer := add(0x40, dataIndexPointer) 243 | } 244 | // we did not yet hash last index if odd-length 245 | if oddLength { 246 | let data1 := mload(dataIndexPointer) 247 | let nextValue 248 | switch _hashOddWithZero 249 | case 0 { 250 | nextValue := data1 251 | } 252 | default { 253 | nextValue := hashLeafPairs(data1, 0) 254 | } 255 | mstore(resultIndexPointer, nextValue) 256 | } 257 | } 258 | 259 | // set result pointer to free memory 260 | result := mload(0x40) 261 | // get pointer to first index of result 262 | let resultIndexPtr := add(0x20, result) 263 | // declare so we can use later 264 | let newLength 265 | // put length of data onto stack 266 | let dataLength := mload(data) 267 | for { 268 | // repeat until only one element is left 269 | } gt(dataLength, 1) { 270 | 271 | } { 272 | // bool if node is odd 273 | let oddNodeIndex := and(node, 1) 274 | // bool if node is last 275 | let lastNodeIndex := eq(dataLength, add(1, node)) 276 | // store both bools in one value so we can switch on it 277 | let switchVal := or(shl(1, lastNodeIndex), oddNodeIndex) 278 | switch switchVal 279 | // 00 - neither odd nor last 280 | case 0 { 281 | // store data[node+1] at result[i] 282 | // get pointer to result[node+1] by adding 2 to node and multiplying by 0x20 283 | // to account for the fact that result points to array length, not first index 284 | mstore( 285 | resultIndexPtr, 286 | mload(add(data, mul(0x20, add(2, node)))) 287 | ) 288 | } 289 | // 10 - node is last 290 | case 2 { 291 | // store 0 at result[i] 292 | mstore(resultIndexPtr, 0) 293 | } 294 | // 01 or 11 - node is odd (and possibly also last) 295 | default { 296 | // store data[node-1] at result[i] 297 | mstore(resultIndexPtr, mload(add(data, mul(0x20, node)))) 298 | } 299 | // increment result index 300 | resultIndexPtr := add(0x20, resultIndexPtr) 301 | 302 | // get new node index 303 | node := div(node, 2) 304 | // keep track of how long result array is 305 | newLength := add(1, newLength) 306 | // compute the next hash level, overwriting data, and get the new length 307 | dataLength := hashLevel(data, dataLength, hashOddWithZero) 308 | } 309 | // store length of result array at pointer 310 | mstore(result, newLength) 311 | // set free mem pointer to word after end of result array 312 | mstore(0x40, resultIndexPtr) 313 | } 314 | } 315 | 316 | /** 317 | * Hashes each element of the input array in place using keccak256 318 | */ 319 | function _processInput(uint256[] memory data) 320 | private 321 | pure 322 | returns (bool sorted) 323 | { 324 | sorted = true; 325 | 326 | // Hash inputs with keccak256 327 | for (uint256 i = 0; i < data.length; ++i) { 328 | assembly { 329 | mstore( 330 | add(data, mul(0x20, add(1, i))), 331 | keccak256(add(data, mul(0x20, add(1, i))), 0x20) 332 | ) 333 | // for every element after the first, hashed value must be greater than the last one 334 | if and( 335 | gt(i, 0), 336 | iszero( 337 | gt( 338 | mload(add(data, mul(0x20, add(1, i)))), 339 | mload(add(data, mul(0x20, add(1, sub(i, 1))))) 340 | ) 341 | ) 342 | ) { 343 | sorted := 0 // Elements not ordered by hash 344 | } 345 | } 346 | } 347 | } 348 | 349 | // Sort uint256 in order of the keccak256 hashes 350 | struct HashAndIntTuple { 351 | uint256 num; 352 | bytes32 hash; 353 | } 354 | 355 | function _sortUint256ByHash(uint256[] memory values) 356 | internal 357 | pure 358 | returns (uint256[] memory sortedValues) 359 | { 360 | HashAndIntTuple[] memory toSort = new HashAndIntTuple[](values.length); 361 | for (uint256 i = 0; i < values.length; i++) { 362 | toSort[i] = HashAndIntTuple( 363 | values[i], 364 | keccak256(abi.encode(values[i])) 365 | ); 366 | } 367 | 368 | _quickSort(toSort, 0, int256(toSort.length - 1)); 369 | 370 | sortedValues = new uint256[](values.length); 371 | for (uint256 i = 0; i < values.length; i++) { 372 | sortedValues[i] = toSort[i].num; 373 | } 374 | } 375 | 376 | function _quickSort( 377 | HashAndIntTuple[] memory arr, 378 | int256 left, 379 | int256 right 380 | ) internal pure { 381 | int256 i = left; 382 | int256 j = right; 383 | if (i == j) return; 384 | bytes32 pivot = arr[uint256(left + (right - left) / 2)].hash; 385 | while (i <= j) { 386 | while (arr[uint256(i)].hash < pivot) i++; 387 | while (pivot < arr[uint256(j)].hash) j--; 388 | if (i <= j) { 389 | (arr[uint256(i)], arr[uint256(j)]) = ( 390 | arr[uint256(j)], 391 | arr[uint256(i)] 392 | ); 393 | i++; 394 | j--; 395 | } 396 | } 397 | if (left < j) _quickSort(arr, left, j); 398 | if (i < right) _quickSort(arr, i, right); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /contracts/lib/SafeStaticCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | library SafeStaticCall { 5 | function safeStaticCallBool( 6 | address target, 7 | bytes memory callData, 8 | bool expectedReturn 9 | ) internal view returns (bool) { 10 | (bool success, bytes memory res) = target.staticcall(callData); 11 | if (!success) return false; 12 | if (res.length != 32) return false; 13 | 14 | if ( 15 | bytes32(res) & 16 | 0x0000000000000000000000000000000000000000000000000000000000000001 != 17 | bytes32(res) 18 | ) { 19 | return false; 20 | } 21 | 22 | return expectedReturn ? res[31] == 0x01 : res[31] == 0; 23 | } 24 | 25 | function safeStaticCallAddress( 26 | address target, 27 | bytes memory callData, 28 | address expectedReturn 29 | ) internal view returns (bool) { 30 | (bool success, bytes memory res) = target.staticcall(callData); 31 | if (!success) return false; 32 | if (res.length != 32) return false; 33 | 34 | if ( 35 | bytes32(res) & 36 | 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF != 37 | bytes32(res) 38 | ) { 39 | // Ensure only 20 bytes used 40 | return false; 41 | } 42 | 43 | return abi.decode(res, (address)) == expectedReturn; 44 | } 45 | 46 | function safeStaticCallUint256( 47 | address target, 48 | bytes memory callData, 49 | uint256 minExpectedReturn 50 | ) internal view returns (bool) { 51 | (bool success, bytes memory res) = target.staticcall(callData); 52 | if (!success) return false; 53 | if (res.length != 32) return false; 54 | 55 | return abi.decode(res, (uint256)) >= minExpectedReturn; 56 | } 57 | 58 | function safeStaticCallBytes4( 59 | address target, 60 | bytes memory callData, 61 | bytes4 expectedReturn 62 | ) internal view returns (bool) { 63 | (bool success, bytes memory res) = target.staticcall(callData); 64 | if (!success) return false; 65 | if (res.length != 32) return false; 66 | if ( 67 | bytes32(res) & 68 | 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 != 69 | bytes32(res) 70 | ) { 71 | // Ensure only 4 bytes used 72 | return false; 73 | } 74 | 75 | return abi.decode(res, (bytes4)) == expectedReturn; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/lib/SeaportValidatorTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | struct ValidationConfiguration { 5 | /// @notice Recipient for primary fee payments. 6 | address primaryFeeRecipient; 7 | /// @notice Bips for primary fee payments. 8 | uint256 primaryFeeBips; 9 | /// @notice Should creator fees be checked? 10 | bool checkCreatorFee; 11 | /// @notice Should strict validation be skipped? 12 | bool skipStrictValidation; 13 | /// @notice Short order duration in seconds 14 | uint256 shortOrderDuration; 15 | /// @notice Distant order expiration delta in seconds. Warning if order expires in longer than this. 16 | uint256 distantOrderExpiration; 17 | } 18 | 19 | enum TimeIssue { 20 | EndTimeBeforeStartTime, 21 | Expired, 22 | DistantExpiration, 23 | NotActive, 24 | ShortOrder 25 | } 26 | 27 | enum StatusIssue { 28 | Cancelled, 29 | FullyFilled 30 | } 31 | 32 | enum OfferIssue { 33 | ZeroItems, 34 | AmountZero, 35 | MoreThanOneItem, 36 | NativeItem, 37 | DuplicateItem, 38 | AmountVelocityHigh, 39 | AmountStepLarge 40 | } 41 | 42 | enum ConsiderationIssue { 43 | AmountZero, 44 | NullRecipient, 45 | ExtraItems, 46 | PrivateSaleToSelf, 47 | ZeroItems, 48 | DuplicateItem, 49 | PrivateSale, 50 | AmountVelocityHigh, 51 | AmountStepLarge 52 | } 53 | 54 | enum PrimaryFeeIssue { 55 | Missing, 56 | ItemType, 57 | Token, 58 | StartAmount, 59 | EndAmount, 60 | Recipient 61 | } 62 | 63 | enum ERC721Issue { 64 | AmountNotOne, 65 | InvalidToken, 66 | IdentifierDNE, 67 | NotOwner, 68 | NotApproved, 69 | CriteriaNotPartialFill 70 | } 71 | 72 | enum ERC1155Issue { 73 | InvalidToken, 74 | NotApproved, 75 | InsufficientBalance 76 | } 77 | 78 | enum ERC20Issue { 79 | IdentifierNonZero, 80 | InvalidToken, 81 | InsufficientAllowance, 82 | InsufficientBalance 83 | } 84 | 85 | enum NativeIssue { 86 | TokenAddress, 87 | IdentifierNonZero, 88 | InsufficientBalance 89 | } 90 | 91 | enum ZoneIssue { 92 | RejectedOrder, 93 | NotSet 94 | } 95 | 96 | enum ConduitIssue { 97 | KeyInvalid 98 | } 99 | 100 | enum CreatorFeeIssue { 101 | Missing, 102 | ItemType, 103 | Token, 104 | StartAmount, 105 | EndAmount, 106 | Recipient 107 | } 108 | 109 | enum SignatureIssue { 110 | Invalid, 111 | LowCounter, 112 | HighCounter, 113 | OriginalConsiderationItems 114 | } 115 | 116 | enum GenericIssue { 117 | InvalidOrderFormat 118 | } 119 | 120 | enum MerkleIssue { 121 | SingleLeaf, 122 | Unsorted 123 | } 124 | 125 | /** 126 | * @title IssueParser - parse issues into integers 127 | * @notice Implements a `parseInt` function for each issue type. 128 | * offsets the enum value to place within the issue range. 129 | */ 130 | library IssueParser { 131 | function parseInt(GenericIssue err) internal pure returns (uint16) { 132 | return uint16(err) + 100; 133 | } 134 | 135 | function parseInt(ERC20Issue err) internal pure returns (uint16) { 136 | return uint16(err) + 200; 137 | } 138 | 139 | function parseInt(ERC721Issue err) internal pure returns (uint16) { 140 | return uint16(err) + 300; 141 | } 142 | 143 | function parseInt(ERC1155Issue err) internal pure returns (uint16) { 144 | return uint16(err) + 400; 145 | } 146 | 147 | function parseInt(ConsiderationIssue err) internal pure returns (uint16) { 148 | return uint16(err) + 500; 149 | } 150 | 151 | function parseInt(OfferIssue err) internal pure returns (uint16) { 152 | return uint16(err) + 600; 153 | } 154 | 155 | function parseInt(PrimaryFeeIssue err) internal pure returns (uint16) { 156 | return uint16(err) + 700; 157 | } 158 | 159 | function parseInt(StatusIssue err) internal pure returns (uint16) { 160 | return uint16(err) + 800; 161 | } 162 | 163 | function parseInt(TimeIssue err) internal pure returns (uint16) { 164 | return uint16(err) + 900; 165 | } 166 | 167 | function parseInt(ConduitIssue err) internal pure returns (uint16) { 168 | return uint16(err) + 1000; 169 | } 170 | 171 | function parseInt(SignatureIssue err) internal pure returns (uint16) { 172 | return uint16(err) + 1100; 173 | } 174 | 175 | function parseInt(CreatorFeeIssue err) internal pure returns (uint16) { 176 | return uint16(err) + 1200; 177 | } 178 | 179 | function parseInt(NativeIssue err) internal pure returns (uint16) { 180 | return uint16(err) + 1300; 181 | } 182 | 183 | function parseInt(ZoneIssue err) internal pure returns (uint16) { 184 | return uint16(err) + 1400; 185 | } 186 | 187 | function parseInt(MerkleIssue err) internal pure returns (uint16) { 188 | return uint16(err) + 1500; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /contracts/lib/SignatureVerification.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import "./ConsiderationConstants.sol"; 5 | import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol"; 6 | import { SafeStaticCall } from "./SafeStaticCall.sol"; 7 | 8 | /** 9 | * @title SignatureVerification 10 | * @author 0age 11 | * @notice SignatureVerification contains logic for verifying signatures. 12 | */ 13 | abstract contract SignatureVerification { 14 | using SafeStaticCall for address; 15 | 16 | /** 17 | * @dev Internal view function to verify the signature of an order. An 18 | * ERC-1271 fallback will be attempted if either the signature length 19 | * is not 64 or 65 bytes or if the recovered signer does not match the 20 | * supplied signer. Note that in cases where a 64 or 65 byte signature 21 | * is supplied, only standard ECDSA signatures that recover to a 22 | * non-zero address are supported. 23 | * 24 | * @param signer The signer for the order. 25 | * @param digest The digest to verify the signature against. 26 | * @param signature A signature from the signer indicating that the order 27 | * has been approved. 28 | */ 29 | function _isValidSignature( 30 | address signer, 31 | bytes32 digest, 32 | bytes memory signature 33 | ) internal view returns (bool) { 34 | // Declare r, s, and v signature parameters. 35 | bytes32 r; 36 | bytes32 s; 37 | uint8 v; 38 | 39 | if (signer.code.length > 0) { 40 | // If signer is a contract, try verification via EIP-1271. 41 | return _isValidEIP1271Signature(signer, digest, signature); 42 | } else if (signature.length == 64) { 43 | // If signature contains 64 bytes, parse as EIP-2098 signature. (r+s&v) 44 | // Declare temporary vs that will be decomposed into s and v. 45 | bytes32 vs; 46 | 47 | (r, vs) = abi.decode(signature, (bytes32, bytes32)); 48 | 49 | s = vs & EIP2098_allButHighestBitMask; 50 | 51 | v = uint8(uint256(vs >> 255)) + 27; 52 | } else if (signature.length == 65) { 53 | (r, s) = abi.decode(signature, (bytes32, bytes32)); 54 | v = uint8(signature[64]); 55 | 56 | // Ensure v value is properly formatted. 57 | if (v != 27 && v != 28) { 58 | return false; 59 | } 60 | } else { 61 | return false; 62 | } 63 | 64 | // Attempt to recover signer using the digest and signature parameters. 65 | address recoveredSigner = ecrecover(digest, v, r, s); 66 | 67 | // Disallow invalid signers. 68 | if (recoveredSigner == address(0) || recoveredSigner != signer) { 69 | return false; 70 | // Should a signer be recovered, but it doesn't match the signer... 71 | } 72 | 73 | return true; 74 | } 75 | 76 | /** 77 | * @dev Internal view function to verify the signature of an order using 78 | * ERC-1271 (i.e. contract signatures via `isValidSignature`). 79 | * 80 | * @param signer The signer for the order. 81 | * @param digest The signature digest, derived from the domain separator 82 | * and the order hash. 83 | * @param signature A signature (or other data) used to validate the digest. 84 | */ 85 | function _isValidEIP1271Signature( 86 | address signer, 87 | bytes32 digest, 88 | bytes memory signature 89 | ) internal view returns (bool) { 90 | if ( 91 | !signer.safeStaticCallBytes4( 92 | abi.encodeWithSelector( 93 | IERC1271.isValidSignature.selector, 94 | digest, 95 | signature 96 | ), 97 | IERC1271.isValidSignature.selector 98 | ) 99 | ) { 100 | return false; 101 | } 102 | return true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/test/TestERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 5 | 6 | contract TestERC1155 is ERC1155 { 7 | constructor(string memory uri) ERC1155(uri) {} 8 | 9 | function mint( 10 | address to, 11 | uint256 id, 12 | uint256 amount 13 | ) external { 14 | _mint(to, id, amount, ""); 15 | } 16 | 17 | function burn( 18 | address from, 19 | uint256 id, 20 | uint256 amount 21 | ) external { 22 | _burn(from, id, amount); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/test/TestERC1271.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol"; 5 | 6 | contract TestERC1271 is IERC1271 { 7 | address public immutable owner; 8 | 9 | constructor(address owner_) { 10 | owner = owner_; 11 | } 12 | 13 | function isValidSignature(bytes32 digest, bytes memory signature) 14 | external 15 | view 16 | returns (bytes4) 17 | { 18 | bytes32 r; 19 | bytes32 s; 20 | uint8 v; 21 | 22 | assembly { 23 | r := mload(add(signature, 0x20)) 24 | s := mload(add(signature, 0x40)) 25 | v := byte(0, mload(add(signature, 0x60))) 26 | } 27 | 28 | if ( 29 | uint256(s) > 30 | 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 31 | ) { 32 | revert(); 33 | } 34 | 35 | if (v != 27 && v != 28) { 36 | revert(); 37 | } 38 | 39 | address signer = ecrecover(digest, v, r, s); 40 | 41 | if (signer == address(0)) { 42 | revert(); 43 | } 44 | 45 | if (signer != owner) { 46 | revert(); 47 | } 48 | 49 | return IERC1271.isValidSignature.selector; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestERC20 is ERC20 { 7 | constructor(string memory name, string memory symbol) ERC20(name, symbol) {} 8 | 9 | function mint(address to, uint256 id) external { 10 | _mint(to, id); 11 | } 12 | 13 | function burn(address from, uint256 id) external { 14 | _burn(from, id); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/TestERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import { ERC2981 } from "@openzeppelin/contracts/token/common/ERC2981.sol"; 6 | 7 | contract TestERC721 is ERC721, ERC2981 { 8 | /// @notice When set to false, `royaltyInfo` reverts 9 | bool creatorFeeEnabled = false; 10 | /// @notice Below the min transaction price, `royaltyInfo` reverts 11 | uint256 minTransactionPrice = 0; 12 | 13 | constructor(string memory name, string memory symbol) 14 | ERC721(name, symbol) 15 | {} 16 | 17 | function supportsInterface(bytes4 interfaceId) 18 | public 19 | view 20 | override(ERC721, ERC2981) 21 | returns (bool) 22 | { 23 | return 24 | ERC721.supportsInterface(interfaceId) || 25 | ERC2981.supportsInterface(interfaceId); 26 | } 27 | 28 | function mint(address to, uint256 id) external { 29 | _mint(to, id); 30 | } 31 | 32 | function burn(uint256 id) external { 33 | _burn(id); 34 | } 35 | 36 | function royaltyInfo(uint256, uint256 _salePrice) 37 | public 38 | view 39 | override 40 | returns (address, uint256) 41 | { 42 | if (!creatorFeeEnabled) { 43 | revert("creator fee disabled"); 44 | } 45 | if (_salePrice < minTransactionPrice) { 46 | revert("sale price too low"); 47 | } 48 | 49 | return ( 50 | 0x000000000000000000000000000000000000fEE2, 51 | (_salePrice * (creatorFeeEnabled ? 250 : 0)) / 10000 52 | ); // 2.5% fee to 0xFEE2 53 | } 54 | 55 | function setCreatorFeeEnabled(bool enabled) public { 56 | creatorFeeEnabled = enabled; 57 | } 58 | 59 | function setMinTransactionPrice(uint256 minTransactionPrice_) public { 60 | minTransactionPrice = minTransactionPrice_; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/test/TestERC721Funky.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | 6 | /** 7 | * @notice TestERC721Funky is an ERC721 that implements ERC2981 with an incorrect return type. 8 | */ 9 | contract TestERC721Funky is ERC721 { 10 | constructor(string memory name, string memory symbol) 11 | ERC721(name, symbol) 12 | {} 13 | 14 | function mint(address to, uint256 id) external { 15 | _mint(to, id); 16 | } 17 | 18 | function burn(uint256 id) external { 19 | _burn(id); 20 | } 21 | 22 | function royaltyInfo(uint256, uint256) public pure returns (address) { 23 | return (0x000000000000000000000000000000000000fEE2); // 2.5% fee to 0xFEE2 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/TestEW.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { 5 | ErrorsAndWarnings, 6 | ErrorsAndWarningsLib 7 | } from "../lib/ErrorsAndWarnings.sol"; 8 | 9 | contract TestEW { 10 | using ErrorsAndWarningsLib for ErrorsAndWarnings; 11 | 12 | ErrorsAndWarnings errorsAndWarnings; 13 | 14 | constructor() { 15 | errorsAndWarnings = ErrorsAndWarnings(new uint16[](0), new uint16[](0)); 16 | } 17 | 18 | function addError(uint16 err) public { 19 | ErrorsAndWarnings memory memEw = errorsAndWarnings; 20 | memEw.addError(err); 21 | errorsAndWarnings = memEw; 22 | } 23 | 24 | function addWarning(uint16 warn) public { 25 | ErrorsAndWarnings memory memEw = errorsAndWarnings; 26 | memEw.addWarning(warn); 27 | errorsAndWarnings = memEw; 28 | } 29 | 30 | function hasErrors() public view returns (bool) { 31 | return errorsAndWarnings.hasErrors(); 32 | } 33 | 34 | function hasWarnings() public view returns (bool) { 35 | return errorsAndWarnings.hasWarnings(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/test/TestZone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { ZoneInterface } from "../interfaces/ZoneInterface.sol"; 5 | 6 | import { 7 | AdvancedOrder, 8 | CriteriaResolver 9 | } from "../lib/ConsiderationStructs.sol"; 10 | 11 | contract TestZone is ZoneInterface { 12 | function isValidOrder( 13 | bytes32 orderHash, 14 | address caller, 15 | address offerer, 16 | bytes32 zoneHash 17 | ) external pure override returns (bytes4 validOrderMagicValue) { 18 | orderHash; 19 | caller; 20 | offerer; 21 | 22 | if (zoneHash == bytes32(uint256(1))) { 23 | revert("Revert on zone hash 1"); 24 | } else if (zoneHash == bytes32(uint256(2))) { 25 | assembly { 26 | revert(0, 0) 27 | } 28 | } 29 | 30 | validOrderMagicValue = zoneHash != bytes32(uint256(3)) 31 | ? ZoneInterface.isValidOrder.selector 32 | : bytes4(0xffffffff); 33 | } 34 | 35 | function isValidOrderIncludingExtraData( 36 | bytes32 orderHash, 37 | address caller, 38 | AdvancedOrder calldata order, 39 | bytes32[] calldata priorOrderHashes, 40 | CriteriaResolver[] calldata criteriaResolvers 41 | ) external pure override returns (bytes4 validOrderMagicValue) { 42 | orderHash; 43 | caller; 44 | order; 45 | priorOrderHashes; 46 | criteriaResolvers; 47 | 48 | if (order.extraData.length == 4) { 49 | revert("Revert on extraData length 4"); 50 | } else if (order.extraData.length == 5) { 51 | assembly { 52 | revert(0, 0) 53 | } 54 | } 55 | 56 | validOrderMagicValue = order.parameters.zoneHash != bytes32(uint256(3)) 57 | ? ZoneInterface.isValidOrder.selector 58 | : bytes4(0xffffffff); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import type { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import "dotenv/config"; 4 | 5 | const config: HardhatUserConfig = { 6 | solidity: { 7 | compilers: [ 8 | { 9 | version: "0.8.14", 10 | settings: { 11 | optimizer: { 12 | enabled: true, 13 | runs: 200, 14 | }, 15 | }, 16 | }, 17 | ], 18 | }, 19 | 20 | networks: { 21 | hardhat: { 22 | allowUnlimitedContractSize: true, 23 | chainId: 1, 24 | forking: { 25 | enabled: true, 26 | url: "https://mainnet.infura.io/v3/" + (process.env.INFURA_KEY ?? ""), 27 | }, 28 | }, 29 | verificationNetwork: { 30 | url: process.env.VERIFICATION_NETWORK_RPC ?? "", 31 | }, 32 | }, 33 | 34 | etherscan: { 35 | apiKey: process.env.EXPLORER_API_KEY, 36 | }, 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /hardhatArbitrum.config.ts: -------------------------------------------------------------------------------- 1 | import type { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import "dotenv/config"; 4 | 5 | const config: HardhatUserConfig = { 6 | solidity: { 7 | compilers: [ 8 | { 9 | version: "0.8.14", 10 | settings: { 11 | optimizer: { 12 | enabled: true, 13 | runs: 200, 14 | }, 15 | }, 16 | }, 17 | ], 18 | }, 19 | 20 | networks: { 21 | hardhat: { 22 | allowUnlimitedContractSize: true, 23 | chainId: 42161, 24 | forking: { 25 | enabled: true, 26 | url: 27 | "https://arbitrum-mainnet.infura.io/v3/" + 28 | (process.env.INFURA_KEY ?? ""), 29 | }, 30 | }, 31 | }, 32 | }; 33 | 34 | export default config; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opensea/seaport-order-validator", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "author": "OpenSea Developers", 6 | "homepage": "https://github.com/ProjectOpenSea/seaport-order-validator#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/ProjectOpenSea/seaport-order-validator.git" 10 | }, 11 | "scripts": { 12 | "test": "hardhat compile && hardhat test 'test/ValidateOrdersMainnet.spec.ts' 'test/TestErrorsAndWarningsMainnet.spec.ts' && hardhat test 'test/ValidateOrderArbitrum.spec.ts' --config hardhatArbitrum.config.ts", 13 | "profile": "REPORT_GAS=true yarn test", 14 | "coverage": "hardhat compile && hardhat coverage --testfiles 'test/*Mainnet.spec.ts' --solcoverjs ./configs/solcover-mainnet.js && hardhat coverage --testfiles 'test/*Arbitrum.spec.ts' --config hardhatArbitrum.config.ts --solcoverjs ./configs/solcover-arbitrum.js && mkdir -p coverage && ./node_modules/.bin/lcov-result-merger 'coverage-*/lcov.info' 'coverage/lcov.info'", 15 | "prepare": "husky install", 16 | "build": "hardhat clean && hardhat compile && tsc -p ./tsconfig.build.json", 17 | "lint:fix": "prettier --write **.{sol,js,ts} && eslint --fix . --ext js,ts", 18 | "lint:check": "concurrently \"prettier --check **.{sol,js,ts}\" \"eslint . --ext js,ts\"", 19 | "verify": "hardhat verify --network verificationNetwork 0xF75194740067D6E4000000003b350688DD770000", 20 | "prepack": "yarn build" 21 | }, 22 | "main": "dist/src/index.js", 23 | "files": [ 24 | "dist", 25 | "artifacts/contracts/lib/SeaportValidator.sol/SeaportValidator.json", 26 | "contracts/README.md" 27 | ], 28 | "types": "dist/src/index.d.ts", 29 | "dependencies": { 30 | "ethers": "^5.7.0", 31 | "got": ">=11.8.5", 32 | "minimatch": ">=3.0.5", 33 | "undici": ">=5.8.2" 34 | }, 35 | "devDependencies": { 36 | "@ethersproject/abi": "^5.4.7", 37 | "@ethersproject/providers": "^5.4.7", 38 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 39 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 40 | "@nomicfoundation/hardhat-toolbox": "^1.0.1", 41 | "@nomiclabs/hardhat-ethers": "^2.0.0", 42 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 43 | "@openzeppelin/contracts": "^4.7.3", 44 | "@typechain/ethers-v5": "^10.1.0", 45 | "@typechain/hardhat": "^6.1.2", 46 | "@types/chai": "^4.2.0", 47 | "@types/mocha": "^9.1.0", 48 | "@types/node": ">=12.0.0", 49 | "@typescript-eslint/eslint-plugin": "^5.30.6", 50 | "@typescript-eslint/parser": "^5.30.6", 51 | "chai": "^4.2.0", 52 | "concurrently": "^7.2.2", 53 | "dotenv": "^16.0.1", 54 | "eslint": "^8.19.0", 55 | "eslint-config-prettier": "^8.5.0", 56 | "eslint-config-standard": "^17.0.0", 57 | "eslint-plugin-import": "^2.26.0", 58 | "eslint-plugin-n": "^15.2.4", 59 | "eslint-plugin-prettier": "^4.2.1", 60 | "eslint-plugin-promise": "^6.0.0", 61 | "hardhat": "^2.10.1", 62 | "hardhat-gas-reporter": "^1.0.8", 63 | "husky": "^8.0.1", 64 | "lcov-result-merger": "^3.3.0", 65 | "lint-staged": "^13.0.3", 66 | "prettier-plugin-solidity": "^1.0.0-dev.22", 67 | "solidity-coverage": "^0.7.21", 68 | "ts-node": ">=8.0.0", 69 | "typechain": "^8.1.0", 70 | "typescript": ">=4.5.0" 71 | }, 72 | "lint-staged": { 73 | "*.sol": "prettier --check", 74 | "*.js": "prettier --check", 75 | "*.ts": "prettier --check" 76 | }, 77 | "resolutions": { 78 | "got": ">=11.8.5", 79 | "undici": ">=5.8.2", 80 | "glob-parent": ">=5.1.2", 81 | "minimatch": ">=3.0.5" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { SeaportOrderValidator } from "./seaportOrderValidator"; 2 | export { SeaportOrderValidator }; 3 | -------------------------------------------------------------------------------- /src/seaportOrderValidator.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | 5 | import type { 6 | ErrorsAndWarningsStruct, 7 | OrderParametersStruct, 8 | OrderStruct, 9 | ValidationConfigurationStruct, 10 | } from "../typechain-types/contracts/lib/SeaportValidator"; 11 | import type { BigNumber, BigNumberish, Contract } from "ethers"; 12 | 13 | const seaportValidatorArtifact = JSON.parse( 14 | fs.readFileSync( 15 | path.join( 16 | __dirname, 17 | "../../artifacts/contracts/lib/SeaportValidator.sol/SeaportValidator.json" 18 | ), 19 | "utf8" 20 | ) 21 | ); 22 | 23 | export const SEAPORT_VALIDATOR_ABI = seaportValidatorArtifact.abi; 24 | export const SEAPORT_VALIDATOR_ADDRESS = 25 | "0xF75194740067D6E4000000003b350688DD770000"; 26 | 27 | export class SeaportOrderValidator { 28 | private seaportValidator: Contract; 29 | 30 | /** 31 | * Create a `SeaportOrderValidator` instance. 32 | * @param provider The ethers provider to use for the contract 33 | */ 34 | public constructor(provider: ethers.providers.JsonRpcProvider) { 35 | if (!provider) { 36 | throw new Error("No provider provided"); 37 | } 38 | 39 | this.seaportValidator = new ethers.Contract( 40 | SEAPORT_VALIDATOR_ADDRESS, 41 | SEAPORT_VALIDATOR_ABI, 42 | provider 43 | ); 44 | } 45 | 46 | /** 47 | * Conduct a comprehensive validation of the given order. 48 | * `isValidOrder` validates simple orders that adhere to a set of rules defined below: 49 | * - The order is either a listing or an offer order (one NFT to buy or one NFT to sell). 50 | * - The first consideration is the primary consideration. 51 | * - The order pays up to two fees in the fungible token currency. First fee is primary fee, second is creator fee. 52 | * - In private orders, the last consideration specifies a recipient for the offer item. 53 | * - Offer items must be owned and properly approved by the offerer. 54 | * - There must be one offer item 55 | * - Consideration items must exist. 56 | * - The signature must be valid, or the order must be already validated on chain 57 | * @param order The order to validate. 58 | * @return The errors and warnings found in the order. 59 | */ 60 | public async isValidOrder( 61 | order: OrderStruct 62 | ): Promise { 63 | return processErrorsAndWarnings( 64 | await this.seaportValidator.isValidOrder(order) 65 | ); 66 | } 67 | 68 | /** 69 | * Same as `isValidOrder` but allows for more configuration related to fee validation. 70 | * If `skipStrictValidation` is set order logic validation is not carried out: fees are not 71 | * checked and there may be more than one offer item as well as any number of consideration items. 72 | */ 73 | public async isValidOrderWithConfiguration( 74 | validationConfiguration: ValidationConfigurationStruct, 75 | order: OrderStruct 76 | ): Promise { 77 | return processErrorsAndWarnings( 78 | await this.seaportValidator.isValidOrderWithConfiguration( 79 | validationConfiguration, 80 | order 81 | ) 82 | ); 83 | } 84 | 85 | /** 86 | * Checks if a conduit key is valid. 87 | * @param conduitKey The conduit key to check. 88 | * @return The errors and warnings 89 | */ 90 | public async isValidConduit( 91 | conduitKey: string 92 | ): Promise { 93 | return processErrorsAndWarnings( 94 | await this.seaportValidator.isValidConduit(conduitKey) 95 | ); 96 | } 97 | 98 | /** 99 | * Gets the approval address for the given conduit key 100 | * @param conduitKey Conduit key to get approval address for 101 | * @return The address to use for approvals 102 | * @return An ErrorsAndWarnings structs with results 103 | */ 104 | public async getApprovalAddress(conduitKey: string): Promise<{ 105 | approvalAddress: string; 106 | errorsAndWarnings: ErrorsAndWarningsStruct; 107 | }> { 108 | const res = await this.seaportValidator.getApprovalAddress(conduitKey); 109 | return { 110 | approvalAddress: res[0], 111 | errorsAndWarnings: processErrorsAndWarnings(res[1]), 112 | }; 113 | } 114 | 115 | /** 116 | * Validates the signature for the order using the offerer's current counter 117 | * Will also check if order is validated on chain. 118 | */ 119 | public async validateSignature( 120 | order: OrderStruct 121 | ): Promise { 122 | return await processErrorsAndWarnings( 123 | this.seaportValidator.validateSignature(order) 124 | ); 125 | } 126 | 127 | /** 128 | * Validates the signature for the order using the given counter 129 | * Will also check if order is validated on chain. 130 | */ 131 | public async validateSignatureWithCounter( 132 | order: OrderStruct 133 | ): Promise { 134 | return processErrorsAndWarnings( 135 | await this.seaportValidator.validateSignatureWithCounter(order) 136 | ); 137 | } 138 | 139 | /** 140 | * Check the time validity of an order 141 | * @param orderParameters The parameters for the order to validate 142 | * @param shortOrderDuration The duration of which an order is considered short 143 | * @param distantOrderExpiration Distant order expiration delta in seconds. 144 | * @return The errors and warnings 145 | */ 146 | public async validateTime( 147 | orderParameters: OrderParametersStruct 148 | ): Promise { 149 | return processErrorsAndWarnings( 150 | await this.seaportValidator.validateTime(orderParameters) 151 | ); 152 | } 153 | 154 | /** 155 | * Validate the status of an order 156 | * @param orderParameters The parameters for the order to validate 157 | * @return errorsAndWarnings The errors and warnings 158 | */ 159 | public async validateOrderStatus( 160 | orderParameters: OrderParametersStruct 161 | ): Promise { 162 | return processErrorsAndWarnings( 163 | await this.seaportValidator.validateOrderStatus(orderParameters) 164 | ); 165 | } 166 | 167 | /** 168 | * Validate all offer items for an order. Ensures that 169 | * offerer has sufficient balance and approval for each item. 170 | * Amounts are not summed and verified, just the individual amounts. 171 | * @param orderParameters The parameters for the order to validate 172 | * @return errorsAndWarnings The errors and warnings 173 | */ 174 | public async validateOfferItems( 175 | orderParameters: OrderParametersStruct 176 | ): Promise { 177 | return processErrorsAndWarnings( 178 | await this.seaportValidator.validateOfferItems(orderParameters) 179 | ); 180 | } 181 | 182 | /** 183 | * @notice Validates an offer item 184 | * @param orderParameters The parameters for the order to validate 185 | * @param offerItemIndex The index of the offerItem in offer array to validate 186 | * @return An ErrorsAndWarnings structs with results 187 | */ 188 | public async validateOfferItem( 189 | orderParameters: OrderParametersStruct, 190 | offerItemIndex: number 191 | ): Promise { 192 | return processErrorsAndWarnings( 193 | await this.seaportValidator.validateOfferItem( 194 | orderParameters, 195 | offerItemIndex 196 | ) 197 | ); 198 | } 199 | 200 | /** 201 | * @notice Validates the OfferItem parameters. This includes token contract validation 202 | * @dev OfferItems with criteria are currently not allowed 203 | * @param orderParameters The parameters for the order to validate 204 | * @param offerItemIndex The index of the offerItem in offer array to validate 205 | * @return An ErrorsAndWarnings structs with results 206 | */ 207 | public async validateOfferItemParameters( 208 | orderParameters: OrderParametersStruct, 209 | offerItemIndex: number 210 | ): Promise { 211 | return processErrorsAndWarnings( 212 | await this.seaportValidator.validateOfferItemParameters( 213 | orderParameters, 214 | offerItemIndex 215 | ) 216 | ); 217 | } 218 | 219 | /** 220 | * Validates the OfferItem approvals and balances 221 | * @param orderParameters The parameters for the order to validate 222 | * @param offerItemIndex The index of the offerItem in offer array to validate 223 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 224 | */ 225 | public async validateOfferItemApprovalAndBalance( 226 | orderParameters: OrderParametersStruct, 227 | offerItemIndex: number 228 | ): Promise { 229 | return await processErrorsAndWarnings( 230 | this.seaportValidator.validateOfferItemApprovalAndBalance( 231 | orderParameters, 232 | offerItemIndex 233 | ) 234 | ); 235 | } 236 | 237 | /** 238 | * Validate all consideration items for an order 239 | * @param orderParameters The parameters for the order to validate 240 | * @return errorsAndWarnings The errors and warnings 241 | */ 242 | public async validateConsiderationItems( 243 | orderParameters: OrderParametersStruct 244 | ): Promise { 245 | return processErrorsAndWarnings( 246 | await this.seaportValidator.validateConsiderationItems(orderParameters) 247 | ); 248 | } 249 | 250 | /** 251 | * Validate a consideration item 252 | * @param orderParameters The parameters for the order to validate 253 | * @param considerationItemIndex The index of the consideration item to validate 254 | * @return errorsAndWarnings The errors and warnings 255 | */ 256 | public async validateConsiderationItem( 257 | orderParameters: OrderParametersStruct, 258 | considerationItemIndex: number 259 | ): Promise { 260 | return processErrorsAndWarnings( 261 | await this.seaportValidator.validateConsiderationItem( 262 | orderParameters, 263 | considerationItemIndex 264 | ) 265 | ); 266 | } 267 | 268 | /** 269 | * Validates the parameters of a consideration item including contract validation 270 | * @param orderParameters The parameters for the order to validate 271 | * @param considerationItemIndex The index of the consideration item to validate 272 | * @return errorsAndWarnings The errors and warnings 273 | */ 274 | public async validateConsiderationItemParameters( 275 | orderParameters: OrderParametersStruct, 276 | considerationItemIndex: number 277 | ): Promise { 278 | return processErrorsAndWarnings( 279 | await this.seaportValidator.validateConsiderationItemParameters( 280 | orderParameters, 281 | considerationItemIndex 282 | ) 283 | ); 284 | } 285 | 286 | /** 287 | * Strict validation operates under tight assumptions. It validates primary 288 | * fee, creator fee, private sale consideration, and overall order format. 289 | * Only checks first fee recipient provided by CreatorFeeEngine. 290 | * Order of consideration items must be as follows: 291 | * 1. Primary consideration 292 | * 2. Primary fee 293 | * 3. Creator Fee 294 | * 4. Private sale consideration 295 | * @param orderParameters The parameters for the order to validate. 296 | * @param primaryFeeRecipient The primary fee recipient. Set to null address for no primary fee. 297 | * @param primaryFeeBips The primary fee in BIPs. 298 | * @param checkCreatorFee Should check for creator fee. If true, creator fee must be present as 299 | * according to creator fee engine. If false, must not have creator fee. 300 | * @return errorsAndWarnings The errors and warnings. 301 | */ 302 | public async validateStrictLogic( 303 | orderParameters: OrderParametersStruct, 304 | primaryFeeRecipient: string, 305 | primaryFeeBips: BigNumberish, 306 | checkCreatorFee: boolean 307 | ): Promise { 308 | return processErrorsAndWarnings( 309 | await this.seaportValidator.validateStrictLogic( 310 | orderParameters, 311 | primaryFeeRecipient, 312 | primaryFeeBips, 313 | checkCreatorFee 314 | ) 315 | ); 316 | } 317 | 318 | /** 319 | * Fetches the on chain creator fees. 320 | * Uses the creatorFeeEngine when available, otherwise fallback to `IERC2981`. 321 | * @param token The token address 322 | * @param tokenId The token identifier 323 | * @param transactionAmountStart The transaction start amount 324 | * @param transactionAmountEnd The transaction end amount 325 | * @return recipient creator fee recipient 326 | * @return creator fee start amount 327 | * @return creator fee end amount 328 | */ 329 | public async getCreatorFeeInfo( 330 | token: string, 331 | tokenId: BigNumberish, 332 | transactionAmountStart: BigNumberish, 333 | transactionAmountEnd: BigNumberish 334 | ): Promise<{ 335 | recipient: string; 336 | creatorFeeAmountStart: BigNumber; 337 | creatorFeeAmountEnd: BigNumber; 338 | }> { 339 | const res = await this.seaportValidator.getCreatorFeeInfo( 340 | token, 341 | tokenId, 342 | transactionAmountStart, 343 | transactionAmountEnd 344 | ); 345 | 346 | return { 347 | recipient: res.recipient, 348 | creatorFeeAmountStart: res.creatorFeeAmountStart, 349 | creatorFeeAmountEnd: res.creatorFeeAmountEnd, 350 | }; 351 | } 352 | 353 | /** 354 | * Validates the zone call for an order 355 | * @param {OrderParametersStruct} orderParameters The parameters for the order to validate 356 | * @return errorsAndWarnings An ErrorsAndWarnings structs with results 357 | */ 358 | public async isValidZone( 359 | orderParameters: OrderParametersStruct 360 | ): Promise { 361 | return processErrorsAndWarnings( 362 | await this.seaportValidator.isValidZone(orderParameters) 363 | ); 364 | } 365 | 366 | /** 367 | * Sorts an array of token ids by the keccak256 hash of the id. Required ordering of ids 368 | * for other merkle operations. 369 | * @param {BigNumberish[]} includedTokens An array of included token ids. 370 | * @return The sorted `includedTokens` array. 371 | */ 372 | public async sortMerkleTokens( 373 | includedTokens: BigNumberish[] 374 | ): Promise { 375 | return await this.seaportValidator.sortMerkleTokens(includedTokens); 376 | } 377 | 378 | /** 379 | * Creates a merkle root for includedTokens. 380 | * @dev `includedTokens` must be sorting in strictly ascending order according to the keccak256 hash of the value. 381 | * @return The merkle root 382 | * @return Errors and warnings from the operation 383 | */ 384 | public async getMerkleRoot(includedTokens: BigNumberish[]): Promise<{ 385 | merkleRoot: string; 386 | errorsAndWarnings: ErrorsAndWarningsStruct; 387 | }> { 388 | const res = await this.seaportValidator.getMerkleRoot(includedTokens); 389 | return { 390 | merkleRoot: res.merkleRoot, 391 | errorsAndWarnings: processErrorsAndWarnings(res.errorsAndWarnings), 392 | }; 393 | } 394 | 395 | /** 396 | * Creates a merkle proof for the the targetIndex contained in includedTokens. 397 | * `targetIndex` is referring to the index of an element in `includedTokens`. 398 | * `includedTokens` must be sorting in ascending order according to the keccak256 hash of the value. 399 | * 400 | * @return merkleProof The merkle proof 401 | * @return errorsAndWarnings Errors and warnings from the operation 402 | */ 403 | public async getMerkleProof( 404 | includedTokens: BigNumberish[], 405 | targetIndex: BigNumberish 406 | ): Promise<{ 407 | merkleProof: string[]; 408 | errorsAndWarnings: ErrorsAndWarningsStruct; 409 | }> { 410 | const res = await this.seaportValidator.getMerkleProof( 411 | includedTokens, 412 | targetIndex 413 | ); 414 | return { 415 | merkleProof: res.merkleProof, 416 | errorsAndWarnings: processErrorsAndWarnings(res.errorsAndWarnings), 417 | }; 418 | } 419 | 420 | /** 421 | * Verifies a merkle proof for the value to prove and given root and proof. 422 | * The `valueToProve` is hashed prior to executing the proof verification. 423 | * @param merkleRoot The root of the merkle tree 424 | * @param merkleProof The merkle proof 425 | * @param valueToProve The value to prove 426 | * @return whether proof is valid 427 | */ 428 | public async verifyMerkleProof( 429 | merkleRoot: string, 430 | merkleProof: string[], 431 | valueToProve: BigNumberish 432 | ): Promise { 433 | return await this.seaportValidator.verifyMerkleProof( 434 | merkleRoot, 435 | merkleProof, 436 | valueToProve 437 | ); 438 | } 439 | } 440 | 441 | function processErrorsAndWarnings(rawReturn: any): ErrorsAndWarningsStruct { 442 | const errors = rawReturn.errors; 443 | const warnings = rawReturn.warnings; 444 | return { errors, warnings }; 445 | } 446 | -------------------------------------------------------------------------------- /test/TestErrorsAndWarningsMainnet.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | 4 | import type { Contract } from "ethers"; 5 | 6 | describe("Test Errors and Warnings", function () { 7 | let testEw: Contract; 8 | 9 | beforeEach(async function () { 10 | const testEWFactory = await ethers.getContractFactory("TestEW"); 11 | testEw = await testEWFactory.deploy(); 12 | }); 13 | 14 | it("Test EW", async function () { 15 | expect(await testEw.hasWarnings()).to.be.false; 16 | await testEw.addWarning("15"); 17 | expect(await testEw.hasWarnings()).to.be.true; 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/ValidateOrderArbitrum.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | 5 | import { 6 | CROSS_CHAIN_SEAPORT_ADDRESS, 7 | ConsiderationIssue, 8 | EIP_712_ORDER_TYPE, 9 | EMPTY_BYTES32, 10 | ItemType, 11 | NULL_ADDRESS, 12 | OrderType, 13 | } from "./constants"; 14 | 15 | import type { 16 | ConsiderationInterface, 17 | SeaportValidator, 18 | TestERC1155, 19 | TestERC721, 20 | TestERC721Funky, 21 | } from "../typechain-types"; 22 | import type { OrderComponentsStruct } from "../typechain-types/contracts/interfaces/ConsiderationInterface"; 23 | import type { 24 | OrderParametersStruct, 25 | OrderStruct, 26 | } from "../typechain-types/contracts/lib/SeaportValidator"; 27 | import type { TestERC20 } from "../typechain-types/contracts/test/TestERC20"; 28 | import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 29 | 30 | describe("Validate Orders (Arbitrum)", function () { 31 | const feeRecipient = "0x0000000000000000000000000000000000000FEE"; 32 | const coder = new ethers.utils.AbiCoder(); 33 | let baseOrderParameters: OrderParametersStruct; 34 | let validator: SeaportValidator; 35 | let seaport: ConsiderationInterface; 36 | let owner: SignerWithAddress; 37 | let otherAccounts: SignerWithAddress[]; 38 | let erc721_1: TestERC721; 39 | let erc721_2: TestERC721; 40 | let erc1155_1: TestERC1155; 41 | let erc20_1: TestERC20; 42 | let erc721_funky: TestERC721Funky; 43 | 44 | before(async function () { 45 | seaport = await ethers.getContractAt( 46 | "ConsiderationInterface", 47 | CROSS_CHAIN_SEAPORT_ADDRESS 48 | ); 49 | }); 50 | 51 | async function deployFixture() { 52 | const [owner, ...otherAccounts] = await ethers.getSigners(); 53 | 54 | const Validator = await ethers.getContractFactory("SeaportValidator"); 55 | const TestERC721Factory = await ethers.getContractFactory("TestERC721"); 56 | const TestERC1155Factory = await ethers.getContractFactory("TestERC1155"); 57 | const TestERC20Factory = await ethers.getContractFactory("TestERC20"); 58 | const TestERC721FunkyFactory = await ethers.getContractFactory( 59 | "TestERC721Funky" 60 | ); 61 | 62 | const validator = await Validator.deploy(); 63 | 64 | const erc721_1 = await TestERC721Factory.deploy("NFT1", "NFT1"); 65 | const erc721_2 = await TestERC721Factory.deploy("NFT2", "NFT2"); 66 | const erc1155_1 = await TestERC1155Factory.deploy("uri_here"); 67 | const erc20_1 = await TestERC20Factory.deploy("ERC20", "ERC20"); 68 | const erc721_funky = await TestERC721FunkyFactory.deploy("NFT3", "NFT3"); 69 | 70 | return { 71 | validator, 72 | owner, 73 | otherAccounts, 74 | erc721_1, 75 | erc721_2, 76 | erc1155_1, 77 | erc20_1, 78 | erc721_funky, 79 | }; 80 | } 81 | 82 | beforeEach(async function () { 83 | const res = await loadFixture(deployFixture); 84 | validator = res.validator; 85 | owner = res.owner; 86 | otherAccounts = res.otherAccounts; 87 | erc721_1 = res.erc721_1; 88 | erc721_2 = res.erc721_2; 89 | erc1155_1 = res.erc1155_1; 90 | erc20_1 = res.erc20_1; 91 | erc721_funky = res.erc721_funky; 92 | 93 | baseOrderParameters = { 94 | offerer: owner.address, 95 | zone: NULL_ADDRESS, 96 | orderType: OrderType.FULL_OPEN, 97 | startTime: "0", 98 | endTime: Math.round(Date.now() / 1000 + 4000).toString(), 99 | salt: "0", 100 | totalOriginalConsiderationItems: 0, 101 | offer: [], 102 | consideration: [], 103 | zoneHash: EMPTY_BYTES32, 104 | conduitKey: EMPTY_BYTES32, 105 | }; 106 | }); 107 | 108 | describe("Check Creator Fees", function () { 109 | // We are checking creator fees solely based on EIP2981 here 110 | 111 | it("Check creator fees success", async function () { 112 | // Enable creator fees on token 113 | await erc721_1.setCreatorFeeEnabled(true); 114 | 115 | baseOrderParameters.offer = [ 116 | { 117 | itemType: ItemType.ERC20, 118 | token: erc20_1.address, 119 | identifierOrCriteria: "0", 120 | startAmount: "1000", 121 | endAmount: "1000", 122 | }, 123 | ]; 124 | baseOrderParameters.consideration = [ 125 | { 126 | itemType: ItemType.ERC721, 127 | token: erc721_1.address, 128 | identifierOrCriteria: "1", 129 | startAmount: "1", 130 | endAmount: "1", 131 | recipient: owner.address, 132 | }, 133 | { 134 | itemType: ItemType.ERC20, 135 | token: erc20_1.address, 136 | identifierOrCriteria: "0", 137 | startAmount: "25", 138 | endAmount: "25", 139 | recipient: feeRecipient, 140 | }, 141 | { 142 | itemType: ItemType.ERC20, 143 | token: erc20_1.address, 144 | identifierOrCriteria: "0", 145 | startAmount: "25", 146 | endAmount: "25", 147 | recipient: "0x000000000000000000000000000000000000FEE2", 148 | }, 149 | ]; 150 | 151 | expect( 152 | await validator.validateStrictLogic( 153 | baseOrderParameters, 154 | feeRecipient, 155 | "250", 156 | true 157 | ) 158 | ).to.include.deep.ordered.members([[], []]); 159 | }); 160 | 161 | it("Check creator fees reverts", async function () { 162 | baseOrderParameters.offer = [ 163 | { 164 | itemType: ItemType.ERC20, 165 | token: erc20_1.address, 166 | identifierOrCriteria: "0", 167 | startAmount: "1000", 168 | endAmount: "1000", 169 | }, 170 | ]; 171 | baseOrderParameters.consideration = [ 172 | { 173 | itemType: ItemType.ERC721, 174 | token: erc721_1.address, 175 | identifierOrCriteria: "1", 176 | startAmount: "1", 177 | endAmount: "1", 178 | recipient: owner.address, 179 | }, 180 | { 181 | itemType: ItemType.ERC20, 182 | token: erc20_1.address, 183 | identifierOrCriteria: "0", 184 | startAmount: "25", 185 | endAmount: "25", 186 | recipient: "0x000000000000000000000000000000000000FEE2", 187 | }, 188 | ]; 189 | 190 | expect( 191 | await validator.validateStrictLogic( 192 | baseOrderParameters, 193 | NULL_ADDRESS, 194 | "0", 195 | true 196 | ) 197 | ).to.include.deep.ordered.members([[ConsiderationIssue.ExtraItems], []]); 198 | }); 199 | 200 | it("Check creator fees returns unexpected value", async function () { 201 | baseOrderParameters.offer = [ 202 | { 203 | itemType: ItemType.ERC20, 204 | token: erc20_1.address, 205 | identifierOrCriteria: "0", 206 | startAmount: "1000", 207 | endAmount: "1000", 208 | }, 209 | ]; 210 | baseOrderParameters.consideration = [ 211 | { 212 | itemType: ItemType.ERC721, 213 | token: erc721_funky.address, 214 | identifierOrCriteria: "1", 215 | startAmount: "1", 216 | endAmount: "1", 217 | recipient: owner.address, 218 | }, 219 | { 220 | itemType: ItemType.ERC20, 221 | token: erc20_1.address, 222 | identifierOrCriteria: "0", 223 | startAmount: "25", 224 | endAmount: "25", 225 | recipient: "0x000000000000000000000000000000000000FEE2", 226 | }, 227 | ]; 228 | 229 | expect( 230 | await validator.validateStrictLogic( 231 | baseOrderParameters, 232 | NULL_ADDRESS, 233 | "0", 234 | true 235 | ) 236 | ).to.include.deep.ordered.members([[ConsiderationIssue.ExtraItems], []]); 237 | }); 238 | 239 | it("Check creator fees second reverts", async function () { 240 | await erc721_1.setCreatorFeeEnabled(true); 241 | await erc721_1.setMinTransactionPrice("10"); 242 | 243 | baseOrderParameters.offer = [ 244 | { 245 | itemType: ItemType.ERC20, 246 | token: erc20_1.address, 247 | identifierOrCriteria: "0", 248 | startAmount: "1000", 249 | endAmount: "0", 250 | }, 251 | ]; 252 | baseOrderParameters.consideration = [ 253 | { 254 | itemType: ItemType.ERC721, 255 | token: erc721_1.address, 256 | identifierOrCriteria: "1", 257 | startAmount: "1", 258 | endAmount: "1", 259 | recipient: owner.address, 260 | }, 261 | { 262 | itemType: ItemType.ERC20, 263 | token: erc20_1.address, 264 | identifierOrCriteria: "0", 265 | startAmount: "25", 266 | endAmount: "0", 267 | recipient: "0x000000000000000000000000000000000000FEE2", 268 | }, 269 | ]; 270 | 271 | expect( 272 | await validator.validateStrictLogic( 273 | baseOrderParameters, 274 | NULL_ADDRESS, 275 | "0", 276 | true 277 | ) 278 | ).to.include.deep.ordered.members([[], []]); 279 | }); 280 | }); 281 | 282 | async function signOrder( 283 | orderParameters: OrderParametersStruct, 284 | signer: SignerWithAddress, 285 | counter?: number 286 | ): Promise { 287 | const sig = await signer._signTypedData( 288 | { 289 | name: "Seaport", 290 | version: "1.1", 291 | chainId: "1", 292 | verifyingContract: seaport.address, 293 | }, 294 | EIP_712_ORDER_TYPE, 295 | await getOrderComponents(orderParameters, signer, counter) 296 | ); 297 | 298 | return { 299 | parameters: orderParameters, 300 | signature: sig, 301 | }; 302 | } 303 | 304 | async function getOrderComponents( 305 | orderParameters: OrderParametersStruct, 306 | signer: SignerWithAddress, 307 | counter?: number 308 | ): Promise { 309 | return { 310 | ...orderParameters, 311 | counter: counter ?? (await seaport.getCounter(signer.address)), 312 | }; 313 | } 314 | }); 315 | -------------------------------------------------------------------------------- /test/constants.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | 3 | export const SEAPORT_CONTRACT_NAME = "Seaport"; 4 | export const SEAPORT_CONTRACT_VERSION = "1.1"; 5 | export const OPENSEA_CONDUIT_KEY = 6 | "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000"; 7 | export const OPENSEA_CONDUIT_ADDRESS = 8 | "0x1E0049783F008A0085193E00003D00cd54003c71"; 9 | export const EIP_712_ORDER_TYPE = { 10 | OrderComponents: [ 11 | { name: "offerer", type: "address" }, 12 | { name: "zone", type: "address" }, 13 | { name: "offer", type: "OfferItem[]" }, 14 | { name: "consideration", type: "ConsiderationItem[]" }, 15 | { name: "orderType", type: "uint8" }, 16 | { name: "startTime", type: "uint256" }, 17 | { name: "endTime", type: "uint256" }, 18 | { name: "zoneHash", type: "bytes32" }, 19 | { name: "salt", type: "uint256" }, 20 | { name: "conduitKey", type: "bytes32" }, 21 | { name: "counter", type: "uint256" }, 22 | ], 23 | OfferItem: [ 24 | { name: "itemType", type: "uint8" }, 25 | { name: "token", type: "address" }, 26 | { name: "identifierOrCriteria", type: "uint256" }, 27 | { name: "startAmount", type: "uint256" }, 28 | { name: "endAmount", type: "uint256" }, 29 | ], 30 | ConsiderationItem: [ 31 | { name: "itemType", type: "uint8" }, 32 | { name: "token", type: "address" }, 33 | { name: "identifierOrCriteria", type: "uint256" }, 34 | { name: "startAmount", type: "uint256" }, 35 | { name: "endAmount", type: "uint256" }, 36 | { name: "recipient", type: "address" }, 37 | ], 38 | }; 39 | 40 | export enum OrderType { 41 | FULL_OPEN = 0, // No partial fills, anyone can execute 42 | PARTIAL_OPEN = 1, // Partial fills supported, anyone can execute 43 | FULL_RESTRICTED = 2, // No partial fills, only offerer or zone can execute 44 | PARTIAL_RESTRICTED = 3, // Partial fills supported, only offerer or zone can execute 45 | } 46 | 47 | export enum ItemType { 48 | NATIVE = 0, 49 | ERC20 = 1, 50 | ERC721 = 2, 51 | ERC1155 = 3, 52 | ERC721_WITH_CRITERIA = 4, 53 | ERC1155_WITH_CRITERIA = 5, 54 | } 55 | 56 | export enum Side { 57 | OFFER = 0, 58 | CONSIDERATION = 1, 59 | } 60 | 61 | export type NftItemType = 62 | | ItemType.ERC721 63 | | ItemType.ERC1155 64 | | ItemType.ERC721_WITH_CRITERIA 65 | | ItemType.ERC1155_WITH_CRITERIA; 66 | 67 | export enum BasicOrderRouteType { 68 | ETH_TO_ERC721, 69 | ETH_TO_ERC1155, 70 | ERC20_TO_ERC721, 71 | ERC20_TO_ERC1155, 72 | ERC721_TO_ERC20, 73 | ERC1155_TO_ERC20, 74 | } 75 | 76 | export const MAX_INT = BigNumber.from( 77 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 78 | ); 79 | export const ONE_HUNDRED_PERCENT_BP = 10000; 80 | export const NO_CONDUIT = 81 | "0x0000000000000000000000000000000000000000000000000000000000000000"; 82 | 83 | // Supply here any known conduit keys as well as their conduits 84 | export const KNOWN_CONDUIT_KEYS_TO_CONDUIT = { 85 | [OPENSEA_CONDUIT_KEY]: OPENSEA_CONDUIT_ADDRESS, 86 | }; 87 | 88 | export const CROSS_CHAIN_SEAPORT_ADDRESS = 89 | "0x00000000006c3852cbEf3e08E8dF289169EdE581"; 90 | 91 | export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000"; 92 | export const EMPTY_BYTES32 = 93 | "0x0000000000000000000000000000000000000000000000000000000000000000"; 94 | 95 | export enum TimeIssue { 96 | EndTimeBeforeStartTime = 900, 97 | Expired, 98 | DistantExpiration, 99 | NotActive, 100 | ShortOrder, 101 | } 102 | 103 | export enum StatusIssue { 104 | Cancelled = 800, 105 | FullyFilled, 106 | } 107 | 108 | export enum OfferIssue { 109 | ZeroItems = 600, 110 | AmountZero, 111 | MoreThanOneItem, 112 | NativeItem, 113 | DuplicateItem, 114 | AmountVelocityHigh, 115 | AmountStepLarge, 116 | } 117 | 118 | export enum ConsiderationIssue { 119 | AmountZero = 500, 120 | NullRecipient, 121 | ExtraItems, 122 | PrivateSaleToSelf, 123 | ZeroItems, 124 | DuplicateItem, 125 | PrivateSale, 126 | AmountVelocityHigh, 127 | AmountStepLarge, 128 | } 129 | 130 | export enum PrimaryFeeIssue { 131 | Missing = 700, 132 | ItemType, 133 | Token, 134 | StartAmount, 135 | EndAmount, 136 | Recipient, 137 | } 138 | 139 | export enum ERC721Issue { 140 | AmountNotOne = 300, 141 | InvalidToken, 142 | IdentifierDNE, 143 | NotOwner, 144 | NotApproved, 145 | CriteriaNotPartialFill, 146 | } 147 | 148 | export enum ERC1155Issue { 149 | InvalidToken = 400, 150 | NotApproved, 151 | InsufficientBalance, 152 | } 153 | 154 | export enum ERC20Issue { 155 | IdentifierNonZero = 200, 156 | InvalidToken, 157 | InsufficientAllowance, 158 | InsufficientBalance, 159 | } 160 | 161 | export enum NativeIssue { 162 | TokenAddress = 1300, 163 | IdentifierNonZero, 164 | InsufficientBalance, 165 | } 166 | 167 | export enum ZoneIssue { 168 | RejectedOrder = 1400, 169 | NotSet, 170 | } 171 | 172 | export enum ConduitIssue { 173 | KeyInvalid = 1000, 174 | } 175 | 176 | export enum CreatorFeeIssue { 177 | Missing = 1200, 178 | ItemType, 179 | Token, 180 | StartAmount, 181 | EndAmount, 182 | Recipient, 183 | } 184 | 185 | export enum SignatureIssue { 186 | Invalid = 1100, 187 | LowCounter, 188 | HighCounter, 189 | OriginalConsiderationItems, 190 | } 191 | 192 | export enum GenericIssue { 193 | InvalidOrderFormat = 100, 194 | } 195 | 196 | export enum MerkleIssue { 197 | SingleLeaf = 1500, 198 | Unsorted, 199 | } 200 | 201 | export const THIRTY_MINUTES = 30 * 60; 202 | export const WEEKS_26 = 60 * 60 * 24 * 7 * 26; 203 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "rootDirs": ["./src"], 12 | "declaration": true, 13 | }, 14 | "include": [ 15 | "src/**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------