├── .github └── workflows │ ├── publish.yml │ ├── tag.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── foundry.toml ├── package.json ├── src ├── Seaport.sol ├── conduit │ ├── Conduit.sol │ └── ConduitController.sol ├── helpers │ └── SeaportRouter.sol └── lib │ ├── AmountDeriver.sol │ ├── Assertions.sol │ ├── BasicOrderFulfiller.sol │ ├── Consideration.sol │ ├── ConsiderationBase.sol │ ├── ConsiderationDecoder.sol │ ├── ConsiderationEncoder.sol │ ├── CounterManager.sol │ ├── CriteriaResolution.sol │ ├── Executor.sol │ ├── FulfillmentApplier.sol │ ├── GettersAndDerivers.sol │ ├── LowLevelHelpers.sol │ ├── OrderCombiner.sol │ ├── OrderFulfiller.sol │ ├── OrderValidator.sol │ ├── ReentrancyGuard.sol │ ├── SignatureVerification.sol │ ├── TokenTransferrer.sol │ ├── Verifiers.sol │ └── ZoneInteraction.sol └── yarn.lock /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v2 11 | with: 12 | node-version: "16.x" 13 | registry-url: "https://registry.npmjs.org" 14 | - run: yarn 15 | - run: npm publish --access public 16 | env: 17 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: Auto-tag commit with YY-MM-DD/branchname 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | tag-commit: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Get current date 20 | run: echo "::set-output name=date::$(date +%y-%m-%d)" 21 | id: date 22 | - name: Get branch name 23 | run: echo "::set-output name=branch::${GITHUB_REF#refs/heads/}" 24 | id: branch 25 | - name: Create tag string 26 | run: echo "::set-output name=tag::${{ steps.date.outputs.date }}-${{ steps.branch.outputs.branch }}" 27 | id: tag 28 | - name: Delete tag if exists 29 | run: | 30 | if git rev-parse --verify --quiet ${{ steps.tag.outputs.tag }}; then 31 | git tag -d ${{ steps.tag.outputs.tag }} 32 | git push --delete origin ${{ steps.tag.outputs.tag }} 33 | fi 34 | - name: Tag commit with YY-MM-DD-branchname 35 | run: | 36 | git tag ${{ steps.tag.outputs.tag }} 37 | git push --tags --force 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/seaport-types"] 2 | path = lib/seaport-types 3 | url = https://github.com/projectopensea/seaport-types 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 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 | # seaport-core 2 | 3 | [![Version][version-badge]][version-link] 4 | [![npm][npm-badge]][npm-link] 5 | 6 | This repo contains the core Seaport smart contracts (with no reference contracts, helpers, or tests) and is meant to facilitate building on Seaport without needing to grapple with long compile times or other complications. 7 | 8 | For more information on Seaport, see the [main Seaport repo][seaport]. 9 | 10 | Related repositories: 11 | 12 | - [seaport-types][seaport-types] 13 | - [seaport-sol][seaport-sol] 14 | 15 | [seaport]: https://github.com/ProjectOpenSea/seaport 16 | [seaport-core]: https://github.com/ProjectOpenSea/seaport-core 17 | [seaport-types]: https://github.com/ProjectOpenSea/seaport-types 18 | [seaport-sol]: https://github.com/ProjectOpenSea/seaport-sol 19 | [version-badge]: https://img.shields.io/github/package-json/v/ProjectOpenSea/seaport-core 20 | [version-link]: https://github.com/ProjectOpenSea/seaport-core/releases 21 | [npm-badge]: https://img.shields.io/npm/v/seaport-core?color=red 22 | [npm-link]: https://www.npmjs.com/package/seaport-core 23 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc = "0.8.24" 3 | evm_version = "cancun" 4 | via_ir = true 5 | bytecode_hash = "none" 6 | optimizer_runs = 4_294_967_295 7 | src = "src" 8 | out = "out" 9 | libs = ["lib"] 10 | remappings = ["seaport-types/=lib/seaport-types/"] 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seaport-core", 3 | "version": "1.6.6", 4 | "description": "Core smart contracts for the Seaport protocol", 5 | "main": "src/", 6 | "repository": "https://github.com/ProjectOpenSea/seaport-core.git", 7 | "author": "OpenSea", 8 | "license": "MIT", 9 | "files": [ 10 | "node_modules", 11 | "package.json", 12 | "src", 13 | "LICENSE", 14 | "README.md" 15 | ], 16 | "dependencies": { 17 | "seaport-types": "1.6.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Seaport.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { Consideration } from "./lib/Consideration.sol"; 5 | 6 | /** 7 | * @title Seaport 8 | * @custom:version 1.6 9 | * @author 0age (0age.eth) 10 | * @custom:coauthor d1ll0n (d1ll0n.eth) 11 | * @custom:coauthor transmissions11 (t11s.eth) 12 | * @custom:coauthor James Wenzel (emo.eth) 13 | * @custom:coauthor Daniel Viau (snotrocket.eth) 14 | * @custom:contributor Kartik (slokh.eth) 15 | * @custom:contributor LeFevre (lefevre.eth) 16 | * @custom:contributor Joseph Schiarizzi (CupOJoseph.eth) 17 | * @custom:contributor Aspyn Palatnick (stuckinaboot.eth) 18 | * @custom:contributor Stephan Min (stephanm.eth) 19 | * @custom:contributor Ryan Ghods (ralxz.eth) 20 | * @custom:contributor hack3r-0m (hack3r-0m.eth) 21 | * @custom:contributor Diego Estevez (antidiego.eth) 22 | * @custom:contributor Chomtana (chomtana.eth) 23 | * @custom:contributor Saw-mon and Natalie (sawmonandnatalie.eth) 24 | * @custom:contributor 0xBeans (0xBeans.eth) 25 | * @custom:contributor 0x4non (punkdev.eth) 26 | * @custom:contributor Laurence E. Day (norsefire.eth) 27 | * @custom:contributor vectorized.eth (vectorized.eth) 28 | * @custom:contributor karmacoma (karmacoma.eth) 29 | * @custom:contributor horsefacts (horsefacts.eth) 30 | * @custom:contributor UncarvedBlock (uncarvedblock.eth) 31 | * @custom:contributor Zoraiz Mahmood (zorz.eth) 32 | * @custom:contributor William Poulin (wpoulin.eth) 33 | * @custom:contributor Rajiv Patel-O'Connor (rajivpoc.eth) 34 | * @custom:contributor tserg (tserg.eth) 35 | * @custom:contributor cygaar (cygaar.eth) 36 | * @custom:contributor Meta0xNull (meta0xnull.eth) 37 | * @custom:contributor gpersoon (gpersoon.eth) 38 | * @custom:contributor Matt Solomon (msolomon.eth) 39 | * @custom:contributor Weikang Song (weikangs.eth) 40 | * @custom:contributor zer0dot (zer0dot.eth) 41 | * @custom:contributor Mudit Gupta (mudit.eth) 42 | * @custom:contributor leonardoalt (leoalt.eth) 43 | * @custom:contributor cmichel (cmichel.eth) 44 | * @custom:contributor PraneshASP (pranesh.eth) 45 | * @custom:contributor JasperAlexander (jasperalexander.eth) 46 | * @custom:contributor Ellahi (ellahi.eth) 47 | * @custom:contributor zaz (1zaz1.eth) 48 | * @custom:contributor berndartmueller (berndartmueller.eth) 49 | * @custom:contributor dmfxyz (dmfxyz.eth) 50 | * @custom:contributor daltoncoder (dontkillrobots.eth) 51 | * @custom:contributor 0xf4ce (0xf4ce.eth) 52 | * @custom:contributor phaze (phaze.eth) 53 | * @custom:contributor hrkrshnn (hrkrshnn.eth) 54 | * @custom:contributor axic (axic.eth) 55 | * @custom:contributor leastwood (leastwood.eth) 56 | * @custom:contributor 0xsanson (sanson.eth) 57 | * @custom:contributor blockdev (blockd3v.eth) 58 | * @custom:contributor fiveoutofnine (fiveoutofnine.eth) 59 | * @custom:contributor shuklaayush (shuklaayush.eth) 60 | * @custom:contributor dravee (dravee.eth) 61 | * @custom:contributor 0xPatissier 62 | * @custom:contributor pcaversaccio 63 | * @custom:contributor David Eiber 64 | * @custom:contributor csanuragjain 65 | * @custom:contributor sach1r0 66 | * @custom:contributor twojoy0 67 | * @custom:contributor ori_dabush 68 | * @custom:contributor Daniel Gelfand 69 | * @custom:contributor okkothejawa 70 | * @custom:contributor FlameHorizon 71 | * @custom:contributor vdrg 72 | * @custom:contributor dmitriia 73 | * @custom:contributor bokeh-eth 74 | * @custom:contributor asutorufos 75 | * @custom:contributor rfart(rfa) 76 | * @custom:contributor Riley Holterhus 77 | * @custom:contributor big-tech-sux 78 | * @notice Seaport is a generalized native token/ERC20/ERC721/ERC1155 79 | * marketplace with lightweight methods for common routes as well as 80 | * more flexible methods for composing advanced orders or groups of 81 | * orders. Each order contains an arbitrary number of items that may be 82 | * spent (the "offer") along with an arbitrary number of items that must 83 | * be received back by the indicated recipients (the "consideration"). 84 | */ 85 | contract Seaport is Consideration { 86 | /** 87 | * @notice Derive and set hashes, reference chainId, and associated domain 88 | * separator during deployment. 89 | * 90 | * @param conduitController A contract that deploys conduits, or proxies 91 | * that may optionally be used to transfer approved 92 | * ERC20/721/1155 tokens. 93 | */ 94 | constructor(address conduitController) Consideration(conduitController) {} 95 | 96 | /** 97 | * @dev Internal pure function to retrieve and return the name of this 98 | * contract. 99 | * 100 | * @return The name of this contract. 101 | */ 102 | function _name() internal pure override returns (string memory) { 103 | // Return the name of the contract. 104 | assembly { 105 | mstore(0x20, 0x20) 106 | mstore(0x47, 0x07536561706f7274) 107 | return(0x20, 0x60) 108 | } 109 | } 110 | 111 | /** 112 | * @dev Internal pure function to retrieve the name of this contract as a 113 | * string that will be used to derive the name hash in the constructor. 114 | * 115 | * @return The name of this contract as a string. 116 | */ 117 | function _nameString() internal pure override returns (string memory) { 118 | // Return the name of the contract. 119 | return "Seaport"; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/conduit/Conduit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.14; 3 | 4 | import { 5 | ConduitInterface 6 | } from "seaport-types/src/interfaces/ConduitInterface.sol"; 7 | 8 | import { 9 | ConduitItemType 10 | } from "seaport-types/src/conduit/lib/ConduitEnums.sol"; 11 | 12 | import { TokenTransferrer } from "../lib/TokenTransferrer.sol"; 13 | 14 | import { 15 | ConduitBatch1155Transfer, 16 | ConduitTransfer 17 | } from "seaport-types/src/conduit/lib/ConduitStructs.sol"; 18 | 19 | import { 20 | ChannelClosed_channel_ptr, 21 | ChannelClosed_error_length, 22 | ChannelClosed_error_ptr, 23 | ChannelClosed_error_signature, 24 | ChannelKey_channel_ptr, 25 | ChannelKey_length, 26 | ChannelKey_slot_ptr 27 | } from "seaport-types/src/conduit/lib/ConduitConstants.sol"; 28 | 29 | /** 30 | * @title Conduit 31 | * @author 0age 32 | * @notice This contract serves as an originator for "proxied" transfers. Each 33 | * conduit is deployed and controlled by a "conduit controller" that can 34 | * add and remove "channels" or contracts that can instruct the conduit 35 | * to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each 36 | * conduit has an owner that can arbitrarily add or remove channels, and 37 | * a malicious or negligent owner can add a channel that allows for any 38 | * approved ERC20/721/1155 tokens to be taken immediately — be extremely 39 | * cautious with what conduits you give token approvals to!* 40 | */ 41 | contract Conduit is ConduitInterface, TokenTransferrer { 42 | // Set deployer as an immutable controller that can update channel statuses. 43 | address private immutable _controller; 44 | 45 | // Track the status of each channel. 46 | mapping(address => bool) private _channels; 47 | 48 | /** 49 | * @notice Ensure that the caller is currently registered as an open channel 50 | * on the conduit. 51 | */ 52 | modifier onlyOpenChannel() { 53 | // Utilize assembly to access channel storage mapping directly. 54 | assembly { 55 | // Write the caller to scratch space. 56 | mstore(ChannelKey_channel_ptr, caller()) 57 | 58 | // Write the storage slot for _channels to scratch space. 59 | mstore(ChannelKey_slot_ptr, _channels.slot) 60 | 61 | // Derive the position in storage of _channels[msg.sender] 62 | // and check if the stored value is zero. 63 | if iszero( 64 | sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length)) 65 | ) { 66 | // The caller is not an open channel; revert with 67 | // ChannelClosed(caller). First, set error signature in memory. 68 | mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature) 69 | 70 | // Next, set the caller as the argument. 71 | mstore(ChannelClosed_channel_ptr, caller()) 72 | 73 | // Finally, revert, returning full custom error with argument 74 | // data in memory. 75 | // revert(abi.encodeWithSignature( 76 | // "ChannelClosed(address)", caller() 77 | // )) 78 | revert(ChannelClosed_error_ptr, ChannelClosed_error_length) 79 | } 80 | } 81 | 82 | // Continue with function execution. 83 | _; 84 | } 85 | 86 | /** 87 | * @notice In the constructor, set the deployer as the controller. 88 | */ 89 | constructor() { 90 | // Set the deployer as the controller. 91 | _controller = msg.sender; 92 | } 93 | 94 | /** 95 | * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller 96 | * with an open channel can call this function. Note that channels 97 | * are expected to implement reentrancy protection if desired, and 98 | * that cross-channel reentrancy may be possible if the conduit has 99 | * multiple open channels at once. Also note that channels are 100 | * expected to implement checks against transferring any zero-amount 101 | * items if that constraint is desired. 102 | * 103 | * @param transfers The ERC20/721/1155 transfers to perform. 104 | * 105 | * @return magicValue A magic value indicating that the transfers were 106 | * performed successfully. 107 | */ 108 | function execute( 109 | ConduitTransfer[] calldata transfers 110 | ) external override onlyOpenChannel returns (bytes4 magicValue) { 111 | // Retrieve the total number of transfers and place on the stack. 112 | uint256 totalStandardTransfers = transfers.length; 113 | 114 | // Iterate over each transfer. 115 | for (uint256 i = 0; i < totalStandardTransfers; ) { 116 | // Retrieve the transfer in question and perform the transfer. 117 | _transfer(transfers[i]); 118 | 119 | // Skip overflow check as for loop is indexed starting at zero. 120 | unchecked { 121 | ++i; 122 | } 123 | } 124 | 125 | // Return a magic value indicating that the transfers were performed. 126 | magicValue = this.execute.selector; 127 | } 128 | 129 | /** 130 | * @notice Execute a sequence of batch 1155 item transfers. Only a caller 131 | * with an open channel can call this function. Note that channels 132 | * are expected to implement reentrancy protection if desired, and 133 | * that cross-channel reentrancy may be possible if the conduit has 134 | * multiple open channels at once. Also note that channels are 135 | * expected to implement checks against transferring any zero-amount 136 | * items if that constraint is desired. 137 | * 138 | * @param batchTransfers The 1155 batch item transfers to perform. 139 | * 140 | * @return magicValue A magic value indicating that the item transfers were 141 | * performed successfully. 142 | */ 143 | function executeBatch1155( 144 | ConduitBatch1155Transfer[] calldata batchTransfers 145 | ) external override onlyOpenChannel returns (bytes4 magicValue) { 146 | // Perform 1155 batch transfers. Note that memory should be considered 147 | // entirely corrupted from this point forward. 148 | _performERC1155BatchTransfers(batchTransfers); 149 | 150 | // Return a magic value indicating that the transfers were performed. 151 | magicValue = this.executeBatch1155.selector; 152 | } 153 | 154 | /** 155 | * @notice Execute a sequence of transfers, both single ERC20/721/1155 item 156 | * transfers as well as batch 1155 item transfers. Only a caller 157 | * with an open channel can call this function. Note that channels 158 | * are expected to implement reentrancy protection if desired, and 159 | * that cross-channel reentrancy may be possible if the conduit has 160 | * multiple open channels at once. Also note that channels are 161 | * expected to implement checks against transferring any zero-amount 162 | * items if that constraint is desired. 163 | * 164 | * @param standardTransfers The ERC20/721/1155 item transfers to perform. 165 | * @param batchTransfers The 1155 batch item transfers to perform. 166 | * 167 | * @return magicValue A magic value indicating that the item transfers were 168 | * performed successfully. 169 | */ 170 | function executeWithBatch1155( 171 | ConduitTransfer[] calldata standardTransfers, 172 | ConduitBatch1155Transfer[] calldata batchTransfers 173 | ) external override onlyOpenChannel returns (bytes4 magicValue) { 174 | // Retrieve the total number of transfers and place on the stack. 175 | uint256 totalStandardTransfers = standardTransfers.length; 176 | 177 | // Iterate over each standard transfer. 178 | for (uint256 i = 0; i < totalStandardTransfers; ) { 179 | // Retrieve the transfer in question and perform the transfer. 180 | _transfer(standardTransfers[i]); 181 | 182 | // Skip overflow check as for loop is indexed starting at zero. 183 | unchecked { 184 | ++i; 185 | } 186 | } 187 | 188 | // Perform 1155 batch transfers. Note that memory should be considered 189 | // entirely corrupted from this point forward aside from the free memory 190 | // pointer having the default value. 191 | _performERC1155BatchTransfers(batchTransfers); 192 | 193 | // Return a magic value indicating that the transfers were performed. 194 | magicValue = this.executeWithBatch1155.selector; 195 | } 196 | 197 | /** 198 | * @notice Open or close a given channel. Only callable by the controller. 199 | * 200 | * @param channel The channel to open or close. 201 | * @param isOpen The status of the channel (either open or closed). 202 | */ 203 | function updateChannel(address channel, bool isOpen) external override { 204 | // Ensure that the caller is the controller of this contract. 205 | if (msg.sender != _controller) { 206 | revert InvalidController(); 207 | } 208 | 209 | // Ensure that the channel does not already have the indicated status. 210 | if (_channels[channel] == isOpen) { 211 | revert ChannelStatusAlreadySet(channel, isOpen); 212 | } 213 | 214 | // Update the status of the channel. 215 | _channels[channel] = isOpen; 216 | 217 | // Emit a corresponding event. 218 | emit ChannelUpdated(channel, isOpen); 219 | } 220 | 221 | /** 222 | * @dev Internal function to transfer a given ERC20/721/1155 item. Note that 223 | * channels are expected to implement checks against transferring any 224 | * zero-amount items if that constraint is desired. 225 | * 226 | * @param item The ERC20/721/1155 item to transfer. 227 | */ 228 | function _transfer(ConduitTransfer calldata item) internal { 229 | // Determine the transfer method based on the respective item type. 230 | if (item.itemType == ConduitItemType.ERC20) { 231 | // Transfer ERC20 token. Note that item.identifier is ignored and 232 | // therefore ERC20 transfer items are potentially malleable — this 233 | // check should be performed by the calling channel if a constraint 234 | // on item malleability is desired. 235 | _performERC20Transfer(item.token, item.from, item.to, item.amount); 236 | } else if (item.itemType == ConduitItemType.ERC721) { 237 | // Ensure that exactly one 721 item is being transferred. 238 | if (item.amount != 1) { 239 | revert InvalidERC721TransferAmount(item.amount); 240 | } 241 | 242 | // Transfer ERC721 token. 243 | _performERC721Transfer( 244 | item.token, 245 | item.from, 246 | item.to, 247 | item.identifier 248 | ); 249 | } else if (item.itemType == ConduitItemType.ERC1155) { 250 | // Transfer ERC1155 token. 251 | _performERC1155Transfer( 252 | item.token, 253 | item.from, 254 | item.to, 255 | item.identifier, 256 | item.amount 257 | ); 258 | } else { 259 | // Throw with an error. 260 | revert InvalidItemType(); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/conduit/ConduitController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.14; 3 | 4 | import { 5 | ConduitControllerInterface 6 | } from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; 7 | 8 | import { 9 | ConduitInterface 10 | } from "seaport-types/src/interfaces/ConduitInterface.sol"; 11 | 12 | import { Conduit } from "./Conduit.sol"; 13 | 14 | /** 15 | * @title ConduitController 16 | * @author 0age 17 | * @notice ConduitController enables deploying and managing new conduits, or 18 | * contracts that allow registered callers (or open "channels") to 19 | * transfer approved ERC20/721/1155 tokens on their behalf. 20 | */ 21 | contract ConduitController is ConduitControllerInterface { 22 | // Register keys, owners, new potential owners, and channels by conduit. 23 | mapping(address => ConduitProperties) internal _conduits; 24 | 25 | // Set conduit creation code and runtime code hashes as immutable arguments. 26 | bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; 27 | bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH; 28 | 29 | /** 30 | * @dev Initialize contract by deploying a conduit and setting the creation 31 | * code and runtime code hashes as immutable arguments. 32 | */ 33 | constructor() { 34 | // Derive the conduit creation code hash and set it as an immutable. 35 | _CONDUIT_CREATION_CODE_HASH = keccak256(type(Conduit).creationCode); 36 | 37 | // Deploy a conduit with the zero hash as the salt. 38 | Conduit zeroConduit = new Conduit{ salt: bytes32(0) }(); 39 | 40 | // Retrieve the conduit runtime code hash and set it as an immutable. 41 | _CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash; 42 | } 43 | 44 | /** 45 | * @notice Deploy a new conduit using a supplied conduit key and assigning 46 | * an initial owner for the deployed conduit. Note that the first 47 | * twenty bytes of the supplied conduit key must match the caller 48 | * and that a new conduit cannot be created if one has already been 49 | * deployed using the same conduit key. 50 | * 51 | * @param conduitKey The conduit key used to deploy the conduit. Note that 52 | * the first twenty bytes of the conduit key must match 53 | * the caller of this contract. 54 | * @param initialOwner The initial owner to set for the new conduit. 55 | * 56 | * @return conduit The address of the newly deployed conduit. 57 | */ 58 | function createConduit( 59 | bytes32 conduitKey, 60 | address initialOwner 61 | ) external override returns (address conduit) { 62 | // Ensure that an initial owner has been supplied. 63 | if (initialOwner == address(0)) { 64 | revert InvalidInitialOwner(); 65 | } 66 | 67 | // If the first 20 bytes of the conduit key do not match the caller... 68 | if (address(uint160(bytes20(conduitKey))) != msg.sender) { 69 | // Revert with an error indicating that the creator is invalid. 70 | revert InvalidCreator(); 71 | } 72 | 73 | // Derive address from deployer, conduit key and creation code hash. 74 | conduit = address( 75 | uint160( 76 | uint256( 77 | keccak256( 78 | abi.encodePacked( 79 | bytes1(0xff), 80 | address(this), 81 | conduitKey, 82 | _CONDUIT_CREATION_CODE_HASH 83 | ) 84 | ) 85 | ) 86 | ) 87 | ); 88 | 89 | // If derived conduit exists, as evidenced by comparing runtime code... 90 | if (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH) { 91 | // Revert with an error indicating that the conduit already exists. 92 | revert ConduitAlreadyExists(conduit); 93 | } 94 | 95 | // Deploy the conduit via CREATE2 using the conduit key as the salt. 96 | new Conduit{ salt: conduitKey }(); 97 | 98 | // Initialize storage variable referencing conduit properties. 99 | ConduitProperties storage conduitProperties = _conduits[conduit]; 100 | 101 | // Set the supplied initial owner as the owner of the conduit. 102 | conduitProperties.owner = initialOwner; 103 | 104 | // Set conduit key used to deploy the conduit to enable reverse lookup. 105 | conduitProperties.key = conduitKey; 106 | 107 | // Emit an event indicating that the conduit has been deployed. 108 | emit NewConduit(conduit, conduitKey); 109 | 110 | // Emit an event indicating that conduit ownership has been assigned. 111 | emit OwnershipTransferred(conduit, address(0), initialOwner); 112 | } 113 | 114 | /** 115 | * @notice Open or close a channel on a given conduit, thereby allowing the 116 | * specified account to execute transfers against that conduit. 117 | * Extreme care must be taken when updating channels, as malicious 118 | * or vulnerable channels can transfer any ERC20, ERC721 and ERC1155 119 | * tokens where the token holder has granted the conduit approval. 120 | * Only the owner of the conduit in question may call this function. 121 | * 122 | * @param conduit The conduit for which to open or close the channel. 123 | * @param channel The channel to open or close on the conduit. 124 | * @param isOpen A boolean indicating whether to open or close the channel. 125 | */ 126 | function updateChannel( 127 | address conduit, 128 | address channel, 129 | bool isOpen 130 | ) external override { 131 | // Ensure the caller is the current owner of the conduit in question. 132 | _assertCallerIsConduitOwner(conduit); 133 | 134 | // Call the conduit, updating the channel. 135 | ConduitInterface(conduit).updateChannel(channel, isOpen); 136 | 137 | // Retrieve storage region where channels for the conduit are tracked. 138 | ConduitProperties storage conduitProperties = _conduits[conduit]; 139 | 140 | // Retrieve the index, if one currently exists, for the updated channel. 141 | uint256 channelIndexPlusOne = ( 142 | conduitProperties.channelIndexesPlusOne[channel] 143 | ); 144 | 145 | // Determine whether the updated channel is already tracked as open. 146 | bool channelPreviouslyOpen = channelIndexPlusOne != 0; 147 | 148 | // If the channel has been set to open and was previously closed... 149 | if (isOpen && !channelPreviouslyOpen) { 150 | // Add the channel to the channels array for the conduit. 151 | conduitProperties.channels.push(channel); 152 | 153 | // Add new open channel length to associated mapping as index + 1. 154 | conduitProperties.channelIndexesPlusOne[channel] = ( 155 | conduitProperties.channels.length 156 | ); 157 | } else if (!isOpen && channelPreviouslyOpen) { 158 | // Set a previously open channel as closed via "swap & pop" method. 159 | // Decrement located index to get the index of the closed channel. 160 | uint256 removedChannelIndex; 161 | 162 | // Skip underflow check as channelPreviouslyOpen being true ensures 163 | // that channelIndexPlusOne is nonzero. 164 | unchecked { 165 | removedChannelIndex = channelIndexPlusOne - 1; 166 | } 167 | 168 | // Use length of channels array to determine index of last channel. 169 | uint256 finalChannelIndex = conduitProperties.channels.length - 1; 170 | 171 | // If closed channel is not last channel in the channels array... 172 | if (finalChannelIndex != removedChannelIndex) { 173 | // Retrieve the final channel and place the value on the stack. 174 | address finalChannel = ( 175 | conduitProperties.channels[finalChannelIndex] 176 | ); 177 | 178 | // Overwrite the removed channel using the final channel value. 179 | conduitProperties.channels[removedChannelIndex] = finalChannel; 180 | 181 | // Update final index in associated mapping to removed index. 182 | conduitProperties.channelIndexesPlusOne[finalChannel] = ( 183 | channelIndexPlusOne 184 | ); 185 | } 186 | 187 | // Remove the last channel from the channels array for the conduit. 188 | conduitProperties.channels.pop(); 189 | 190 | // Remove the closed channel from associated mapping of indexes. 191 | delete conduitProperties.channelIndexesPlusOne[channel]; 192 | } 193 | } 194 | 195 | /** 196 | * @notice Initiate conduit ownership transfer by assigning a new potential 197 | * owner for the given conduit. Once set, the new potential owner 198 | * may call `acceptOwnership` to claim ownership of the conduit. 199 | * Only the owner of the conduit in question may call this function. 200 | * 201 | * @param conduit The conduit for which to initiate ownership transfer. 202 | * @param newPotentialOwner The new potential owner of the conduit. 203 | */ 204 | function transferOwnership( 205 | address conduit, 206 | address newPotentialOwner 207 | ) external override { 208 | // Ensure the caller is the current owner of the conduit in question. 209 | _assertCallerIsConduitOwner(conduit); 210 | 211 | // Ensure the new potential owner is not an invalid address. 212 | if (newPotentialOwner == address(0)) { 213 | revert NewPotentialOwnerIsZeroAddress(conduit); 214 | } 215 | 216 | // Ensure the new potential owner is not already set. 217 | if (newPotentialOwner == _conduits[conduit].potentialOwner) { 218 | revert NewPotentialOwnerAlreadySet(conduit, newPotentialOwner); 219 | } 220 | 221 | // Emit an event indicating that the potential owner has been updated. 222 | emit PotentialOwnerUpdated(newPotentialOwner); 223 | 224 | // Set the new potential owner as the potential owner of the conduit. 225 | _conduits[conduit].potentialOwner = newPotentialOwner; 226 | } 227 | 228 | /** 229 | * @notice Clear the currently set potential owner, if any, from a conduit. 230 | * Only the owner of the conduit in question may call this function. 231 | * 232 | * @param conduit The conduit for which to cancel ownership transfer. 233 | */ 234 | function cancelOwnershipTransfer(address conduit) external override { 235 | // Ensure the caller is the current owner of the conduit in question. 236 | _assertCallerIsConduitOwner(conduit); 237 | 238 | // Ensure that ownership transfer is currently possible. 239 | if (_conduits[conduit].potentialOwner == address(0)) { 240 | revert NoPotentialOwnerCurrentlySet(conduit); 241 | } 242 | 243 | // Emit an event indicating that the potential owner has been cleared. 244 | emit PotentialOwnerUpdated(address(0)); 245 | 246 | // Clear the current new potential owner from the conduit. 247 | _conduits[conduit].potentialOwner = address(0); 248 | } 249 | 250 | /** 251 | * @notice Accept ownership of a supplied conduit. Only accounts that the 252 | * current owner has set as the new potential owner may call this 253 | * function. 254 | * 255 | * @param conduit The conduit for which to accept ownership. 256 | */ 257 | function acceptOwnership(address conduit) external override { 258 | // Ensure that the conduit in question exists. 259 | _assertConduitExists(conduit); 260 | 261 | // If caller does not match current potential owner of the conduit... 262 | if (msg.sender != _conduits[conduit].potentialOwner) { 263 | // Revert, indicating that caller is not current potential owner. 264 | revert CallerIsNotNewPotentialOwner(conduit); 265 | } 266 | 267 | // Emit an event indicating that the potential owner has been cleared. 268 | emit PotentialOwnerUpdated(address(0)); 269 | 270 | // Clear the current new potential owner from the conduit. 271 | _conduits[conduit].potentialOwner = address(0); 272 | 273 | // Emit an event indicating conduit ownership has been transferred. 274 | emit OwnershipTransferred( 275 | conduit, 276 | _conduits[conduit].owner, 277 | msg.sender 278 | ); 279 | 280 | // Set the caller as the owner of the conduit. 281 | _conduits[conduit].owner = msg.sender; 282 | } 283 | 284 | /** 285 | * @notice Retrieve the current owner of a deployed conduit. 286 | * 287 | * @param conduit The conduit for which to retrieve the associated owner. 288 | * 289 | * @return owner The owner of the supplied conduit. 290 | */ 291 | function ownerOf( 292 | address conduit 293 | ) external view override returns (address owner) { 294 | // Ensure that the conduit in question exists. 295 | _assertConduitExists(conduit); 296 | 297 | // Retrieve the current owner of the conduit in question. 298 | owner = _conduits[conduit].owner; 299 | } 300 | 301 | /** 302 | * @notice Retrieve the conduit key for a deployed conduit via reverse 303 | * lookup. 304 | * 305 | * @param conduit The conduit for which to retrieve the associated conduit 306 | * key. 307 | * 308 | * @return conduitKey The conduit key used to deploy the supplied conduit. 309 | */ 310 | function getKey( 311 | address conduit 312 | ) external view override returns (bytes32 conduitKey) { 313 | // Attempt to retrieve a conduit key for the conduit in question. 314 | conduitKey = _conduits[conduit].key; 315 | 316 | // Revert if no conduit key was located. 317 | if (conduitKey == bytes32(0)) { 318 | revert NoConduit(); 319 | } 320 | } 321 | 322 | /** 323 | * @notice Derive the conduit associated with a given conduit key and 324 | * determine whether that conduit exists (i.e. whether it has been 325 | * deployed). 326 | * 327 | * @param conduitKey The conduit key used to derive the conduit. 328 | * 329 | * @return conduit The derived address of the conduit. 330 | * @return exists A boolean indicating whether the derived conduit has been 331 | * deployed or not. 332 | */ 333 | function getConduit( 334 | bytes32 conduitKey 335 | ) external view override returns (address conduit, bool exists) { 336 | // Derive address from deployer, conduit key and creation code hash. 337 | conduit = address( 338 | uint160( 339 | uint256( 340 | keccak256( 341 | abi.encodePacked( 342 | bytes1(0xff), 343 | address(this), 344 | conduitKey, 345 | _CONDUIT_CREATION_CODE_HASH 346 | ) 347 | ) 348 | ) 349 | ) 350 | ); 351 | 352 | // Determine whether conduit exists by retrieving its runtime code. 353 | exists = (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH); 354 | } 355 | 356 | /** 357 | * @notice Retrieve the potential owner, if any, for a given conduit. The 358 | * current owner may set a new potential owner via 359 | * `transferOwnership` and that owner may then accept ownership of 360 | * the conduit in question via `acceptOwnership`. 361 | * 362 | * @param conduit The conduit for which to retrieve the potential owner. 363 | * 364 | * @return potentialOwner The potential owner, if any, for the conduit. 365 | */ 366 | function getPotentialOwner( 367 | address conduit 368 | ) external view override returns (address potentialOwner) { 369 | // Ensure that the conduit in question exists. 370 | _assertConduitExists(conduit); 371 | 372 | // Retrieve the current potential owner of the conduit in question. 373 | potentialOwner = _conduits[conduit].potentialOwner; 374 | } 375 | 376 | /** 377 | * @notice Retrieve the status (either open or closed) of a given channel on 378 | * a conduit. 379 | * 380 | * @param conduit The conduit for which to retrieve the channel status. 381 | * @param channel The channel for which to retrieve the status. 382 | * 383 | * @return isOpen The status of the channel on the given conduit. 384 | */ 385 | function getChannelStatus( 386 | address conduit, 387 | address channel 388 | ) external view override returns (bool isOpen) { 389 | // Ensure that the conduit in question exists. 390 | _assertConduitExists(conduit); 391 | 392 | // Retrieve the current channel status for the conduit in question. 393 | isOpen = _conduits[conduit].channelIndexesPlusOne[channel] != 0; 394 | } 395 | 396 | /** 397 | * @notice Retrieve the total number of open channels for a given conduit. 398 | * 399 | * @param conduit The conduit for which to retrieve the total channel count. 400 | * 401 | * @return totalChannels The total number of open channels for the conduit. 402 | */ 403 | function getTotalChannels( 404 | address conduit 405 | ) external view override returns (uint256 totalChannels) { 406 | // Ensure that the conduit in question exists. 407 | _assertConduitExists(conduit); 408 | 409 | // Retrieve the total open channel count for the conduit in question. 410 | totalChannels = _conduits[conduit].channels.length; 411 | } 412 | 413 | /** 414 | * @notice Retrieve an open channel at a specific index for a given conduit. 415 | * Note that the index of a channel can change as a result of other 416 | * channels being closed on the conduit. 417 | * 418 | * @param conduit The conduit for which to retrieve the open channel. 419 | * @param channelIndex The index of the channel in question. 420 | * 421 | * @return channel The open channel, if any, at the specified channel index. 422 | */ 423 | function getChannel( 424 | address conduit, 425 | uint256 channelIndex 426 | ) external view override returns (address channel) { 427 | // Ensure that the conduit in question exists. 428 | _assertConduitExists(conduit); 429 | 430 | // Retrieve the total open channel count for the conduit in question. 431 | uint256 totalChannels = _conduits[conduit].channels.length; 432 | 433 | // Ensure that the supplied index is within range. 434 | if (channelIndex >= totalChannels) { 435 | revert ChannelOutOfRange(conduit); 436 | } 437 | 438 | // Retrieve the channel at the given index. 439 | channel = _conduits[conduit].channels[channelIndex]; 440 | } 441 | 442 | /** 443 | * @notice Retrieve all open channels for a given conduit. Note that calling 444 | * this function for a conduit with many channels will revert with 445 | * an out-of-gas error. 446 | * 447 | * @param conduit The conduit for which to retrieve open channels. 448 | * 449 | * @return channels An array of open channels on the given conduit. 450 | */ 451 | function getChannels( 452 | address conduit 453 | ) external view override returns (address[] memory channels) { 454 | // Ensure that the conduit in question exists. 455 | _assertConduitExists(conduit); 456 | 457 | // Retrieve all of the open channels on the conduit in question. 458 | channels = _conduits[conduit].channels; 459 | } 460 | 461 | /** 462 | * @dev Retrieve the conduit creation code and runtime code hashes. 463 | */ 464 | function getConduitCodeHashes() 465 | external 466 | view 467 | override 468 | returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash) 469 | { 470 | // Retrieve the conduit creation code hash from runtime. 471 | creationCodeHash = _CONDUIT_CREATION_CODE_HASH; 472 | 473 | // Retrieve the conduit runtime code hash from runtime. 474 | runtimeCodeHash = _CONDUIT_RUNTIME_CODE_HASH; 475 | } 476 | 477 | /** 478 | * @dev Private view function to revert if the caller is not the owner of a 479 | * given conduit. 480 | * 481 | * @param conduit The conduit for which to assert ownership. 482 | */ 483 | function _assertCallerIsConduitOwner(address conduit) private view { 484 | // Ensure that the conduit in question exists. 485 | _assertConduitExists(conduit); 486 | 487 | // If the caller does not match the current owner of the conduit... 488 | if (msg.sender != _conduits[conduit].owner) { 489 | // Revert, indicating that the caller is not the owner. 490 | revert CallerIsNotOwner(conduit); 491 | } 492 | } 493 | 494 | /** 495 | * @dev Private view function to revert if a given conduit does not exist. 496 | * 497 | * @param conduit The conduit for which to assert existence. 498 | */ 499 | function _assertConduitExists(address conduit) private view { 500 | // Attempt to retrieve a conduit key for the conduit in question. 501 | if (_conduits[conduit].key == bytes32(0)) { 502 | // Revert if no conduit key was located. 503 | revert NoConduit(); 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/helpers/SeaportRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | SeaportRouterInterface 6 | } from "seaport-types/src/interfaces/SeaportRouterInterface.sol"; 7 | 8 | import { 9 | SeaportInterface 10 | } from "seaport-types/src/interfaces/SeaportInterface.sol"; 11 | 12 | import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol"; 13 | 14 | import { 15 | AdvancedOrder, 16 | CriteriaResolver, 17 | Execution, 18 | FulfillmentComponent 19 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 20 | 21 | /** 22 | * @title SeaportRouter 23 | * @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth) 24 | * @notice A utility contract for fulfilling orders with multiple 25 | * Seaport versions. DISCLAIMER: This contract only works when 26 | * all consideration items across all listings are native tokens. 27 | */ 28 | contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard { 29 | /// @dev The allowed v1.4 contract usable through this router. 30 | address private immutable _SEAPORT_V1_4; 31 | /// @dev The allowed v1.5 contract usable through this router. 32 | address private immutable _SEAPORT_V1_5; 33 | 34 | /** 35 | * @dev Deploy contract with the supported Seaport contracts. 36 | * 37 | * @param seaportV1point4 The address of the Seaport v1.4 contract. 38 | * @param seaportV1point5 The address of the Seaport v1.5 contract. 39 | */ 40 | constructor(address seaportV1point4, address seaportV1point5) { 41 | _SEAPORT_V1_4 = seaportV1point4; 42 | _SEAPORT_V1_5 = seaportV1point5; 43 | } 44 | 45 | /** 46 | * @dev Fallback function to receive excess ether, in case total amount of 47 | * ether sent is more than the amount required to fulfill the order. 48 | */ 49 | receive() external payable override { 50 | // Ensure we only receive ether from Seaport. 51 | _assertSeaportAllowed(msg.sender); 52 | } 53 | 54 | /** 55 | * @notice Fulfill available advanced orders through multiple Seaport 56 | * versions. 57 | * See {SeaportInterface-fulfillAvailableAdvancedOrders} 58 | * 59 | * @param params The parameters for fulfilling available advanced orders. 60 | */ 61 | function fulfillAvailableAdvancedOrders( 62 | FulfillAvailableAdvancedOrdersParams calldata params 63 | ) 64 | external 65 | payable 66 | override 67 | returns ( 68 | bool[][] memory availableOrders, 69 | Execution[][] memory executions 70 | ) 71 | { 72 | // Ensure this function cannot be triggered during a reentrant call. 73 | _setReentrancyGuard(true); 74 | 75 | // Put the number of Seaport contracts on the stack. 76 | uint256 seaportContractsLength = params.seaportContracts.length; 77 | 78 | // Set the availableOrders and executions arrays to the correct length. 79 | availableOrders = new bool[][](seaportContractsLength); 80 | executions = new Execution[][](seaportContractsLength); 81 | 82 | // Track the number of order fulfillments left. 83 | uint256 fulfillmentsLeft = params.maximumFulfilled; 84 | 85 | // To help avoid stack too deep errors, we format the calldata 86 | // params in a struct and put it on the stack. 87 | AdvancedOrder[] memory emptyAdvancedOrders; 88 | CriteriaResolver[] memory emptyCriteriaResolvers; 89 | FulfillmentComponent[][] memory emptyFulfillmentComponents; 90 | CalldataParams memory calldataParams = CalldataParams({ 91 | advancedOrders: emptyAdvancedOrders, 92 | criteriaResolvers: emptyCriteriaResolvers, 93 | offerFulfillments: emptyFulfillmentComponents, 94 | considerationFulfillments: emptyFulfillmentComponents, 95 | fulfillerConduitKey: params.fulfillerConduitKey, 96 | recipient: params.recipient, 97 | maximumFulfilled: fulfillmentsLeft 98 | }); 99 | 100 | // If recipient is not provided assign to msg.sender. 101 | if (calldataParams.recipient == address(0)) { 102 | calldataParams.recipient = msg.sender; 103 | } 104 | 105 | // Iterate through the provided Seaport contracts. 106 | for (uint256 i = 0; i < params.seaportContracts.length; ) { 107 | // Ensure the provided Seaport contract is allowed. 108 | _assertSeaportAllowed(params.seaportContracts[i]); 109 | 110 | // Put the order params on the stack. 111 | AdvancedOrderParams calldata orderParams = params 112 | .advancedOrderParams[i]; 113 | 114 | // Assign the variables to the calldata params. 115 | calldataParams.advancedOrders = orderParams.advancedOrders; 116 | calldataParams.criteriaResolvers = orderParams.criteriaResolvers; 117 | calldataParams.offerFulfillments = orderParams.offerFulfillments; 118 | calldataParams.considerationFulfillments = orderParams 119 | .considerationFulfillments; 120 | 121 | // Execute the orders, collecting availableOrders and executions. 122 | // This is wrapped in a try/catch in case a single order is 123 | // executed that is no longer available, leading to a revert 124 | // with `NoSpecifiedOrdersAvailable()` that can be ignored. 125 | try 126 | SeaportInterface(params.seaportContracts[i]) 127 | .fulfillAvailableAdvancedOrders{ 128 | value: orderParams.etherValue 129 | }( 130 | calldataParams.advancedOrders, 131 | calldataParams.criteriaResolvers, 132 | calldataParams.offerFulfillments, 133 | calldataParams.considerationFulfillments, 134 | calldataParams.fulfillerConduitKey, 135 | calldataParams.recipient, 136 | calldataParams.maximumFulfilled 137 | ) 138 | returns ( 139 | bool[] memory newAvailableOrders, 140 | Execution[] memory newExecutions 141 | ) { 142 | availableOrders[i] = newAvailableOrders; 143 | executions[i] = newExecutions; 144 | 145 | // Subtract the number of orders fulfilled. 146 | uint256 newAvailableOrdersLength = newAvailableOrders.length; 147 | for (uint256 j = 0; j < newAvailableOrdersLength; ) { 148 | if (newAvailableOrders[j]) { 149 | unchecked { 150 | --fulfillmentsLeft; 151 | ++j; 152 | } 153 | } 154 | } 155 | 156 | // Break if the maximum number of executions has been reached. 157 | if (fulfillmentsLeft == 0) { 158 | break; 159 | } 160 | } catch (bytes memory data) { 161 | // Set initial value of first four bytes of revert data 162 | // to the mask. 163 | bytes4 customErrorSelector = bytes4(0xffffffff); 164 | 165 | // Utilize assembly to read first four bytes 166 | // (if present) directly. 167 | assembly { 168 | // Combine original mask with first four bytes of 169 | // revert data. 170 | customErrorSelector := and( 171 | // Data begins after length offset. 172 | mload(add(data, 0x20)), 173 | customErrorSelector 174 | ) 175 | } 176 | 177 | // Pass through the custom error if the error is 178 | // not NoSpecifiedOrdersAvailable() 179 | if ( 180 | customErrorSelector != NoSpecifiedOrdersAvailable.selector 181 | ) { 182 | assembly { 183 | revert(add(data, 32), mload(data)) 184 | } 185 | } 186 | } 187 | 188 | // Update fulfillments left. 189 | calldataParams.maximumFulfilled = fulfillmentsLeft; 190 | 191 | unchecked { 192 | ++i; 193 | } 194 | } 195 | 196 | // Throw an error if no orders were fulfilled. 197 | if (fulfillmentsLeft == params.maximumFulfilled) { 198 | revert NoSpecifiedOrdersAvailable(); 199 | } 200 | 201 | // Return excess ether that may not have been used or was sent back. 202 | if (address(this).balance > 0) { 203 | _returnExcessEther(); 204 | } 205 | 206 | // Clear the reentrancy guard. 207 | _clearReentrancyGuard(); 208 | } 209 | 210 | /** 211 | * @notice Returns the Seaport contracts allowed to be used through this 212 | * router. 213 | */ 214 | function getAllowedSeaportContracts() 215 | external 216 | view 217 | override 218 | returns (address[] memory seaportContracts) 219 | { 220 | seaportContracts = new address[](2); 221 | seaportContracts[0] = _SEAPORT_V1_4; 222 | seaportContracts[1] = _SEAPORT_V1_5; 223 | } 224 | 225 | /** 226 | * @dev Reverts if the provided Seaport contract is not allowed. 227 | */ 228 | function _assertSeaportAllowed(address seaport) internal view { 229 | if ( 230 | _cast(seaport == _SEAPORT_V1_4) | _cast(seaport == _SEAPORT_V1_5) == 231 | 0 232 | ) { 233 | revert SeaportNotAllowed(seaport); 234 | } 235 | } 236 | 237 | /** 238 | * @dev Function to return excess ether, in case total amount of 239 | * ether sent is more than the amount required to fulfill the order. 240 | */ 241 | function _returnExcessEther() private { 242 | // Send received funds back to msg.sender. 243 | (bool success, bytes memory data) = payable(msg.sender).call{ 244 | value: address(this).balance 245 | }(""); 246 | 247 | // Revert with an error if the ether transfer failed. 248 | if (!success) { 249 | revert EtherReturnTransferFailed( 250 | msg.sender, 251 | address(this).balance, 252 | data 253 | ); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/lib/AmountDeriver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | AmountDerivationErrors 6 | } from "seaport-types/src/interfaces/AmountDerivationErrors.sol"; 7 | 8 | import { 9 | Error_selector_offset, 10 | InexactFraction_error_length, 11 | InexactFraction_error_selector 12 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 13 | 14 | /** 15 | * @title AmountDeriver 16 | * @author 0age 17 | * @notice AmountDeriver contains view and pure functions related to deriving 18 | * item amounts based on partial fill quantity and on linear 19 | * interpolation based on current time when the start amount and end 20 | * amount differ. 21 | */ 22 | contract AmountDeriver is AmountDerivationErrors { 23 | /** 24 | * @dev Internal view function to derive the current amount of a given item 25 | * based on the current price, the starting price, and the ending 26 | * price. If the start and end prices differ, the current price will be 27 | * interpolated on a linear basis. Note that this function expects that 28 | * the startTime parameter of orderParameters is not greater than the 29 | * current block timestamp and that the endTime parameter is greater 30 | * than the current block timestamp. If this condition is not upheld, 31 | * duration / elapsed / remaining variables will underflow. 32 | * 33 | * @param startAmount The starting amount of the item. 34 | * @param endAmount The ending amount of the item. 35 | * @param startTime The starting time of the order. 36 | * @param endTime The end time of the order. 37 | * @param roundUp A boolean indicating whether the resultant amount 38 | * should be rounded up or down. 39 | * 40 | * @return amount The current amount. 41 | */ 42 | function _locateCurrentAmount( 43 | uint256 startAmount, 44 | uint256 endAmount, 45 | uint256 startTime, 46 | uint256 endTime, 47 | bool roundUp 48 | ) internal view returns (uint256 amount) { 49 | // Only modify end amount if it doesn't already equal start amount. 50 | if (startAmount != endAmount) { 51 | // Declare variables to derive in the subsequent unchecked scope. 52 | uint256 duration; 53 | uint256 elapsed; 54 | uint256 remaining; 55 | 56 | // Skip underflow checks as startTime <= block.timestamp < endTime. 57 | unchecked { 58 | // Derive the duration for the order and place it on the stack. 59 | duration = endTime - startTime; 60 | 61 | // Derive time elapsed since the order started & place on stack. 62 | elapsed = block.timestamp - startTime; 63 | 64 | // Derive time remaining until order expires and place on stack. 65 | remaining = duration - elapsed; 66 | } 67 | 68 | // Aggregate new amounts weighted by time with rounding factor. 69 | uint256 totalBeforeDivision = ((startAmount * remaining) + 70 | (endAmount * elapsed)); 71 | 72 | // Use assembly to combine operations and skip divide-by-zero check. 73 | assembly { 74 | // Multiply by iszero(iszero(totalBeforeDivision)) to ensure 75 | // amount is set to zero if totalBeforeDivision is zero, 76 | // as intermediate overflow can occur if it is zero. 77 | amount := mul( 78 | iszero(iszero(totalBeforeDivision)), 79 | // Subtract 1 from the numerator and add 1 to the result 80 | // if roundUp is true to get proper rounding direction. 81 | // Division is performed with no zero check as duration 82 | // cannot be zero as long as startTime < endTime. 83 | add( 84 | div(sub(totalBeforeDivision, roundUp), duration), 85 | roundUp 86 | ) 87 | ) 88 | } 89 | 90 | // Return the current amount. 91 | return amount; 92 | } 93 | 94 | // Return the original amount as startAmount == endAmount. 95 | return endAmount; 96 | } 97 | 98 | /** 99 | * @dev Internal pure function to return a fraction of a given value and to 100 | * ensure the resultant value does not have any fractional component. 101 | * Note that this function assumes that zero will never be supplied as 102 | * the denominator parameter; invalid / undefined behavior will result 103 | * should a denominator of zero be provided. 104 | * 105 | * @param numerator A value indicating the portion of the order that 106 | * should be filled. 107 | * @param denominator A value indicating the total size of the order. Note 108 | * that this value cannot be equal to zero. 109 | * @param value The value for which to compute the fraction. 110 | * 111 | * @return newValue The value after applying the fraction. 112 | */ 113 | function _getFraction( 114 | uint256 numerator, 115 | uint256 denominator, 116 | uint256 value 117 | ) internal pure returns (uint256 newValue) { 118 | // Return value early in cases where the fraction resolves to 1. 119 | if (numerator == denominator) { 120 | return value; 121 | } 122 | 123 | // Ensure fraction can be applied to the value with no remainder. Note 124 | // that the denominator cannot be zero. 125 | assembly { 126 | // Ensure new value contains no remainder via mulmod operator. 127 | // Credit to @hrkrshnn + @axic for proposing this optimal solution. 128 | if mulmod(value, numerator, denominator) { 129 | // Store left-padded selector with push4, mem[28:32] = selector 130 | mstore(0, InexactFraction_error_selector) 131 | 132 | // revert(abi.encodeWithSignature("InexactFraction()")) 133 | revert(Error_selector_offset, InexactFraction_error_length) 134 | } 135 | } 136 | 137 | // Multiply the numerator by the value and ensure no overflow occurs. 138 | uint256 valueTimesNumerator = value * numerator; 139 | 140 | // Divide by the denominator (note that denominator cannot be zero). 141 | assembly { 142 | // Perform division without zero check. 143 | newValue := div(valueTimesNumerator, denominator) 144 | } 145 | } 146 | 147 | /** 148 | * @dev Internal view function to apply a fraction to a consideration 149 | * or offer item. 150 | * 151 | * @param startAmount The starting amount of the item. 152 | * @param endAmount The ending amount of the item. 153 | * @param numerator A value indicating the portion of the order that 154 | * should be filled. 155 | * @param denominator A value indicating the total size of the order. 156 | * @param startTime The starting time of the order. 157 | * @param endTime The end time of the order. 158 | * @param roundUp A boolean indicating whether the resultant 159 | * amount should be rounded up or down. 160 | * 161 | * @return amount The received item to transfer with the final amount. 162 | */ 163 | function _applyFraction( 164 | uint256 startAmount, 165 | uint256 endAmount, 166 | uint256 numerator, 167 | uint256 denominator, 168 | uint256 startTime, 169 | uint256 endTime, 170 | bool roundUp 171 | ) internal view returns (uint256 amount) { 172 | // If start amount equals end amount, apply fraction to end amount. 173 | if (startAmount == endAmount) { 174 | // Apply fraction to end amount. 175 | amount = _getFraction(numerator, denominator, endAmount); 176 | } else { 177 | // Otherwise, apply fraction to both and interpolated final amount. 178 | amount = _locateCurrentAmount( 179 | _getFraction(numerator, denominator, startAmount), 180 | _getFraction(numerator, denominator, endAmount), 181 | startTime, 182 | endTime, 183 | roundUp 184 | ); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/lib/Assertions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | OrderParameters 6 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 7 | 8 | import { GettersAndDerivers } from "./GettersAndDerivers.sol"; 9 | 10 | import { 11 | TokenTransferrerErrors 12 | } from "seaport-types/src/interfaces/TokenTransferrerErrors.sol"; 13 | 14 | import { CounterManager } from "./CounterManager.sol"; 15 | 16 | import { 17 | AdditionalRecipient_size_shift, 18 | AddressDirtyUpperBitThreshold, 19 | BasicOrder_additionalRecipients_head_cdPtr, 20 | BasicOrder_additionalRecipients_head_ptr, 21 | BasicOrder_addlRecipients_length_cdPtr, 22 | BasicOrder_basicOrderType_cdPtr, 23 | BasicOrder_basicOrderType_range, 24 | BasicOrder_considerationToken_cdPtr, 25 | BasicOrder_offerer_cdPtr, 26 | BasicOrder_offerToken_cdPtr, 27 | BasicOrder_parameters_cdPtr, 28 | BasicOrder_parameters_ptr, 29 | BasicOrder_signature_cdPtr, 30 | BasicOrder_signature_ptr, 31 | BasicOrder_zone_cdPtr 32 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 33 | 34 | import { 35 | Error_selector_offset, 36 | MissingItemAmount_error_length, 37 | MissingItemAmount_error_selector 38 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 39 | 40 | import { 41 | _revertInvalidBasicOrderParameterEncoding, 42 | _revertMissingOriginalConsiderationItems 43 | } from "seaport-types/src/lib/ConsiderationErrors.sol"; 44 | 45 | /** 46 | * @title Assertions 47 | * @author 0age 48 | * @notice Assertions contains logic for making various assertions that do not 49 | * fit neatly within a dedicated semantic scope. 50 | */ 51 | contract Assertions is 52 | GettersAndDerivers, 53 | CounterManager, 54 | TokenTransferrerErrors 55 | { 56 | /** 57 | * @dev Derive and set hashes, reference chainId, and associated domain 58 | * separator during deployment. 59 | * 60 | * @param conduitController A contract that deploys conduits, or proxies 61 | * that may optionally be used to transfer approved 62 | * ERC20/721/1155 tokens. 63 | */ 64 | constructor( 65 | address conduitController 66 | ) GettersAndDerivers(conduitController) {} 67 | 68 | /** 69 | * @dev Internal view function to ensure that the supplied consideration 70 | * array length on a given set of order parameters is not less than the 71 | * original consideration array length for that order and to retrieve 72 | * the current counter for a given order's offerer and zone and use it 73 | * to derive the order hash. 74 | * 75 | * @param orderParameters The parameters of the order to hash. 76 | * 77 | * @return The hash. 78 | */ 79 | function _assertConsiderationLengthAndGetOrderHash( 80 | OrderParameters memory orderParameters 81 | ) internal view returns (bytes32) { 82 | // Ensure supplied consideration array length is not less than original. 83 | _assertConsiderationLengthIsNotLessThanOriginalConsiderationLength( 84 | orderParameters.consideration.length, 85 | orderParameters.totalOriginalConsiderationItems 86 | ); 87 | 88 | // Derive and return order hash using current counter for the offerer. 89 | return 90 | _deriveOrderHash( 91 | orderParameters, 92 | _getCounter(orderParameters.offerer) 93 | ); 94 | } 95 | 96 | /** 97 | * @dev Internal pure function to ensure that the supplied consideration 98 | * array length for an order to be fulfilled is not less than the 99 | * original consideration array length for that order. 100 | * 101 | * @param suppliedConsiderationItemTotal The number of consideration items 102 | * supplied when fulfilling the order. 103 | * @param originalConsiderationItemTotal The number of consideration items 104 | * supplied on initial order creation. 105 | */ 106 | function _assertConsiderationLengthIsNotLessThanOriginalConsiderationLength( 107 | uint256 suppliedConsiderationItemTotal, 108 | uint256 originalConsiderationItemTotal 109 | ) internal pure { 110 | // Ensure supplied consideration array length is not less than original. 111 | if (suppliedConsiderationItemTotal < originalConsiderationItemTotal) { 112 | _revertMissingOriginalConsiderationItems(); 113 | } 114 | } 115 | 116 | /** 117 | * @dev Internal pure function to ensure that a given item amount is not 118 | * zero. 119 | * 120 | * @param amount The amount to check. 121 | */ 122 | function _assertNonZeroAmount(uint256 amount) internal pure { 123 | assembly { 124 | if iszero(amount) { 125 | // Store left-padded selector with push4, mem[28:32] = selector 126 | mstore(0, MissingItemAmount_error_selector) 127 | 128 | // revert(abi.encodeWithSignature("MissingItemAmount()")) 129 | revert(Error_selector_offset, MissingItemAmount_error_length) 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * @dev Internal pure function to validate calldata offsets for dynamic 136 | * types in BasicOrderParameters and other parameters. This ensures 137 | * that functions using the calldata object normally will be using the 138 | * same data as the assembly functions and that values that are bound 139 | * to a given range are within that range. Note that no parameters are 140 | * supplied as all basic order functions use the same calldata 141 | * encoding. 142 | */ 143 | function _assertValidBasicOrderParameters() internal pure { 144 | // Declare a boolean designating basic order parameter offset validity. 145 | bool validOffsets; 146 | 147 | // Utilize assembly in order to read offset data directly from calldata. 148 | assembly { 149 | /* 150 | * Checks: 151 | * 1. Order parameters struct offset == 0x20 152 | * 2. Additional recipients arr offset == 0x240 153 | * 3. Signature offset == 0x260 + (recipients.length * 0x40) 154 | * 4. BasicOrderType between 0 and 23 (i.e. < 24) 155 | * 5. Offerer, zone, offer token, and consideration token have no 156 | * upper dirty bits — each argument is type(uint160).max or less 157 | */ 158 | validOffsets := and( 159 | and( 160 | and( 161 | // Order parameters at cd 0x04 offset = 0x20. 162 | eq( 163 | calldataload(BasicOrder_parameters_cdPtr), 164 | BasicOrder_parameters_ptr 165 | ), 166 | // Additional recipients at cd 0x224 offset = 0x240. 167 | eq( 168 | calldataload( 169 | BasicOrder_additionalRecipients_head_cdPtr 170 | ), 171 | BasicOrder_additionalRecipients_head_ptr 172 | ) 173 | ), 174 | // Signature offset = 0x260 + recipients.length * 0x40. 175 | eq( 176 | // Load signature offset from calldata 0x244. 177 | calldataload(BasicOrder_signature_cdPtr), 178 | // Expected offset = start of recipients + len * 64. 179 | add( 180 | BasicOrder_signature_ptr, 181 | shl( 182 | // Each additional recipient length = 0x40. 183 | AdditionalRecipient_size_shift, 184 | // Additional recipients length at cd 0x264. 185 | calldataload( 186 | BasicOrder_addlRecipients_length_cdPtr 187 | ) 188 | ) 189 | ) 190 | ) 191 | ), 192 | and( 193 | // Ensure BasicOrderType parameter is less than 0x18. 194 | lt( 195 | // BasicOrderType parameter = calldata offset 0x124. 196 | calldataload(BasicOrder_basicOrderType_cdPtr), 197 | // Value should be less than 24. 198 | BasicOrder_basicOrderType_range 199 | ), 200 | // Ensure no dirty upper bits are present on offerer, 201 | // zone, offer token, or consideration token. 202 | lt( 203 | or( 204 | or( 205 | // Offerer parameter = calldata offset 0x84. 206 | calldataload(BasicOrder_offerer_cdPtr), 207 | // Zone parameter = calldata offset 0xa4. 208 | calldataload(BasicOrder_zone_cdPtr) 209 | ), 210 | or( 211 | // Offer token parameter = cd offset 0xc4. 212 | calldataload(BasicOrder_offerToken_cdPtr), 213 | // Consideration parameter = offset 0x24. 214 | calldataload( 215 | BasicOrder_considerationToken_cdPtr 216 | ) 217 | ) 218 | ), 219 | AddressDirtyUpperBitThreshold 220 | ) 221 | ) 222 | ) 223 | } 224 | 225 | // Revert with an error if basic order parameter offsets are invalid. 226 | if (!validOffsets) { 227 | _revertInvalidBasicOrderParameterEncoding(); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/lib/ConsiderationBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | ConduitControllerInterface 6 | } from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; 7 | 8 | import { 9 | ConsiderationEventsAndErrors 10 | } from "seaport-types/src/interfaces/ConsiderationEventsAndErrors.sol"; 11 | 12 | import { 13 | BulkOrder_Typehash_Height_One, 14 | BulkOrder_Typehash_Height_Two, 15 | BulkOrder_Typehash_Height_Three, 16 | BulkOrder_Typehash_Height_Four, 17 | BulkOrder_Typehash_Height_Five, 18 | BulkOrder_Typehash_Height_Six, 19 | BulkOrder_Typehash_Height_Seven, 20 | BulkOrder_Typehash_Height_Eight, 21 | BulkOrder_Typehash_Height_Nine, 22 | BulkOrder_Typehash_Height_Ten, 23 | BulkOrder_Typehash_Height_Eleven, 24 | BulkOrder_Typehash_Height_Twelve, 25 | BulkOrder_Typehash_Height_Thirteen, 26 | BulkOrder_Typehash_Height_Fourteen, 27 | BulkOrder_Typehash_Height_Fifteen, 28 | BulkOrder_Typehash_Height_Sixteen, 29 | BulkOrder_Typehash_Height_Seventeen, 30 | BulkOrder_Typehash_Height_Eighteen, 31 | BulkOrder_Typehash_Height_Nineteen, 32 | BulkOrder_Typehash_Height_Twenty, 33 | BulkOrder_Typehash_Height_TwentyOne, 34 | BulkOrder_Typehash_Height_TwentyTwo, 35 | BulkOrder_Typehash_Height_TwentyThree, 36 | BulkOrder_Typehash_Height_TwentyFour, 37 | EIP712_domainData_chainId_offset, 38 | EIP712_domainData_nameHash_offset, 39 | EIP712_domainData_size, 40 | EIP712_domainData_verifyingContract_offset, 41 | EIP712_domainData_versionHash_offset, 42 | FreeMemoryPointerSlot, 43 | NameLengthPtr, 44 | NameWithLength, 45 | OneWord, 46 | Slot0x80, 47 | ThreeWords, 48 | ZeroSlot 49 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 50 | 51 | import { ConsiderationDecoder } from "./ConsiderationDecoder.sol"; 52 | 53 | import { ConsiderationEncoder } from "./ConsiderationEncoder.sol"; 54 | 55 | /** 56 | * @title ConsiderationBase 57 | * @author 0age 58 | * @notice ConsiderationBase contains immutable constants and constructor logic. 59 | */ 60 | contract ConsiderationBase is 61 | ConsiderationDecoder, 62 | ConsiderationEncoder, 63 | ConsiderationEventsAndErrors 64 | { 65 | // Precompute hashes, original chainId, and domain separator on deployment. 66 | bytes32 internal immutable _NAME_HASH; 67 | bytes32 internal immutable _VERSION_HASH; 68 | bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH; 69 | bytes32 internal immutable _OFFER_ITEM_TYPEHASH; 70 | bytes32 internal immutable _CONSIDERATION_ITEM_TYPEHASH; 71 | bytes32 internal immutable _ORDER_TYPEHASH; 72 | uint256 internal immutable _CHAIN_ID; 73 | bytes32 internal immutable _DOMAIN_SEPARATOR; 74 | 75 | // Allow for interaction with the conduit controller. 76 | ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER; 77 | 78 | // Cache the conduit creation code hash used by the conduit controller. 79 | bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH; 80 | 81 | /** 82 | * @dev Derive and set hashes, reference chainId, and associated domain 83 | * separator during deployment. 84 | * 85 | * @param conduitController A contract that deploys conduits, or proxies 86 | * that may optionally be used to transfer approved 87 | * ERC20/721/1155 tokens. 88 | */ 89 | constructor(address conduitController) { 90 | // Derive name and version hashes alongside required EIP-712 typehashes. 91 | ( 92 | _NAME_HASH, 93 | _VERSION_HASH, 94 | _EIP_712_DOMAIN_TYPEHASH, 95 | _OFFER_ITEM_TYPEHASH, 96 | _CONSIDERATION_ITEM_TYPEHASH, 97 | _ORDER_TYPEHASH 98 | ) = _deriveTypehashes(); 99 | 100 | // Store the current chainId and derive the current domain separator. 101 | _CHAIN_ID = block.chainid; 102 | _DOMAIN_SEPARATOR = _deriveDomainSeparator(); 103 | 104 | // Set the supplied conduit controller. 105 | _CONDUIT_CONTROLLER = ConduitControllerInterface(conduitController); 106 | 107 | // Retrieve the conduit creation code hash from the supplied controller. 108 | (_CONDUIT_CREATION_CODE_HASH, ) = ( 109 | _CONDUIT_CONTROLLER.getConduitCodeHashes() 110 | ); 111 | } 112 | 113 | /** 114 | * @dev Internal view function to derive the EIP-712 domain separator. 115 | * 116 | * @return domainSeparator The derived domain separator. 117 | */ 118 | function _deriveDomainSeparator() 119 | internal 120 | view 121 | returns (bytes32 domainSeparator) 122 | { 123 | bytes32 typehash = _EIP_712_DOMAIN_TYPEHASH; 124 | bytes32 nameHash = _NAME_HASH; 125 | bytes32 versionHash = _VERSION_HASH; 126 | 127 | // Leverage scratch space and other memory to perform an efficient hash. 128 | assembly { 129 | // Retrieve the free memory pointer; it will be replaced afterwards. 130 | let freeMemoryPointer := mload(FreeMemoryPointerSlot) 131 | 132 | // Retrieve value at 0x80; it will also be replaced afterwards. 133 | let slot0x80 := mload(Slot0x80) 134 | 135 | // Place typehash, name hash, and version hash at start of memory. 136 | mstore(0, typehash) 137 | mstore(EIP712_domainData_nameHash_offset, nameHash) 138 | mstore(EIP712_domainData_versionHash_offset, versionHash) 139 | 140 | // Place chainId in the next memory location. 141 | mstore(EIP712_domainData_chainId_offset, chainid()) 142 | 143 | // Place the address of this contract in the next memory location. 144 | mstore(EIP712_domainData_verifyingContract_offset, address()) 145 | 146 | // Hash relevant region of memory to derive the domain separator. 147 | domainSeparator := keccak256(0, EIP712_domainData_size) 148 | 149 | // Restore the free memory pointer. 150 | mstore(FreeMemoryPointerSlot, freeMemoryPointer) 151 | 152 | // Restore the zero slot to zero. 153 | mstore(ZeroSlot, 0) 154 | 155 | // Restore the value at 0x80. 156 | mstore(Slot0x80, slot0x80) 157 | } 158 | } 159 | 160 | /** 161 | * @dev Internal pure function to retrieve the default name of this 162 | * contract and return. 163 | * 164 | * @return The name of this contract. 165 | */ 166 | function _name() internal pure virtual returns (string memory) { 167 | // Return the name of the contract. 168 | assembly { 169 | // First element is the offset for the returned string. Offset the 170 | // value in memory by one word so that the free memory pointer will 171 | // be overwritten by the next write. 172 | mstore(OneWord, OneWord) 173 | 174 | // Name is right padded, so it touches the length which is left 175 | // padded. This enables writing both values at once. The free memory 176 | // pointer will be overwritten in the process. 177 | mstore(NameLengthPtr, NameWithLength) 178 | 179 | // Standard ABI encoding pads returned data to the nearest word. Use 180 | // the already empty zero slot memory region for this purpose and 181 | // return the final name string, offset by the original single word. 182 | return(OneWord, ThreeWords) 183 | } 184 | } 185 | 186 | /** 187 | * @dev Internal pure function to retrieve the default name of this contract 188 | * as a string that can be used internally. 189 | * 190 | * @return The name of this contract. 191 | */ 192 | function _nameString() internal pure virtual returns (string memory) { 193 | // Return the name of the contract. 194 | return "Consideration"; 195 | } 196 | 197 | /** 198 | * @dev Internal pure function to derive required EIP-712 typehashes and 199 | * other hashes during contract creation. 200 | * 201 | * @return nameHash The hash of the name of the contract. 202 | * @return versionHash The hash of the version string of the 203 | * contract. 204 | * @return eip712DomainTypehash The primary EIP-712 domain typehash. 205 | * @return offerItemTypehash The EIP-712 typehash for OfferItem 206 | * types. 207 | * @return considerationItemTypehash The EIP-712 typehash for 208 | * ConsiderationItem types. 209 | * @return orderTypehash The EIP-712 typehash for Order types. 210 | */ 211 | function _deriveTypehashes() 212 | internal 213 | pure 214 | returns ( 215 | bytes32 nameHash, 216 | bytes32 versionHash, 217 | bytes32 eip712DomainTypehash, 218 | bytes32 offerItemTypehash, 219 | bytes32 considerationItemTypehash, 220 | bytes32 orderTypehash 221 | ) 222 | { 223 | // Derive hash of the name of the contract. 224 | nameHash = keccak256(bytes(_nameString())); 225 | 226 | // Derive hash of the version string of the contract. 227 | versionHash = keccak256(bytes("1.6")); 228 | 229 | // Construct the OfferItem type string. 230 | bytes memory offerItemTypeString = bytes( 231 | "OfferItem(" 232 | "uint8 itemType," 233 | "address token," 234 | "uint256 identifierOrCriteria," 235 | "uint256 startAmount," 236 | "uint256 endAmount" 237 | ")" 238 | ); 239 | 240 | // Construct the ConsiderationItem type string. 241 | bytes memory considerationItemTypeString = bytes( 242 | "ConsiderationItem(" 243 | "uint8 itemType," 244 | "address token," 245 | "uint256 identifierOrCriteria," 246 | "uint256 startAmount," 247 | "uint256 endAmount," 248 | "address recipient" 249 | ")" 250 | ); 251 | 252 | // Construct the OrderComponents type string, not including the above. 253 | bytes memory orderComponentsPartialTypeString = bytes( 254 | "OrderComponents(" 255 | "address offerer," 256 | "address zone," 257 | "OfferItem[] offer," 258 | "ConsiderationItem[] consideration," 259 | "uint8 orderType," 260 | "uint256 startTime," 261 | "uint256 endTime," 262 | "bytes32 zoneHash," 263 | "uint256 salt," 264 | "bytes32 conduitKey," 265 | "uint256 counter" 266 | ")" 267 | ); 268 | 269 | // Construct the primary EIP-712 domain type string. 270 | eip712DomainTypehash = keccak256( 271 | bytes( 272 | "EIP712Domain(" 273 | "string name," 274 | "string version," 275 | "uint256 chainId," 276 | "address verifyingContract" 277 | ")" 278 | ) 279 | ); 280 | 281 | // Derive the OfferItem type hash using the corresponding type string. 282 | offerItemTypehash = keccak256(offerItemTypeString); 283 | 284 | // Derive ConsiderationItem type hash using corresponding type string. 285 | considerationItemTypehash = keccak256(considerationItemTypeString); 286 | 287 | bytes memory orderTypeString = bytes.concat( 288 | orderComponentsPartialTypeString, 289 | considerationItemTypeString, 290 | offerItemTypeString 291 | ); 292 | 293 | // Derive OrderItem type hash via combination of relevant type strings. 294 | orderTypehash = keccak256(orderTypeString); 295 | } 296 | 297 | /** 298 | * @dev Internal pure function to look up one of twenty-four potential bulk 299 | * order typehash constants based on the height of the bulk order tree. 300 | * Note that values between one and twenty-four are supported, which is 301 | * enforced by _isValidBulkOrderSize. 302 | * 303 | * @param _treeHeight The height of the bulk order tree. The value must be 304 | * between one and twenty-four. 305 | * 306 | * @return _typeHash The EIP-712 typehash for the bulk order type with the 307 | * given height. 308 | */ 309 | function _lookupBulkOrderTypehash( 310 | uint256 _treeHeight 311 | ) internal pure returns (bytes32 _typeHash) { 312 | // Utilize assembly to efficiently retrieve correct bulk order typehash. 313 | assembly { 314 | // Use a Yul function to enable use of the `leave` keyword 315 | // to stop searching once the appropriate type hash is found. 316 | function lookupTypeHash(treeHeight) -> typeHash { 317 | // Handle tree heights one through eight. 318 | if lt(treeHeight, 9) { 319 | // Handle tree heights one through four. 320 | if lt(treeHeight, 5) { 321 | // Handle tree heights one and two. 322 | if lt(treeHeight, 3) { 323 | // Utilize branchless logic to determine typehash. 324 | typeHash := ternary( 325 | eq(treeHeight, 1), 326 | BulkOrder_Typehash_Height_One, 327 | BulkOrder_Typehash_Height_Two 328 | ) 329 | 330 | // Exit the function once typehash has been located. 331 | leave 332 | } 333 | 334 | // Handle height three and four via branchless logic. 335 | typeHash := ternary( 336 | eq(treeHeight, 3), 337 | BulkOrder_Typehash_Height_Three, 338 | BulkOrder_Typehash_Height_Four 339 | ) 340 | 341 | // Exit the function once typehash has been located. 342 | leave 343 | } 344 | 345 | // Handle tree height five and six. 346 | if lt(treeHeight, 7) { 347 | // Utilize branchless logic to determine typehash. 348 | typeHash := ternary( 349 | eq(treeHeight, 5), 350 | BulkOrder_Typehash_Height_Five, 351 | BulkOrder_Typehash_Height_Six 352 | ) 353 | 354 | // Exit the function once typehash has been located. 355 | leave 356 | } 357 | 358 | // Handle height seven and eight via branchless logic. 359 | typeHash := ternary( 360 | eq(treeHeight, 7), 361 | BulkOrder_Typehash_Height_Seven, 362 | BulkOrder_Typehash_Height_Eight 363 | ) 364 | 365 | // Exit the function once typehash has been located. 366 | leave 367 | } 368 | 369 | // Handle tree height nine through sixteen. 370 | if lt(treeHeight, 17) { 371 | // Handle tree height nine through twelve. 372 | if lt(treeHeight, 13) { 373 | // Handle tree height nine and ten. 374 | if lt(treeHeight, 11) { 375 | // Utilize branchless logic to determine typehash. 376 | typeHash := ternary( 377 | eq(treeHeight, 9), 378 | BulkOrder_Typehash_Height_Nine, 379 | BulkOrder_Typehash_Height_Ten 380 | ) 381 | 382 | // Exit the function once typehash has been located. 383 | leave 384 | } 385 | 386 | // Handle height eleven and twelve via branchless logic. 387 | typeHash := ternary( 388 | eq(treeHeight, 11), 389 | BulkOrder_Typehash_Height_Eleven, 390 | BulkOrder_Typehash_Height_Twelve 391 | ) 392 | 393 | // Exit the function once typehash has been located. 394 | leave 395 | } 396 | 397 | // Handle tree height thirteen and fourteen. 398 | if lt(treeHeight, 15) { 399 | // Utilize branchless logic to determine typehash. 400 | typeHash := ternary( 401 | eq(treeHeight, 13), 402 | BulkOrder_Typehash_Height_Thirteen, 403 | BulkOrder_Typehash_Height_Fourteen 404 | ) 405 | 406 | // Exit the function once typehash has been located. 407 | leave 408 | } 409 | // Handle height fifteen and sixteen via branchless logic. 410 | typeHash := ternary( 411 | eq(treeHeight, 15), 412 | BulkOrder_Typehash_Height_Fifteen, 413 | BulkOrder_Typehash_Height_Sixteen 414 | ) 415 | 416 | // Exit the function once typehash has been located. 417 | leave 418 | } 419 | 420 | // Handle tree height seventeen through twenty. 421 | if lt(treeHeight, 21) { 422 | // Handle tree height seventeen and eighteen. 423 | if lt(treeHeight, 19) { 424 | // Utilize branchless logic to determine typehash. 425 | typeHash := ternary( 426 | eq(treeHeight, 17), 427 | BulkOrder_Typehash_Height_Seventeen, 428 | BulkOrder_Typehash_Height_Eighteen 429 | ) 430 | 431 | // Exit the function once typehash has been located. 432 | leave 433 | } 434 | 435 | // Handle height nineteen and twenty via branchless logic. 436 | typeHash := ternary( 437 | eq(treeHeight, 19), 438 | BulkOrder_Typehash_Height_Nineteen, 439 | BulkOrder_Typehash_Height_Twenty 440 | ) 441 | 442 | // Exit the function once typehash has been located. 443 | leave 444 | } 445 | 446 | // Handle tree height twenty-one and twenty-two. 447 | if lt(treeHeight, 23) { 448 | // Utilize branchless logic to determine typehash. 449 | typeHash := ternary( 450 | eq(treeHeight, 21), 451 | BulkOrder_Typehash_Height_TwentyOne, 452 | BulkOrder_Typehash_Height_TwentyTwo 453 | ) 454 | 455 | // Exit the function once typehash has been located. 456 | leave 457 | } 458 | 459 | // Handle height twenty-three & twenty-four w/ branchless logic. 460 | typeHash := ternary( 461 | eq(treeHeight, 23), 462 | BulkOrder_Typehash_Height_TwentyThree, 463 | BulkOrder_Typehash_Height_TwentyFour 464 | ) 465 | 466 | // Exit the function once typehash has been located. 467 | leave 468 | } 469 | 470 | // Implement ternary conditional using branchless logic. 471 | function ternary(cond, ifTrue, ifFalse) -> c { 472 | c := xor(ifFalse, mul(cond, xor(ifFalse, ifTrue))) 473 | } 474 | 475 | // Look up the typehash using the supplied tree height. 476 | _typeHash := lookupTypeHash(_treeHeight) 477 | } 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /src/lib/CounterManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | ConsiderationEventsAndErrors 6 | } from "seaport-types/src/interfaces/ConsiderationEventsAndErrors.sol"; 7 | 8 | import { ReentrancyGuard } from "./ReentrancyGuard.sol"; 9 | 10 | import { 11 | Counter_blockhash_shift, 12 | OneWord, 13 | TwoWords 14 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 15 | 16 | /** 17 | * @title CounterManager 18 | * @author 0age 19 | * @notice CounterManager contains a storage mapping and related functionality 20 | * for retrieving and incrementing a per-offerer counter. 21 | */ 22 | contract CounterManager is ConsiderationEventsAndErrors, ReentrancyGuard { 23 | // Only orders signed using an offerer's current counter are fulfillable. 24 | mapping(address => uint256) private _counters; 25 | 26 | /** 27 | * @dev Internal function to cancel all orders from a given offerer in bulk 28 | * by incrementing a counter by a large, quasi-random interval. Note 29 | * that only the offerer may increment the counter. Note that the 30 | * counter is incremented by a large, quasi-random interval, which 31 | * makes it infeasible to "activate" signed orders by incrementing the 32 | * counter. This activation functionality can be achieved instead with 33 | * restricted orders or contract orders. 34 | * 35 | * @return newCounter The new counter. 36 | */ 37 | function _incrementCounter() internal returns (uint256 newCounter) { 38 | // Ensure that the reentrancy guard is not currently set. 39 | _assertNonReentrant(); 40 | 41 | // Utilize assembly to access counters storage mapping directly. Skip 42 | // overflow check as counter cannot be incremented that far. 43 | assembly { 44 | // Use second half of previous block hash as a quasi-random number. 45 | let quasiRandomNumber := shr( 46 | Counter_blockhash_shift, 47 | blockhash(sub(number(), 1)) 48 | ) 49 | 50 | // Write the caller to scratch space. 51 | mstore(0, caller()) 52 | 53 | // Write the storage slot for _counters to scratch space. 54 | mstore(OneWord, _counters.slot) 55 | 56 | // Derive the storage pointer for the counter value. 57 | let storagePointer := keccak256(0, TwoWords) 58 | 59 | // Derive new counter value using random number and original value. 60 | newCounter := add(quasiRandomNumber, sload(storagePointer)) 61 | 62 | // Store the updated counter value. 63 | sstore(storagePointer, newCounter) 64 | } 65 | 66 | // Emit an event containing the new counter. 67 | emit CounterIncremented(newCounter, msg.sender); 68 | } 69 | 70 | /** 71 | * @dev Internal view function to retrieve the current counter for a given 72 | * offerer. 73 | * 74 | * @param offerer The offerer in question. 75 | * 76 | * @return currentCounter The current counter. 77 | */ 78 | function _getCounter( 79 | address offerer 80 | ) internal view returns (uint256 currentCounter) { 81 | // Return the counter for the supplied offerer. 82 | currentCounter = _counters[offerer]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/lib/CriteriaResolution.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | ItemType, 6 | OrderType, 7 | Side 8 | } from "seaport-types/src/lib/ConsiderationEnums.sol"; 9 | 10 | import { 11 | AdvancedOrder, 12 | ConsiderationItem, 13 | CriteriaResolver, 14 | MemoryPointer, 15 | OfferItem, 16 | OrderParameters 17 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 18 | 19 | import { 20 | _revertCriteriaNotEnabledForItem, 21 | _revertInvalidProof, 22 | _revertOrderCriteriaResolverOutOfRange 23 | } from "seaport-types/src/lib/ConsiderationErrors.sol"; 24 | 25 | import { 26 | CriteriaResolutionErrors 27 | } from "seaport-types/src/interfaces/CriteriaResolutionErrors.sol"; 28 | 29 | import { 30 | OneWord, 31 | OneWordShift, 32 | OrderParameters_consideration_head_offset, 33 | Selector_length, 34 | TwoWords 35 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 36 | 37 | import { 38 | ConsiderationCriteriaResolverOutOfRange_err_selector, 39 | Error_selector_offset, 40 | OfferCriteriaResolverOutOfRange_error_selector, 41 | UnresolvedConsiderationCriteria_error_itemIndex_ptr, 42 | UnresolvedConsiderationCriteria_error_length, 43 | UnresolvedConsiderationCriteria_error_orderIndex_ptr, 44 | UnresolvedConsiderationCriteria_error_selector, 45 | UnresolvedOfferCriteria_error_selector 46 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 47 | 48 | /** 49 | * @title CriteriaResolution 50 | * @author 0age 51 | * @notice CriteriaResolution contains a collection of pure functions related to 52 | * resolving criteria-based items. 53 | */ 54 | contract CriteriaResolution is CriteriaResolutionErrors { 55 | /** 56 | * @dev Internal pure function to apply criteria resolvers containing 57 | * specific token identifiers and associated proofs to order items. 58 | * 59 | * @param advancedOrders The orders to apply criteria resolvers to. 60 | * @param criteriaResolvers An array where each element contains a 61 | * reference to a specific order as well as that 62 | * order's offer or consideration, a token 63 | * identifier, and a proof that the supplied token 64 | * identifier is contained in the order's merkle 65 | * root. Note that a root of zero indicates that 66 | * any transferable token identifier is valid and 67 | * that no proof needs to be supplied. 68 | */ 69 | function _applyCriteriaResolvers( 70 | AdvancedOrder[] memory advancedOrders, 71 | CriteriaResolver[] memory criteriaResolvers 72 | ) internal pure { 73 | // Skip overflow checks as all for loops are indexed starting at zero. 74 | unchecked { 75 | // Retrieve length of criteria resolvers array and place on stack. 76 | uint256 totalCriteriaResolvers = criteriaResolvers.length; 77 | 78 | // Retrieve length of orders array and place on stack. 79 | uint256 totalAdvancedOrders = advancedOrders.length; 80 | 81 | // Iterate over each criteria resolver. 82 | for (uint256 i = 0; i < totalCriteriaResolvers; ++i) { 83 | // Retrieve the criteria resolver. 84 | CriteriaResolver memory criteriaResolver = ( 85 | criteriaResolvers[i] 86 | ); 87 | 88 | // Read the order index from memory and place it on the stack. 89 | uint256 orderIndex = criteriaResolver.orderIndex; 90 | 91 | // Ensure that the order index is in range. 92 | if (orderIndex >= totalAdvancedOrders) { 93 | _revertOrderCriteriaResolverOutOfRange( 94 | criteriaResolver.side 95 | ); 96 | } 97 | 98 | // Retrieve the referenced advanced order. 99 | AdvancedOrder memory advancedOrder = advancedOrders[orderIndex]; 100 | 101 | // Skip criteria resolution for order if not fulfilled. 102 | if (advancedOrder.numerator == 0) { 103 | continue; 104 | } 105 | 106 | // Retrieve the parameters for the order. 107 | OrderParameters memory orderParameters = ( 108 | advancedOrder.parameters 109 | ); 110 | 111 | { 112 | // Get a pointer to the list of items to give to 113 | // _updateCriteriaItem. If the resolver refers to a 114 | // consideration item, this array pointer will be replaced 115 | // with the consideration array. 116 | OfferItem[] memory items = orderParameters.offer; 117 | 118 | // Read component index from memory and place it on stack. 119 | uint256 componentIndex = criteriaResolver.index; 120 | 121 | // Get error selector for `OfferCriteriaResolverOutOfRange`. 122 | uint256 errorSelector = ( 123 | OfferCriteriaResolverOutOfRange_error_selector 124 | ); 125 | 126 | // If the resolver refers to a consideration item... 127 | if (criteriaResolver.side != Side.OFFER) { 128 | // Get the pointer to `orderParameters.consideration` 129 | // Using the array directly has a significant impact on 130 | // the optimized compiler output. 131 | MemoryPointer considerationPtr = orderParameters 132 | .toMemoryPointer() 133 | .pptrOffset( 134 | OrderParameters_consideration_head_offset 135 | ); 136 | 137 | // Replace the items pointer with a pointer to the 138 | // consideration array. 139 | assembly { 140 | items := considerationPtr 141 | } 142 | 143 | // Replace the error selector with the selector for 144 | // `ConsiderationCriteriaResolverOutOfRange`. 145 | errorSelector = ( 146 | ConsiderationCriteriaResolverOutOfRange_err_selector 147 | ); 148 | } 149 | 150 | // Ensure that the component index is in range. 151 | if (componentIndex >= items.length) { 152 | assembly { 153 | // Revert with either 154 | // `OfferCriteriaResolverOutOfRange()` or 155 | // `ConsiderationCriteriaResolverOutOfRange()`, 156 | // depending on whether the resolver refers to a 157 | // consideration item. 158 | mstore(0, errorSelector) 159 | // revert(abi.encodeWithSignature( 160 | // "OfferCriteriaResolverOutOfRange()" 161 | // )) 162 | // or 163 | // revert(abi.encodeWithSignature( 164 | // "ConsiderationCriteriaResolverOutOfRange()" 165 | // )) 166 | revert(Error_selector_offset, Selector_length) 167 | } 168 | } 169 | 170 | // Apply the criteria resolver to the item in question. 171 | _updateCriteriaItem( 172 | items, 173 | componentIndex, 174 | criteriaResolver 175 | ); 176 | } 177 | } 178 | 179 | // Iterate over each advanced order. 180 | for (uint256 i = 0; i < totalAdvancedOrders; ++i) { 181 | // Retrieve the advanced order. 182 | AdvancedOrder memory advancedOrder = advancedOrders[i]; 183 | 184 | // Skip criteria resolution for order if not fulfilled. 185 | if (advancedOrder.numerator == 0) { 186 | continue; 187 | } 188 | 189 | // Retrieve the parameters for the order. 190 | OrderParameters memory orderParameters = ( 191 | advancedOrder.parameters 192 | ); 193 | 194 | OrderType orderType = orderParameters.orderType; 195 | 196 | _ensureAllRequiredCriteriaResolved( 197 | i, 198 | orderParameters.consideration, 199 | orderType, 200 | UnresolvedConsiderationCriteria_error_selector 201 | ); 202 | 203 | _toOfferItemArgumentType(_ensureAllRequiredCriteriaResolved)( 204 | i, 205 | orderParameters.offer, 206 | orderType, 207 | UnresolvedOfferCriteria_error_selector 208 | ); 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * @dev Internal pure function to examine an array of items and ensure that 215 | * all criteria-based items (with the exception of wildcard items on 216 | * contract orders) have had a criteria resolver successfully applied. 217 | * 218 | * @param orderIndex The index of the order being examined. 219 | * @param items The items to examine. These are consideration items 220 | * in the default case, but offer items are also 221 | * casted to consideration items as required. 222 | * @param orderType The type of order being examined. 223 | * @param revertSelector The selector to use when reverting. 224 | */ 225 | function _ensureAllRequiredCriteriaResolved( 226 | uint256 orderIndex, 227 | ConsiderationItem[] memory items, 228 | OrderType orderType, 229 | uint256 revertSelector 230 | ) internal pure { 231 | // Read items array length from memory and place on stack. 232 | uint256 totalItems = items.length; 233 | 234 | // Iterate over each item on the order. 235 | for (uint256 i = 0; i < totalItems; ++i) { 236 | ConsiderationItem memory item = items[i]; 237 | 238 | // Revert if the item is still a criteria item unless the 239 | // order is a contract order and the identifier is 0. 240 | ItemType itemType = item.itemType; 241 | uint256 identifierOrCriteria = item.identifierOrCriteria; 242 | 243 | assembly { 244 | if and( 245 | gt(itemType, 3), // Criteria-based item 246 | or( 247 | iszero(eq(orderType, 4)), // not OrderType.CONTRACT 248 | iszero(iszero(identifierOrCriteria)) // not wildcard 249 | ) 250 | ) { 251 | // Store left-padded selector with push4 (reduces bytecode), 252 | // mem[28:32] = selector 253 | mstore(0, revertSelector) 254 | 255 | // Store arguments. 256 | mstore( 257 | UnresolvedConsiderationCriteria_error_orderIndex_ptr, 258 | orderIndex 259 | ) 260 | mstore( 261 | UnresolvedConsiderationCriteria_error_itemIndex_ptr, 262 | i 263 | ) 264 | 265 | // Revert with appropriate UnresolvedCriteria error message. 266 | // Unresolved[Offer|Consideration]Criteria(uint256, uint256) 267 | revert( 268 | Error_selector_offset, 269 | UnresolvedConsiderationCriteria_error_length 270 | ) 271 | } 272 | } 273 | } 274 | } 275 | 276 | /** 277 | * @dev Internal pure function to perform a function cast from a function 278 | * that accepts consideration items to a function that accepts offer 279 | * items, used by _ensureAllRequiredCriteriaResolved to ensure that 280 | * all necessary criteria items have been resolved for an order. 281 | * 282 | * @param inFn The function that accepts consideration items. 283 | * @param outFn The function that accepts offer items. 284 | */ 285 | function _toOfferItemArgumentType( 286 | function(uint256, ConsiderationItem[] memory, OrderType, uint256) 287 | internal 288 | pure inFn 289 | ) 290 | internal 291 | pure 292 | returns ( 293 | function(uint256, OfferItem[] memory, OrderType, uint256) 294 | internal 295 | pure outFn 296 | ) 297 | { 298 | assembly { 299 | outFn := inFn 300 | } 301 | } 302 | 303 | /** 304 | * @dev Internal pure function to update a criteria item. 305 | * 306 | * @param offer The offer containing the item to update. 307 | * @param componentIndex The index of the item to update. 308 | * @param criteriaResolver The criteria resolver to use to update the item. 309 | */ 310 | function _updateCriteriaItem( 311 | OfferItem[] memory offer, 312 | uint256 componentIndex, 313 | CriteriaResolver memory criteriaResolver 314 | ) internal pure { 315 | // Retrieve relevant item using the component index. 316 | OfferItem memory offerItem = offer[componentIndex]; 317 | 318 | // Read item type and criteria from memory & place on stack. 319 | ItemType itemType = offerItem.itemType; 320 | 321 | // Ensure the specified item type indicates criteria usage. 322 | if (!_isItemWithCriteria(itemType)) { 323 | _revertCriteriaNotEnabledForItem(); 324 | } 325 | 326 | uint256 identifierOrCriteria = offerItem.identifierOrCriteria; 327 | 328 | // If criteria is not 0 (i.e. a collection-wide criteria-based item)... 329 | if (identifierOrCriteria != uint256(0)) { 330 | // Verify identifier inclusion in criteria root using proof. 331 | _verifyProof( 332 | criteriaResolver.identifier, 333 | identifierOrCriteria, 334 | criteriaResolver.criteriaProof 335 | ); 336 | } else if (criteriaResolver.criteriaProof.length != 0) { 337 | // Revert if non-empty proof is supplied for a collection-wide item. 338 | _revertInvalidProof(); 339 | } 340 | 341 | // Update item type to remove criteria usage. 342 | // Use assembly to operate on ItemType enum as a number. 343 | ItemType newItemType; 344 | assembly { 345 | // Item type 4 becomes 2 and item type 5 becomes 3. 346 | newItemType := sub(itemType, 2) 347 | } 348 | offerItem.itemType = newItemType; 349 | 350 | // Update identifier w/ supplied identifier. 351 | offerItem.identifierOrCriteria = criteriaResolver.identifier; 352 | } 353 | 354 | /** 355 | * @dev Internal pure function to check whether a given item type represents 356 | * a criteria-based ERC721 or ERC1155 item (e.g. an item that can be 357 | * resolved to one of a number of different identifiers at the time of 358 | * order fulfillment). 359 | * 360 | * @param itemType The item type in question. 361 | * 362 | * @return withCriteria A boolean indicating that the item type in question 363 | * represents a criteria-based item. 364 | */ 365 | function _isItemWithCriteria( 366 | ItemType itemType 367 | ) internal pure returns (bool withCriteria) { 368 | // ERC721WithCriteria is ItemType 4. ERC1155WithCriteria is ItemType 5. 369 | assembly { 370 | withCriteria := gt(itemType, 3) 371 | } 372 | } 373 | 374 | /** 375 | * @dev Internal pure function to ensure that a given element is contained 376 | * in a merkle root via a supplied proof. 377 | * 378 | * @param leaf The element for which to prove inclusion. 379 | * @param root The merkle root that inclusion will be proved against. 380 | * @param proof The merkle proof. 381 | */ 382 | function _verifyProof( 383 | uint256 leaf, 384 | uint256 root, 385 | bytes32[] memory proof 386 | ) internal pure { 387 | // Declare a variable that will be used to determine proof validity. 388 | bool isValid; 389 | 390 | // Utilize assembly to efficiently verify the proof against the root. 391 | assembly { 392 | // Store the leaf at the beginning of scratch space. 393 | mstore(0, leaf) 394 | 395 | // Derive the hash of the leaf to use as the initial proof element. 396 | let computedHash := keccak256(0, OneWord) 397 | 398 | // Get memory start location of the first element in proof array. 399 | let data := add(proof, OneWord) 400 | 401 | // Iterate over each proof element to compute the root hash. 402 | for { 403 | // Left shift by 5 is equivalent to multiplying by 0x20. 404 | let end := add(data, shl(OneWordShift, mload(proof))) 405 | } lt(data, end) { 406 | // Increment by one word at a time. 407 | data := add(data, OneWord) 408 | } { 409 | // Get the proof element. 410 | let loadedData := mload(data) 411 | 412 | // Sort proof elements and place them in scratch space. 413 | // Slot of `computedHash` in scratch space. 414 | // If the condition is true: 0x20, otherwise: 0x00. 415 | let scratch := shl(OneWordShift, gt(computedHash, loadedData)) 416 | 417 | // Store elements to hash contiguously in scratch space. Scratch 418 | // space is 64 bytes (0x00 - 0x3f) & both elements are 32 bytes. 419 | mstore(scratch, computedHash) 420 | mstore(xor(scratch, OneWord), loadedData) 421 | 422 | // Derive the updated hash. 423 | computedHash := keccak256(0, TwoWords) 424 | } 425 | 426 | // Compare the final hash to the supplied root. 427 | isValid := eq(computedHash, root) 428 | } 429 | 430 | // Revert if computed hash does not equal supplied root. 431 | if (!isValid) { 432 | _revertInvalidProof(); 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /src/lib/GettersAndDerivers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | OrderParameters 6 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 7 | 8 | import { ConsiderationBase } from "./ConsiderationBase.sol"; 9 | 10 | import { 11 | Create2AddressDerivation_length, 12 | Create2AddressDerivation_ptr, 13 | EIP_712_PREFIX, 14 | EIP712_ConsiderationItem_size, 15 | EIP712_DigestPayload_size, 16 | EIP712_DomainSeparator_offset, 17 | EIP712_OfferItem_size, 18 | EIP712_Order_size, 19 | EIP712_OrderHash_offset, 20 | FreeMemoryPointerSlot, 21 | information_conduitController_offset, 22 | information_domainSeparator_offset, 23 | information_length, 24 | information_version_cd_offset, 25 | information_version_offset, 26 | information_versionLengthPtr, 27 | information_versionWithLength, 28 | MaskOverByteTwelve, 29 | MaskOverLastTwentyBytes, 30 | OneWord, 31 | OneWordShift, 32 | OrderParameters_consideration_head_offset, 33 | OrderParameters_counter_offset, 34 | OrderParameters_offer_head_offset, 35 | TwoWords 36 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 37 | 38 | /** 39 | * @title GettersAndDerivers 40 | * @author 0age 41 | * @notice ConsiderationInternal contains pure and internal view functions 42 | * related to getting or deriving various values. 43 | */ 44 | contract GettersAndDerivers is ConsiderationBase { 45 | /** 46 | * @dev Derive and set hashes, reference chainId, and associated domain 47 | * separator during deployment. 48 | * 49 | * @param conduitController A contract that deploys conduits, or proxies 50 | * that may optionally be used to transfer approved 51 | * ERC20/721/1155 tokens. 52 | */ 53 | constructor( 54 | address conduitController 55 | ) ConsiderationBase(conduitController) {} 56 | 57 | /** 58 | * @dev Internal view function to derive the order hash for a given order. 59 | * Note that only the original consideration items are included in the 60 | * order hash, as additional consideration items may be supplied by the 61 | * caller. 62 | * 63 | * @param orderParameters The parameters of the order to hash. 64 | * @param counter The counter of the order to hash. 65 | * 66 | * @return orderHash The hash. 67 | */ 68 | function _deriveOrderHash( 69 | OrderParameters memory orderParameters, 70 | uint256 counter 71 | ) internal view returns (bytes32 orderHash) { 72 | // Get length of original consideration array and place it on the stack. 73 | uint256 originalConsiderationLength = ( 74 | orderParameters.totalOriginalConsiderationItems 75 | ); 76 | 77 | /* 78 | * Memory layout for an array of structs (dynamic or not) is similar 79 | * to ABI encoding of dynamic types, with a head segment followed by 80 | * a data segment. The main difference is that the head of an element 81 | * is a memory pointer rather than an offset. 82 | */ 83 | 84 | // Declare a variable for the derived hash of the offer array. 85 | bytes32 offerHash; 86 | 87 | // Read offer item EIP-712 typehash from runtime code & place on stack. 88 | bytes32 typeHash = _OFFER_ITEM_TYPEHASH; 89 | 90 | // Utilize assembly so that memory regions can be reused across hashes. 91 | assembly { 92 | // Retrieve the free memory pointer and place on the stack. 93 | let hashArrPtr := mload(FreeMemoryPointerSlot) 94 | 95 | // Get the pointer to the offers array. 96 | let offerArrPtr := mload( 97 | add(orderParameters, OrderParameters_offer_head_offset) 98 | ) 99 | 100 | // Load the length. 101 | let offerLength := mload(offerArrPtr) 102 | 103 | // Set the pointer to the first offer's head. 104 | offerArrPtr := add(offerArrPtr, OneWord) 105 | 106 | // Iterate over the offer items. 107 | for { 108 | let i := 0 109 | } lt(i, offerLength) { 110 | i := add(i, 1) 111 | } { 112 | // Read the pointer to the offer data and subtract one word 113 | // to get typeHash pointer. 114 | let ptr := sub(mload(offerArrPtr), OneWord) 115 | 116 | // Read the current value before the offer data. 117 | let value := mload(ptr) 118 | 119 | // Write the type hash to the previous word. 120 | mstore(ptr, typeHash) 121 | 122 | // Take the EIP712 hash and store it in the hash array. 123 | mstore(hashArrPtr, keccak256(ptr, EIP712_OfferItem_size)) 124 | 125 | // Restore the previous word. 126 | mstore(ptr, value) 127 | 128 | // Increment the array pointers by one word. 129 | offerArrPtr := add(offerArrPtr, OneWord) 130 | hashArrPtr := add(hashArrPtr, OneWord) 131 | } 132 | 133 | // Derive the offer hash using the hashes of each item. 134 | offerHash := keccak256( 135 | mload(FreeMemoryPointerSlot), 136 | shl(OneWordShift, offerLength) 137 | ) 138 | } 139 | 140 | // Declare a variable for the derived hash of the consideration array. 141 | bytes32 considerationHash; 142 | 143 | // Read consideration item typehash from runtime code & place on stack. 144 | typeHash = _CONSIDERATION_ITEM_TYPEHASH; 145 | 146 | // Utilize assembly so that memory regions can be reused across hashes. 147 | assembly { 148 | // Retrieve the free memory pointer and place on the stack. 149 | let hashArrPtr := mload(FreeMemoryPointerSlot) 150 | 151 | // Get the pointer to the consideration array. 152 | let considerationArrPtr := add( 153 | mload( 154 | add( 155 | orderParameters, 156 | OrderParameters_consideration_head_offset 157 | ) 158 | ), 159 | OneWord 160 | ) 161 | 162 | // Iterate over the consideration items (not including tips). 163 | for { 164 | let i := 0 165 | } lt(i, originalConsiderationLength) { 166 | i := add(i, 1) 167 | } { 168 | // Read the pointer to the consideration data and subtract one 169 | // word to get typeHash pointer. 170 | let ptr := sub(mload(considerationArrPtr), OneWord) 171 | 172 | // Read the current value before the consideration data. 173 | let value := mload(ptr) 174 | 175 | // Write the type hash to the previous word. 176 | mstore(ptr, typeHash) 177 | 178 | // Take the EIP712 hash and store it in the hash array. 179 | mstore( 180 | hashArrPtr, 181 | keccak256(ptr, EIP712_ConsiderationItem_size) 182 | ) 183 | 184 | // Restore the previous word. 185 | mstore(ptr, value) 186 | 187 | // Increment the array pointers by one word. 188 | considerationArrPtr := add(considerationArrPtr, OneWord) 189 | hashArrPtr := add(hashArrPtr, OneWord) 190 | } 191 | 192 | // Derive the consideration hash using the hashes of each item. 193 | considerationHash := keccak256( 194 | mload(FreeMemoryPointerSlot), 195 | shl(OneWordShift, originalConsiderationLength) 196 | ) 197 | } 198 | 199 | // Read order item EIP-712 typehash from runtime code & place on stack. 200 | typeHash = _ORDER_TYPEHASH; 201 | 202 | // Utilize assembly to access derived hashes & other arguments directly. 203 | assembly { 204 | // Retrieve pointer to the region located just behind parameters. 205 | let typeHashPtr := sub(orderParameters, OneWord) 206 | 207 | // Store the value at that pointer location to restore later. 208 | let previousValue := mload(typeHashPtr) 209 | 210 | // Store the order item EIP-712 typehash at the typehash location. 211 | mstore(typeHashPtr, typeHash) 212 | 213 | // Retrieve the pointer for the offer array head. 214 | let offerHeadPtr := add( 215 | orderParameters, 216 | OrderParameters_offer_head_offset 217 | ) 218 | 219 | // Retrieve the data pointer referenced by the offer head. 220 | let offerDataPtr := mload(offerHeadPtr) 221 | 222 | // Store the offer hash at the retrieved memory location. 223 | mstore(offerHeadPtr, offerHash) 224 | 225 | // Retrieve the pointer for the consideration array head. 226 | let considerationHeadPtr := add( 227 | orderParameters, 228 | OrderParameters_consideration_head_offset 229 | ) 230 | 231 | // Retrieve the data pointer referenced by the consideration head. 232 | let considerationDataPtr := mload(considerationHeadPtr) 233 | 234 | // Store the consideration hash at the retrieved memory location. 235 | mstore(considerationHeadPtr, considerationHash) 236 | 237 | // Retrieve the pointer for the counter. 238 | let counterPtr := add( 239 | orderParameters, 240 | OrderParameters_counter_offset 241 | ) 242 | 243 | // Store the counter at the retrieved memory location. 244 | mstore(counterPtr, counter) 245 | 246 | // Derive the order hash using the full range of order parameters. 247 | orderHash := keccak256(typeHashPtr, EIP712_Order_size) 248 | 249 | // Restore the value previously held at typehash pointer location. 250 | mstore(typeHashPtr, previousValue) 251 | 252 | // Restore offer data pointer at the offer head pointer location. 253 | mstore(offerHeadPtr, offerDataPtr) 254 | 255 | // Restore consideration data pointer at the consideration head ptr. 256 | mstore(considerationHeadPtr, considerationDataPtr) 257 | 258 | // Restore consideration item length at the counter pointer. 259 | mstore(counterPtr, originalConsiderationLength) 260 | } 261 | } 262 | 263 | /** 264 | * @dev Internal view function to derive the address of a given conduit 265 | * using a corresponding conduit key. 266 | * 267 | * @param conduitKey A bytes32 value indicating what corresponding conduit, 268 | * if any, to source token approvals from. This value is 269 | * the "salt" parameter supplied by the deployer (i.e. the 270 | * conduit controller) when deploying the given conduit. 271 | * 272 | * @return conduit The address of the conduit associated with the given 273 | * conduit key. 274 | */ 275 | function _deriveConduit( 276 | bytes32 conduitKey 277 | ) internal view returns (address conduit) { 278 | // Read conduit controller address from runtime and place on the stack. 279 | address conduitController = address(_CONDUIT_CONTROLLER); 280 | 281 | // Read conduit creation code hash from runtime and place on the stack. 282 | bytes32 conduitCreationCodeHash = _CONDUIT_CREATION_CODE_HASH; 283 | 284 | // Leverage scratch space to perform an efficient hash. 285 | assembly { 286 | // Retrieve the free memory pointer; it will be replaced afterwards. 287 | let freeMemoryPointer := mload(FreeMemoryPointerSlot) 288 | 289 | // Place the control character and the conduit controller in scratch 290 | // space; note that eleven bytes at the beginning are left unused. 291 | mstore(0, or(MaskOverByteTwelve, conduitController)) 292 | 293 | // Place the conduit key in the next region of scratch space. 294 | mstore(OneWord, conduitKey) 295 | 296 | // Place conduit creation code hash in free memory pointer location. 297 | mstore(TwoWords, conduitCreationCodeHash) 298 | 299 | // Derive conduit by hashing and applying a mask over last 20 bytes. 300 | conduit := and( 301 | // Hash the relevant region. 302 | keccak256( 303 | // The region starts at memory pointer 11. 304 | Create2AddressDerivation_ptr, 305 | // The region is 85 bytes long (1 + 20 + 32 + 32). 306 | Create2AddressDerivation_length 307 | ), 308 | // The address equals the last twenty bytes of the hash. 309 | MaskOverLastTwentyBytes 310 | ) 311 | 312 | // Restore the free memory pointer. 313 | mstore(FreeMemoryPointerSlot, freeMemoryPointer) 314 | } 315 | } 316 | 317 | /** 318 | * @dev Internal view function to get the EIP-712 domain separator. If the 319 | * chainId matches the chainId set on deployment, the cached domain 320 | * separator will be returned; otherwise, it will be derived from 321 | * scratch. 322 | * 323 | * @return The domain separator. 324 | */ 325 | function _domainSeparator() internal view returns (bytes32) { 326 | return 327 | block.chainid == _CHAIN_ID 328 | ? _DOMAIN_SEPARATOR 329 | : _deriveDomainSeparator(); 330 | } 331 | 332 | /** 333 | * @dev Internal view function to retrieve configuration information for 334 | * this contract. 335 | * 336 | * @return The contract version. 337 | * @return The domain separator for this contract. 338 | * @return The conduit Controller set for this contract. 339 | */ 340 | function _information() 341 | internal 342 | view 343 | returns ( 344 | string memory /* version */, 345 | bytes32 /* domainSeparator */, 346 | address /* conduitController */ 347 | ) 348 | { 349 | // Derive the domain separator. 350 | bytes32 domainSeparator = _domainSeparator(); 351 | 352 | // Declare variable as immutables cannot be accessed within assembly. 353 | address conduitController = address(_CONDUIT_CONTROLLER); 354 | 355 | // Return the version, domain separator, and conduit controller. 356 | assembly { 357 | mstore(information_version_offset, information_version_cd_offset) 358 | mstore(information_domainSeparator_offset, domainSeparator) 359 | mstore(information_conduitController_offset, conduitController) 360 | mstore(information_versionLengthPtr, information_versionWithLength) 361 | return(information_version_offset, information_length) 362 | } 363 | } 364 | 365 | /** 366 | * @dev Internal pure function to efficiently derive an digest to sign for 367 | * an order in accordance with EIP-712. 368 | * 369 | * @param domainSeparator The domain separator. 370 | * @param orderHash The order hash. 371 | * 372 | * @return value The hash. 373 | */ 374 | function _deriveEIP712Digest( 375 | bytes32 domainSeparator, 376 | bytes32 orderHash 377 | ) internal pure returns (bytes32 value) { 378 | // Leverage scratch space to perform an efficient hash. 379 | assembly { 380 | // Place the EIP-712 prefix at the start of scratch space. 381 | mstore(0, EIP_712_PREFIX) 382 | 383 | // Place the domain separator in the next region of scratch space. 384 | mstore(EIP712_DomainSeparator_offset, domainSeparator) 385 | 386 | // Place the order hash in scratch space, spilling into the first 387 | // two bytes of the free memory pointer — this should never be set 388 | // as memory cannot be expanded to that size, and will be zeroed out 389 | // after the hash is performed. 390 | mstore(EIP712_OrderHash_offset, orderHash) 391 | 392 | // Hash the relevant region (65 bytes). 393 | value := keccak256(0, EIP712_DigestPayload_size) 394 | 395 | // Clear out the dirtied bits in the memory pointer. 396 | mstore(EIP712_OrderHash_offset, 0) 397 | } 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/lib/LowLevelHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | CostPerWord, 6 | ExtraGasBuffer, 7 | FreeMemoryPointerSlot, 8 | MemoryExpansionCoefficientShift, 9 | OneWord, 10 | OneWordShift, 11 | ThirtyOneBytes 12 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 13 | 14 | import { 15 | MemoryPointer, 16 | MemoryPointerLib 17 | } from "seaport-types/src/helpers/PointerLibraries.sol"; 18 | 19 | import { 20 | AdvancedOrder, 21 | Execution 22 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 23 | 24 | /** 25 | * @title LowLevelHelpers 26 | * @author 0age 27 | * @notice LowLevelHelpers contains logic for performing various low-level 28 | * operations. 29 | */ 30 | contract LowLevelHelpers { 31 | /** 32 | * @dev Internal view function to revert and pass along the revert reason if 33 | * data was returned by the last call and that the size of that data 34 | * does not exceed the currently allocated memory size. 35 | */ 36 | function _revertWithReasonIfOneIsReturned() internal view { 37 | assembly { 38 | // If it returned a message, bubble it up as long as sufficient gas 39 | // remains to do so: 40 | if returndatasize() { 41 | // Ensure that sufficient gas is available to copy returndata 42 | // while expanding memory where necessary. Start by computing 43 | // the word size of returndata and allocated memory. 44 | let returnDataWords := shr( 45 | OneWordShift, 46 | add(returndatasize(), ThirtyOneBytes) 47 | ) 48 | 49 | // Note: use the free memory pointer in place of msize() to work 50 | // around a Yul warning that prevents accessing msize directly 51 | // when the IR pipeline is activated. 52 | let msizeWords := shr( 53 | OneWordShift, 54 | mload(FreeMemoryPointerSlot) 55 | ) 56 | 57 | // Next, compute the cost of the returndatacopy. 58 | let cost := mul(CostPerWord, returnDataWords) 59 | 60 | // Then, compute cost of new memory allocation. 61 | if gt(returnDataWords, msizeWords) { 62 | cost := add( 63 | cost, 64 | add( 65 | mul(sub(returnDataWords, msizeWords), CostPerWord), 66 | shr( 67 | MemoryExpansionCoefficientShift, 68 | sub( 69 | mul(returnDataWords, returnDataWords), 70 | mul(msizeWords, msizeWords) 71 | ) 72 | ) 73 | ) 74 | ) 75 | } 76 | 77 | // Finally, add a small constant and compare to gas remaining; 78 | // bubble up the revert data if enough gas is still available. 79 | if lt(add(cost, ExtraGasBuffer), gas()) { 80 | // Copy returndata to memory; overwrite existing memory. 81 | returndatacopy(0, 0, returndatasize()) 82 | 83 | // Revert, specifying memory region with copied returndata. 84 | revert(0, returndatasize()) 85 | } 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * @dev Internal view function to branchlessly select either the caller (if 92 | * a supplied recipient is equal to zero) or the supplied recipient (if 93 | * that recipient is a nonzero value). 94 | * 95 | * @param recipient The supplied recipient. 96 | * 97 | * @return updatedRecipient The updated recipient. 98 | */ 99 | function _substituteCallerForEmptyRecipient( 100 | address recipient 101 | ) internal view returns (address updatedRecipient) { 102 | // Utilize assembly to perform a branchless operation on the recipient. 103 | assembly { 104 | // Add caller to recipient if recipient equals 0; otherwise add 0. 105 | updatedRecipient := add(recipient, mul(iszero(recipient), caller())) 106 | } 107 | } 108 | 109 | /** 110 | * @dev Internal pure function to cast a `bool` value to a `uint256` value. 111 | * 112 | * @param b The `bool` value to cast. 113 | * 114 | * @return u The `uint256` value. 115 | */ 116 | function _cast(bool b) internal pure returns (uint256 u) { 117 | assembly { 118 | u := b 119 | } 120 | } 121 | 122 | /** 123 | * @dev Internal pure function to cast the `pptrOffset` function from 124 | * `MemoryPointerLib` to a function that takes a memory array of 125 | * `AdvancedOrder` and an offset in memory and returns the 126 | * `AdvancedOrder` whose pointer is stored at that offset from the 127 | * array length. 128 | */ 129 | function _getReadAdvancedOrderByOffset() 130 | internal 131 | pure 132 | returns ( 133 | function(AdvancedOrder[] memory, uint256) 134 | internal 135 | pure 136 | returns (AdvancedOrder memory) fn2 137 | ) 138 | { 139 | function(MemoryPointer, uint256) 140 | internal 141 | pure 142 | returns (MemoryPointer) fn1 = MemoryPointerLib.pptrOffset; 143 | 144 | assembly { 145 | fn2 := fn1 146 | } 147 | } 148 | 149 | /** 150 | * @dev Internal pure function to cast the `pptrOffset` function from 151 | * `MemoryPointerLib` to a function that takes a memory array of 152 | * `Execution` and an offset in memory and returns the 153 | * `Execution` whose pointer is stored at that offset from the 154 | * array length. 155 | */ 156 | function _getReadExecutionByOffset() 157 | internal 158 | pure 159 | returns ( 160 | function(Execution[] memory, uint256) 161 | internal 162 | pure 163 | returns (Execution memory) fn2 164 | ) 165 | { 166 | function(MemoryPointer, uint256) 167 | internal 168 | pure 169 | returns (MemoryPointer) fn1 = MemoryPointerLib.pptrOffset; 170 | 171 | assembly { 172 | fn2 := fn1 173 | } 174 | } 175 | 176 | /** 177 | * @dev Internal pure function to return a `true` value that solc 178 | * will not recognize as a compile time constant. 179 | * 180 | * This function is used to bypass function specialization for 181 | * functions which take a constant boolean as an input parameter. 182 | * 183 | * This should only be used in cases where specialization has a 184 | * negligible impact on the gas cost of the function. 185 | * 186 | * Note: assumes the calldatasize is non-zero. 187 | */ 188 | function _runTimeConstantTrue() internal pure returns (bool) { 189 | return msg.data.length > 0; 190 | } 191 | 192 | /** 193 | * @dev Internal pure function to return a `false` value that solc 194 | * will not recognize as a compile time constant. 195 | * 196 | * This function is used to bypass function specialization for 197 | * functions which take a constant boolean as an input parameter. 198 | * 199 | * This should only be used in cases where specialization has a 200 | * negligible impact on the gas cost of the function. 201 | * 202 | * Note: assumes the calldatasize is non-zero. 203 | */ 204 | function _runTimeConstantFalse() internal pure returns (bool) { 205 | return msg.data.length == 0; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/lib/ReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | ReentrancyErrors 6 | } from "seaport-types/src/interfaces/ReentrancyErrors.sol"; 7 | 8 | import { LowLevelHelpers } from "./LowLevelHelpers.sol"; 9 | 10 | import { 11 | _revertInvalidMsgValue, 12 | _revertNoReentrantCalls 13 | } from "seaport-types/src/lib/ConsiderationErrors.sol"; 14 | 15 | import { 16 | _ENTERED_AND_ACCEPTING_NATIVE_TOKENS_SSTORE, 17 | _ENTERED_SSTORE, 18 | _NOT_ENTERED_SSTORE, 19 | _ENTERED_AND_ACCEPTING_NATIVE_TOKENS_TSTORE, 20 | _ENTERED_TSTORE, 21 | _NOT_ENTERED_TSTORE, 22 | _TSTORE_ENABLED_SSTORE, 23 | _REENTRANCY_GUARD_SLOT, 24 | _TLOAD_TEST_PAYLOAD, 25 | _TLOAD_TEST_PAYLOAD_OFFSET, 26 | _TLOAD_TEST_PAYLOAD_LENGTH 27 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 28 | 29 | import { 30 | InvalidMsgValue_error_selector, 31 | InvalidMsgValue_error_length, 32 | InvalidMsgValue_error_value_ptr, 33 | NoReentrantCalls_error_selector, 34 | NoReentrantCalls_error_length, 35 | Error_selector_offset 36 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 37 | 38 | /** 39 | * @title ReentrancyGuard 40 | * @author 0age 41 | * @notice ReentrancyGuard contains a storage variable (or a transient storage 42 | * variable in EVM environments that support it once activated) and 43 | * related functionality for protecting against reentrancy. 44 | */ 45 | contract ReentrancyGuard is ReentrancyErrors, LowLevelHelpers { 46 | // Declare an immutable variable to store the initial TSTORE support status. 47 | bool private immutable _tstoreInitialSupport; 48 | 49 | // Declare an immutable variable to store the tstore test contract address. 50 | address private immutable _tloadTestContract; 51 | 52 | /** 53 | * @dev Initialize the reentrancy guard during deployment. This involves 54 | * attempting to deploy a contract that utilizes TLOAD as part of the 55 | * contract construction bytecode, and configuring initial support for 56 | * using TSTORE in place of SSTORE for the reentrancy lock based on the 57 | * result. 58 | */ 59 | constructor() { 60 | // Deploy the contract testing TLOAD support and store the address. 61 | address tloadTestContract = _prepareTloadTest(); 62 | 63 | // Ensure the deployment was successful. 64 | if (tloadTestContract == address(0)) { 65 | revert TloadTestContractDeploymentFailed(); 66 | } 67 | 68 | // Determine if TSTORE is supported. 69 | bool tstoreInitialSupport = _testTload(tloadTestContract); 70 | 71 | // Store the result as an immutable. 72 | _tstoreInitialSupport = tstoreInitialSupport; 73 | 74 | // Set the address of the deployed TLOAD test contract as an immutable. 75 | _tloadTestContract = tloadTestContract; 76 | 77 | // If not using TSTORE (where _NOT_ENTERED_TSTORE = 0), set initial 78 | // sentinel value (where _NOT_ENTERED_SSTORE = 1). 79 | if (!tstoreInitialSupport) { 80 | // Initialize storage for the reentrancy guard in a cleared state. 81 | assembly { 82 | sstore(_REENTRANCY_GUARD_SLOT, _NOT_ENTERED_SSTORE) 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * @dev External function to activate TSTORE usage for the reentrancy guard. 89 | * Does not need to be called if TSTORE is supported from deployment, 90 | * and only needs to be called once. Reverts if TSTORE has already been 91 | * activated, if the opcode is not available, or if the reentrancy 92 | * guard is currently set. 93 | */ 94 | function __activateTstore() external { 95 | // Determine if TSTORE can potentially be activated. If it has already 96 | // been activated, or if the reentrancy guard is currently set, then 97 | // it cannot be activated. 98 | bool tstoreActivatable; 99 | assembly { 100 | tstoreActivatable := eq( 101 | sload(_REENTRANCY_GUARD_SLOT), 102 | _NOT_ENTERED_SSTORE 103 | ) 104 | } 105 | 106 | // Revert if TSTORE is already activated or not activatable. 107 | if (_tstoreInitialSupport || !tstoreActivatable) { 108 | revert TStoreAlreadyActivated(); 109 | } 110 | 111 | // Determine if TSTORE can be activated and revert if not. 112 | if (!_testTload(_tloadTestContract)) { 113 | revert TStoreNotSupported(); 114 | } 115 | 116 | // Mark TSTORE as activated. 117 | assembly { 118 | sstore(_REENTRANCY_GUARD_SLOT, _TSTORE_ENABLED_SSTORE) 119 | } 120 | } 121 | 122 | /** 123 | * @dev Internal function to ensure that a sentinel value for the reentrancy 124 | * guard is not currently set and, if not, to set a sentinel value for 125 | * the reentrancy guard based on whether or not native tokens may be 126 | * received during execution or not. 127 | * 128 | * @param acceptNativeTokens A boolean indicating whether native tokens may 129 | * be received during execution or not. 130 | */ 131 | function _setReentrancyGuard(bool acceptNativeTokens) internal { 132 | // Place immutable variable on the stack access within inline assembly. 133 | bool tstoreInitialSupport = _tstoreInitialSupport; 134 | 135 | // Utilize assembly to set the reentrancy guard based on tstore support. 136 | assembly { 137 | // "Loop" over three possible cases for setting the reentrancy guard 138 | // based on tstore support and state, exiting once the respective 139 | // state has been identified and a corresponding guard has been set. 140 | for {} 1 {} { 141 | // 1: handle case where tstore is supported from the start. 142 | if tstoreInitialSupport { 143 | // Ensure that the reentrancy guard is not already set. 144 | if tload(_REENTRANCY_GUARD_SLOT) { 145 | // Store left-padded selector with push4, 146 | // mem[28:32] = selector 147 | mstore(0, NoReentrantCalls_error_selector) 148 | 149 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 150 | revert( 151 | Error_selector_offset, 152 | NoReentrantCalls_error_length 153 | ) 154 | } 155 | 156 | // Set the reentrancy guard. A value of 1 indicates that 157 | // native tokens may not be accepted during execution, 158 | // whereas a value of 2 indicates that they will be accepted 159 | // (returning any remaining native tokens to the caller). 160 | tstore( 161 | _REENTRANCY_GUARD_SLOT, 162 | add(_ENTERED_TSTORE, acceptNativeTokens) 163 | ) 164 | 165 | // Exit the loop. 166 | break 167 | } 168 | 169 | // Retrieve the reentrancy guard sentinel value. 170 | let reentrancyGuard := sload(_REENTRANCY_GUARD_SLOT) 171 | 172 | // 2: handle tstore support that was activated post-deployment. 173 | if iszero(reentrancyGuard) { 174 | // Ensure that the reentrancy guard is not already set. 175 | if tload(_REENTRANCY_GUARD_SLOT) { 176 | // Store left-padded selector with push4, 177 | // mem[28:32] = selector 178 | mstore(0, NoReentrantCalls_error_selector) 179 | 180 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 181 | revert( 182 | Error_selector_offset, 183 | NoReentrantCalls_error_length 184 | ) 185 | } 186 | 187 | // Set the reentrancy guard. A value of 1 indicates that 188 | // native tokens may not be accepted during execution, 189 | // whereas a value of 2 indicates that they will be accepted 190 | // (returning any remaining native tokens to the caller). 191 | tstore( 192 | _REENTRANCY_GUARD_SLOT, 193 | add(_ENTERED_TSTORE, acceptNativeTokens) 194 | ) 195 | 196 | // Exit the loop. 197 | break 198 | } 199 | 200 | // 3: handle case where tstore support has not been activated. 201 | // Ensure that the reentrancy guard is not already set. 202 | if iszero(eq(reentrancyGuard, _NOT_ENTERED_SSTORE)) { 203 | // Store left-padded selector with push4 (reduces bytecode), 204 | // mem[28:32] = selector 205 | mstore(0, NoReentrantCalls_error_selector) 206 | 207 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 208 | revert(Error_selector_offset, NoReentrantCalls_error_length) 209 | } 210 | 211 | // Set the reentrancy guard. A value of 2 indicates that native 212 | // tokens may not be accepted during execution, whereas a value 213 | // of 3 indicates that they will be accepted (with any remaining 214 | // native tokens returned to the caller). 215 | sstore( 216 | _REENTRANCY_GUARD_SLOT, 217 | add(_ENTERED_SSTORE, acceptNativeTokens) 218 | ) 219 | 220 | // Exit the loop. 221 | break 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * @dev Internal function to unset the reentrancy guard sentinel value. 228 | */ 229 | function _clearReentrancyGuard() internal { 230 | // Place immutable variable on the stack access within inline assembly. 231 | bool tstoreInitialSupport = _tstoreInitialSupport; 232 | 233 | // Utilize assembly to clear reentrancy guard based on tstore support. 234 | assembly { 235 | // "Loop" over three possible cases for clearing reentrancy guard 236 | // based on tstore support and state, exiting once the respective 237 | // state has been identified and corresponding guard cleared. 238 | for {} 1 {} { 239 | // 1: handle case where tstore is supported from the start. 240 | if tstoreInitialSupport { 241 | // Clear the reentrancy guard. 242 | tstore(_REENTRANCY_GUARD_SLOT, _NOT_ENTERED_TSTORE) 243 | 244 | // Exit the loop. 245 | break 246 | } 247 | 248 | // Retrieve the reentrancy guard sentinel value. 249 | let reentrancyGuard := sload(_REENTRANCY_GUARD_SLOT) 250 | 251 | // 2: handle tstore support that was activated post-deployment. 252 | if iszero(reentrancyGuard) { 253 | // Clear the reentrancy guard. 254 | tstore(_REENTRANCY_GUARD_SLOT, _NOT_ENTERED_TSTORE) 255 | 256 | // Exit the loop. 257 | break 258 | } 259 | 260 | // 3: handle case where tstore support has not been activated. 261 | // Clear the reentrancy guard. 262 | sstore(_REENTRANCY_GUARD_SLOT, _NOT_ENTERED_SSTORE) 263 | 264 | // Exit the loop. 265 | break 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * @dev Internal view function to ensure that a sentinel value for the 272 | * reentrancy guard is not currently set. 273 | */ 274 | function _assertNonReentrant() internal view { 275 | // Place immutable variable on the stack access within inline assembly. 276 | bool tstoreInitialSupport = _tstoreInitialSupport; 277 | 278 | // Utilize assembly to check reentrancy guard based on tstore support. 279 | assembly { 280 | // 1: handle case where tstore is supported from the start. 281 | if tstoreInitialSupport { 282 | // Ensure that the reentrancy guard is not currently set. 283 | if tload(_REENTRANCY_GUARD_SLOT) { 284 | // Store left-padded selector with push4, 285 | // mem[28:32] = selector 286 | mstore(0, NoReentrantCalls_error_selector) 287 | 288 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 289 | revert(Error_selector_offset, NoReentrantCalls_error_length) 290 | } 291 | } 292 | 293 | // Handle cases where tstore is not initially supported. 294 | if iszero(tstoreInitialSupport) { 295 | // Retrieve the reentrancy guard sentinel value. 296 | let reentrancyGuard := sload(_REENTRANCY_GUARD_SLOT) 297 | 298 | // 2: handle tstore support that was activated post-deployment. 299 | if iszero(reentrancyGuard) { 300 | // Ensure that the reentrancy guard is not currently set. 301 | if tload(_REENTRANCY_GUARD_SLOT) { 302 | // Store left-padded selector with push4, 303 | // mem[28:32] = selector 304 | mstore(0, NoReentrantCalls_error_selector) 305 | 306 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 307 | revert( 308 | Error_selector_offset, 309 | NoReentrantCalls_error_length 310 | ) 311 | } 312 | } 313 | 314 | // 3: handle case where tstore support has not been activated. 315 | // Ensure that the reentrancy guard is not currently set. 316 | if gt(reentrancyGuard, _NOT_ENTERED_SSTORE) { 317 | // Store left-padded selector with push4 (reduces bytecode), 318 | // mem[28:32] = selector 319 | mstore(0, NoReentrantCalls_error_selector) 320 | 321 | // revert(abi.encodeWithSignature("NoReentrantCalls()")) 322 | revert(Error_selector_offset, NoReentrantCalls_error_length) 323 | } 324 | } 325 | } 326 | } 327 | 328 | /** 329 | * @dev Internal view function to ensure that the sentinel value indicating 330 | * native tokens may be received during execution is currently set. 331 | */ 332 | function _assertAcceptingNativeTokens() internal view { 333 | // Place immutable variable on the stack access within inline assembly. 334 | bool tstoreInitialSupport = _tstoreInitialSupport; 335 | 336 | // Utilize assembly to check reentrancy guard based on tstore support. 337 | assembly { 338 | // 1: handle case where tstore is supported from the start. 339 | if tstoreInitialSupport { 340 | // Ensure reentrancy guard is set to accept native tokens. 341 | if iszero( 342 | eq( 343 | tload(_REENTRANCY_GUARD_SLOT), 344 | _ENTERED_AND_ACCEPTING_NATIVE_TOKENS_TSTORE 345 | ) 346 | ) { 347 | // Store left-padded selector with push4, 348 | // mem[28:32] = selector 349 | mstore(0, InvalidMsgValue_error_selector) 350 | 351 | // Store argument. 352 | mstore(InvalidMsgValue_error_value_ptr, callvalue()) 353 | 354 | // revert(abi.encodeWithSignature( 355 | // "InvalidMsgValue(uint256)", value) 356 | // ) 357 | revert(Error_selector_offset, InvalidMsgValue_error_length) 358 | } 359 | } 360 | 361 | // Handle cases where tstore is not initially supported. 362 | if iszero(tstoreInitialSupport) { 363 | // Retrieve the reentrancy guard sentinel value. 364 | let reentrancyGuard := sload(_REENTRANCY_GUARD_SLOT) 365 | 366 | // 2: handle tstore support that was activated post-deployment. 367 | if iszero(reentrancyGuard) { 368 | // Ensure reentrancy guard is set to accept native tokens. 369 | if iszero( 370 | eq( 371 | tload(_REENTRANCY_GUARD_SLOT), 372 | _ENTERED_AND_ACCEPTING_NATIVE_TOKENS_TSTORE 373 | ) 374 | ) { 375 | // Store left-padded selector with push4, 376 | // mem[28:32] = selector 377 | mstore(0, InvalidMsgValue_error_selector) 378 | 379 | // Store argument. 380 | mstore(InvalidMsgValue_error_value_ptr, callvalue()) 381 | 382 | // revert(abi.encodeWithSignature( 383 | // "InvalidMsgValue(uint256)", value) 384 | // ) 385 | revert( 386 | Error_selector_offset, 387 | InvalidMsgValue_error_length 388 | ) 389 | } 390 | } 391 | 392 | // 3: handle case where tstore support has not been activated. 393 | // Ensure reentrancy guard is set to accepting native tokens. 394 | if and( 395 | iszero(iszero(reentrancyGuard)), 396 | iszero( 397 | eq( 398 | reentrancyGuard, 399 | _ENTERED_AND_ACCEPTING_NATIVE_TOKENS_SSTORE 400 | ) 401 | ) 402 | ) { 403 | // Store left-padded selector with push4 (reduces bytecode), 404 | // mem[28:32] = selector 405 | mstore(0, InvalidMsgValue_error_selector) 406 | 407 | // Store argument. 408 | mstore(InvalidMsgValue_error_value_ptr, callvalue()) 409 | 410 | // revert(abi.encodeWithSignature( 411 | // "InvalidMsgValue(uint256)", value) 412 | // ) 413 | revert(Error_selector_offset, InvalidMsgValue_error_length) 414 | } 415 | } 416 | } 417 | } 418 | 419 | /** 420 | * @dev Private function to deploy a test contract that utilizes TLOAD as 421 | * part of its fallback logic. 422 | */ 423 | function _prepareTloadTest() private returns (address contractAddress) { 424 | // Utilize assembly to deploy a contract testing TLOAD support. 425 | assembly { 426 | // Write the contract deployment code payload to scratch space. 427 | mstore(0, _TLOAD_TEST_PAYLOAD) 428 | 429 | // Deploy the contract. 430 | contractAddress := create( 431 | 0, 432 | _TLOAD_TEST_PAYLOAD_OFFSET, 433 | _TLOAD_TEST_PAYLOAD_LENGTH 434 | ) 435 | } 436 | } 437 | 438 | /** 439 | * @dev Private view function to determine if TSTORE/TLOAD are supported by 440 | * the current EVM implementation by attempting to call the test 441 | * contract, which utilizes TLOAD as part of its fallback logic. 442 | */ 443 | function _testTload( 444 | address tloadTestContract 445 | ) private view returns (bool ok) { 446 | // Call the test contract, which will perform a TLOAD test. If the call 447 | // does not revert, then TLOAD/TSTORE is supported. Do not forward all 448 | // available gas, as all forwarded gas will be consumed on revert. 449 | (ok, ) = tloadTestContract.staticcall{ gas: gasleft() / 10 }(""); 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /src/lib/SignatureVerification.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | SignatureVerificationErrors 6 | } from "seaport-types/src/interfaces/SignatureVerificationErrors.sol"; 7 | 8 | import { LowLevelHelpers } from "./LowLevelHelpers.sol"; 9 | 10 | import { 11 | ECDSA_MaxLength, 12 | ECDSA_signature_s_offset, 13 | ECDSA_signature_v_offset, 14 | ECDSA_twentySeventhAndTwentyEighthBytesSet, 15 | Ecrecover_args_size, 16 | Ecrecover_precompile, 17 | EIP1271_isValidSignature_calldata_baseLength, 18 | EIP1271_isValidSignature_digest_negativeOffset, 19 | EIP1271_isValidSignature_selector_negativeOffset, 20 | EIP1271_isValidSignature_selector, 21 | EIP1271_isValidSignature_signature_head_offset, 22 | EIP2098_allButHighestBitMask, 23 | MaxUint8, 24 | OneWord, 25 | Signature_lower_v 26 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 27 | 28 | import { 29 | BadContractSignature_error_length, 30 | BadContractSignature_error_selector, 31 | BadSignatureV_error_length, 32 | BadSignatureV_error_selector, 33 | BadSignatureV_error_v_ptr, 34 | Error_selector_offset, 35 | InvalidSignature_error_length, 36 | InvalidSignature_error_selector, 37 | InvalidSigner_error_length, 38 | InvalidSigner_error_selector 39 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 40 | 41 | /** 42 | * @title SignatureVerification 43 | * @author 0age 44 | * @notice SignatureVerification contains logic for verifying signatures. 45 | */ 46 | contract SignatureVerification is SignatureVerificationErrors, LowLevelHelpers { 47 | /** 48 | * @dev Internal view function to verify the signature of an order. An 49 | * ERC-1271 fallback will be attempted if either the signature length 50 | * is not 64 or 65 bytes or if the recovered signer does not match the 51 | * supplied signer. 52 | * 53 | * @param signer The signer for the order. 54 | * @param digest The digest to verify signature against. 55 | * @param originalDigest The original digest to verify signature 56 | * against. 57 | * @param originalSignatureLength The original signature length. 58 | * @param signature A signature from the signer indicating 59 | * that the order has been approved. 60 | */ 61 | function _assertValidSignature( 62 | address signer, 63 | bytes32 digest, 64 | bytes32 originalDigest, 65 | uint256 originalSignatureLength, 66 | bytes memory signature 67 | ) internal view { 68 | // Declare value for ecrecover equality or 1271 call success status. 69 | bool success; 70 | 71 | // Utilize assembly to perform optimized signature verification check. 72 | assembly { 73 | // Ensure that first word of scratch space is empty. 74 | mstore(0, 0) 75 | 76 | // Get the length of the signature. 77 | let signatureLength := mload(signature) 78 | 79 | // Get the pointer to the value preceding the signature length. 80 | // This will be used for temporary memory overrides - either the 81 | // signature head for isValidSignature or the digest for ecrecover. 82 | let wordBeforeSignaturePtr := sub(signature, OneWord) 83 | 84 | // Cache the current value behind the signature to restore it later. 85 | let cachedWordBeforeSignature := mload(wordBeforeSignaturePtr) 86 | 87 | // Declare lenDiff + recoveredSigner scope to manage stack pressure. 88 | { 89 | // Take the difference between the max ECDSA signature length 90 | // and the actual signature length. Overflow desired for any 91 | // values > 65. If the diff is not 0 or 1, it is not a valid 92 | // ECDSA signature - move on to EIP1271 check. 93 | let lenDiff := sub(ECDSA_MaxLength, signatureLength) 94 | 95 | // Declare variable for recovered signer. 96 | let recoveredSigner 97 | 98 | // If diff is 0 or 1, it may be an ECDSA signature. 99 | // Try to recover signer. 100 | if iszero(gt(lenDiff, 1)) { 101 | // Read the signature `s` value. 102 | let originalSignatureS := mload( 103 | add(signature, ECDSA_signature_s_offset) 104 | ) 105 | 106 | // Read the first byte of the word after `s`. If the 107 | // signature is 65 bytes, this will be the real `v` value. 108 | // If not, it will need to be modified - doing it this way 109 | // saves an extra condition. 110 | let v := byte( 111 | 0, 112 | mload(add(signature, ECDSA_signature_v_offset)) 113 | ) 114 | 115 | // If lenDiff is 1, parse 64-byte signature as ECDSA. 116 | if lenDiff { 117 | // Extract yParity from highest bit of vs and add 27 to 118 | // get v. 119 | v := add( 120 | shr(MaxUint8, originalSignatureS), 121 | Signature_lower_v 122 | ) 123 | 124 | // Extract canonical s from vs, all but the highest bit. 125 | // Temporarily overwrite the original `s` value in the 126 | // signature. 127 | mstore( 128 | add(signature, ECDSA_signature_s_offset), 129 | and( 130 | originalSignatureS, 131 | EIP2098_allButHighestBitMask 132 | ) 133 | ) 134 | } 135 | // Temporarily overwrite the signature length with `v` to 136 | // conform to the expected input for ecrecover. 137 | mstore(signature, v) 138 | 139 | // Temporarily overwrite the word before the length with 140 | // `digest` to conform to the expected input for ecrecover. 141 | mstore(wordBeforeSignaturePtr, digest) 142 | 143 | // Attempt to recover the signer for the given signature. Do 144 | // not check the call status as ecrecover will return a null 145 | // address if the signature is invalid. 146 | pop( 147 | staticcall( 148 | gas(), 149 | Ecrecover_precompile, // Call ecrecover precompile. 150 | wordBeforeSignaturePtr, // Use data memory location. 151 | Ecrecover_args_size, // Size of digest, v, r, and s. 152 | 0, // Write result to scratch space. 153 | OneWord // Provide size of returned result. 154 | ) 155 | ) 156 | 157 | // Restore cached word before signature. 158 | mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature) 159 | 160 | // Restore cached signature length. 161 | mstore(signature, signatureLength) 162 | 163 | // Restore cached signature `s` value. 164 | mstore( 165 | add(signature, ECDSA_signature_s_offset), 166 | originalSignatureS 167 | ) 168 | 169 | // Read the recovered signer from the buffer given as return 170 | // space for ecrecover. 171 | recoveredSigner := mload(0) 172 | } 173 | 174 | // Set success to true if the signature provided was a valid 175 | // ECDSA signature and the signer is not the null address. Use 176 | // gt instead of direct as success is used outside of assembly. 177 | success := and(eq(signer, recoveredSigner), gt(signer, 0)) 178 | } 179 | 180 | // If the signature was not verified with ecrecover, try EIP1271. 181 | if iszero(success) { 182 | // Reset the original signature length. 183 | mstore(signature, originalSignatureLength) 184 | 185 | // Temporarily overwrite the word before the signature length 186 | // and use it as the head of the signature input to 187 | // `isValidSignature`, which has a value of 64. 188 | mstore( 189 | wordBeforeSignaturePtr, 190 | EIP1271_isValidSignature_signature_head_offset 191 | ) 192 | 193 | // Get pointer to use for the selector of `isValidSignature`. 194 | let selectorPtr := sub( 195 | signature, 196 | EIP1271_isValidSignature_selector_negativeOffset 197 | ) 198 | 199 | // Cache the value currently stored at the selector pointer. 200 | let cachedWordOverwrittenBySelector := mload(selectorPtr) 201 | 202 | // Cache the value currently stored at the digest pointer. 203 | let cachedWordOverwrittenByDigest := mload( 204 | sub( 205 | signature, 206 | EIP1271_isValidSignature_digest_negativeOffset 207 | ) 208 | ) 209 | 210 | // Write the selector first, since it overlaps the digest. 211 | mstore(selectorPtr, EIP1271_isValidSignature_selector) 212 | 213 | // Next, write the original digest. 214 | mstore( 215 | sub( 216 | signature, 217 | EIP1271_isValidSignature_digest_negativeOffset 218 | ), 219 | originalDigest 220 | ) 221 | 222 | // Call signer with `isValidSignature` to validate signature. 223 | success := staticcall( 224 | gas(), 225 | signer, 226 | selectorPtr, 227 | add( 228 | originalSignatureLength, 229 | EIP1271_isValidSignature_calldata_baseLength 230 | ), 231 | 0, 232 | OneWord 233 | ) 234 | 235 | // Determine if the signature is valid on successful calls. 236 | if success { 237 | // If first word of scratch space does not contain EIP-1271 238 | // signature selector, revert. 239 | if iszero(eq(mload(0), EIP1271_isValidSignature_selector)) { 240 | // Revert with bad 1271 signature if signer has code. 241 | if extcodesize(signer) { 242 | // Bad contract signature. 243 | // Store left-padded selector with push4, mem[28:32] 244 | mstore(0, BadContractSignature_error_selector) 245 | 246 | // revert(abi.encodeWithSignature( 247 | // "BadContractSignature()" 248 | // )) 249 | revert( 250 | Error_selector_offset, 251 | BadContractSignature_error_length 252 | ) 253 | } 254 | 255 | // Check if signature length was invalid. 256 | if gt(sub(ECDSA_MaxLength, signatureLength), 1) { 257 | // Revert with generic invalid signature error. 258 | // Store left-padded selector with push4, mem[28:32] 259 | mstore(0, InvalidSignature_error_selector) 260 | 261 | // revert(abi.encodeWithSignature( 262 | // "InvalidSignature()" 263 | // )) 264 | revert( 265 | Error_selector_offset, 266 | InvalidSignature_error_length 267 | ) 268 | } 269 | 270 | // Check if v was invalid. 271 | if and( 272 | eq(signatureLength, ECDSA_MaxLength), 273 | iszero( 274 | byte( 275 | byte( 276 | 0, 277 | mload( 278 | add( 279 | signature, 280 | ECDSA_signature_v_offset 281 | ) 282 | ) 283 | ), 284 | ECDSA_twentySeventhAndTwentyEighthBytesSet 285 | ) 286 | ) 287 | ) { 288 | // Revert with invalid v value. 289 | // Store left-padded selector with push4, mem[28:32] 290 | mstore(0, BadSignatureV_error_selector) 291 | mstore( 292 | BadSignatureV_error_v_ptr, 293 | byte( 294 | 0, 295 | mload( 296 | add(signature, ECDSA_signature_v_offset) 297 | ) 298 | ) 299 | ) 300 | 301 | // revert(abi.encodeWithSignature( 302 | // "BadSignatureV(uint8)", v 303 | // )) 304 | revert( 305 | Error_selector_offset, 306 | BadSignatureV_error_length 307 | ) 308 | } 309 | 310 | // Revert with generic invalid signer error message. 311 | // Store left-padded selector with push4, mem[28:32] 312 | mstore(0, InvalidSigner_error_selector) 313 | 314 | // revert(abi.encodeWithSignature("InvalidSigner()")) 315 | revert( 316 | Error_selector_offset, 317 | InvalidSigner_error_length 318 | ) 319 | } 320 | } 321 | 322 | // Restore the cached values overwritten by selector, digest and 323 | // signature head. 324 | mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature) 325 | mstore(selectorPtr, cachedWordOverwrittenBySelector) 326 | mstore( 327 | sub( 328 | signature, 329 | EIP1271_isValidSignature_digest_negativeOffset 330 | ), 331 | cachedWordOverwrittenByDigest 332 | ) 333 | } 334 | } 335 | 336 | // If the call failed... 337 | if (!success) { 338 | // Revert and pass reason along if one was returned. 339 | _revertWithReasonIfOneIsReturned(); 340 | 341 | // Otherwise, revert with error indicating bad contract signature. 342 | assembly { 343 | // Store left-padded selector with push4, mem[28:32] = selector 344 | mstore(0, BadContractSignature_error_selector) 345 | // revert(abi.encodeWithSignature("BadContractSignature()")) 346 | revert(Error_selector_offset, BadContractSignature_error_length) 347 | } 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/lib/Verifiers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { OrderStatus } from "seaport-types/src/lib/ConsiderationStructs.sol"; 5 | 6 | import { Assertions } from "./Assertions.sol"; 7 | 8 | import { SignatureVerification } from "./SignatureVerification.sol"; 9 | 10 | import { 11 | _revertInvalidTime, 12 | _revertOrderAlreadyFilled, 13 | _revertOrderIsCancelled, 14 | _revertOrderPartiallyFilled 15 | } from "seaport-types/src/lib/ConsiderationErrors.sol"; 16 | 17 | import { 18 | BulkOrderProof_keyShift, 19 | BulkOrderProof_keySize, 20 | BulkOrderProof_lengthAdjustmentBeforeMask, 21 | BulkOrderProof_lengthRangeAfterMask, 22 | BulkOrderProof_minSize, 23 | BulkOrderProof_rangeSize, 24 | ECDSA_MaxLength, 25 | OneWord, 26 | OneWordShift, 27 | ThirtyOneBytes, 28 | TwoWords 29 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 30 | 31 | /** 32 | * @title Verifiers 33 | * @author 0age 34 | * @notice Verifiers contains functions for performing verifications. 35 | */ 36 | contract Verifiers is Assertions, SignatureVerification { 37 | /** 38 | * @dev Derive and set hashes, reference chainId, and associated domain 39 | * separator during deployment. 40 | * 41 | * @param conduitController A contract that deploys conduits, or proxies 42 | * that may optionally be used to transfer approved 43 | * ERC20/721/1155 tokens. 44 | */ 45 | constructor(address conduitController) Assertions(conduitController) {} 46 | 47 | /** 48 | * @dev Internal view function to ensure that the current time falls within 49 | * an order's valid timespan. 50 | * 51 | * @param startTime The time at which the order becomes active. 52 | * @param endTime The time at which the order becomes inactive. 53 | * @param revertOnInvalid A boolean indicating whether to revert if the 54 | * order is not active. 55 | * 56 | * @return valid A boolean indicating whether the order is active. 57 | */ 58 | function _verifyTime( 59 | uint256 startTime, 60 | uint256 endTime, 61 | bool revertOnInvalid 62 | ) internal view returns (bool valid) { 63 | // Mark as valid if order has started and has not already ended. 64 | assembly { 65 | valid := and( 66 | iszero(gt(startTime, timestamp())), 67 | gt(endTime, timestamp()) 68 | ) 69 | } 70 | 71 | // Only revert on invalid if revertOnInvalid has been supplied as true. 72 | if (revertOnInvalid && !valid) { 73 | _revertInvalidTime(startTime, endTime); 74 | } 75 | } 76 | 77 | /** 78 | * @dev Internal view function to verify the signature of an order. An 79 | * ERC-1271 fallback will be attempted if either the signature length 80 | * is not 64 or 65 bytes or if the recovered signer does not match the 81 | * supplied offerer. Note that in cases where a 64 or 65 byte signature 82 | * is supplied, only standard ECDSA signatures that recover to a 83 | * non-zero address are supported. 84 | * 85 | * @param offerer The offerer for the order. 86 | * @param orderHash The order hash. 87 | * @param signature A signature from the offerer indicating that the order 88 | * has been approved. 89 | */ 90 | function _verifySignature( 91 | address offerer, 92 | bytes32 orderHash, 93 | bytes memory signature 94 | ) internal view { 95 | // Determine whether the offerer is the caller. 96 | bool offererIsCaller; 97 | assembly { 98 | offererIsCaller := eq(offerer, caller()) 99 | } 100 | 101 | // Skip signature verification if the offerer is the caller. 102 | if (offererIsCaller) { 103 | return; 104 | } 105 | 106 | // Derive the EIP-712 domain separator. 107 | bytes32 domainSeparator = _domainSeparator(); 108 | 109 | // Derive original EIP-712 digest using domain separator and order hash. 110 | bytes32 originalDigest = _deriveEIP712Digest( 111 | domainSeparator, 112 | orderHash 113 | ); 114 | 115 | // Read the length of the signature from memory and place on the stack. 116 | uint256 originalSignatureLength = signature.length; 117 | 118 | // Determine effective digest if signature has a valid bulk order size. 119 | bytes32 digest; 120 | if (_isValidBulkOrderSize(originalSignatureLength)) { 121 | // Rederive order hash and digest using bulk order proof. 122 | (orderHash) = _computeBulkOrderProof(signature, orderHash); 123 | digest = _deriveEIP712Digest(domainSeparator, orderHash); 124 | } else { 125 | // Supply the original digest as the effective digest. 126 | digest = originalDigest; 127 | } 128 | 129 | // Ensure that the signature for the digest is valid for the offerer. 130 | _assertValidSignature( 131 | offerer, 132 | digest, 133 | originalDigest, 134 | originalSignatureLength, 135 | signature 136 | ); 137 | } 138 | 139 | /** 140 | * @dev Determines whether the specified bulk order size is valid. 141 | * 142 | * @param signatureLength The signature length of the bulk order to check. 143 | * 144 | * @return validLength True if bulk order size is valid, false otherwise. 145 | */ 146 | function _isValidBulkOrderSize( 147 | uint256 signatureLength 148 | ) internal pure returns (bool validLength) { 149 | // Utilize assembly to validate the length; the equivalent logic is 150 | // (64 + x) + 3 + 32y where (0 <= x <= 1) and (1 <= y <= 24). 151 | assembly { 152 | validLength := and( 153 | lt( 154 | sub(signatureLength, BulkOrderProof_minSize), 155 | BulkOrderProof_rangeSize 156 | ), 157 | lt( 158 | and( 159 | add( 160 | signatureLength, 161 | BulkOrderProof_lengthAdjustmentBeforeMask 162 | ), 163 | ThirtyOneBytes 164 | ), 165 | BulkOrderProof_lengthRangeAfterMask 166 | ) 167 | ) 168 | } 169 | } 170 | 171 | /** 172 | * @dev Computes the bulk order hash for the specified proof and leaf. Note 173 | * that if an index that exceeds the number of orders in the bulk order 174 | * payload will instead "wrap around" and refer to an earlier index. 175 | * 176 | * @param proofAndSignature The proof and signature of the bulk order. 177 | * @param leaf The leaf of the bulk order tree. 178 | * 179 | * @return bulkOrderHash The bulk order hash. 180 | */ 181 | function _computeBulkOrderProof( 182 | bytes memory proofAndSignature, 183 | bytes32 leaf 184 | ) internal pure returns (bytes32 bulkOrderHash) { 185 | // Declare arguments for the root hash and the height of the proof. 186 | bytes32 root; 187 | uint256 height; 188 | 189 | // Utilize assembly to efficiently derive the root hash using the proof. 190 | assembly { 191 | // Retrieve the length of the proof, key, and signature combined. 192 | let fullLength := mload(proofAndSignature) 193 | 194 | // If proofAndSignature has odd length, it is a compact signature 195 | // with 64 bytes. 196 | let signatureLength := sub(ECDSA_MaxLength, and(fullLength, 1)) 197 | 198 | // Derive height (or depth of tree) with signature and proof length. 199 | height := shr(OneWordShift, sub(fullLength, signatureLength)) 200 | 201 | // Update the length in memory to only include the signature. 202 | mstore(proofAndSignature, signatureLength) 203 | 204 | // Derive the pointer for the key using the signature length. 205 | let keyPtr := add(proofAndSignature, add(OneWord, signatureLength)) 206 | 207 | // Retrieve the three-byte key using the derived pointer. 208 | let key := shr(BulkOrderProof_keyShift, mload(keyPtr)) 209 | 210 | /// Retrieve pointer to first proof element by applying a constant 211 | // for the key size to the derived key pointer. 212 | let proof := add(keyPtr, BulkOrderProof_keySize) 213 | 214 | // Compute level 1. 215 | let scratchPtr1 := shl(OneWordShift, and(key, 1)) 216 | mstore(scratchPtr1, leaf) 217 | mstore(xor(scratchPtr1, OneWord), mload(proof)) 218 | 219 | // Compute remaining proofs. 220 | for { 221 | let i := 1 222 | } lt(i, height) { 223 | i := add(i, 1) 224 | } { 225 | proof := add(proof, OneWord) 226 | let scratchPtr := shl(OneWordShift, and(shr(i, key), 1)) 227 | mstore(scratchPtr, keccak256(0, TwoWords)) 228 | mstore(xor(scratchPtr, OneWord), mload(proof)) 229 | } 230 | 231 | // Compute root hash. 232 | root := keccak256(0, TwoWords) 233 | } 234 | 235 | // Retrieve appropriate typehash constant based on height. 236 | bytes32 rootTypeHash = _lookupBulkOrderTypehash(height); 237 | 238 | // Use the typehash and the root hash to derive final bulk order hash. 239 | assembly { 240 | mstore(0, rootTypeHash) 241 | mstore(OneWord, root) 242 | bulkOrderHash := keccak256(0, TwoWords) 243 | } 244 | } 245 | 246 | /** 247 | * @dev Internal view function to validate that a given order is fillable 248 | * and not cancelled based on the order status. 249 | * 250 | * @param orderHash The order hash. 251 | * @param orderStatus The status of the order, including whether it has 252 | * been cancelled and the fraction filled. 253 | * @param onlyAllowUnused A boolean flag indicating whether partial fills 254 | * are supported by the calling function. 255 | * @param revertOnInvalid A boolean indicating whether to revert if the 256 | * order has been cancelled or filled beyond the 257 | * allowable amount. 258 | * 259 | * @return valid A boolean indicating whether the order is valid. 260 | */ 261 | function _verifyOrderStatus( 262 | bytes32 orderHash, 263 | OrderStatus storage orderStatus, 264 | bool onlyAllowUnused, 265 | bool revertOnInvalid 266 | ) internal view returns (bool valid) { 267 | // Ensure that the order has not been cancelled. 268 | if (orderStatus.isCancelled) { 269 | // Only revert if revertOnInvalid has been supplied as true. 270 | if (revertOnInvalid) { 271 | _revertOrderIsCancelled(orderHash); 272 | } 273 | 274 | // Return false as the order status is invalid. 275 | return false; 276 | } 277 | 278 | // Read order status numerator from storage and place on stack. 279 | uint256 orderStatusNumerator = orderStatus.numerator; 280 | 281 | // If the order is not entirely unused... 282 | if (orderStatusNumerator != 0) { 283 | // ensure the order has not been partially filled when not allowed. 284 | if (onlyAllowUnused) { 285 | // Always revert on partial fills when onlyAllowUnused is true. 286 | _revertOrderPartiallyFilled(orderHash); 287 | } 288 | // Otherwise, ensure that order has not been entirely filled. 289 | else if (orderStatusNumerator >= orderStatus.denominator) { 290 | // Only revert if revertOnInvalid has been supplied as true. 291 | if (revertOnInvalid) { 292 | _revertOrderAlreadyFilled(orderHash); 293 | } 294 | 295 | // Return false as the order status is invalid. 296 | return false; 297 | } 298 | } 299 | 300 | // Return true as the order status is valid. 301 | valid = true; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/lib/ZoneInteraction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { OrderType } from "seaport-types/src/lib/ConsiderationEnums.sol"; 5 | 6 | import { 7 | AdvancedOrder, 8 | BasicOrderParameters, 9 | OrderParameters 10 | } from "seaport-types/src/lib/ConsiderationStructs.sol"; 11 | 12 | import { 13 | ZoneInteractionErrors 14 | } from "seaport-types/src/interfaces/ZoneInteractionErrors.sol"; 15 | 16 | import { LowLevelHelpers } from "./LowLevelHelpers.sol"; 17 | 18 | import { ConsiderationEncoder } from "./ConsiderationEncoder.sol"; 19 | 20 | import { 21 | CalldataPointer, 22 | MemoryPointer, 23 | OffsetOrLengthMask, 24 | ZeroSlotPtr 25 | } from "seaport-types/src/helpers/PointerLibraries.sol"; 26 | 27 | import { 28 | authorizeOrder_selector_offset, 29 | BasicOrder_zone_cdPtr, 30 | ContractOrder_orderHash_offerer_shift, 31 | MaskOverFirstFourBytes, 32 | OneWord, 33 | OrderParameters_salt_offset, 34 | OrderParameters_zone_offset, 35 | validateOrder_selector_offset 36 | } from "seaport-types/src/lib/ConsiderationConstants.sol"; 37 | 38 | import { 39 | Error_selector_offset, 40 | InvalidContractOrder_error_selector, 41 | InvalidRestrictedOrder_error_length, 42 | InvalidRestrictedOrder_error_orderHash_ptr, 43 | InvalidRestrictedOrder_error_selector 44 | } from "seaport-types/src/lib/ConsiderationErrorConstants.sol"; 45 | 46 | /** 47 | * @title ZoneInteraction 48 | * @author 0age 49 | * @notice ZoneInteraction contains logic related to interacting with zones. 50 | */ 51 | contract ZoneInteraction is 52 | ConsiderationEncoder, 53 | ZoneInteractionErrors, 54 | LowLevelHelpers 55 | { 56 | /** 57 | * @dev Internal function to determine if an order has a restricted order 58 | * type and, if so, to ensure that either the zone is the caller or 59 | * that a call to `validateOrder` on the zone returns a magic value 60 | * indicating that the order is currently valid. Note that contract 61 | * orders are not accessible via the basic fulfillment method. 62 | * 63 | * @param orderHash The hash of the order. 64 | * @param orderType The order type. 65 | */ 66 | function _assertRestrictedBasicOrderAuthorization( 67 | bytes32 orderHash, 68 | OrderType orderType 69 | ) internal returns (uint256 callDataPointer) { 70 | // Order type 2-3 require zone be caller or zone to approve. 71 | // Note that in cases where fulfiller == zone, the restricted order 72 | // validation will be skipped. 73 | if ( 74 | _isRestrictedAndCallerNotZone( 75 | orderType, 76 | CalldataPointer.wrap(BasicOrder_zone_cdPtr).readAddress() 77 | ) 78 | ) { 79 | // Encode the `authorizeOrder` call in memory. 80 | ( 81 | MemoryPointer callData, 82 | uint256 size, 83 | uint256 memoryLocationForOrderHashes 84 | ) = _encodeAuthorizeBasicOrder(orderHash); 85 | 86 | // Write the error selector to memory at the zero slot where it can 87 | // be used to revert with a specific error message. 88 | ZeroSlotPtr.write(InvalidRestrictedOrder_error_selector); 89 | 90 | // Perform `authorizeOrder` call & ensure magic value was returned. 91 | _callAndCheckStatus( 92 | CalldataPointer.wrap(BasicOrder_zone_cdPtr).readAddress(), 93 | orderHash, 94 | callData.offset(authorizeOrder_selector_offset), 95 | size 96 | ); 97 | 98 | // Restore the zero slot. 99 | ZeroSlotPtr.write(0); 100 | 101 | // Register the calldata pointer for the encoded calldata. 102 | callDataPointer = MemoryPointer.unwrap(callData); 103 | 104 | // Utilize unchecked logic as size value cannot be so large as to 105 | // cause an overflow. 106 | unchecked { 107 | // Write the packed encoding of size and memory location for 108 | // order hashes to memory at the head of the encoded calldata. 109 | callData.write( 110 | ((size + OneWord) << 128) | memoryLocationForOrderHashes 111 | ); 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * @dev Internal function to determine if an order has a restricted order 118 | * type and, if so, to ensure that either the zone is the caller or 119 | * that a call to `validateOrder` on the zone returns a magic value 120 | * indicating that the order is currently valid. Note that contract 121 | * orders are not accessible via the basic fulfillment method. 122 | * 123 | * @param orderHash The hash of the order. 124 | * @param orderType The order type. 125 | * @param callDataPtr The pointer to the call data for the basic order. 126 | * Note that the initial value will contain the size 127 | * and the memory location for order hashes length. 128 | */ 129 | function _assertRestrictedBasicOrderValidity( 130 | bytes32 orderHash, 131 | OrderType orderType, 132 | uint256 callDataPtr 133 | ) internal { 134 | // Order type 2-3 require zone be caller or zone to approve. 135 | // Note that in cases where fulfiller == zone, the restricted order 136 | // validation will be skipped. 137 | if ( 138 | _isRestrictedAndCallerNotZone( 139 | orderType, 140 | CalldataPointer.wrap(BasicOrder_zone_cdPtr).readAddress() 141 | ) 142 | ) { 143 | // Cast the call data pointer to a memory pointer. 144 | MemoryPointer callData = MemoryPointer.wrap(callDataPtr); 145 | 146 | // Retrieve the size and memory location for order hashes from the 147 | // head of the encoded calldata where it was previously written. 148 | uint256 sizeAndMemoryLocationForOrderHashes = ( 149 | callData.readUint256() 150 | ); 151 | 152 | // Split the packed encoding to retrieve size and memory location. 153 | uint256 size = sizeAndMemoryLocationForOrderHashes >> 128; 154 | uint256 memoryLocationForOrderHashes = ( 155 | sizeAndMemoryLocationForOrderHashes & OffsetOrLengthMask 156 | ); 157 | 158 | // Encode the `validateOrder` call in memory. 159 | _encodeValidateBasicOrder(callData, memoryLocationForOrderHashes); 160 | 161 | // Account for the offset of the selector in the encoded call data. 162 | callData = callData.offset(validateOrder_selector_offset); 163 | 164 | // Write the error selector to memory at the zero slot where it can 165 | // be used to revert with a specific error message. 166 | ZeroSlotPtr.write(InvalidRestrictedOrder_error_selector); 167 | 168 | // Perform `validateOrder` call and ensure magic value was returned. 169 | _callAndCheckStatus( 170 | CalldataPointer.wrap(BasicOrder_zone_cdPtr).readAddress(), 171 | orderHash, 172 | callData, 173 | size 174 | ); 175 | 176 | // Restore the zero slot. 177 | ZeroSlotPtr.write(0); 178 | } 179 | } 180 | 181 | /** 182 | * @dev Internal function to determine the pre-execution validity of 183 | * restricted orders, signaling whether or not the order is valid. 184 | * Restricted orders where the caller is not the zone must 185 | * successfully call `authorizeOrder` with the correct magic value 186 | * returned. 187 | * 188 | * @param advancedOrder The advanced order in question. 189 | * @param orderHashes The order hashes of each order included as part 190 | * of the current fulfillment. 191 | * @param orderHash The hash of the order. 192 | * @param orderIndex The index of the order. 193 | * @param revertOnInvalid Whether to revert if the call is invalid. 194 | * 195 | * @return isValid True if the order is valid, false otherwise (unless 196 | * revertOnInvalid is true, in which case this function 197 | * will revert). 198 | */ 199 | function _checkRestrictedAdvancedOrderAuthorization( 200 | AdvancedOrder memory advancedOrder, 201 | bytes32[] memory orderHashes, 202 | bytes32 orderHash, 203 | uint256 orderIndex, 204 | bool revertOnInvalid 205 | ) internal returns (bool isValid) { 206 | // Retrieve the parameters of the order in question. 207 | OrderParameters memory parameters = advancedOrder.parameters; 208 | 209 | // OrderType 2-3 require zone to be caller or approve via validateOrder. 210 | if ( 211 | _isRestrictedAndCallerNotZone(parameters.orderType, parameters.zone) 212 | ) { 213 | // Encode the `validateOrder` call in memory. 214 | (MemoryPointer callData, uint256 size) = _encodeAuthorizeOrder( 215 | orderHash, 216 | parameters, 217 | advancedOrder.extraData, 218 | orderHashes, 219 | orderIndex 220 | ); 221 | 222 | // Perform call and ensure a corresponding magic value was returned. 223 | return 224 | _callAndCheckStatusWithSkip( 225 | parameters.zone, 226 | orderHash, 227 | callData, 228 | size, 229 | InvalidRestrictedOrder_error_selector, 230 | revertOnInvalid 231 | ); 232 | } 233 | 234 | return true; 235 | } 236 | 237 | /** 238 | * @dev Internal function to determine the pre-execution validity of 239 | * restricted orders and to revert if the order is invalid. 240 | * Restricted orders where the caller is not the zone must 241 | * successfully call `authorizeOrder` with the correct magic value 242 | * returned. 243 | * 244 | * @param advancedOrder The advanced order in question. 245 | * @param orderHashes The order hashes of each order included as part 246 | * of the current fulfillment. 247 | * @param orderHash The hash of the order. 248 | * @param orderIndex The index of the order. 249 | */ 250 | function _assertRestrictedAdvancedOrderAuthorization( 251 | AdvancedOrder memory advancedOrder, 252 | bytes32[] memory orderHashes, 253 | bytes32 orderHash, 254 | uint256 orderIndex 255 | ) internal { 256 | // Retrieve the parameters of the order in question. 257 | OrderParameters memory parameters = advancedOrder.parameters; 258 | 259 | // OrderType 2-3 requires zone to call or approve via authorizeOrder. 260 | if ( 261 | _isRestrictedAndCallerNotZone(parameters.orderType, parameters.zone) 262 | ) { 263 | // Encode the `authorizeOrder` call in memory. 264 | (MemoryPointer callData, uint256 size) = _encodeAuthorizeOrder( 265 | orderHash, 266 | parameters, 267 | advancedOrder.extraData, 268 | orderHashes, 269 | orderIndex 270 | ); 271 | 272 | // Write the error selector to memory at the zero slot where it can 273 | // be used to revert with a specific error message. 274 | ZeroSlotPtr.write(InvalidRestrictedOrder_error_selector); 275 | 276 | // Perform call and ensure a corresponding magic value was returned. 277 | _callAndCheckStatus(parameters.zone, orderHash, callData, size); 278 | 279 | // Restore the zero slot. 280 | ZeroSlotPtr.write(0); 281 | } 282 | } 283 | 284 | /** 285 | * @dev Internal function to determine the post-execution validity of 286 | * restricted and contract orders. Restricted orders where the caller 287 | * is not the zone must successfully call `validateOrder` with the 288 | * correct magic value returned. Contract orders must successfully call 289 | * `ratifyOrder` with the correct magic value returned. 290 | * 291 | * @param advancedOrder The advanced order in question. 292 | * @param orderHashes The order hashes of each order included as part of 293 | * the current fulfillment. 294 | * @param orderHash The hash of the order. 295 | */ 296 | function _assertRestrictedAdvancedOrderValidity( 297 | AdvancedOrder memory advancedOrder, 298 | bytes32[] memory orderHashes, 299 | bytes32 orderHash 300 | ) internal { 301 | // Declare variables that will be assigned based on the order type. 302 | address target; 303 | uint256 errorSelector; 304 | MemoryPointer callData; 305 | uint256 size; 306 | 307 | // Retrieve the parameters of the order in question. 308 | OrderParameters memory parameters = advancedOrder.parameters; 309 | 310 | // OrderType 2-3 require zone to be caller or approve via validateOrder. 311 | if ( 312 | _isRestrictedAndCallerNotZone(parameters.orderType, parameters.zone) 313 | ) { 314 | // Encode the `validateOrder` call in memory. 315 | (callData, size) = _encodeValidateOrder( 316 | parameters 317 | .toMemoryPointer() 318 | .offset(OrderParameters_salt_offset) 319 | .readUint256(), 320 | orderHashes 321 | ); 322 | 323 | // Set the target to the zone. 324 | target = ( 325 | parameters 326 | .toMemoryPointer() 327 | .offset(OrderParameters_zone_offset) 328 | .readAddress() 329 | ); 330 | 331 | // Set the restricted-order-specific error selector. 332 | errorSelector = InvalidRestrictedOrder_error_selector; 333 | } else if (parameters.orderType == OrderType.CONTRACT) { 334 | // Set the target to the offerer (note the offerer has no offset). 335 | target = parameters.toMemoryPointer().readAddress(); 336 | 337 | // Shift the target 96 bits to the left. 338 | uint256 shiftedOfferer; 339 | assembly { 340 | shiftedOfferer := shl( 341 | ContractOrder_orderHash_offerer_shift, 342 | target 343 | ) 344 | } 345 | 346 | // Encode the `ratifyOrder` call in memory. 347 | (callData, size) = _encodeRatifyOrder( 348 | orderHash, 349 | parameters, 350 | advancedOrder.extraData, 351 | orderHashes, 352 | shiftedOfferer 353 | ); 354 | 355 | // Set the contract-order-specific error selector. 356 | errorSelector = InvalidContractOrder_error_selector; 357 | } else { 358 | return; 359 | } 360 | 361 | // Write the error selector to memory at the zero slot where it can be 362 | // used to revert with a specific error message. 363 | ZeroSlotPtr.write(errorSelector); 364 | 365 | // Perform call and ensure a corresponding magic value was returned. 366 | _callAndCheckStatus(target, orderHash, callData, size); 367 | 368 | // Restore the zero slot. 369 | ZeroSlotPtr.write(0); 370 | } 371 | 372 | /** 373 | * @dev Determines whether the specified order type is restricted and the 374 | * caller is not the specified zone. 375 | * 376 | * @param orderType The type of the order to check. 377 | * @param zone The address of the zone to check against. 378 | * 379 | * @return mustValidate True if the order type is restricted and the caller 380 | * is not the specified zone, false otherwise. 381 | */ 382 | function _isRestrictedAndCallerNotZone( 383 | OrderType orderType, 384 | address zone 385 | ) internal view returns (bool mustValidate) { 386 | // Utilize assembly to efficiently perform the check. 387 | assembly { 388 | mustValidate := and( 389 | // Note that this check requires that there are no order 390 | // types beyond the current set (0-4). It will need to be 391 | // modified if more order types are added. 392 | and(lt(orderType, 4), gt(orderType, 1)), 393 | iszero(eq(caller(), zone)) 394 | ) 395 | } 396 | } 397 | 398 | /** 399 | * @dev Calls the specified target with the given data and checks the status 400 | * of the call. Revert reasons will be "bubbled up" if one is returned, 401 | * otherwise reverting calls will throw a generic error based on the 402 | * supplied error handler. Note that the custom error selector must 403 | * already be in memory at the zero slot when this function is called. 404 | * 405 | * @param target The address of the contract to call. 406 | * @param orderHash The hash of the order associated with the call. 407 | * @param callData The data to pass to the contract call. 408 | * @param size The size of calldata. 409 | */ 410 | function _callAndCheckStatus( 411 | address target, 412 | bytes32 orderHash, 413 | MemoryPointer callData, 414 | uint256 size 415 | ) internal { 416 | bool success; 417 | bool magicMatch; 418 | assembly { 419 | // Get magic value from the selector at start of provided calldata. 420 | let magic := and(mload(callData), MaskOverFirstFourBytes) 421 | 422 | // Clear the start of scratch space. 423 | mstore(0, 0) 424 | 425 | // Perform call, placing result in the first word of scratch space. 426 | success := call(gas(), target, 0, callData, size, 0, OneWord) 427 | 428 | // Determine if returned magic value matches the calldata selector. 429 | magicMatch := eq(magic, mload(0)) 430 | } 431 | 432 | // Revert if the call was not successful. 433 | if (!success) { 434 | // Revert and pass reason along if one was returned. 435 | _revertWithReasonIfOneIsReturned(); 436 | 437 | // If no reason was returned, revert with supplied error selector. 438 | assembly { 439 | // The error selector is already in memory at the zero slot. 440 | mstore(0x80, orderHash) 441 | // revert(abi.encodeWithSelector( 442 | // "InvalidRestrictedOrder(bytes32)", 443 | // orderHash 444 | // )) 445 | revert(0x7c, InvalidRestrictedOrder_error_length) 446 | } 447 | } 448 | 449 | // Revert if the correct magic value was not returned. 450 | if (!magicMatch) { 451 | // Revert with a generic error message. 452 | assembly { 453 | // The error selector is already in memory at the zero slot. 454 | mstore(0x80, orderHash) 455 | // revert(abi.encodeWithSelector( 456 | // "InvalidRestrictedOrder(bytes32)", 457 | // orderHash 458 | // )) 459 | revert(0x7c, InvalidRestrictedOrder_error_length) 460 | } 461 | } 462 | } 463 | 464 | /** 465 | * @dev Calls the specified target with the given data and checks the status 466 | * of the call. Revert reasons will be "bubbled up" if one is returned, 467 | * otherwise reverting calls will throw a generic error based on the 468 | * supplied error handler. 469 | * 470 | * @param target The address of the contract to call. 471 | * @param orderHash The hash of the order associated with the call. 472 | * @param callData The data to pass to the contract call. 473 | * @param size The size of calldata. 474 | * @param errorSelector The error handling function to call if the call 475 | * fails or the magic value does not match. 476 | * @param revertOnInvalid Whether to revert if the call is invalid. Must 477 | * still revert if the call returns invalid data. 478 | */ 479 | function _callAndCheckStatusWithSkip( 480 | address target, 481 | bytes32 orderHash, 482 | MemoryPointer callData, 483 | uint256 size, 484 | uint256 errorSelector, 485 | bool revertOnInvalid 486 | ) internal returns (bool) { 487 | bool success; 488 | bool magicMatch; 489 | assembly { 490 | // Get magic value from the selector at start of provided calldata. 491 | let magic := and(mload(callData), MaskOverFirstFourBytes) 492 | 493 | // Clear the start of scratch space. 494 | mstore(0, 0) 495 | 496 | // Perform call, placing result in the first word of scratch space. 497 | success := call(gas(), target, 0, callData, size, 0, OneWord) 498 | 499 | // Determine if returned magic value matches the calldata selector. 500 | magicMatch := eq(magic, mload(0)) 501 | } 502 | 503 | // Revert or return false if the call was not successful. 504 | if (!success) { 505 | if (!revertOnInvalid) { 506 | return false; 507 | } 508 | 509 | // Revert and pass reason along if one was returned. 510 | _revertWithReasonIfOneIsReturned(); 511 | 512 | // If no reason was returned, revert with supplied error selector. 513 | assembly { 514 | mstore(0, errorSelector) 515 | mstore(InvalidRestrictedOrder_error_orderHash_ptr, orderHash) 516 | // revert(abi.encodeWithSelector( 517 | // "InvalidRestrictedOrder(bytes32)", 518 | // orderHash 519 | // )) 520 | revert( 521 | Error_selector_offset, 522 | InvalidRestrictedOrder_error_length 523 | ) 524 | } 525 | } 526 | 527 | // Revert if the correct magic value was not returned. 528 | if (!magicMatch) { 529 | // Revert with a generic error message. 530 | assembly { 531 | mstore(0, errorSelector) 532 | mstore(InvalidRestrictedOrder_error_orderHash_ptr, orderHash) 533 | 534 | // revert(abi.encodeWithSelector( 535 | // "InvalidRestrictedOrder(bytes32)", 536 | // orderHash 537 | // )) 538 | revert( 539 | Error_selector_offset, 540 | InvalidRestrictedOrder_error_length 541 | ) 542 | } 543 | } 544 | 545 | return true; 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | seaport-types@^0.0.1: 6 | version "0.0.1" 7 | resolved "https://registry.yarnpkg.com/seaport-types/-/seaport-types-0.0.1.tgz#e2a32fe8641853d7dadb1b0232d911d88ccc3f1a" 8 | integrity sha512-m7MLa7sq3YPwojxXiVvoX1PM9iNVtQIn7AdEtBnKTwgxPfGRWUlbs/oMgetpjT/ZYTmv3X5/BghOcstWYvKqRA== 9 | --------------------------------------------------------------------------------