Transaction Hash:
Block:
16378528 at Jan-10-2023 07:34:11 PM +UTC
Transaction Fee:
0.005356332030465448 ETH
$17.23
Gas Used:
190,984 Gas / 28.045972597 Gwei
Emitted Events:
| 86 |
Seaport.OrderFulfilled( orderHash=E73C3A4C5460FB8C4DA979D5BDF8C00E64901A410C5E3721244BDEFE2ADF31A5, offerer=0xb02160d146901e838f9e9e796296ece30c129e0a, zone=PausableZone, recipient=[Sender] 0x97cb3692848e43828c8289105e6c84e66bcbd169, offer=, consideration= )
|
| 87 |
Feetpix.Transfer( from=0xb02160d146901e838f9e9e796296ece30c129e0a, to=[Sender] 0x97cb3692848e43828c8289105e6c84e66bcbd169, tokenId=7808 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x00000000...169EdE581 | (Seaport 1.1) | ||||
| 0x0000a26b...000fAa719 | 308.161837767286555285 Eth | 308.164675267286555285 Eth | 0.0028375 | ||
| 0x50789815...546aAdc51 | |||||
| 0x92116195...7d60a8DC8 | (feetpix.wtf: Deployer) | 41.589258401936677923 Eth | 41.594933401936677923 Eth | 0.005675 | |
| 0x97cb3692...66bcBD169 |
1.137329064325171821 Eth
Nonce: 17
|
1.018472732294706373 Eth
Nonce: 18
| 0.118856332030465448 | ||
| 0xb02160d1...30c129e0A | 11.762834188932386382 Eth | 11.867821688932386382 Eth | 0.1049875 | ||
|
0xDAFEA492...692c98Bc5
Miner
| (Flashbots: Builder) | 1.227153637317214828 Eth | 1.227535605317214828 Eth | 0.000381968 |
Execution Trace
ETH 0.1135
Seaport.fulfillBasicOrder( parameters=[{name:considerationToken, type:address, order:1, indexed:false, value:0x0000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000}, {name:considerationIdentifier, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:considerationAmount, type:uint256, order:3, indexed:false, value:104987500000000000, valueString:104987500000000000}, {name:offerer, type:address, order:4, indexed:false, value:0xb02160d146901E838F9e9E796296ece30c129e0A, valueString:0xb02160d146901E838F9e9E796296ece30c129e0A}, {name:zone, type:address, order:5, indexed:false, value:0x004C00500000aD104D7DBd00e3ae0A5C00560C00, valueString:0x004C00500000aD104D7DBd00e3ae0A5C00560C00}, {name:offerToken, type:address, order:6, indexed:false, value:0x5078981549A1CC18673eb76fb47468f546aAdc51, valueString:0x5078981549A1CC18673eb76fb47468f546aAdc51}, {name:offerIdentifier, type:uint256, order:7, indexed:false, value:7808, valueString:7808}, {name:offerAmount, type:uint256, order:8, indexed:false, value:1, valueString:1}, {name:basicOrderType, type:uint8, order:9, indexed:false, value:2, valueString:2}, {name:startTime, type:uint256, order:10, indexed:false, value:1673379080, valueString:1673379080}, {name:endTime, type:uint256, order:11, indexed:false, value:1675971078, valueString:1675971078}, {name:zoneHash, type:bytes32, order:12, indexed:false, value:0000000000000000000000000000000000000000000000000000000000000000, valueString:0000000000000000000000000000000000000000000000000000000000000000}, {name:salt, type:uint256, order:13, indexed:false, value:24446860302761739304752683030156737591518664810215442929815724221065823713498, valueString:24446860302761739304752683030156737591518664810215442929815724221065823713498}, {name:offererConduitKey, type:bytes32, order:14, indexed:false, value:0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000, valueString:0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000}, {name:fulfillerConduitKey, type:bytes32, order:15, indexed:false, value:0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000, valueString:0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000}, {name:totalOriginalAdditionalRecipients, type:uint256, order:16, indexed:false, value:2, valueString:2}, {name:additionalRecipients, type:tuple[], order:17, indexed:false}, {name:signature, type:bytes, order:18, indexed:false, value:0x8A29686C40854AA6FC8D6CB8930DC8C6B99C2F26EEAA52041E3437547DAF361B728486399911C2608DAC18214F62F8BD1E3B10A736F72D01E3E7DB9A53E52125, valueString:0x8A29686C40854AA6FC8D6CB8930DC8C6B99C2F26EEAA52041E3437547DAF361B728486399911C2608DAC18214F62F8BD1E3B10A736F72D01E3E7DB9A53E52125}] ) => ( fulfilled=True )
-
PausableZone.isValidOrder( orderHash=E73C3A4C5460FB8C4DA979D5BDF8C00E64901A410C5E3721244BDEFE2ADF31A5, caller=0x97cb3692848E43828c8289105e6c84E66bcBD169, offerer=0xb02160d146901E838F9e9E796296ece30c129e0A, zoneHash=0000000000000000000000000000000000000000000000000000000000000000 ) -
Null: 0x000...001.9bec67c3( ) OpenSea: Conduit.4ce34aa2( )
Feetpix.transferFrom( from=0xb02160d146901E838F9e9E796296ece30c129e0A, to=0x97cb3692848E43828c8289105e6c84E66bcBD169, tokenId=7808 )
-
OperatorFilterRegistry.isOperatorAllowed( registrant=0x5078981549A1CC18673eb76fb47468f546aAdc51, operator=0x1E0049783F008A0085193E00003D00cd54003c71 ) => ( True )
-
- ETH 0.0028375
PayableProxy.CALL( )
- ETH 0.005675
feetpix.wtf: Deployer.CALL( ) - ETH 0.1049875
0xb02160d146901e838f9e9e796296ece30c129e0a.CALL( )
fulfillBasicOrder[Consideration (ln:9074)]
_validateAndFulfillBasicOrder[Consideration (ln:9081)]
File 1 of 5: Seaport
File 2 of 5: Feetpix
File 3 of 5: PausableZone
File 4 of 5: OperatorFilterRegistry
File 5 of 5: PayableProxy
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { Consideration } from "./lib/Consideration.sol";
/**
* @title Seaport
* @custom:version 1.1
* @author 0age (0age.eth)
* @custom:coauthor d1ll0n (d1ll0n.eth)
* @custom:coauthor transmissions11 (t11s.eth)
* @custom:contributor Kartik (slokh.eth)
* @custom:contributor LeFevre (lefevre.eth)
* @custom:contributor Joseph Schiarizzi (CupOJoseph.eth)
* @custom:contributor Aspyn Palatnick (stuckinaboot.eth)
* @custom:contributor James Wenzel (emo.eth)
* @custom:contributor Stephan Min (stephanm.eth)
* @custom:contributor Ryan Ghods (ralxz.eth)
* @custom:contributor hack3r-0m (hack3r-0m.eth)
* @custom:contributor Diego Estevez (antidiego.eth)
* @custom:contributor Chomtana (chomtana.eth)
* @custom:contributor Saw-mon and Natalie (sawmonandnatalie.eth)
* @custom:contributor 0xBeans (0xBeans.eth)
* @custom:contributor 0x4non (punkdev.eth)
* @custom:contributor Laurence E. Day (norsefire.eth)
* @custom:contributor vectorized.eth (vectorized.eth)
* @custom:contributor karmacoma (karmacoma.eth)
* @custom:contributor horsefacts (horsefacts.eth)
* @custom:contributor UncarvedBlock (uncarvedblock.eth)
* @custom:contributor Zoraiz Mahmood (zorz.eth)
* @custom:contributor William Poulin (wpoulin.eth)
* @custom:contributor Rajiv Patel-O'Connor (rajivpoc.eth)
* @custom:contributor tserg (tserg.eth)
* @custom:contributor cygaar (cygaar.eth)
* @custom:contributor Meta0xNull (meta0xnull.eth)
* @custom:contributor gpersoon (gpersoon.eth)
* @custom:contributor Matt Solomon (msolomon.eth)
* @custom:contributor Weikang Song (weikangs.eth)
* @custom:contributor zer0dot (zer0dot.eth)
* @custom:contributor Mudit Gupta (mudit.eth)
* @custom:contributor leonardoalt (leoalt.eth)
* @custom:contributor cmichel (cmichel.eth)
* @custom:contributor PraneshASP (pranesh.eth)
* @custom:contributor JasperAlexander (jasperalexander.eth)
* @custom:contributor Ellahi (ellahi.eth)
* @custom:contributor zaz (1zaz1.eth)
* @custom:contributor berndartmueller (berndartmueller.eth)
* @custom:contributor dmfxyz (dmfxyz.eth)
* @custom:contributor daltoncoder (dontkillrobots.eth)
* @custom:contributor 0xf4ce (0xf4ce.eth)
* @custom:contributor phaze (phaze.eth)
* @custom:contributor hrkrshnn (hrkrshnn.eth)
* @custom:contributor axic (axic.eth)
* @custom:contributor leastwood (leastwood.eth)
* @custom:contributor 0xsanson (sanson.eth)
* @custom:contributor blockdev (blockd3v.eth)
* @custom:contributor fiveoutofnine (fiveoutofnine.eth)
* @custom:contributor shuklaayush (shuklaayush.eth)
* @custom:contributor 0xPatissier
* @custom:contributor pcaversaccio
* @custom:contributor David Eiber
* @custom:contributor csanuragjain
* @custom:contributor sach1r0
* @custom:contributor twojoy0
* @custom:contributor ori_dabush
* @custom:contributor Daniel Gelfand
* @custom:contributor okkothejawa
* @custom:contributor FlameHorizon
* @custom:contributor vdrg
* @custom:contributor dmitriia
* @custom:contributor bokeh-eth
* @custom:contributor asutorufos
* @custom:contributor rfart(rfa)
* @custom:contributor Riley Holterhus
* @custom:contributor big-tech-sux
* @notice Seaport is a generalized ETH/ERC20/ERC721/ERC1155 marketplace. It
* minimizes external calls to the greatest extent possible and provides
* lightweight methods for common routes as well as more flexible
* methods for composing advanced orders or groups of orders. Each order
* contains an arbitrary number of items that may be spent (the "offer")
* along with an arbitrary number of items that must be received back by
* the indicated recipients (the "consideration").
*/
contract Seaport is Consideration {
/**
* @notice Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) Consideration(conduitController) {}
/**
* @dev Internal pure function to retrieve and return the name of this
* contract.
*
* @return The name of this contract.
*/
function _name() internal pure override returns (string memory) {
// Return the name of the contract.
assembly {
mstore(0x20, 0x20)
mstore(0x47, 0x07536561706f7274)
return(0x20, 0x60)
}
}
/**
* @dev Internal pure function to retrieve the name of this contract as a
* string that will be used to derive the name hash in the constructor.
*
* @return The name of this contract as a string.
*/
function _nameString() internal pure override returns (string memory) {
// Return the name of the contract.
return "Seaport";
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
BasicOrderParameters,
OrderComponents,
Fulfillment,
FulfillmentComponent,
Execution,
Order,
AdvancedOrder,
OrderStatus,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
/**
* @title SeaportInterface
* @author 0age
* @custom:version 1.1
* @notice Seaport is a generalized ETH/ERC20/ERC721/ERC1155 marketplace. It
* minimizes external calls to the greatest extent possible and provides
* lightweight methods for common routes as well as more flexible
* methods for composing advanced orders.
*
* @dev SeaportInterface contains all external function interfaces for Seaport.
*/
interface SeaportInterface {
/**
* @notice Fulfill an order offering an ERC721 token by supplying Ether (or
* the native token for the given chain) as consideration for the
* order. An arbitrary number of "additional recipients" may also be
* supplied which will each receive native tokens from the fulfiller
* as consideration.
*
* @param parameters Additional information on the fulfilled order. Note
* that the offerer must first approve this contract (or
* their preferred conduit if indicated by the order) for
* their offered ERC721 token to be transferred.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillBasicOrder(BasicOrderParameters calldata parameters)
external
payable
returns (bool fulfilled);
/**
* @notice Fulfill an order with an arbitrary number of items for offer and
* consideration. Note that this function does not support
* criteria-based orders or partial filling of orders (though
* filling the remainder of a partially-filled order is supported).
*
* @param order The order to fulfill. Note that both the
* offerer and the fulfiller must first approve
* this contract (or the corresponding conduit if
* indicated) to transfer any relevant tokens on
* their behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155 tokens
* as consideration.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Seaport.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey)
external
payable
returns (bool fulfilled);
/**
* @notice Fill an order, fully or partially, with an arbitrary number of
* items for offer and consideration alongside criteria resolvers
* containing specific token identifiers and associated proofs.
*
* @param advancedOrder The order to fulfill along with the fraction
* of the order to attempt to fill. Note that
* both the offerer and the fulfiller must first
* approve this contract (or their preferred
* conduit if indicated by the order) to transfer
* any relevant tokens on their behalf and that
* contracts must implement `onERC1155Received`
* to receive ERC1155 tokens as consideration.
* Also note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount with
* the supplied fraction for the partial fill to
* be considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a proof
* that the supplied token identifier is
* contained in the merkle root held by the item
* in question's criteria element. Note that an
* empty criteria indicates that any
* (transferable) token identifier on the token
* in question is valid and that no associated
* proof needs to be supplied.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Seaport.
* @param recipient The intended recipient for all received items,
* with `address(0)` indicating that the caller
* should receive the items.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillAdvancedOrder(
AdvancedOrder calldata advancedOrder,
CriteriaResolver[] calldata criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) external payable returns (bool fulfilled);
/**
* @notice Attempt to fill a group of orders, each with an arbitrary number
* of items for offer and consideration. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
* Note that this function does not support criteria-based orders or
* partial filling of orders (though filling the remainder of a
* partially-filled order is supported).
*
* @param orders The orders to fulfill. Note that both
* the offerer and the fulfiller must first
* approve this contract (or the
* corresponding conduit if indicated) to
* transfer any relevant tokens on their
* behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155
* tokens as consideration.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableOrders(
Order[] calldata orders,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Attempt to fill a group of orders, fully or partially, with an
* arbitrary number of items for offer and consideration per order
* alongside criteria resolvers containing specific token
* identifiers and associated proofs. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or their preferred conduit if
* indicated by the order) to transfer any
* relevant tokens on their behalf and that
* contracts must implement
* `onERC1155Received` to enable receipt of
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param recipient The intended recipient for all received
* items, with `address(0)` indicating that
* the caller should receive the items.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableAdvancedOrders(
AdvancedOrder[] calldata advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Match an arbitrary number of orders, each with an arbitrary
* number of items for offer and consideration along with as set of
* fulfillments allocating offer components to consideration
* components. Note that this function does not support
* criteria-based or partial filling of orders (though filling the
* remainder of a partially-filled order is supported).
*
* @param orders The orders to match. Note that both the offerer and
* fulfiller on each order must first approve this
* contract (or their conduit if indicated by the order)
* to transfer any relevant tokens on their behalf and
* each consideration recipient must implement
* `onERC1155Received` to enable ERC1155 token receipt.
* @param fulfillments An array of elements allocating offer components to
* consideration components. Note that each
* consideration component must be fully met for the
* match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchOrders(
Order[] calldata orders,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Match an arbitrary number of full or partial orders, each with an
* arbitrary number of items for offer and consideration, supplying
* criteria resolvers containing specific token identifiers and
* associated proofs as well as fulfillments allocating offer
* components to consideration components.
*
* @param orders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or a preferred conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchAdvancedOrders(
AdvancedOrder[] calldata orders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Cancel an arbitrary number of orders. Note that only the offerer
* or the zone of a given order may cancel it. Callers should ensure
* that the intended order was cancelled by calling `getOrderStatus`
* and confirming that `isCancelled` returns `true`.
*
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancel(OrderComponents[] calldata orders)
external
returns (bool cancelled);
/**
* @notice Validate an arbitrary number of orders, thereby registering their
* signatures as valid and allowing the fulfiller to skip signature
* verification on fulfillment. Note that validated orders may still
* be unfulfillable due to invalid item amounts or other factors;
* callers should determine whether validated orders are fulfillable
* by simulating the fulfillment call prior to execution. Also note
* that anyone can validate a signed order, but only the offerer can
* validate an order without supplying a signature.
*
* @param orders The orders to validate.
*
* @return validated A boolean indicating whether the supplied orders have
* been successfully validated.
*/
function validate(Order[] calldata orders)
external
returns (bool validated);
/**
* @notice Cancel all orders from a given offerer with a given zone in bulk
* by incrementing a counter. Note that only the offerer may
* increment the counter.
*
* @return newCounter The new counter.
*/
function incrementCounter() external returns (uint256 newCounter);
/**
* @notice Retrieve the order hash for a given order.
*
* @param order The components of the order.
*
* @return orderHash The order hash.
*/
function getOrderHash(OrderComponents calldata order)
external
view
returns (bytes32 orderHash);
/**
* @notice Retrieve the status of a given order by hash, including whether
* the order has been cancelled or validated and the fraction of the
* order that has been filled.
*
* @param orderHash The order hash in question.
*
* @return isValidated A boolean indicating whether the order in question
* has been validated (i.e. previously approved or
* partially filled).
* @return isCancelled A boolean indicating whether the order in question
* has been cancelled.
* @return totalFilled The total portion of the order that has been filled
* (i.e. the "numerator").
* @return totalSize The total size of the order that is either filled or
* unfilled (i.e. the "denominator").
*/
function getOrderStatus(bytes32 orderHash)
external
view
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
);
/**
* @notice Retrieve the current counter for a given offerer.
*
* @param offerer The offerer in question.
*
* @return counter The current counter.
*/
function getCounter(address offerer)
external
view
returns (uint256 counter);
/**
* @notice Retrieve configuration information for this contract.
*
* @return version The contract version.
* @return domainSeparator The domain separator for this contract.
* @return conduitController The conduit Controller set for this contract.
*/
function information()
external
view
returns (
string memory version,
bytes32 domainSeparator,
address conduitController
);
/**
* @notice Retrieve the name of this contract.
*
* @return contractName The name of this contract.
*/
function name() external view returns (string memory contractName);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import "./TransferHelperStructs.sol";
import { TokenTransferrer } from "../lib/TokenTransferrer.sol";
import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
// prettier-ignore
import {
ConduitControllerInterface
} from "../interfaces/ConduitControllerInterface.sol";
import { Conduit } from "../conduit/Conduit.sol";
import { ConduitTransfer } from "../conduit/lib/ConduitStructs.sol";
// prettier-ignore
import {
TransferHelperInterface
} from "../interfaces/TransferHelperInterface.sol";
/**
* @title TransferHelper
* @author stuckinaboot, stephankmin
* @notice TransferHelper is a utility contract for transferring
* ERC20/ERC721/ERC1155 items in bulk to a specific recipient.
*/
contract TransferHelper is TransferHelperInterface, TokenTransferrer {
// Allow for interaction with the conduit controller.
ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER;
// Cache the conduit creation hash used by the conduit controller.
bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
/**
* @dev Set the supplied conduit controller and retrieve its
* conduit creation code hash.
*
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) {
// Get the conduit creation code hash from the supplied conduit
// controller and set it as an immutable.
ConduitControllerInterface controller = ConduitControllerInterface(
conduitController
);
(_CONDUIT_CREATION_CODE_HASH, ) = controller.getConduitCodeHashes();
// Set the supplied conduit controller as an immutable.
_CONDUIT_CONTROLLER = controller;
}
/**
* @notice Transfer multiple items to a single recipient.
*
* @param items The items to transfer.
* @param recipient The address the items should be transferred to.
* @param conduitKey The key of the conduit through which the bulk transfer
* should occur.
*
* @return magicValue A value indicating that the transfers were successful.
*/
function bulkTransfer(
TransferHelperItem[] calldata items,
address recipient,
bytes32 conduitKey
) external override returns (bytes4 magicValue) {
// Retrieve total number of transfers and place on stack.
uint256 totalTransfers = items.length;
// If no conduitKey is given, use TokenTransferrer to perform transfers.
if (conduitKey == bytes32(0)) {
// Skip overflow checks: all for loops are indexed starting at zero.
unchecked {
// Iterate over each transfer.
for (uint256 i = 0; i < totalTransfers; ++i) {
// Retrieve the transfer in question.
TransferHelperItem calldata item = items[i];
// Perform a transfer based on the transfer's item type.
// Revert if item being transferred is a native token.
if (item.itemType == ConduitItemType.NATIVE) {
revert InvalidItemType();
} else if (item.itemType == ConduitItemType.ERC20) {
_performERC20Transfer(
item.token,
msg.sender,
recipient,
item.amount
);
} else if (item.itemType == ConduitItemType.ERC721) {
_performERC721Transfer(
item.token,
msg.sender,
recipient,
item.identifier
);
} else {
_performERC1155Transfer(
item.token,
msg.sender,
recipient,
item.identifier,
item.amount
);
}
}
}
}
// Otherwise, a conduitKey was provided.
else {
// Derive the conduit address from the deployer, conduit key
// and creation code hash.
address conduit = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(_CONDUIT_CONTROLLER),
conduitKey,
_CONDUIT_CREATION_CODE_HASH
)
)
)
)
);
// Declare a new array to populate with each token transfer.
ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[](
totalTransfers
);
// Skip overflow checks: all for loops are indexed starting at zero.
unchecked {
// Iterate over each transfer.
for (uint256 i = 0; i < totalTransfers; ++i) {
// Retrieve the transfer in question.
TransferHelperItem calldata item = items[i];
// Create a ConduitTransfer corresponding to each
// TransferHelperItem.
conduitTransfers[i] = ConduitTransfer(
item.itemType,
item.token,
msg.sender,
recipient,
item.identifier,
item.amount
);
}
}
// Call the conduit and execute bulk transfers.
ConduitInterface(conduit).execute(conduitTransfers);
}
// Return a magic value indicating that the transfers were performed.
magicValue = this.bulkTransfer.selector;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol";
struct TransferHelperItem {
ConduitItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import "./TokenTransferrerConstants.sol";
// prettier-ignore
import {
TokenTransferrerErrors
} from "../interfaces/TokenTransferrerErrors.sol";
import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol";
/**
* @title TokenTransferrer
* @author 0age
* @custom:coauthor d1ll0n
* @custom:coauthor transmissions11
* @notice TokenTransferrer is a library for performing optimized ERC20, ERC721,
* ERC1155, and batch ERC1155 transfers, used by both Seaport as well as
* by conduits deployed by the ConduitController. Use great caution when
* considering these functions for use in other codebases, as there are
* significant side effects and edge cases that need to be thoroughly
* understood and carefully addressed.
*/
contract TokenTransferrer is TokenTransferrerErrors {
/**
* @dev Internal function to transfer ERC20 tokens from a given originator
* to a given recipient. Sufficient approvals must be set on the
* contract performing the transfer.
*
* @param token The ERC20 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param amount The amount to transfer.
*/
function _performERC20Transfer(
address token,
address from,
address to,
uint256 amount
) internal {
// Utilize assembly to perform an optimized ERC20 token transfer.
assembly {
// The free memory pointer memory slot will be used when populating
// call data for the transfer; read the value and restore it later.
let memPointer := mload(FreeMemoryPointerSlot)
// Write call data into memory, starting with function selector.
mstore(ERC20_transferFrom_sig_ptr, ERC20_transferFrom_signature)
mstore(ERC20_transferFrom_from_ptr, from)
mstore(ERC20_transferFrom_to_ptr, to)
mstore(ERC20_transferFrom_amount_ptr, amount)
// Make call & copy up to 32 bytes of return data to scratch space.
// Scratch space does not need to be cleared ahead of time, as the
// subsequent check will ensure that either at least a full word of
// return data is received (in which case it will be overwritten) or
// that no data is received (in which case scratch space will be
// ignored) on a successful call to the given token.
let callStatus := call(
gas(),
token,
0,
ERC20_transferFrom_sig_ptr,
ERC20_transferFrom_length,
0,
OneWord
)
// Determine whether transfer was successful using status & result.
let success := and(
// Set success to whether the call reverted, if not check it
// either returned exactly 1 (can't just be non-zero data), or
// had no return data.
or(
and(eq(mload(0), 1), gt(returndatasize(), 31)),
iszero(returndatasize())
),
callStatus
)
// Handle cases where either the transfer failed or no data was
// returned. Group these, as most transfers will succeed with data.
// Equivalent to `or(iszero(success), iszero(returndatasize()))`
// but after it's inverted for JUMPI this expression is cheaper.
if iszero(and(success, iszero(iszero(returndatasize())))) {
// If the token has no code or the transfer failed: Equivalent
// to `or(iszero(success), iszero(extcodesize(token)))` but
// after it's inverted for JUMPI this expression is cheaper.
if iszero(and(iszero(iszero(extcodesize(token))), success)) {
// If the transfer failed:
if iszero(success) {
// If it was due to a revert:
if iszero(callStatus) {
// If it returned a message, bubble it up as long as
// sufficient gas remains to do so:
if returndatasize() {
// Ensure that sufficient gas is available to
// copy returndata while expanding memory where
// necessary. Start by computing the word size
// of returndata and allocated memory. Round up
// to the nearest full word.
let returnDataWords := div(
add(returndatasize(), AlmostOneWord),
OneWord
)
// Note: use the free memory pointer in place of
// msize() to work around a Yul warning that
// prevents accessing msize directly when the IR
// pipeline is activated.
let msizeWords := div(memPointer, OneWord)
// Next, compute the cost of the returndatacopy.
let cost := mul(CostPerWord, returnDataWords)
// Then, compute cost of new memory allocation.
if gt(returnDataWords, msizeWords) {
cost := add(
cost,
add(
mul(
sub(
returnDataWords,
msizeWords
),
CostPerWord
),
div(
sub(
mul(
returnDataWords,
returnDataWords
),
mul(msizeWords, msizeWords)
),
MemoryExpansionCoefficient
)
)
)
}
// Finally, add a small constant and compare to
// gas remaining; bubble up the revert data if
// enough gas is still available.
if lt(add(cost, ExtraGasBuffer), gas()) {
// Copy returndata to memory; overwrite
// existing memory.
returndatacopy(0, 0, returndatasize())
// Revert, specifying memory region with
// copied returndata.
revert(0, returndatasize())
}
}
// Otherwise revert with a generic error message.
mstore(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_signature
)
mstore(
TokenTransferGenericFailure_error_token_ptr,
token
)
mstore(
TokenTransferGenericFailure_error_from_ptr,
from
)
mstore(TokenTransferGenericFailure_error_to_ptr, to)
mstore(TokenTransferGenericFailure_error_id_ptr, 0)
mstore(
TokenTransferGenericFailure_error_amount_ptr,
amount
)
revert(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_length
)
}
// Otherwise revert with a message about the token
// returning false or non-compliant return values.
mstore(
BadReturnValueFromERC20OnTransfer_error_sig_ptr,
BadReturnValueFromERC20OnTransfer_error_signature
)
mstore(
BadReturnValueFromERC20OnTransfer_error_token_ptr,
token
)
mstore(
BadReturnValueFromERC20OnTransfer_error_from_ptr,
from
)
mstore(
BadReturnValueFromERC20OnTransfer_error_to_ptr,
to
)
mstore(
BadReturnValueFromERC20OnTransfer_error_amount_ptr,
amount
)
revert(
BadReturnValueFromERC20OnTransfer_error_sig_ptr,
BadReturnValueFromERC20OnTransfer_error_length
)
}
// Otherwise, revert with error about token not having code:
mstore(NoContract_error_sig_ptr, NoContract_error_signature)
mstore(NoContract_error_token_ptr, token)
revert(NoContract_error_sig_ptr, NoContract_error_length)
}
// Otherwise, the token just returned no data despite the call
// having succeeded; no need to optimize for this as it's not
// technically ERC20 compliant.
}
// Restore the original free memory pointer.
mstore(FreeMemoryPointerSlot, memPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
}
}
/**
* @dev Internal function to transfer an ERC721 token from a given
* originator to a given recipient. Sufficient approvals must be set on
* the contract performing the transfer. Note that this function does
* not check whether the receiver can accept the ERC721 token (i.e. it
* does not use `safeTransferFrom`).
*
* @param token The ERC721 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The tokenId to transfer.
*/
function _performERC721Transfer(
address token,
address from,
address to,
uint256 identifier
) internal {
// Utilize assembly to perform an optimized ERC721 token transfer.
assembly {
// If the token has no code, revert.
if iszero(extcodesize(token)) {
mstore(NoContract_error_sig_ptr, NoContract_error_signature)
mstore(NoContract_error_token_ptr, token)
revert(NoContract_error_sig_ptr, NoContract_error_length)
}
// The free memory pointer memory slot will be used when populating
// call data for the transfer; read the value and restore it later.
let memPointer := mload(FreeMemoryPointerSlot)
// Write call data to memory starting with function selector.
mstore(ERC721_transferFrom_sig_ptr, ERC721_transferFrom_signature)
mstore(ERC721_transferFrom_from_ptr, from)
mstore(ERC721_transferFrom_to_ptr, to)
mstore(ERC721_transferFrom_id_ptr, identifier)
// Perform the call, ignoring return data.
let success := call(
gas(),
token,
0,
ERC721_transferFrom_sig_ptr,
ERC721_transferFrom_length,
0,
0
)
// If the transfer reverted:
if iszero(success) {
// If it returned a message, bubble it up as long as sufficient
// gas remains to do so:
if returndatasize() {
// Ensure that sufficient gas is available to copy
// returndata while expanding memory where necessary. Start
// by computing word size of returndata & allocated memory.
// Round up to the nearest full word.
let returnDataWords := div(
add(returndatasize(), AlmostOneWord),
OneWord
)
// Note: use the free memory pointer in place of msize() to
// work around a Yul warning that prevents accessing msize
// directly when the IR pipeline is activated.
let msizeWords := div(memPointer, OneWord)
// Next, compute the cost of the returndatacopy.
let cost := mul(CostPerWord, returnDataWords)
// Then, compute cost of new memory allocation.
if gt(returnDataWords, msizeWords) {
cost := add(
cost,
add(
mul(
sub(returnDataWords, msizeWords),
CostPerWord
),
div(
sub(
mul(returnDataWords, returnDataWords),
mul(msizeWords, msizeWords)
),
MemoryExpansionCoefficient
)
)
)
}
// Finally, add a small constant and compare to gas
// remaining; bubble up the revert data if enough gas is
// still available.
if lt(add(cost, ExtraGasBuffer), gas()) {
// Copy returndata to memory; overwrite existing memory.
returndatacopy(0, 0, returndatasize())
// Revert, giving memory region with copied returndata.
revert(0, returndatasize())
}
}
// Otherwise revert with a generic error message.
mstore(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_signature
)
mstore(TokenTransferGenericFailure_error_token_ptr, token)
mstore(TokenTransferGenericFailure_error_from_ptr, from)
mstore(TokenTransferGenericFailure_error_to_ptr, to)
mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
mstore(TokenTransferGenericFailure_error_amount_ptr, 1)
revert(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_length
)
}
// Restore the original free memory pointer.
mstore(FreeMemoryPointerSlot, memPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
}
}
/**
* @dev Internal function to transfer ERC1155 tokens from a given
* originator to a given recipient. Sufficient approvals must be set on
* the contract performing the transfer and contract recipients must
* implement the ERC1155TokenReceiver interface to indicate that they
* are willing to accept the transfer.
*
* @param token The ERC1155 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The id to transfer.
* @param amount The amount to transfer.
*/
function _performERC1155Transfer(
address token,
address from,
address to,
uint256 identifier,
uint256 amount
) internal {
// Utilize assembly to perform an optimized ERC1155 token transfer.
assembly {
// If the token has no code, revert.
if iszero(extcodesize(token)) {
mstore(NoContract_error_sig_ptr, NoContract_error_signature)
mstore(NoContract_error_token_ptr, token)
revert(NoContract_error_sig_ptr, NoContract_error_length)
}
// The following memory slots will be used when populating call data
// for the transfer; read the values and restore them later.
let memPointer := mload(FreeMemoryPointerSlot)
let slot0x80 := mload(Slot0x80)
let slot0xA0 := mload(Slot0xA0)
let slot0xC0 := mload(Slot0xC0)
// Write call data into memory, beginning with function selector.
mstore(
ERC1155_safeTransferFrom_sig_ptr,
ERC1155_safeTransferFrom_signature
)
mstore(ERC1155_safeTransferFrom_from_ptr, from)
mstore(ERC1155_safeTransferFrom_to_ptr, to)
mstore(ERC1155_safeTransferFrom_id_ptr, identifier)
mstore(ERC1155_safeTransferFrom_amount_ptr, amount)
mstore(
ERC1155_safeTransferFrom_data_offset_ptr,
ERC1155_safeTransferFrom_data_length_offset
)
mstore(ERC1155_safeTransferFrom_data_length_ptr, 0)
// Perform the call, ignoring return data.
let success := call(
gas(),
token,
0,
ERC1155_safeTransferFrom_sig_ptr,
ERC1155_safeTransferFrom_length,
0,
0
)
// If the transfer reverted:
if iszero(success) {
// If it returned a message, bubble it up as long as sufficient
// gas remains to do so:
if returndatasize() {
// Ensure that sufficient gas is available to copy
// returndata while expanding memory where necessary. Start
// by computing word size of returndata & allocated memory.
// Round up to the nearest full word.
let returnDataWords := div(
add(returndatasize(), AlmostOneWord),
OneWord
)
// Note: use the free memory pointer in place of msize() to
// work around a Yul warning that prevents accessing msize
// directly when the IR pipeline is activated.
let msizeWords := div(memPointer, OneWord)
// Next, compute the cost of the returndatacopy.
let cost := mul(CostPerWord, returnDataWords)
// Then, compute cost of new memory allocation.
if gt(returnDataWords, msizeWords) {
cost := add(
cost,
add(
mul(
sub(returnDataWords, msizeWords),
CostPerWord
),
div(
sub(
mul(returnDataWords, returnDataWords),
mul(msizeWords, msizeWords)
),
MemoryExpansionCoefficient
)
)
)
}
// Finally, add a small constant and compare to gas
// remaining; bubble up the revert data if enough gas is
// still available.
if lt(add(cost, ExtraGasBuffer), gas()) {
// Copy returndata to memory; overwrite existing memory.
returndatacopy(0, 0, returndatasize())
// Revert, giving memory region with copied returndata.
revert(0, returndatasize())
}
}
// Otherwise revert with a generic error message.
mstore(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_signature
)
mstore(TokenTransferGenericFailure_error_token_ptr, token)
mstore(TokenTransferGenericFailure_error_from_ptr, from)
mstore(TokenTransferGenericFailure_error_to_ptr, to)
mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
mstore(TokenTransferGenericFailure_error_amount_ptr, amount)
revert(
TokenTransferGenericFailure_error_sig_ptr,
TokenTransferGenericFailure_error_length
)
}
mstore(Slot0x80, slot0x80) // Restore slot 0x80.
mstore(Slot0xA0, slot0xA0) // Restore slot 0xA0.
mstore(Slot0xC0, slot0xC0) // Restore slot 0xC0.
// Restore the original free memory pointer.
mstore(FreeMemoryPointerSlot, memPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
}
}
/**
* @dev Internal function to transfer ERC1155 tokens from a given
* originator to a given recipient. Sufficient approvals must be set on
* the contract performing the transfer and contract recipients must
* implement the ERC1155TokenReceiver interface to indicate that they
* are willing to accept the transfer. NOTE: this function is not
* memory-safe; it will overwrite existing memory, restore the free
* memory pointer to the default value, and overwrite the zero slot.
* This function should only be called once memory is no longer
* required and when uninitialized arrays are not utilized, and memory
* should be considered fully corrupted (aside from the existence of a
* default-value free memory pointer) after calling this function.
*
* @param batchTransfers The group of 1155 batch transfers to perform.
*/
function _performERC1155BatchTransfers(
ConduitBatch1155Transfer[] calldata batchTransfers
) internal {
// Utilize assembly to perform optimized batch 1155 transfers.
assembly {
let len := batchTransfers.length
// Pointer to first head in the array, which is offset to the struct
// at each index. This gets incremented after each loop to avoid
// multiplying by 32 to get the offset for each element.
let nextElementHeadPtr := batchTransfers.offset
// Pointer to beginning of the head of the array. This is the
// reference position each offset references. It's held static to
// let each loop calculate the data position for an element.
let arrayHeadPtr := nextElementHeadPtr
// Write the function selector, which will be reused for each call:
// safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
mstore(
ConduitBatch1155Transfer_from_offset,
ERC1155_safeBatchTransferFrom_signature
)
// Iterate over each batch transfer.
for {
let i := 0
} lt(i, len) {
i := add(i, 1)
} {
// Read the offset to the beginning of the element and add
// it to pointer to the beginning of the array head to get
// the absolute position of the element in calldata.
let elementPtr := add(
arrayHeadPtr,
calldataload(nextElementHeadPtr)
)
// Retrieve the token from calldata.
let token := calldataload(elementPtr)
// If the token has no code, revert.
if iszero(extcodesize(token)) {
mstore(NoContract_error_sig_ptr, NoContract_error_signature)
mstore(NoContract_error_token_ptr, token)
revert(NoContract_error_sig_ptr, NoContract_error_length)
}
// Get the total number of supplied ids.
let idsLength := calldataload(
add(elementPtr, ConduitBatch1155Transfer_ids_length_offset)
)
// Determine the expected offset for the amounts array.
let expectedAmountsOffset := add(
ConduitBatch1155Transfer_amounts_length_baseOffset,
mul(idsLength, OneWord)
)
// Validate struct encoding.
let invalidEncoding := iszero(
and(
// ids.length == amounts.length
eq(
idsLength,
calldataload(add(elementPtr, expectedAmountsOffset))
),
and(
// ids_offset == 0xa0
eq(
calldataload(
add(
elementPtr,
ConduitBatch1155Transfer_ids_head_offset
)
),
ConduitBatch1155Transfer_ids_length_offset
),
// amounts_offset == 0xc0 + ids.length*32
eq(
calldataload(
add(
elementPtr,
ConduitBatchTransfer_amounts_head_offset
)
),
expectedAmountsOffset
)
)
)
)
// Revert with an error if the encoding is not valid.
if invalidEncoding {
mstore(
Invalid1155BatchTransferEncoding_ptr,
Invalid1155BatchTransferEncoding_selector
)
revert(
Invalid1155BatchTransferEncoding_ptr,
Invalid1155BatchTransferEncoding_length
)
}
// Update the offset position for the next loop
nextElementHeadPtr := add(nextElementHeadPtr, OneWord)
// Copy the first section of calldata (before dynamic values).
calldatacopy(
BatchTransfer1155Params_ptr,
add(elementPtr, ConduitBatch1155Transfer_from_offset),
ConduitBatch1155Transfer_usable_head_size
)
// Determine size of calldata required for ids and amounts. Note
// that the size includes both lengths as well as the data.
let idsAndAmountsSize := add(TwoWords, mul(idsLength, TwoWords))
// Update the offset for the data array in memory.
mstore(
BatchTransfer1155Params_data_head_ptr,
add(
BatchTransfer1155Params_ids_length_offset,
idsAndAmountsSize
)
)
// Set the length of the data array in memory to zero.
mstore(
add(
BatchTransfer1155Params_data_length_basePtr,
idsAndAmountsSize
),
0
)
// Determine the total calldata size for the call to transfer.
let transferDataSize := add(
BatchTransfer1155Params_calldata_baseSize,
idsAndAmountsSize
)
// Copy second section of calldata (including dynamic values).
calldatacopy(
BatchTransfer1155Params_ids_length_ptr,
add(elementPtr, ConduitBatch1155Transfer_ids_length_offset),
idsAndAmountsSize
)
// Perform the call to transfer 1155 tokens.
let success := call(
gas(),
token,
0,
ConduitBatch1155Transfer_from_offset, // Data portion start.
transferDataSize, // Location of the length of callData.
0,
0
)
// If the transfer reverted:
if iszero(success) {
// If it returned a message, bubble it up as long as
// sufficient gas remains to do so:
if returndatasize() {
// Ensure that sufficient gas is available to copy
// returndata while expanding memory where necessary.
// Start by computing word size of returndata and
// allocated memory. Round up to the nearest full word.
let returnDataWords := div(
add(returndatasize(), AlmostOneWord),
OneWord
)
// Note: use transferDataSize in place of msize() to
// work around a Yul warning that prevents accessing
// msize directly when the IR pipeline is activated.
// The free memory pointer is not used here because
// this function does almost all memory management
// manually and does not update it, and transferDataSize
// should be the largest memory value used (unless a
// previous batch was larger).
let msizeWords := div(transferDataSize, OneWord)
// Next, compute the cost of the returndatacopy.
let cost := mul(CostPerWord, returnDataWords)
// Then, compute cost of new memory allocation.
if gt(returnDataWords, msizeWords) {
cost := add(
cost,
add(
mul(
sub(returnDataWords, msizeWords),
CostPerWord
),
div(
sub(
mul(
returnDataWords,
returnDataWords
),
mul(msizeWords, msizeWords)
),
MemoryExpansionCoefficient
)
)
)
}
// Finally, add a small constant and compare to gas
// remaining; bubble up the revert data if enough gas is
// still available.
if lt(add(cost, ExtraGasBuffer), gas()) {
// Copy returndata to memory; overwrite existing.
returndatacopy(0, 0, returndatasize())
// Revert with memory region containing returndata.
revert(0, returndatasize())
}
}
// Set the error signature.
mstore(
0,
ERC1155BatchTransferGenericFailure_error_signature
)
// Write the token.
mstore(ERC1155BatchTransferGenericFailure_token_ptr, token)
// Increase the offset to ids by 32.
mstore(
BatchTransfer1155Params_ids_head_ptr,
ERC1155BatchTransferGenericFailure_ids_offset
)
// Increase the offset to amounts by 32.
mstore(
BatchTransfer1155Params_amounts_head_ptr,
add(
OneWord,
mload(BatchTransfer1155Params_amounts_head_ptr)
)
)
// Return modified region. The total size stays the same as
// `token` uses the same number of bytes as `data.length`.
revert(0, transferDataSize)
}
}
// Reset the free memory pointer to the default value; memory must
// be assumed to be dirtied and not reused from this point forward.
// Also note that the zero slot is not reset to zero, meaning empty
// arrays cannot be safely created or utilized until it is restored.
mstore(FreeMemoryPointerSlot, DefaultFreeMemoryPointer)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
ConduitTransfer,
ConduitBatch1155Transfer
} from "../conduit/lib/ConduitStructs.sol";
/**
* @title ConduitInterface
* @author 0age
* @notice ConduitInterface contains all external function interfaces, events,
* and errors for conduit contracts.
*/
interface ConduitInterface {
/**
* @dev Revert with an error when attempting to execute transfers using a
* caller that does not have an open channel.
*/
error ChannelClosed(address channel);
/**
* @dev Revert with an error when attempting to update a channel to the
* current status of that channel.
*/
error ChannelStatusAlreadySet(address channel, bool isOpen);
/**
* @dev Revert with an error when attempting to execute a transfer for an
* item that does not have an ERC20/721/1155 item type.
*/
error InvalidItemType();
/**
* @dev Revert with an error when attempting to update the status of a
* channel from a caller that is not the conduit controller.
*/
error InvalidController();
/**
* @dev Emit an event whenever a channel is opened or closed.
*
* @param channel The channel that has been updated.
* @param open A boolean indicating whether the conduit is open or not.
*/
event ChannelUpdated(address indexed channel, bool open);
/**
* @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
* with an open channel can call this function.
*
* @param transfers The ERC20/721/1155 transfers to perform.
*
* @return magicValue A magic value indicating that the transfers were
* performed successfully.
*/
function execute(ConduitTransfer[] calldata transfers)
external
returns (bytes4 magicValue);
/**
* @notice Execute a sequence of batch 1155 transfers. Only a caller with an
* open channel can call this function.
*
* @param batch1155Transfers The 1155 batch transfers to perform.
*
* @return magicValue A magic value indicating that the transfers were
* performed successfully.
*/
function executeBatch1155(
ConduitBatch1155Transfer[] calldata batch1155Transfers
) external returns (bytes4 magicValue);
/**
* @notice Execute a sequence of transfers, both single and batch 1155. Only
* a caller with an open channel can call this function.
*
* @param standardTransfers The ERC20/721/1155 transfers to perform.
* @param batch1155Transfers The 1155 batch transfers to perform.
*
* @return magicValue A magic value indicating that the transfers were
* performed successfully.
*/
function executeWithBatch1155(
ConduitTransfer[] calldata standardTransfers,
ConduitBatch1155Transfer[] calldata batch1155Transfers
) external returns (bytes4 magicValue);
/**
* @notice Open or close a given channel. Only callable by the controller.
*
* @param channel The channel to open or close.
* @param isOpen The status of the channel (either open or closed).
*/
function updateChannel(address channel, bool isOpen) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title ConduitControllerInterface
* @author 0age
* @notice ConduitControllerInterface contains all external function interfaces,
* structs, events, and errors for the conduit controller.
*/
interface ConduitControllerInterface {
/**
* @dev Track the conduit key, current owner, new potential owner, and open
* channels for each deployed conduit.
*/
struct ConduitProperties {
bytes32 key;
address owner;
address potentialOwner;
address[] channels;
mapping(address => uint256) channelIndexesPlusOne;
}
/**
* @dev Emit an event whenever a new conduit is created.
*
* @param conduit The newly created conduit.
* @param conduitKey The conduit key used to create the new conduit.
*/
event NewConduit(address conduit, bytes32 conduitKey);
/**
* @dev Emit an event whenever conduit ownership is transferred.
*
* @param conduit The conduit for which ownership has been
* transferred.
* @param previousOwner The previous owner of the conduit.
* @param newOwner The new owner of the conduit.
*/
event OwnershipTransferred(
address indexed conduit,
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev Emit an event whenever a conduit owner registers a new potential
* owner for that conduit.
*
* @param newPotentialOwner The new potential owner of the conduit.
*/
event PotentialOwnerUpdated(address indexed newPotentialOwner);
/**
* @dev Revert with an error when attempting to create a new conduit using a
* conduit key where the first twenty bytes of the key do not match the
* address of the caller.
*/
error InvalidCreator();
/**
* @dev Revert with an error when attempting to create a new conduit when no
* initial owner address is supplied.
*/
error InvalidInitialOwner();
/**
* @dev Revert with an error when attempting to set a new potential owner
* that is already set.
*/
error NewPotentialOwnerAlreadySet(
address conduit,
address newPotentialOwner
);
/**
* @dev Revert with an error when attempting to cancel ownership transfer
* when no new potential owner is currently set.
*/
error NoPotentialOwnerCurrentlySet(address conduit);
/**
* @dev Revert with an error when attempting to interact with a conduit that
* does not yet exist.
*/
error NoConduit();
/**
* @dev Revert with an error when attempting to create a conduit that
* already exists.
*/
error ConduitAlreadyExists(address conduit);
/**
* @dev Revert with an error when attempting to update channels or transfer
* ownership of a conduit when the caller is not the owner of the
* conduit in question.
*/
error CallerIsNotOwner(address conduit);
/**
* @dev Revert with an error when attempting to register a new potential
* owner and supplying the null address.
*/
error NewPotentialOwnerIsZeroAddress(address conduit);
/**
* @dev Revert with an error when attempting to claim ownership of a conduit
* with a caller that is not the current potential owner for the
* conduit in question.
*/
error CallerIsNotNewPotentialOwner(address conduit);
/**
* @dev Revert with an error when attempting to retrieve a channel using an
* index that is out of range.
*/
error ChannelOutOfRange(address conduit);
/**
* @notice Deploy a new conduit using a supplied conduit key and assigning
* an initial owner for the deployed conduit. Note that the first
* twenty bytes of the supplied conduit key must match the caller
* and that a new conduit cannot be created if one has already been
* deployed using the same conduit key.
*
* @param conduitKey The conduit key used to deploy the conduit. Note that
* the first twenty bytes of the conduit key must match
* the caller of this contract.
* @param initialOwner The initial owner to set for the new conduit.
*
* @return conduit The address of the newly deployed conduit.
*/
function createConduit(bytes32 conduitKey, address initialOwner)
external
returns (address conduit);
/**
* @notice Open or close a channel on a given conduit, thereby allowing the
* specified account to execute transfers against that conduit.
* Extreme care must be taken when updating channels, as malicious
* or vulnerable channels can transfer any ERC20, ERC721 and ERC1155
* tokens where the token holder has granted the conduit approval.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to open or close the channel.
* @param channel The channel to open or close on the conduit.
* @param isOpen A boolean indicating whether to open or close the channel.
*/
function updateChannel(
address conduit,
address channel,
bool isOpen
) external;
/**
* @notice Initiate conduit ownership transfer by assigning a new potential
* owner for the given conduit. Once set, the new potential owner
* may call `acceptOwnership` to claim ownership of the conduit.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to initiate ownership transfer.
* @param newPotentialOwner The new potential owner of the conduit.
*/
function transferOwnership(address conduit, address newPotentialOwner)
external;
/**
* @notice Clear the currently set potential owner, if any, from a conduit.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to cancel ownership transfer.
*/
function cancelOwnershipTransfer(address conduit) external;
/**
* @notice Accept ownership of a supplied conduit. Only accounts that the
* current owner has set as the new potential owner may call this
* function.
*
* @param conduit The conduit for which to accept ownership.
*/
function acceptOwnership(address conduit) external;
/**
* @notice Retrieve the current owner of a deployed conduit.
*
* @param conduit The conduit for which to retrieve the associated owner.
*
* @return owner The owner of the supplied conduit.
*/
function ownerOf(address conduit) external view returns (address owner);
/**
* @notice Retrieve the conduit key for a deployed conduit via reverse
* lookup.
*
* @param conduit The conduit for which to retrieve the associated conduit
* key.
*
* @return conduitKey The conduit key used to deploy the supplied conduit.
*/
function getKey(address conduit) external view returns (bytes32 conduitKey);
/**
* @notice Derive the conduit associated with a given conduit key and
* determine whether that conduit exists (i.e. whether it has been
* deployed).
*
* @param conduitKey The conduit key used to derive the conduit.
*
* @return conduit The derived address of the conduit.
* @return exists A boolean indicating whether the derived conduit has been
* deployed or not.
*/
function getConduit(bytes32 conduitKey)
external
view
returns (address conduit, bool exists);
/**
* @notice Retrieve the potential owner, if any, for a given conduit. The
* current owner may set a new potential owner via
* `transferOwnership` and that owner may then accept ownership of
* the conduit in question via `acceptOwnership`.
*
* @param conduit The conduit for which to retrieve the potential owner.
*
* @return potentialOwner The potential owner, if any, for the conduit.
*/
function getPotentialOwner(address conduit)
external
view
returns (address potentialOwner);
/**
* @notice Retrieve the status (either open or closed) of a given channel on
* a conduit.
*
* @param conduit The conduit for which to retrieve the channel status.
* @param channel The channel for which to retrieve the status.
*
* @return isOpen The status of the channel on the given conduit.
*/
function getChannelStatus(address conduit, address channel)
external
view
returns (bool isOpen);
/**
* @notice Retrieve the total number of open channels for a given conduit.
*
* @param conduit The conduit for which to retrieve the total channel count.
*
* @return totalChannels The total number of open channels for the conduit.
*/
function getTotalChannels(address conduit)
external
view
returns (uint256 totalChannels);
/**
* @notice Retrieve an open channel at a specific index for a given conduit.
* Note that the index of a channel can change as a result of other
* channels being closed on the conduit.
*
* @param conduit The conduit for which to retrieve the open channel.
* @param channelIndex The index of the channel in question.
*
* @return channel The open channel, if any, at the specified channel index.
*/
function getChannel(address conduit, uint256 channelIndex)
external
view
returns (address channel);
/**
* @notice Retrieve all open channels for a given conduit. Note that calling
* this function for a conduit with many channels will revert with
* an out-of-gas error.
*
* @param conduit The conduit for which to retrieve open channels.
*
* @return channels An array of open channels on the given conduit.
*/
function getChannels(address conduit)
external
view
returns (address[] memory channels);
/**
* @dev Retrieve the conduit creation code and runtime code hashes.
*/
function getConduitCodeHashes()
external
view
returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
import { ConduitItemType } from "./lib/ConduitEnums.sol";
import { TokenTransferrer } from "../lib/TokenTransferrer.sol";
// prettier-ignore
import {
ConduitTransfer,
ConduitBatch1155Transfer
} from "./lib/ConduitStructs.sol";
import "./lib/ConduitConstants.sol";
/**
* @title Conduit
* @author 0age
* @notice This contract serves as an originator for "proxied" transfers. Each
* conduit is deployed and controlled by a "conduit controller" that can
* add and remove "channels" or contracts that can instruct the conduit
* to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each
* conduit has an owner that can arbitrarily add or remove channels, and
* a malicious or negligent owner can add a channel that allows for any
* approved ERC20/721/1155 tokens to be taken immediately — be extremely
* cautious with what conduits you give token approvals to!*
*/
contract Conduit is ConduitInterface, TokenTransferrer {
// Set deployer as an immutable controller that can update channel statuses.
address private immutable _controller;
// Track the status of each channel.
mapping(address => bool) private _channels;
/**
* @notice Ensure that the caller is currently registered as an open channel
* on the conduit.
*/
modifier onlyOpenChannel() {
// Utilize assembly to access channel storage mapping directly.
assembly {
// Write the caller to scratch space.
mstore(ChannelKey_channel_ptr, caller())
// Write the storage slot for _channels to scratch space.
mstore(ChannelKey_slot_ptr, _channels.slot)
// Derive the position in storage of _channels[msg.sender]
// and check if the stored value is zero.
if iszero(
sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length))
) {
// The caller is not an open channel; revert with
// ChannelClosed(caller). First, set error signature in memory.
mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature)
// Next, set the caller as the argument.
mstore(ChannelClosed_channel_ptr, caller())
// Finally, revert, returning full custom error with argument.
revert(ChannelClosed_error_ptr, ChannelClosed_error_length)
}
}
// Continue with function execution.
_;
}
/**
* @notice In the constructor, set the deployer as the controller.
*/
constructor() {
// Set the deployer as the controller.
_controller = msg.sender;
}
/**
* @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
* with an open channel can call this function. Note that channels
* are expected to implement reentrancy protection if desired, and
* that cross-channel reentrancy may be possible if the conduit has
* multiple open channels at once. Also note that channels are
* expected to implement checks against transferring any zero-amount
* items if that constraint is desired.
*
* @param transfers The ERC20/721/1155 transfers to perform.
*
* @return magicValue A magic value indicating that the transfers were
* performed successfully.
*/
function execute(ConduitTransfer[] calldata transfers)
external
override
onlyOpenChannel
returns (bytes4 magicValue)
{
// Retrieve the total number of transfers and place on the stack.
uint256 totalStandardTransfers = transfers.length;
// Iterate over each transfer.
for (uint256 i = 0; i < totalStandardTransfers; ) {
// Retrieve the transfer in question and perform the transfer.
_transfer(transfers[i]);
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
++i;
}
}
// Return a magic value indicating that the transfers were performed.
magicValue = this.execute.selector;
}
/**
* @notice Execute a sequence of batch 1155 item transfers. Only a caller
* with an open channel can call this function. Note that channels
* are expected to implement reentrancy protection if desired, and
* that cross-channel reentrancy may be possible if the conduit has
* multiple open channels at once. Also note that channels are
* expected to implement checks against transferring any zero-amount
* items if that constraint is desired.
*
* @param batchTransfers The 1155 batch item transfers to perform.
*
* @return magicValue A magic value indicating that the item transfers were
* performed successfully.
*/
function executeBatch1155(
ConduitBatch1155Transfer[] calldata batchTransfers
) external override onlyOpenChannel returns (bytes4 magicValue) {
// Perform 1155 batch transfers. Note that memory should be considered
// entirely corrupted from this point forward.
_performERC1155BatchTransfers(batchTransfers);
// Return a magic value indicating that the transfers were performed.
magicValue = this.executeBatch1155.selector;
}
/**
* @notice Execute a sequence of transfers, both single ERC20/721/1155 item
* transfers as well as batch 1155 item transfers. Only a caller
* with an open channel can call this function. Note that channels
* are expected to implement reentrancy protection if desired, and
* that cross-channel reentrancy may be possible if the conduit has
* multiple open channels at once. Also note that channels are
* expected to implement checks against transferring any zero-amount
* items if that constraint is desired.
*
* @param standardTransfers The ERC20/721/1155 item transfers to perform.
* @param batchTransfers The 1155 batch item transfers to perform.
*
* @return magicValue A magic value indicating that the item transfers were
* performed successfully.
*/
function executeWithBatch1155(
ConduitTransfer[] calldata standardTransfers,
ConduitBatch1155Transfer[] calldata batchTransfers
) external override onlyOpenChannel returns (bytes4 magicValue) {
// Retrieve the total number of transfers and place on the stack.
uint256 totalStandardTransfers = standardTransfers.length;
// Iterate over each standard transfer.
for (uint256 i = 0; i < totalStandardTransfers; ) {
// Retrieve the transfer in question and perform the transfer.
_transfer(standardTransfers[i]);
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
++i;
}
}
// Perform 1155 batch transfers. Note that memory should be considered
// entirely corrupted from this point forward aside from the free memory
// pointer having the default value.
_performERC1155BatchTransfers(batchTransfers);
// Return a magic value indicating that the transfers were performed.
magicValue = this.executeWithBatch1155.selector;
}
/**
* @notice Open or close a given channel. Only callable by the controller.
*
* @param channel The channel to open or close.
* @param isOpen The status of the channel (either open or closed).
*/
function updateChannel(address channel, bool isOpen) external override {
// Ensure that the caller is the controller of this contract.
if (msg.sender != _controller) {
revert InvalidController();
}
// Ensure that the channel does not already have the indicated status.
if (_channels[channel] == isOpen) {
revert ChannelStatusAlreadySet(channel, isOpen);
}
// Update the status of the channel.
_channels[channel] = isOpen;
// Emit a corresponding event.
emit ChannelUpdated(channel, isOpen);
}
/**
* @dev Internal function to transfer a given ERC20/721/1155 item. Note that
* channels are expected to implement checks against transferring any
* zero-amount items if that constraint is desired.
*
* @param item The ERC20/721/1155 item to transfer.
*/
function _transfer(ConduitTransfer calldata item) internal {
// Determine the transfer method based on the respective item type.
if (item.itemType == ConduitItemType.ERC20) {
// Transfer ERC20 token. Note that item.identifier is ignored and
// therefore ERC20 transfer items are potentially malleable — this
// check should be performed by the calling channel if a constraint
// on item malleability is desired.
_performERC20Transfer(item.token, item.from, item.to, item.amount);
} else if (item.itemType == ConduitItemType.ERC721) {
// Ensure that exactly one 721 item is being transferred.
if (item.amount != 1) {
revert InvalidERC721TransferAmount();
}
// Transfer ERC721 token.
_performERC721Transfer(
item.token,
item.from,
item.to,
item.identifier
);
} else if (item.itemType == ConduitItemType.ERC1155) {
// Transfer ERC1155 token.
_performERC1155Transfer(
item.token,
item.from,
item.to,
item.identifier,
item.amount
);
} else {
// Throw with an error.
revert InvalidItemType();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { ConduitItemType } from "./ConduitEnums.sol";
struct ConduitTransfer {
ConduitItemType itemType;
address token;
address from;
address to;
uint256 identifier;
uint256 amount;
}
struct ConduitBatch1155Transfer {
address token;
address from;
address to;
uint256[] ids;
uint256[] amounts;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { TransferHelperItem } from "../helpers/TransferHelperStructs.sol";
interface TransferHelperInterface {
/**
* @dev Revert with an error when attempting to execute transfers with a
* NATIVE itemType.
*/
error InvalidItemType();
/**
* @notice Transfer multiple items to a single recipient.
*
* @param items The items to transfer.
* @param recipient The address the items should be transferred to.
* @param conduitKey The key of the conduit performing the bulk transfer.
*/
function bulkTransfer(
TransferHelperItem[] calldata items,
address recipient,
bytes32 conduitKey
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
enum ConduitItemType {
NATIVE, // unused
ERC20,
ERC721,
ERC1155
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/*
* -------------------------- Disambiguation & Other Notes ---------------------
* - The term "head" is used as it is in the documentation for ABI encoding,
* but only in reference to dynamic types, i.e. it always refers to the
* offset or pointer to the body of a dynamic type. In calldata, the head
* is always an offset (relative to the parent object), while in memory,
* the head is always the pointer to the body. More information found here:
* https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding
* - Note that the length of an array is separate from and precedes the
* head of the array.
*
* - The term "body" is used in place of the term "head" used in the ABI
* documentation. It refers to the start of the data for a dynamic type,
* e.g. the first word of a struct or the first word of the first element
* in an array.
*
* - The term "pointer" is used to describe the absolute position of a value
* and never an offset relative to another value.
* - The suffix "_ptr" refers to a memory pointer.
* - The suffix "_cdPtr" refers to a calldata pointer.
*
* - The term "offset" is used to describe the position of a value relative
* to some parent value. For example, OrderParameters_conduit_offset is the
* offset to the "conduit" value in the OrderParameters struct relative to
* the start of the body.
* - Note: Offsets are used to derive pointers.
*
* - Some structs have pointers defined for all of their fields in this file.
* Lines which are commented out are fields that are not used in the
* codebase but have been left in for readability.
*/
uint256 constant AlmostOneWord = 0x1f;
uint256 constant OneWord = 0x20;
uint256 constant TwoWords = 0x40;
uint256 constant ThreeWords = 0x60;
uint256 constant FreeMemoryPointerSlot = 0x40;
uint256 constant ZeroSlot = 0x60;
uint256 constant DefaultFreeMemoryPointer = 0x80;
uint256 constant Slot0x80 = 0x80;
uint256 constant Slot0xA0 = 0xa0;
uint256 constant Slot0xC0 = 0xc0;
// abi.encodeWithSignature("transferFrom(address,address,uint256)")
uint256 constant ERC20_transferFrom_signature = (
0x23b872dd00000000000000000000000000000000000000000000000000000000
);
uint256 constant ERC20_transferFrom_sig_ptr = 0x0;
uint256 constant ERC20_transferFrom_from_ptr = 0x04;
uint256 constant ERC20_transferFrom_to_ptr = 0x24;
uint256 constant ERC20_transferFrom_amount_ptr = 0x44;
uint256 constant ERC20_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
// abi.encodeWithSignature(
// "safeTransferFrom(address,address,uint256,uint256,bytes)"
// )
uint256 constant ERC1155_safeTransferFrom_signature = (
0xf242432a00000000000000000000000000000000000000000000000000000000
);
uint256 constant ERC1155_safeTransferFrom_sig_ptr = 0x0;
uint256 constant ERC1155_safeTransferFrom_from_ptr = 0x04;
uint256 constant ERC1155_safeTransferFrom_to_ptr = 0x24;
uint256 constant ERC1155_safeTransferFrom_id_ptr = 0x44;
uint256 constant ERC1155_safeTransferFrom_amount_ptr = 0x64;
uint256 constant ERC1155_safeTransferFrom_data_offset_ptr = 0x84;
uint256 constant ERC1155_safeTransferFrom_data_length_ptr = 0xa4;
uint256 constant ERC1155_safeTransferFrom_length = 0xc4; // 4 + 32 * 6 == 196
uint256 constant ERC1155_safeTransferFrom_data_length_offset = 0xa0;
// abi.encodeWithSignature(
// "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"
// )
uint256 constant ERC1155_safeBatchTransferFrom_signature = (
0x2eb2c2d600000000000000000000000000000000000000000000000000000000
);
bytes4 constant ERC1155_safeBatchTransferFrom_selector = bytes4(
bytes32(ERC1155_safeBatchTransferFrom_signature)
);
uint256 constant ERC721_transferFrom_signature = ERC20_transferFrom_signature;
uint256 constant ERC721_transferFrom_sig_ptr = 0x0;
uint256 constant ERC721_transferFrom_from_ptr = 0x04;
uint256 constant ERC721_transferFrom_to_ptr = 0x24;
uint256 constant ERC721_transferFrom_id_ptr = 0x44;
uint256 constant ERC721_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
// abi.encodeWithSignature("NoContract(address)")
uint256 constant NoContract_error_signature = (
0x5f15d67200000000000000000000000000000000000000000000000000000000
);
uint256 constant NoContract_error_sig_ptr = 0x0;
uint256 constant NoContract_error_token_ptr = 0x4;
uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36
// abi.encodeWithSignature(
// "TokenTransferGenericFailure(address,address,address,uint256,uint256)"
// )
uint256 constant TokenTransferGenericFailure_error_signature = (
0xf486bc8700000000000000000000000000000000000000000000000000000000
);
uint256 constant TokenTransferGenericFailure_error_sig_ptr = 0x0;
uint256 constant TokenTransferGenericFailure_error_token_ptr = 0x4;
uint256 constant TokenTransferGenericFailure_error_from_ptr = 0x24;
uint256 constant TokenTransferGenericFailure_error_to_ptr = 0x44;
uint256 constant TokenTransferGenericFailure_error_id_ptr = 0x64;
uint256 constant TokenTransferGenericFailure_error_amount_ptr = 0x84;
// 4 + 32 * 5 == 164
uint256 constant TokenTransferGenericFailure_error_length = 0xa4;
// abi.encodeWithSignature(
// "BadReturnValueFromERC20OnTransfer(address,address,address,uint256)"
// )
uint256 constant BadReturnValueFromERC20OnTransfer_error_signature = (
0x9889192300000000000000000000000000000000000000000000000000000000
);
uint256 constant BadReturnValueFromERC20OnTransfer_error_sig_ptr = 0x0;
uint256 constant BadReturnValueFromERC20OnTransfer_error_token_ptr = 0x4;
uint256 constant BadReturnValueFromERC20OnTransfer_error_from_ptr = 0x24;
uint256 constant BadReturnValueFromERC20OnTransfer_error_to_ptr = 0x44;
uint256 constant BadReturnValueFromERC20OnTransfer_error_amount_ptr = 0x64;
// 4 + 32 * 4 == 132
uint256 constant BadReturnValueFromERC20OnTransfer_error_length = 0x84;
uint256 constant ExtraGasBuffer = 0x20;
uint256 constant CostPerWord = 3;
uint256 constant MemoryExpansionCoefficient = 0x200;
// Values are offset by 32 bytes in order to write the token to the beginning
// in the event of a revert
uint256 constant BatchTransfer1155Params_ptr = 0x24;
uint256 constant BatchTransfer1155Params_ids_head_ptr = 0x64;
uint256 constant BatchTransfer1155Params_amounts_head_ptr = 0x84;
uint256 constant BatchTransfer1155Params_data_head_ptr = 0xa4;
uint256 constant BatchTransfer1155Params_data_length_basePtr = 0xc4;
uint256 constant BatchTransfer1155Params_calldata_baseSize = 0xc4;
uint256 constant BatchTransfer1155Params_ids_length_ptr = 0xc4;
uint256 constant BatchTransfer1155Params_ids_length_offset = 0xa0;
uint256 constant BatchTransfer1155Params_amounts_length_baseOffset = 0xc0;
uint256 constant BatchTransfer1155Params_data_length_baseOffset = 0xe0;
uint256 constant ConduitBatch1155Transfer_usable_head_size = 0x80;
uint256 constant ConduitBatch1155Transfer_from_offset = 0x20;
uint256 constant ConduitBatch1155Transfer_ids_head_offset = 0x60;
uint256 constant ConduitBatch1155Transfer_amounts_head_offset = 0x80;
uint256 constant ConduitBatch1155Transfer_ids_length_offset = 0xa0;
uint256 constant ConduitBatch1155Transfer_amounts_length_baseOffset = 0xc0;
uint256 constant ConduitBatch1155Transfer_calldata_baseSize = 0xc0;
// Note: abbreviated version of above constant to adhere to line length limit.
uint256 constant ConduitBatchTransfer_amounts_head_offset = 0x80;
uint256 constant Invalid1155BatchTransferEncoding_ptr = 0x00;
uint256 constant Invalid1155BatchTransferEncoding_length = 0x04;
uint256 constant Invalid1155BatchTransferEncoding_selector = (
0xeba2084c00000000000000000000000000000000000000000000000000000000
);
uint256 constant ERC1155BatchTransferGenericFailure_error_signature = (
0xafc445e200000000000000000000000000000000000000000000000000000000
);
uint256 constant ERC1155BatchTransferGenericFailure_token_ptr = 0x04;
uint256 constant ERC1155BatchTransferGenericFailure_ids_offset = 0xc0;
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title TokenTransferrerErrors
*/
interface TokenTransferrerErrors {
/**
* @dev Revert with an error when an ERC721 transfer with amount other than
* one is attempted.
*/
error InvalidERC721TransferAmount();
/**
* @dev Revert with an error when attempting to fulfill an order where an
* item has an amount of zero.
*/
error MissingItemAmount();
/**
* @dev Revert with an error when attempting to fulfill an order where an
* item has unused parameters. This includes both the token and the
* identifier parameters for native transfers as well as the identifier
* parameter for ERC20 transfers. Note that the conduit does not
* perform this check, leaving it up to the calling channel to enforce
* when desired.
*/
error UnusedItemParameters();
/**
* @dev Revert with an error when an ERC20, ERC721, or ERC1155 token
* transfer reverts.
*
* @param token The token for which the transfer was attempted.
* @param from The source of the attempted transfer.
* @param to The recipient of the attempted transfer.
* @param identifier The identifier for the attempted transfer.
* @param amount The amount for the attempted transfer.
*/
error TokenTransferGenericFailure(
address token,
address from,
address to,
uint256 identifier,
uint256 amount
);
/**
* @dev Revert with an error when a batch ERC1155 token transfer reverts.
*
* @param token The token for which the transfer was attempted.
* @param from The source of the attempted transfer.
* @param to The recipient of the attempted transfer.
* @param identifiers The identifiers for the attempted transfer.
* @param amounts The amounts for the attempted transfer.
*/
error ERC1155BatchTransferGenericFailure(
address token,
address from,
address to,
uint256[] identifiers,
uint256[] amounts
);
/**
* @dev Revert with an error when an ERC20 token transfer returns a falsey
* value.
*
* @param token The token for which the ERC20 transfer was attempted.
* @param from The source of the attempted ERC20 transfer.
* @param to The recipient of the attempted ERC20 transfer.
* @param amount The amount for the attempted ERC20 transfer.
*/
error BadReturnValueFromERC20OnTransfer(
address token,
address from,
address to,
uint256 amount
);
/**
* @dev Revert with an error when an account being called as an assumed
* contract does not have code and returns no data.
*
* @param account The account that should contain code.
*/
error NoContract(address account);
/**
* @dev Revert with an error when attempting to execute an 1155 batch
* transfer using calldata not produced by default ABI encoding or with
* different lengths for ids and amounts arrays.
*/
error Invalid1155BatchTransferEncoding();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// error ChannelClosed(address channel)
uint256 constant ChannelClosed_error_signature = (
0x93daadf200000000000000000000000000000000000000000000000000000000
);
uint256 constant ChannelClosed_error_ptr = 0x00;
uint256 constant ChannelClosed_channel_ptr = 0x4;
uint256 constant ChannelClosed_error_length = 0x24;
// For the mapping:
// mapping(address => bool) channels
// The position in storage for a particular account is:
// keccak256(abi.encode(account, channels.slot))
uint256 constant ChannelKey_channel_ptr = 0x00;
uint256 constant ChannelKey_slot_ptr = 0x20;
uint256 constant ChannelKey_length = 0x40;
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol";
import { ItemType } from "./ConsiderationEnums.sol";
import { ReceivedItem } from "./ConsiderationStructs.sol";
import { Verifiers } from "./Verifiers.sol";
import { TokenTransferrer } from "./TokenTransferrer.sol";
import "./ConsiderationConstants.sol";
/**
* @title Executor
* @author 0age
* @notice Executor contains functions related to processing executions (i.e.
* transferring items, either directly or via conduits).
*/
contract Executor is Verifiers, TokenTransferrer {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) Verifiers(conduitController) {}
/**
* @dev Internal function to transfer a given item, either directly or via
* a corresponding conduit.
*
* @param item The item to transfer, including an amount and a
* recipient.
* @param from The account supplying the item.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _transfer(
ReceivedItem memory item,
address from,
bytes32 conduitKey,
bytes memory accumulator
) internal {
// If the item type indicates Ether or a native token...
if (item.itemType == ItemType.NATIVE) {
// Ensure neither the token nor the identifier parameters are set.
if ((uint160(item.token) | item.identifier) != 0) {
revert UnusedItemParameters();
}
// transfer the native tokens to the recipient.
_transferEth(item.recipient, item.amount);
} else if (item.itemType == ItemType.ERC20) {
// Ensure that no identifier is supplied.
if (item.identifier != 0) {
revert UnusedItemParameters();
}
// Transfer ERC20 tokens from the source to the recipient.
_transferERC20(
item.token,
from,
item.recipient,
item.amount,
conduitKey,
accumulator
);
} else if (item.itemType == ItemType.ERC721) {
// Transfer ERC721 token from the source to the recipient.
_transferERC721(
item.token,
from,
item.recipient,
item.identifier,
item.amount,
conduitKey,
accumulator
);
} else {
// Transfer ERC1155 token from the source to the recipient.
_transferERC1155(
item.token,
from,
item.recipient,
item.identifier,
item.amount,
conduitKey,
accumulator
);
}
}
/**
* @dev Internal function to transfer an individual ERC721 or ERC1155 item
* from a given originator to a given recipient. The accumulator will
* be bypassed, meaning that this function should be utilized in cases
* where multiple item transfers can be accumulated into a single
* conduit call. Sufficient approvals must be set, either on the
* respective conduit or on this contract itself.
*
* @param itemType The type of item to transfer, either ERC721 or ERC1155.
* @param token The token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The tokenId to transfer.
* @param amount The amount to transfer.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
*/
function _transferIndividual721Or1155Item(
ItemType itemType,
address token,
address from,
address to,
uint256 identifier,
uint256 amount,
bytes32 conduitKey
) internal {
// Determine if the transfer is to be performed via a conduit.
if (conduitKey != bytes32(0)) {
// Use free memory pointer as calldata offset for the conduit call.
uint256 callDataOffset;
// Utilize assembly to place each argument in free memory.
assembly {
// Retrieve the free memory pointer and use it as the offset.
callDataOffset := mload(FreeMemoryPointerSlot)
// Write ConduitInterface.execute.selector to memory.
mstore(callDataOffset, Conduit_execute_signature)
// Write the offset to the ConduitTransfer array in memory.
mstore(
add(
callDataOffset,
Conduit_execute_ConduitTransfer_offset_ptr
),
Conduit_execute_ConduitTransfer_ptr
)
// Write the length of the ConduitTransfer array to memory.
mstore(
add(
callDataOffset,
Conduit_execute_ConduitTransfer_length_ptr
),
Conduit_execute_ConduitTransfer_length
)
// Write the item type to memory.
mstore(
add(callDataOffset, Conduit_execute_transferItemType_ptr),
itemType
)
// Write the token to memory.
mstore(
add(callDataOffset, Conduit_execute_transferToken_ptr),
token
)
// Write the transfer source to memory.
mstore(
add(callDataOffset, Conduit_execute_transferFrom_ptr),
from
)
// Write the transfer recipient to memory.
mstore(add(callDataOffset, Conduit_execute_transferTo_ptr), to)
// Write the token identifier to memory.
mstore(
add(callDataOffset, Conduit_execute_transferIdentifier_ptr),
identifier
)
// Write the transfer amount to memory.
mstore(
add(callDataOffset, Conduit_execute_transferAmount_ptr),
amount
)
}
// Perform the call to the conduit.
_callConduitUsingOffsets(
conduitKey,
callDataOffset,
OneConduitExecute_size
);
} else {
// Otherwise, determine whether it is an ERC721 or ERC1155 item.
if (itemType == ItemType.ERC721) {
// Ensure that exactly one 721 item is being transferred.
if (amount != 1) {
revert InvalidERC721TransferAmount();
}
// Perform transfer via the token contract directly.
_performERC721Transfer(token, from, to, identifier);
} else {
// Perform transfer via the token contract directly.
_performERC1155Transfer(token, from, to, identifier, amount);
}
}
}
/**
* @dev Internal function to transfer Ether or other native tokens to a
* given recipient.
*
* @param to The recipient of the transfer.
* @param amount The amount to transfer.
*/
function _transferEth(address payable to, uint256 amount) internal {
// Ensure that the supplied amount is non-zero.
_assertNonZeroAmount(amount);
// Declare a variable indicating whether the call was successful or not.
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// If the call fails...
if (!success) {
// Revert and pass the revert reason along if one was returned.
_revertWithReasonIfOneIsReturned();
// Otherwise, revert with a generic error message.
revert EtherTransferGenericFailure(to, amount);
}
}
/**
* @dev Internal function to transfer ERC20 tokens from a given originator
* to a given recipient using a given conduit if applicable. Sufficient
* approvals must be set on this contract or on a respective conduit.
*
* @param token The ERC20 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param amount The amount to transfer.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _transferERC20(
address token,
address from,
address to,
uint256 amount,
bytes32 conduitKey,
bytes memory accumulator
) internal {
// Ensure that the supplied amount is non-zero.
_assertNonZeroAmount(amount);
// Trigger accumulated transfers if the conduits differ.
_triggerIfArmedAndNotAccumulatable(accumulator, conduitKey);
// If no conduit has been specified...
if (conduitKey == bytes32(0)) {
// Perform the token transfer directly.
_performERC20Transfer(token, from, to, amount);
} else {
// Insert the call to the conduit into the accumulator.
_insert(
conduitKey,
accumulator,
ConduitItemType.ERC20,
token,
from,
to,
uint256(0),
amount
);
}
}
/**
* @dev Internal function to transfer a single ERC721 token from a given
* originator to a given recipient. Sufficient approvals must be set,
* either on the respective conduit or on this contract itself.
*
* @param token The ERC721 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The tokenId to transfer (must be 1 for ERC721).
* @param amount The amount to transfer.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _transferERC721(
address token,
address from,
address to,
uint256 identifier,
uint256 amount,
bytes32 conduitKey,
bytes memory accumulator
) internal {
// Trigger accumulated transfers if the conduits differ.
_triggerIfArmedAndNotAccumulatable(accumulator, conduitKey);
// If no conduit has been specified...
if (conduitKey == bytes32(0)) {
// Ensure that exactly one 721 item is being transferred.
if (amount != 1) {
revert InvalidERC721TransferAmount();
}
// Perform transfer via the token contract directly.
_performERC721Transfer(token, from, to, identifier);
} else {
// Insert the call to the conduit into the accumulator.
_insert(
conduitKey,
accumulator,
ConduitItemType.ERC721,
token,
from,
to,
identifier,
amount
);
}
}
/**
* @dev Internal function to transfer ERC1155 tokens from a given originator
* to a given recipient. Sufficient approvals must be set, either on
* the respective conduit or on this contract itself.
*
* @param token The ERC1155 token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The id to transfer.
* @param amount The amount to transfer.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _transferERC1155(
address token,
address from,
address to,
uint256 identifier,
uint256 amount,
bytes32 conduitKey,
bytes memory accumulator
) internal {
// Ensure that the supplied amount is non-zero.
_assertNonZeroAmount(amount);
// Trigger accumulated transfers if the conduits differ.
_triggerIfArmedAndNotAccumulatable(accumulator, conduitKey);
// If no conduit has been specified...
if (conduitKey == bytes32(0)) {
// Perform transfer via the token contract directly.
_performERC1155Transfer(token, from, to, identifier, amount);
} else {
// Insert the call to the conduit into the accumulator.
_insert(
conduitKey,
accumulator,
ConduitItemType.ERC1155,
token,
from,
to,
identifier,
amount
);
}
}
/**
* @dev Internal function to trigger a call to the conduit currently held by
* the accumulator if the accumulator contains item transfers (i.e. it
* is "armed") and the supplied conduit key does not match the key held
* by the accumulator.
*
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
*/
function _triggerIfArmedAndNotAccumulatable(
bytes memory accumulator,
bytes32 conduitKey
) internal {
// Retrieve the current conduit key from the accumulator.
bytes32 accumulatorConduitKey = _getAccumulatorConduitKey(accumulator);
// Perform conduit call if the set key does not match the supplied key.
if (accumulatorConduitKey != conduitKey) {
_triggerIfArmed(accumulator);
}
}
/**
* @dev Internal function to trigger a call to the conduit currently held by
* the accumulator if the accumulator contains item transfers (i.e. it
* is "armed").
*
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _triggerIfArmed(bytes memory accumulator) internal {
// Exit if the accumulator is not "armed".
if (accumulator.length != AccumulatorArmed) {
return;
}
// Retrieve the current conduit key from the accumulator.
bytes32 accumulatorConduitKey = _getAccumulatorConduitKey(accumulator);
// Perform conduit call.
_trigger(accumulatorConduitKey, accumulator);
}
/**
* @dev Internal function to trigger a call to the conduit corresponding to
* a given conduit key, supplying all accumulated item transfers. The
* accumulator will be "disarmed" and reset in the process.
*
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _trigger(bytes32 conduitKey, bytes memory accumulator) internal {
// Declare variables for offset in memory & size of calldata to conduit.
uint256 callDataOffset;
uint256 callDataSize;
// Call the conduit with all the accumulated transfers.
assembly {
// Call begins at third word; the first is length or "armed" status,
// and the second is the current conduit key.
callDataOffset := add(accumulator, TwoWords)
// 68 + items * 192
callDataSize := add(
Accumulator_array_offset_ptr,
mul(
mload(add(accumulator, Accumulator_array_length_ptr)),
Conduit_transferItem_size
)
)
}
// Call conduit derived from conduit key & supply accumulated transfers.
_callConduitUsingOffsets(conduitKey, callDataOffset, callDataSize);
// Reset accumulator length to signal that it is now "disarmed".
assembly {
mstore(accumulator, AccumulatorDisarmed)
}
}
/**
* @dev Internal function to perform a call to the conduit corresponding to
* a given conduit key based on the offset and size of the calldata in
* question in memory.
*
* @param conduitKey A bytes32 value indicating what corresponding
* conduit, if any, to source token approvals from.
* The zero hash signifies that no conduit should be
* used, with direct approvals set on this contract.
* @param callDataOffset The memory pointer where calldata is contained.
* @param callDataSize The size of calldata in memory.
*/
function _callConduitUsingOffsets(
bytes32 conduitKey,
uint256 callDataOffset,
uint256 callDataSize
) internal {
// Derive the address of the conduit using the conduit key.
address conduit = _deriveConduit(conduitKey);
bool success;
bytes4 result;
// call the conduit.
assembly {
// Ensure first word of scratch space is empty.
mstore(0, 0)
// Perform call, placing first word of return data in scratch space.
success := call(
gas(),
conduit,
0,
callDataOffset,
callDataSize,
0,
OneWord
)
// Take value from scratch space and place it on the stack.
result := mload(0)
}
// If the call failed...
if (!success) {
// Pass along whatever revert reason was given by the conduit.
_revertWithReasonIfOneIsReturned();
// Otherwise, revert with a generic error.
revert InvalidCallToConduit(conduit);
}
// Ensure result was extracted and matches EIP-1271 magic value.
if (result != ConduitInterface.execute.selector) {
revert InvalidConduit(conduitKey, conduit);
}
}
/**
* @dev Internal pure function to retrieve the current conduit key set for
* the accumulator.
*
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*
* @return accumulatorConduitKey The conduit key currently set for the
* accumulator.
*/
function _getAccumulatorConduitKey(bytes memory accumulator)
internal
pure
returns (bytes32 accumulatorConduitKey)
{
// Retrieve the current conduit key from the accumulator.
assembly {
accumulatorConduitKey := mload(
add(accumulator, Accumulator_conduitKey_ptr)
)
}
}
/**
* @dev Internal pure function to place an item transfer into an accumulator
* that collects a series of transfers to execute against a given
* conduit in a single call.
*
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. The zero hash
* signifies that no conduit should be used, with direct
* approvals set on this contract.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
* @param itemType The type of the item to transfer.
* @param token The token to transfer.
* @param from The originator of the transfer.
* @param to The recipient of the transfer.
* @param identifier The tokenId to transfer.
* @param amount The amount to transfer.
*/
function _insert(
bytes32 conduitKey,
bytes memory accumulator,
ConduitItemType itemType,
address token,
address from,
address to,
uint256 identifier,
uint256 amount
) internal pure {
uint256 elements;
// "Arm" and prime accumulator if it's not already armed. The sentinel
// value is held in the length of the accumulator array.
if (accumulator.length == AccumulatorDisarmed) {
elements = 1;
bytes4 selector = ConduitInterface.execute.selector;
assembly {
mstore(accumulator, AccumulatorArmed) // "arm" the accumulator.
mstore(add(accumulator, Accumulator_conduitKey_ptr), conduitKey)
mstore(add(accumulator, Accumulator_selector_ptr), selector)
mstore(
add(accumulator, Accumulator_array_offset_ptr),
Accumulator_array_offset
)
mstore(add(accumulator, Accumulator_array_length_ptr), elements)
}
} else {
// Otherwise, increase the number of elements by one.
assembly {
elements := add(
mload(add(accumulator, Accumulator_array_length_ptr)),
1
)
mstore(add(accumulator, Accumulator_array_length_ptr), elements)
}
}
// Insert the item.
assembly {
let itemPointer := sub(
add(accumulator, mul(elements, Conduit_transferItem_size)),
Accumulator_itemSizeOffsetDifference
)
mstore(itemPointer, itemType)
mstore(add(itemPointer, Conduit_transferItem_token_ptr), token)
mstore(add(itemPointer, Conduit_transferItem_from_ptr), from)
mstore(add(itemPointer, Conduit_transferItem_to_ptr), to)
mstore(
add(itemPointer, Conduit_transferItem_identifier_ptr),
identifier
)
mstore(add(itemPointer, Conduit_transferItem_amount_ptr), amount)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
enum OrderType {
// 0: no partial fills, anyone can execute
FULL_OPEN,
// 1: partial fills supported, anyone can execute
PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
PARTIAL_RESTRICTED
}
// prettier-ignore
enum BasicOrderType {
// 0: no partial fills, anyone can execute
ETH_TO_ERC721_FULL_OPEN,
// 1: partial fills supported, anyone can execute
ETH_TO_ERC721_PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
ETH_TO_ERC721_FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
ETH_TO_ERC721_PARTIAL_RESTRICTED,
// 4: no partial fills, anyone can execute
ETH_TO_ERC1155_FULL_OPEN,
// 5: partial fills supported, anyone can execute
ETH_TO_ERC1155_PARTIAL_OPEN,
// 6: no partial fills, only offerer or zone can execute
ETH_TO_ERC1155_FULL_RESTRICTED,
// 7: partial fills supported, only offerer or zone can execute
ETH_TO_ERC1155_PARTIAL_RESTRICTED,
// 8: no partial fills, anyone can execute
ERC20_TO_ERC721_FULL_OPEN,
// 9: partial fills supported, anyone can execute
ERC20_TO_ERC721_PARTIAL_OPEN,
// 10: no partial fills, only offerer or zone can execute
ERC20_TO_ERC721_FULL_RESTRICTED,
// 11: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC721_PARTIAL_RESTRICTED,
// 12: no partial fills, anyone can execute
ERC20_TO_ERC1155_FULL_OPEN,
// 13: partial fills supported, anyone can execute
ERC20_TO_ERC1155_PARTIAL_OPEN,
// 14: no partial fills, only offerer or zone can execute
ERC20_TO_ERC1155_FULL_RESTRICTED,
// 15: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC1155_PARTIAL_RESTRICTED,
// 16: no partial fills, anyone can execute
ERC721_TO_ERC20_FULL_OPEN,
// 17: partial fills supported, anyone can execute
ERC721_TO_ERC20_PARTIAL_OPEN,
// 18: no partial fills, only offerer or zone can execute
ERC721_TO_ERC20_FULL_RESTRICTED,
// 19: partial fills supported, only offerer or zone can execute
ERC721_TO_ERC20_PARTIAL_RESTRICTED,
// 20: no partial fills, anyone can execute
ERC1155_TO_ERC20_FULL_OPEN,
// 21: partial fills supported, anyone can execute
ERC1155_TO_ERC20_PARTIAL_OPEN,
// 22: no partial fills, only offerer or zone can execute
ERC1155_TO_ERC20_FULL_RESTRICTED,
// 23: partial fills supported, only offerer or zone can execute
ERC1155_TO_ERC20_PARTIAL_RESTRICTED
}
// prettier-ignore
enum BasicOrderRouteType {
// 0: provide Ether (or other native token) to receive offered ERC721 item.
ETH_TO_ERC721,
// 1: provide Ether (or other native token) to receive offered ERC1155 item.
ETH_TO_ERC1155,
// 2: provide ERC20 item to receive offered ERC721 item.
ERC20_TO_ERC721,
// 3: provide ERC20 item to receive offered ERC1155 item.
ERC20_TO_ERC1155,
// 4: provide ERC721 item to receive offered ERC20 item.
ERC721_TO_ERC20,
// 5: provide ERC1155 item to receive offered ERC20 item.
ERC1155_TO_ERC20
}
// prettier-ignore
enum ItemType {
// 0: ETH on mainnet, MATIC on polygon, etc.
NATIVE,
// 1: ERC20 items (ERC777 and ERC20 analogues could also technically work)
ERC20,
// 2: ERC721 items
ERC721,
// 3: ERC1155 items
ERC1155,
// 4: ERC721 items where a number of tokenIds are supported
ERC721_WITH_CRITERIA,
// 5: ERC1155 items where a number of ids are supported
ERC1155_WITH_CRITERIA
}
// prettier-ignore
enum Side {
// 0: Items that can be spent
OFFER,
// 1: Items that must be received
CONSIDERATION
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
OrderType,
BasicOrderType,
ItemType,
Side
} from "./ConsiderationEnums.sol";
/**
* @dev An order contains eleven components: an offerer, a zone (or account that
* can cancel the order or restrict who can fulfill the order depending on
* the type), the order type (specifying partial fill support as well as
* restricted order status), the start and end time, a hash that will be
* provided to the zone when validating restricted orders, a salt, a key
* corresponding to a given conduit, a counter, and an arbitrary number of
* offer items that can be spent along with consideration items that must
* be received by their respective recipient.
*/
struct OrderComponents {
address offerer;
address zone;
OfferItem[] offer;
ConsiderationItem[] consideration;
OrderType orderType;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
uint256 salt;
bytes32 conduitKey;
uint256 counter;
}
/**
* @dev An offer item has five components: an item type (ETH or other native
* tokens, ERC20, ERC721, and ERC1155, as well as criteria-based ERC721 and
* ERC1155), a token address, a dual-purpose "identifierOrCriteria"
* component that will either represent a tokenId or a merkle root
* depending on the item type, and a start and end amount that support
* increasing or decreasing amounts over the duration of the respective
* order.
*/
struct OfferItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
}
/**
* @dev A consideration item has the same five components as an offer item and
* an additional sixth component designating the required recipient of the
* item.
*/
struct ConsiderationItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
address payable recipient;
}
/**
* @dev A spent item is translated from a utilized offer item and has four
* components: an item type (ETH or other native tokens, ERC20, ERC721, and
* ERC1155), a token address, a tokenId, and an amount.
*/
struct SpentItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
/**
* @dev A received item is translated from a utilized consideration item and has
* the same four components as a spent item, as well as an additional fifth
* component designating the required recipient of the item.
*/
struct ReceivedItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
address payable recipient;
}
/**
* @dev For basic orders involving ETH / native / ERC20 <=> ERC721 / ERC1155
* matching, a group of six functions may be called that only requires a
* subset of the usual order arguments. Note the use of a "basicOrderType"
* enum; this represents both the usual order type as well as the "route"
* of the basic order (a simple derivation function for the basic order
* type is `basicOrderType = orderType + (4 * basicOrderRoute)`.)
*/
struct BasicOrderParameters {
// calldata offset
address considerationToken; // 0x24
uint256 considerationIdentifier; // 0x44
uint256 considerationAmount; // 0x64
address payable offerer; // 0x84
address zone; // 0xa4
address offerToken; // 0xc4
uint256 offerIdentifier; // 0xe4
uint256 offerAmount; // 0x104
BasicOrderType basicOrderType; // 0x124
uint256 startTime; // 0x144
uint256 endTime; // 0x164
bytes32 zoneHash; // 0x184
uint256 salt; // 0x1a4
bytes32 offererConduitKey; // 0x1c4
bytes32 fulfillerConduitKey; // 0x1e4
uint256 totalOriginalAdditionalRecipients; // 0x204
AdditionalRecipient[] additionalRecipients; // 0x224
bytes signature; // 0x244
// Total length, excluding dynamic array data: 0x264 (580)
}
/**
* @dev Basic orders can supply any number of additional recipients, with the
* implied assumption that they are supplied from the offered ETH (or other
* native token) or ERC20 token for the order.
*/
struct AdditionalRecipient {
uint256 amount;
address payable recipient;
}
/**
* @dev The full set of order components, with the exception of the counter,
* must be supplied when fulfilling more sophisticated orders or groups of
* orders. The total number of original consideration items must also be
* supplied, as the caller may specify additional consideration items.
*/
struct OrderParameters {
address offerer; // 0x00
address zone; // 0x20
OfferItem[] offer; // 0x40
ConsiderationItem[] consideration; // 0x60
OrderType orderType; // 0x80
uint256 startTime; // 0xa0
uint256 endTime; // 0xc0
bytes32 zoneHash; // 0xe0
uint256 salt; // 0x100
bytes32 conduitKey; // 0x120
uint256 totalOriginalConsiderationItems; // 0x140
// offer.length // 0x160
}
/**
* @dev Orders require a signature in addition to the other order parameters.
*/
struct Order {
OrderParameters parameters;
bytes signature;
}
/**
* @dev Advanced orders include a numerator (i.e. a fraction to attempt to fill)
* and a denominator (the total size of the order) in addition to the
* signature and other order parameters. It also supports an optional field
* for supplying extra data; this data will be included in a staticcall to
* `isValidOrderIncludingExtraData` on the zone for the order if the order
* type is restricted and the offerer or zone are not the caller.
*/
struct AdvancedOrder {
OrderParameters parameters;
uint120 numerator;
uint120 denominator;
bytes signature;
bytes extraData;
}
/**
* @dev Orders can be validated (either explicitly via `validate`, or as a
* consequence of a full or partial fill), specifically cancelled (they can
* also be cancelled in bulk via incrementing a per-zone counter), and
* partially or fully filled (with the fraction filled represented by a
* numerator and denominator).
*/
struct OrderStatus {
bool isValidated;
bool isCancelled;
uint120 numerator;
uint120 denominator;
}
/**
* @dev A criteria resolver specifies an order, side (offer vs. consideration),
* and item index. It then provides a chosen identifier (i.e. tokenId)
* alongside a merkle proof demonstrating the identifier meets the required
* criteria.
*/
struct CriteriaResolver {
uint256 orderIndex;
Side side;
uint256 index;
uint256 identifier;
bytes32[] criteriaProof;
}
/**
* @dev A fulfillment is applied to a group of orders. It decrements a series of
* offer and consideration items, then generates a single execution
* element. A given fulfillment can be applied to as many offer and
* consideration items as desired, but must contain at least one offer and
* at least one consideration that match. The fulfillment must also remain
* consistent on all key parameters across all offer items (same offerer,
* token, type, tokenId, and conduit preference) as well as across all
* consideration items (token, type, tokenId, and recipient).
*/
struct Fulfillment {
FulfillmentComponent[] offerComponents;
FulfillmentComponent[] considerationComponents;
}
/**
* @dev Each fulfillment component contains one index referencing a specific
* order and another referencing a specific offer or consideration item.
*/
struct FulfillmentComponent {
uint256 orderIndex;
uint256 itemIndex;
}
/**
* @dev An execution is triggered once all consideration items have been zeroed
* out. It sends the item in question from the offerer to the item's
* recipient, optionally sourcing approvals from either this contract
* directly or from the offerer's chosen conduit if one is specified. An
* execution is not provided as an argument, but rather is derived via
* orders, criteria resolvers, and fulfillments (where the total number of
* executions will be less than or equal to the total number of indicated
* fulfillments) and returned as part of `matchOrders`.
*/
struct Execution {
ReceivedItem item;
address offerer;
bytes32 conduitKey;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { OrderStatus } from "./ConsiderationStructs.sol";
import { Assertions } from "./Assertions.sol";
import { SignatureVerification } from "./SignatureVerification.sol";
/**
* @title Verifiers
* @author 0age
* @notice Verifiers contains functions for performing verifications.
*/
contract Verifiers is Assertions, SignatureVerification {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) Assertions(conduitController) {}
/**
* @dev Internal view function to ensure that the current time falls within
* an order's valid timespan.
*
* @param startTime The time at which the order becomes active.
* @param endTime The time at which the order becomes inactive.
* @param revertOnInvalid A boolean indicating whether to revert if the
* order is not active.
*
* @return valid A boolean indicating whether the order is active.
*/
function _verifyTime(
uint256 startTime,
uint256 endTime,
bool revertOnInvalid
) internal view returns (bool valid) {
// Revert if order's timespan hasn't started yet or has already ended.
if (startTime > block.timestamp || endTime <= block.timestamp) {
// Only revert if revertOnInvalid has been supplied as true.
if (revertOnInvalid) {
revert InvalidTime();
}
// Return false as the order is invalid.
return false;
}
// Return true as the order time is valid.
valid = true;
}
/**
* @dev Internal view function to verify the signature of an order. An
* ERC-1271 fallback will be attempted if either the signature length
* is not 32 or 33 bytes or if the recovered signer does not match the
* supplied offerer. Note that in cases where a 32 or 33 byte signature
* is supplied, only standard ECDSA signatures that recover to a
* non-zero address are supported.
*
* @param offerer The offerer for the order.
* @param orderHash The order hash.
* @param signature A signature from the offerer indicating that the order
* has been approved.
*/
function _verifySignature(
address offerer,
bytes32 orderHash,
bytes memory signature
) internal view {
// Skip signature verification if the offerer is the caller.
if (offerer == msg.sender) {
return;
}
// Derive EIP-712 digest using the domain separator and the order hash.
bytes32 digest = _deriveEIP712Digest(_domainSeparator(), orderHash);
// Ensure that the signature for the digest is valid for the offerer.
_assertValidSignature(offerer, digest, signature);
}
/**
* @dev Internal view function to validate that a given order is fillable
* and not cancelled based on the order status.
*
* @param orderHash The order hash.
* @param orderStatus The status of the order, including whether it has
* been cancelled and the fraction filled.
* @param onlyAllowUnused A boolean flag indicating whether partial fills
* are supported by the calling function.
* @param revertOnInvalid A boolean indicating whether to revert if the
* order has been cancelled or filled beyond the
* allowable amount.
*
* @return valid A boolean indicating whether the order is valid.
*/
function _verifyOrderStatus(
bytes32 orderHash,
OrderStatus storage orderStatus,
bool onlyAllowUnused,
bool revertOnInvalid
) internal view returns (bool valid) {
// Ensure that the order has not been cancelled.
if (orderStatus.isCancelled) {
// Only revert if revertOnInvalid has been supplied as true.
if (revertOnInvalid) {
revert OrderIsCancelled(orderHash);
}
// Return false as the order status is invalid.
return false;
}
// Read order status numerator from storage and place on stack.
uint256 orderStatusNumerator = orderStatus.numerator;
// If the order is not entirely unused...
if (orderStatusNumerator != 0) {
// ensure the order has not been partially filled when not allowed.
if (onlyAllowUnused) {
// Always revert on partial fills when onlyAllowUnused is true.
revert OrderPartiallyFilled(orderHash);
}
// Otherwise, ensure that order has not been entirely filled.
else if (orderStatusNumerator >= orderStatus.denominator) {
// Only revert if revertOnInvalid has been supplied as true.
if (revertOnInvalid) {
revert OrderAlreadyFilled(orderHash);
}
// Return false as the order status is invalid.
return false;
}
}
// Return true as the order status is valid.
valid = true;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/*
* -------------------------- Disambiguation & Other Notes ---------------------
* - The term "head" is used as it is in the documentation for ABI encoding,
* but only in reference to dynamic types, i.e. it always refers to the
* offset or pointer to the body of a dynamic type. In calldata, the head
* is always an offset (relative to the parent object), while in memory,
* the head is always the pointer to the body. More information found here:
* https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding
* - Note that the length of an array is separate from and precedes the
* head of the array.
*
* - The term "body" is used in place of the term "head" used in the ABI
* documentation. It refers to the start of the data for a dynamic type,
* e.g. the first word of a struct or the first word of the first element
* in an array.
*
* - The term "pointer" is used to describe the absolute position of a value
* and never an offset relative to another value.
* - The suffix "_ptr" refers to a memory pointer.
* - The suffix "_cdPtr" refers to a calldata pointer.
*
* - The term "offset" is used to describe the position of a value relative
* to some parent value. For example, OrderParameters_conduit_offset is the
* offset to the "conduit" value in the OrderParameters struct relative to
* the start of the body.
* - Note: Offsets are used to derive pointers.
*
* - Some structs have pointers defined for all of their fields in this file.
* Lines which are commented out are fields that are not used in the
* codebase but have been left in for readability.
*/
// Declare constants for name, version, and reentrancy sentinel values.
// Name is right padded, so it touches the length which is left padded. This
// enables writing both values at once. Length goes at byte 95 in memory, and
// name fills bytes 96-109, so both values can be written left-padded to 77.
uint256 constant NameLengthPtr = 77;
uint256 constant NameWithLength = 0x0d436F6E73696465726174696F6E;
uint256 constant Version = 0x312e31;
uint256 constant Version_length = 3;
uint256 constant Version_shift = 0xe8;
uint256 constant _NOT_ENTERED = 1;
uint256 constant _ENTERED = 2;
// Common Offsets
// Offsets for identically positioned fields shared by:
// OfferItem, ConsiderationItem, SpentItem, ReceivedItem
uint256 constant Common_token_offset = 0x20;
uint256 constant Common_identifier_offset = 0x40;
uint256 constant Common_amount_offset = 0x60;
uint256 constant ReceivedItem_size = 0xa0;
uint256 constant ReceivedItem_amount_offset = 0x60;
uint256 constant ReceivedItem_recipient_offset = 0x80;
uint256 constant ReceivedItem_CommonParams_size = 0x60;
uint256 constant ConsiderationItem_recipient_offset = 0xa0;
// Store the same constant in an abbreviated format for a line length fix.
uint256 constant ConsiderItem_recipient_offset = 0xa0;
uint256 constant Execution_offerer_offset = 0x20;
uint256 constant Execution_conduit_offset = 0x40;
uint256 constant InvalidFulfillmentComponentData_error_signature = (
0x7fda727900000000000000000000000000000000000000000000000000000000
);
uint256 constant InvalidFulfillmentComponentData_error_len = 0x04;
uint256 constant Panic_error_signature = (
0x4e487b7100000000000000000000000000000000000000000000000000000000
);
uint256 constant Panic_error_offset = 0x04;
uint256 constant Panic_error_length = 0x24;
uint256 constant Panic_arithmetic = 0x11;
uint256 constant MissingItemAmount_error_signature = (
0x91b3e51400000000000000000000000000000000000000000000000000000000
);
uint256 constant MissingItemAmount_error_len = 0x04;
uint256 constant OrderParameters_offer_head_offset = 0x40;
uint256 constant OrderParameters_consideration_head_offset = 0x60;
uint256 constant OrderParameters_conduit_offset = 0x120;
uint256 constant OrderParameters_counter_offset = 0x140;
uint256 constant Fulfillment_itemIndex_offset = 0x20;
uint256 constant AdvancedOrder_numerator_offset = 0x20;
uint256 constant AlmostOneWord = 0x1f;
uint256 constant OneWord = 0x20;
uint256 constant TwoWords = 0x40;
uint256 constant ThreeWords = 0x60;
uint256 constant FourWords = 0x80;
uint256 constant FiveWords = 0xa0;
uint256 constant FreeMemoryPointerSlot = 0x40;
uint256 constant ZeroSlot = 0x60;
uint256 constant DefaultFreeMemoryPointer = 0x80;
uint256 constant Slot0x80 = 0x80;
uint256 constant Slot0xA0 = 0xa0;
uint256 constant BasicOrder_endAmount_cdPtr = 0x104;
uint256 constant BasicOrder_common_params_size = 0xa0;
uint256 constant BasicOrder_considerationHashesArray_ptr = 0x160;
uint256 constant EIP712_Order_size = 0x180;
uint256 constant EIP712_OfferItem_size = 0xc0;
uint256 constant EIP712_ConsiderationItem_size = 0xe0;
uint256 constant AdditionalRecipients_size = 0x40;
uint256 constant EIP712_DomainSeparator_offset = 0x02;
uint256 constant EIP712_OrderHash_offset = 0x22;
uint256 constant EIP712_DigestPayload_size = 0x42;
uint256 constant receivedItemsHash_ptr = 0x60;
/*
* Memory layout in _prepareBasicFulfillmentFromCalldata of
* data for OrderFulfilled
*
* event OrderFulfilled(
* bytes32 orderHash,
* address indexed offerer,
* address indexed zone,
* address fulfiller,
* SpentItem[] offer,
* > (itemType, token, id, amount)
* ReceivedItem[] consideration
* > (itemType, token, id, amount, recipient)
* )
*
* - 0x00: orderHash
* - 0x20: fulfiller
* - 0x40: offer offset (0x80)
* - 0x60: consideration offset (0x120)
* - 0x80: offer.length (1)
* - 0xa0: offerItemType
* - 0xc0: offerToken
* - 0xe0: offerIdentifier
* - 0x100: offerAmount
* - 0x120: consideration.length (1 + additionalRecipients.length)
* - 0x140: considerationItemType
* - 0x160: considerationToken
* - 0x180: considerationIdentifier
* - 0x1a0: considerationAmount
* - 0x1c0: considerationRecipient
* - ...
*/
// Minimum length of the OrderFulfilled event data.
// Must be added to the size of the ReceivedItem array for additionalRecipients
// (0xa0 * additionalRecipients.length) to calculate full size of the buffer.
uint256 constant OrderFulfilled_baseSize = 0x1e0;
uint256 constant OrderFulfilled_selector = (
0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31
);
// Minimum offset in memory to OrderFulfilled event data.
// Must be added to the size of the EIP712 hash array for additionalRecipients
// (32 * additionalRecipients.length) to calculate the pointer to event data.
uint256 constant OrderFulfilled_baseOffset = 0x180;
uint256 constant OrderFulfilled_consideration_length_baseOffset = 0x2a0;
uint256 constant OrderFulfilled_offer_length_baseOffset = 0x200;
// uint256 constant OrderFulfilled_orderHash_offset = 0x00;
uint256 constant OrderFulfilled_fulfiller_offset = 0x20;
uint256 constant OrderFulfilled_offer_head_offset = 0x40;
uint256 constant OrderFulfilled_offer_body_offset = 0x80;
uint256 constant OrderFulfilled_consideration_head_offset = 0x60;
uint256 constant OrderFulfilled_consideration_body_offset = 0x120;
// BasicOrderParameters
uint256 constant BasicOrder_parameters_cdPtr = 0x04;
uint256 constant BasicOrder_considerationToken_cdPtr = 0x24;
// uint256 constant BasicOrder_considerationIdentifier_cdPtr = 0x44;
uint256 constant BasicOrder_considerationAmount_cdPtr = 0x64;
uint256 constant BasicOrder_offerer_cdPtr = 0x84;
uint256 constant BasicOrder_zone_cdPtr = 0xa4;
uint256 constant BasicOrder_offerToken_cdPtr = 0xc4;
// uint256 constant BasicOrder_offerIdentifier_cdPtr = 0xe4;
uint256 constant BasicOrder_offerAmount_cdPtr = 0x104;
uint256 constant BasicOrder_basicOrderType_cdPtr = 0x124;
uint256 constant BasicOrder_startTime_cdPtr = 0x144;
// uint256 constant BasicOrder_endTime_cdPtr = 0x164;
// uint256 constant BasicOrder_zoneHash_cdPtr = 0x184;
// uint256 constant BasicOrder_salt_cdPtr = 0x1a4;
uint256 constant BasicOrder_offererConduit_cdPtr = 0x1c4;
uint256 constant BasicOrder_fulfillerConduit_cdPtr = 0x1e4;
uint256 constant BasicOrder_totalOriginalAdditionalRecipients_cdPtr = 0x204;
uint256 constant BasicOrder_additionalRecipients_head_cdPtr = 0x224;
uint256 constant BasicOrder_signature_cdPtr = 0x244;
uint256 constant BasicOrder_additionalRecipients_length_cdPtr = 0x264;
uint256 constant BasicOrder_additionalRecipients_data_cdPtr = 0x284;
uint256 constant BasicOrder_parameters_ptr = 0x20;
uint256 constant BasicOrder_basicOrderType_range = 0x18; // 24 values
/*
* Memory layout in _prepareBasicFulfillmentFromCalldata of
* EIP712 data for ConsiderationItem
* - 0x80: ConsiderationItem EIP-712 typehash (constant)
* - 0xa0: itemType
* - 0xc0: token
* - 0xe0: identifier
* - 0x100: startAmount
* - 0x120: endAmount
* - 0x140: recipient
*/
uint256 constant BasicOrder_considerationItem_typeHash_ptr = 0x80; // memoryPtr
uint256 constant BasicOrder_considerationItem_itemType_ptr = 0xa0;
uint256 constant BasicOrder_considerationItem_token_ptr = 0xc0;
uint256 constant BasicOrder_considerationItem_identifier_ptr = 0xe0;
uint256 constant BasicOrder_considerationItem_startAmount_ptr = 0x100;
uint256 constant BasicOrder_considerationItem_endAmount_ptr = 0x120;
// uint256 constant BasicOrder_considerationItem_recipient_ptr = 0x140;
/*
* Memory layout in _prepareBasicFulfillmentFromCalldata of
* EIP712 data for OfferItem
* - 0x80: OfferItem EIP-712 typehash (constant)
* - 0xa0: itemType
* - 0xc0: token
* - 0xe0: identifier (reused for offeredItemsHash)
* - 0x100: startAmount
* - 0x120: endAmount
*/
uint256 constant BasicOrder_offerItem_typeHash_ptr = DefaultFreeMemoryPointer;
uint256 constant BasicOrder_offerItem_itemType_ptr = 0xa0;
uint256 constant BasicOrder_offerItem_token_ptr = 0xc0;
// uint256 constant BasicOrder_offerItem_identifier_ptr = 0xe0;
// uint256 constant BasicOrder_offerItem_startAmount_ptr = 0x100;
uint256 constant BasicOrder_offerItem_endAmount_ptr = 0x120;
/*
* Memory layout in _prepareBasicFulfillmentFromCalldata of
* EIP712 data for Order
* - 0x80: Order EIP-712 typehash (constant)
* - 0xa0: orderParameters.offerer
* - 0xc0: orderParameters.zone
* - 0xe0: keccak256(abi.encodePacked(offerHashes))
* - 0x100: keccak256(abi.encodePacked(considerationHashes))
* - 0x120: orderType
* - 0x140: startTime
* - 0x160: endTime
* - 0x180: zoneHash
* - 0x1a0: salt
* - 0x1c0: conduit
* - 0x1e0: _counters[orderParameters.offerer] (from storage)
*/
uint256 constant BasicOrder_order_typeHash_ptr = 0x80;
uint256 constant BasicOrder_order_offerer_ptr = 0xa0;
// uint256 constant BasicOrder_order_zone_ptr = 0xc0;
uint256 constant BasicOrder_order_offerHashes_ptr = 0xe0;
uint256 constant BasicOrder_order_considerationHashes_ptr = 0x100;
uint256 constant BasicOrder_order_orderType_ptr = 0x120;
uint256 constant BasicOrder_order_startTime_ptr = 0x140;
// uint256 constant BasicOrder_order_endTime_ptr = 0x160;
// uint256 constant BasicOrder_order_zoneHash_ptr = 0x180;
// uint256 constant BasicOrder_order_salt_ptr = 0x1a0;
// uint256 constant BasicOrder_order_conduitKey_ptr = 0x1c0;
uint256 constant BasicOrder_order_counter_ptr = 0x1e0;
uint256 constant BasicOrder_additionalRecipients_head_ptr = 0x240;
uint256 constant BasicOrder_signature_ptr = 0x260;
// Signature-related
bytes32 constant EIP2098_allButHighestBitMask = (
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
bytes32 constant ECDSA_twentySeventhAndTwentyEighthBytesSet = (
0x0000000000000000000000000000000000000000000000000000000101000000
);
uint256 constant ECDSA_MaxLength = 65;
uint256 constant ECDSA_signature_s_offset = 0x40;
uint256 constant ECDSA_signature_v_offset = 0x60;
bytes32 constant EIP1271_isValidSignature_selector = (
0x1626ba7e00000000000000000000000000000000000000000000000000000000
);
uint256 constant EIP1271_isValidSignature_signatureHead_negativeOffset = 0x20;
uint256 constant EIP1271_isValidSignature_digest_negativeOffset = 0x40;
uint256 constant EIP1271_isValidSignature_selector_negativeOffset = 0x44;
uint256 constant EIP1271_isValidSignature_calldata_baseLength = 0x64;
uint256 constant EIP1271_isValidSignature_signature_head_offset = 0x40;
// abi.encodeWithSignature("NoContract(address)")
uint256 constant NoContract_error_signature = (
0x5f15d67200000000000000000000000000000000000000000000000000000000
);
uint256 constant NoContract_error_sig_ptr = 0x0;
uint256 constant NoContract_error_token_ptr = 0x4;
uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36
uint256 constant EIP_712_PREFIX = (
0x1901000000000000000000000000000000000000000000000000000000000000
);
uint256 constant ExtraGasBuffer = 0x20;
uint256 constant CostPerWord = 3;
uint256 constant MemoryExpansionCoefficient = 0x200; // 512
uint256 constant Create2AddressDerivation_ptr = 0x0b;
uint256 constant Create2AddressDerivation_length = 0x55;
uint256 constant MaskOverByteTwelve = (
0x0000000000000000000000ff0000000000000000000000000000000000000000
);
uint256 constant MaskOverLastTwentyBytes = (
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
);
uint256 constant MaskOverFirstFourBytes = (
0xffffffff00000000000000000000000000000000000000000000000000000000
);
uint256 constant Conduit_execute_signature = (
0x4ce34aa200000000000000000000000000000000000000000000000000000000
);
uint256 constant MaxUint8 = 0xff;
uint256 constant MaxUint120 = 0xffffffffffffffffffffffffffffff;
uint256 constant Conduit_execute_ConduitTransfer_ptr = 0x20;
uint256 constant Conduit_execute_ConduitTransfer_length = 0x01;
uint256 constant Conduit_execute_ConduitTransfer_offset_ptr = 0x04;
uint256 constant Conduit_execute_ConduitTransfer_length_ptr = 0x24;
uint256 constant Conduit_execute_transferItemType_ptr = 0x44;
uint256 constant Conduit_execute_transferToken_ptr = 0x64;
uint256 constant Conduit_execute_transferFrom_ptr = 0x84;
uint256 constant Conduit_execute_transferTo_ptr = 0xa4;
uint256 constant Conduit_execute_transferIdentifier_ptr = 0xc4;
uint256 constant Conduit_execute_transferAmount_ptr = 0xe4;
uint256 constant OneConduitExecute_size = 0x104;
// Sentinel value to indicate that the conduit accumulator is not armed.
uint256 constant AccumulatorDisarmed = 0x20;
uint256 constant AccumulatorArmed = 0x40;
uint256 constant Accumulator_conduitKey_ptr = 0x20;
uint256 constant Accumulator_selector_ptr = 0x40;
uint256 constant Accumulator_array_offset_ptr = 0x44;
uint256 constant Accumulator_array_length_ptr = 0x64;
uint256 constant Accumulator_itemSizeOffsetDifference = 0x3c;
uint256 constant Accumulator_array_offset = 0x20;
uint256 constant Conduit_transferItem_size = 0xc0;
uint256 constant Conduit_transferItem_token_ptr = 0x20;
uint256 constant Conduit_transferItem_from_ptr = 0x40;
uint256 constant Conduit_transferItem_to_ptr = 0x60;
uint256 constant Conduit_transferItem_identifier_ptr = 0x80;
uint256 constant Conduit_transferItem_amount_ptr = 0xa0;
// Declare constant for errors related to amount derivation.
// error InexactFraction() @ AmountDerivationErrors.sol
uint256 constant InexactFraction_error_signature = (
0xc63cf08900000000000000000000000000000000000000000000000000000000
);
uint256 constant InexactFraction_error_len = 0x04;
// Declare constant for errors related to signature verification.
uint256 constant Ecrecover_precompile = 1;
uint256 constant Ecrecover_args_size = 0x80;
uint256 constant Signature_lower_v = 27;
// error BadSignatureV(uint8) @ SignatureVerificationErrors.sol
uint256 constant BadSignatureV_error_signature = (
0x1f003d0a00000000000000000000000000000000000000000000000000000000
);
uint256 constant BadSignatureV_error_offset = 0x04;
uint256 constant BadSignatureV_error_length = 0x24;
// error InvalidSigner() @ SignatureVerificationErrors.sol
uint256 constant InvalidSigner_error_signature = (
0x815e1d6400000000000000000000000000000000000000000000000000000000
);
uint256 constant InvalidSigner_error_length = 0x04;
// error InvalidSignature() @ SignatureVerificationErrors.sol
uint256 constant InvalidSignature_error_signature = (
0x8baa579f00000000000000000000000000000000000000000000000000000000
);
uint256 constant InvalidSignature_error_length = 0x04;
// error BadContractSignature() @ SignatureVerificationErrors.sol
uint256 constant BadContractSignature_error_signature = (
0x4f7fb80d00000000000000000000000000000000000000000000000000000000
);
uint256 constant BadContractSignature_error_length = 0x04;
uint256 constant NumBitsAfterSelector = 0xe0;
// 69 is the lowest modulus for which the remainder
// of every selector other than the two match functions
// is greater than those of the match functions.
uint256 constant NonMatchSelector_MagicModulus = 69;
// Of the two match function selectors, the highest
// remainder modulo 69 is 29.
uint256 constant NonMatchSelector_MagicRemainder = 0x1d;
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { OrderParameters } from "./ConsiderationStructs.sol";
import { GettersAndDerivers } from "./GettersAndDerivers.sol";
// prettier-ignore
import {
TokenTransferrerErrors
} from "../interfaces/TokenTransferrerErrors.sol";
import { CounterManager } from "./CounterManager.sol";
import "./ConsiderationConstants.sol";
/**
* @title Assertions
* @author 0age
* @notice Assertions contains logic for making various assertions that do not
* fit neatly within a dedicated semantic scope.
*/
contract Assertions is
GettersAndDerivers,
CounterManager,
TokenTransferrerErrors
{
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController)
GettersAndDerivers(conduitController)
{}
/**
* @dev Internal view function to ensure that the supplied consideration
* array length on a given set of order parameters is not less than the
* original consideration array length for that order and to retrieve
* the current counter for a given order's offerer and zone and use it
* to derive the order hash.
*
* @param orderParameters The parameters of the order to hash.
*
* @return The hash.
*/
function _assertConsiderationLengthAndGetOrderHash(
OrderParameters memory orderParameters
) internal view returns (bytes32) {
// Ensure supplied consideration array length is not less than original.
_assertConsiderationLengthIsNotLessThanOriginalConsiderationLength(
orderParameters.consideration.length,
orderParameters.totalOriginalConsiderationItems
);
// Derive and return order hash using current counter for the offerer.
return
_deriveOrderHash(
orderParameters,
_getCounter(orderParameters.offerer)
);
}
/**
* @dev Internal pure function to ensure that the supplied consideration
* array length for an order to be fulfilled is not less than the
* original consideration array length for that order.
*
* @param suppliedConsiderationItemTotal The number of consideration items
* supplied when fulfilling the order.
* @param originalConsiderationItemTotal The number of consideration items
* supplied on initial order creation.
*/
function _assertConsiderationLengthIsNotLessThanOriginalConsiderationLength(
uint256 suppliedConsiderationItemTotal,
uint256 originalConsiderationItemTotal
) internal pure {
// Ensure supplied consideration array length is not less than original.
if (suppliedConsiderationItemTotal < originalConsiderationItemTotal) {
revert MissingOriginalConsiderationItems();
}
}
/**
* @dev Internal pure function to ensure that a given item amount is not
* zero.
*
* @param amount The amount to check.
*/
function _assertNonZeroAmount(uint256 amount) internal pure {
// Revert if the supplied amount is equal to zero.
if (amount == 0) {
revert MissingItemAmount();
}
}
/**
* @dev Internal pure function to validate calldata offsets for dynamic
* types in BasicOrderParameters and other parameters. This ensures
* that functions using the calldata object normally will be using the
* same data as the assembly functions and that values that are bound
* to a given range are within that range. Note that no parameters are
* supplied as all basic order functions use the same calldata
* encoding.
*/
function _assertValidBasicOrderParameters() internal pure {
// Declare a boolean designating basic order parameter offset validity.
bool validOffsets;
// Utilize assembly in order to read offset data directly from calldata.
assembly {
/*
* Checks:
* 1. Order parameters struct offset == 0x20
* 2. Additional recipients arr offset == 0x240
* 3. Signature offset == 0x260 + (recipients.length * 0x40)
* 4. BasicOrderType between 0 and 23 (i.e. < 24)
*/
validOffsets := and(
// Order parameters at calldata 0x04 must have offset of 0x20.
eq(
calldataload(BasicOrder_parameters_cdPtr),
BasicOrder_parameters_ptr
),
// Additional recipients at cd 0x224 must have offset of 0x240.
eq(
calldataload(BasicOrder_additionalRecipients_head_cdPtr),
BasicOrder_additionalRecipients_head_ptr
)
)
validOffsets := and(
validOffsets,
eq(
// Load signature offset from calldata 0x244.
calldataload(BasicOrder_signature_cdPtr),
// Derive expected offset as start of recipients + len * 64.
add(
BasicOrder_signature_ptr,
mul(
// Additional recipients length at calldata 0x264.
calldataload(
BasicOrder_additionalRecipients_length_cdPtr
),
// Each additional recipient has a length of 0x40.
AdditionalRecipients_size
)
)
)
)
validOffsets := and(
validOffsets,
lt(
// BasicOrderType parameter at calldata offset 0x124.
calldataload(BasicOrder_basicOrderType_cdPtr),
// Value should be less than 24.
BasicOrder_basicOrderType_range
)
)
}
// Revert with an error if basic order parameter offsets are invalid.
if (!validOffsets) {
revert InvalidBasicOrderParameterEncoding();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { EIP1271Interface } from "../interfaces/EIP1271Interface.sol";
// prettier-ignore
import {
SignatureVerificationErrors
} from "../interfaces/SignatureVerificationErrors.sol";
import { LowLevelHelpers } from "./LowLevelHelpers.sol";
import "./ConsiderationConstants.sol";
/**
* @title SignatureVerification
* @author 0age
* @notice SignatureVerification contains logic for verifying signatures.
*/
contract SignatureVerification is SignatureVerificationErrors, LowLevelHelpers {
/**
* @dev Internal view function to verify the signature of an order. An
* ERC-1271 fallback will be attempted if either the signature length
* is not 64 or 65 bytes or if the recovered signer does not match the
* supplied signer.
*
* @param signer The signer for the order.
* @param digest The digest to verify the signature against.
* @param signature A signature from the signer indicating that the order
* has been approved.
*/
function _assertValidSignature(
address signer,
bytes32 digest,
bytes memory signature
) internal view {
// Declare value for ecrecover equality or 1271 call success status.
bool success;
// Utilize assembly to perform optimized signature verification check.
assembly {
// Ensure that first word of scratch space is empty.
mstore(0, 0)
// Declare value for v signature parameter.
let v
// Get the length of the signature.
let signatureLength := mload(signature)
// Get the pointer to the value preceding the signature length.
// This will be used for temporary memory overrides - either the
// signature head for isValidSignature or the digest for ecrecover.
let wordBeforeSignaturePtr := sub(signature, OneWord)
// Cache the current value behind the signature to restore it later.
let cachedWordBeforeSignature := mload(wordBeforeSignaturePtr)
// Declare lenDiff + recoveredSigner scope to manage stack pressure.
{
// Take the difference between the max ECDSA signature length
// and the actual signature length. Overflow desired for any
// values > 65. If the diff is not 0 or 1, it is not a valid
// ECDSA signature - move on to EIP1271 check.
let lenDiff := sub(ECDSA_MaxLength, signatureLength)
// Declare variable for recovered signer.
let recoveredSigner
// If diff is 0 or 1, it may be an ECDSA signature.
// Try to recover signer.
if iszero(gt(lenDiff, 1)) {
// Read the signature `s` value.
let originalSignatureS := mload(
add(signature, ECDSA_signature_s_offset)
)
// Read the first byte of the word after `s`. If the
// signature is 65 bytes, this will be the real `v` value.
// If not, it will need to be modified - doing it this way
// saves an extra condition.
v := byte(
0,
mload(add(signature, ECDSA_signature_v_offset))
)
// If lenDiff is 1, parse 64-byte signature as ECDSA.
if lenDiff {
// Extract yParity from highest bit of vs and add 27 to
// get v.
v := add(
shr(MaxUint8, originalSignatureS),
Signature_lower_v
)
// Extract canonical s from vs, all but the highest bit.
// Temporarily overwrite the original `s` value in the
// signature.
mstore(
add(signature, ECDSA_signature_s_offset),
and(
originalSignatureS,
EIP2098_allButHighestBitMask
)
)
}
// Temporarily overwrite the signature length with `v` to
// conform to the expected input for ecrecover.
mstore(signature, v)
// Temporarily overwrite the word before the length with
// `digest` to conform to the expected input for ecrecover.
mstore(wordBeforeSignaturePtr, digest)
// Attempt to recover the signer for the given signature. Do
// not check the call status as ecrecover will return a null
// address if the signature is invalid.
pop(
staticcall(
gas(),
Ecrecover_precompile, // Call ecrecover precompile.
wordBeforeSignaturePtr, // Use data memory location.
Ecrecover_args_size, // Size of digest, v, r, and s.
0, // Write result to scratch space.
OneWord // Provide size of returned result.
)
)
// Restore cached word before signature.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
// Restore cached signature length.
mstore(signature, signatureLength)
// Restore cached signature `s` value.
mstore(
add(signature, ECDSA_signature_s_offset),
originalSignatureS
)
// Read the recovered signer from the buffer given as return
// space for ecrecover.
recoveredSigner := mload(0)
}
// Set success to true if the signature provided was a valid
// ECDSA signature and the signer is not the null address. Use
// gt instead of direct as success is used outside of assembly.
success := and(eq(signer, recoveredSigner), gt(signer, 0))
}
// If the signature was not verified with ecrecover, try EIP1271.
if iszero(success) {
// Temporarily overwrite the word before the signature length
// and use it as the head of the signature input to
// `isValidSignature`, which has a value of 64.
mstore(
wordBeforeSignaturePtr,
EIP1271_isValidSignature_signature_head_offset
)
// Get pointer to use for the selector of `isValidSignature`.
let selectorPtr := sub(
signature,
EIP1271_isValidSignature_selector_negativeOffset
)
// Cache the value currently stored at the selector pointer.
let cachedWordOverwrittenBySelector := mload(selectorPtr)
// Get pointer to use for `digest` input to `isValidSignature`.
let digestPtr := sub(
signature,
EIP1271_isValidSignature_digest_negativeOffset
)
// Cache the value currently stored at the digest pointer.
let cachedWordOverwrittenByDigest := mload(digestPtr)
// Write the selector first, since it overlaps the digest.
mstore(selectorPtr, EIP1271_isValidSignature_selector)
// Next, write the digest.
mstore(digestPtr, digest)
// Call signer with `isValidSignature` to validate signature.
success := staticcall(
gas(),
signer,
selectorPtr,
add(
signatureLength,
EIP1271_isValidSignature_calldata_baseLength
),
0,
OneWord
)
// Determine if the signature is valid on successful calls.
if success {
// If first word of scratch space does not contain EIP-1271
// signature selector, revert.
if iszero(eq(mload(0), EIP1271_isValidSignature_selector)) {
// Revert with bad 1271 signature if signer has code.
if extcodesize(signer) {
// Bad contract signature.
mstore(0, BadContractSignature_error_signature)
revert(0, BadContractSignature_error_length)
}
// Check if signature length was invalid.
if gt(sub(ECDSA_MaxLength, signatureLength), 1) {
// Revert with generic invalid signature error.
mstore(0, InvalidSignature_error_signature)
revert(0, InvalidSignature_error_length)
}
// Check if v was invalid.
if iszero(
byte(v, ECDSA_twentySeventhAndTwentyEighthBytesSet)
) {
// Revert with invalid v value.
mstore(0, BadSignatureV_error_signature)
mstore(BadSignatureV_error_offset, v)
revert(0, BadSignatureV_error_length)
}
// Revert with generic invalid signer error message.
mstore(0, InvalidSigner_error_signature)
revert(0, InvalidSigner_error_length)
}
}
// Restore the cached values overwritten by selector, digest and
// signature head.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
mstore(selectorPtr, cachedWordOverwrittenBySelector)
mstore(digestPtr, cachedWordOverwrittenByDigest)
}
}
// If the call failed...
if (!success) {
// Revert and pass reason along if one was returned.
_revertWithReasonIfOneIsReturned();
// Otherwise, revert with error indicating bad contract signature.
assembly {
mstore(0, BadContractSignature_error_signature)
revert(0, BadContractSignature_error_length)
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { OrderParameters } from "./ConsiderationStructs.sol";
import { ConsiderationBase } from "./ConsiderationBase.sol";
import "./ConsiderationConstants.sol";
/**
* @title GettersAndDerivers
* @author 0age
* @notice ConsiderationInternal contains pure and internal view functions
* related to getting or deriving various values.
*/
contract GettersAndDerivers is ConsiderationBase {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController)
ConsiderationBase(conduitController)
{}
/**
* @dev Internal view function to derive the order hash for a given order.
* Note that only the original consideration items are included in the
* order hash, as additional consideration items may be supplied by the
* caller.
*
* @param orderParameters The parameters of the order to hash.
* @param counter The counter of the order to hash.
*
* @return orderHash The hash.
*/
function _deriveOrderHash(
OrderParameters memory orderParameters,
uint256 counter
) internal view returns (bytes32 orderHash) {
// Get length of original consideration array and place it on the stack.
uint256 originalConsiderationLength = (
orderParameters.totalOriginalConsiderationItems
);
/*
* Memory layout for an array of structs (dynamic or not) is similar
* to ABI encoding of dynamic types, with a head segment followed by
* a data segment. The main difference is that the head of an element
* is a memory pointer rather than an offset.
*/
// Declare a variable for the derived hash of the offer array.
bytes32 offerHash;
// Read offer item EIP-712 typehash from runtime code & place on stack.
bytes32 typeHash = _OFFER_ITEM_TYPEHASH;
// Utilize assembly so that memory regions can be reused across hashes.
assembly {
// Retrieve the free memory pointer and place on the stack.
let hashArrPtr := mload(FreeMemoryPointerSlot)
// Get the pointer to the offers array.
let offerArrPtr := mload(
add(orderParameters, OrderParameters_offer_head_offset)
)
// Load the length.
let offerLength := mload(offerArrPtr)
// Set the pointer to the first offer's head.
offerArrPtr := add(offerArrPtr, OneWord)
// Iterate over the offer items.
// prettier-ignore
for { let i := 0 } lt(i, offerLength) {
i := add(i, 1)
} {
// Read the pointer to the offer data and subtract one word
// to get typeHash pointer.
let ptr := sub(mload(offerArrPtr), OneWord)
// Read the current value before the offer data.
let value := mload(ptr)
// Write the type hash to the previous word.
mstore(ptr, typeHash)
// Take the EIP712 hash and store it in the hash array.
mstore(hashArrPtr, keccak256(ptr, EIP712_OfferItem_size))
// Restore the previous word.
mstore(ptr, value)
// Increment the array pointers by one word.
offerArrPtr := add(offerArrPtr, OneWord)
hashArrPtr := add(hashArrPtr, OneWord)
}
// Derive the offer hash using the hashes of each item.
offerHash := keccak256(
mload(FreeMemoryPointerSlot),
mul(offerLength, OneWord)
)
}
// Declare a variable for the derived hash of the consideration array.
bytes32 considerationHash;
// Read consideration item typehash from runtime code & place on stack.
typeHash = _CONSIDERATION_ITEM_TYPEHASH;
// Utilize assembly so that memory regions can be reused across hashes.
assembly {
// Retrieve the free memory pointer and place on the stack.
let hashArrPtr := mload(FreeMemoryPointerSlot)
// Get the pointer to the consideration array.
let considerationArrPtr := add(
mload(
add(
orderParameters,
OrderParameters_consideration_head_offset
)
),
OneWord
)
// Iterate over the consideration items (not including tips).
// prettier-ignore
for { let i := 0 } lt(i, originalConsiderationLength) {
i := add(i, 1)
} {
// Read the pointer to the consideration data and subtract one
// word to get typeHash pointer.
let ptr := sub(mload(considerationArrPtr), OneWord)
// Read the current value before the consideration data.
let value := mload(ptr)
// Write the type hash to the previous word.
mstore(ptr, typeHash)
// Take the EIP712 hash and store it in the hash array.
mstore(
hashArrPtr,
keccak256(ptr, EIP712_ConsiderationItem_size)
)
// Restore the previous word.
mstore(ptr, value)
// Increment the array pointers by one word.
considerationArrPtr := add(considerationArrPtr, OneWord)
hashArrPtr := add(hashArrPtr, OneWord)
}
// Derive the consideration hash using the hashes of each item.
considerationHash := keccak256(
mload(FreeMemoryPointerSlot),
mul(originalConsiderationLength, OneWord)
)
}
// Read order item EIP-712 typehash from runtime code & place on stack.
typeHash = _ORDER_TYPEHASH;
// Utilize assembly to access derived hashes & other arguments directly.
assembly {
// Retrieve pointer to the region located just behind parameters.
let typeHashPtr := sub(orderParameters, OneWord)
// Store the value at that pointer location to restore later.
let previousValue := mload(typeHashPtr)
// Store the order item EIP-712 typehash at the typehash location.
mstore(typeHashPtr, typeHash)
// Retrieve the pointer for the offer array head.
let offerHeadPtr := add(
orderParameters,
OrderParameters_offer_head_offset
)
// Retrieve the data pointer referenced by the offer head.
let offerDataPtr := mload(offerHeadPtr)
// Store the offer hash at the retrieved memory location.
mstore(offerHeadPtr, offerHash)
// Retrieve the pointer for the consideration array head.
let considerationHeadPtr := add(
orderParameters,
OrderParameters_consideration_head_offset
)
// Retrieve the data pointer referenced by the consideration head.
let considerationDataPtr := mload(considerationHeadPtr)
// Store the consideration hash at the retrieved memory location.
mstore(considerationHeadPtr, considerationHash)
// Retrieve the pointer for the counter.
let counterPtr := add(
orderParameters,
OrderParameters_counter_offset
)
// Store the counter at the retrieved memory location.
mstore(counterPtr, counter)
// Derive the order hash using the full range of order parameters.
orderHash := keccak256(typeHashPtr, EIP712_Order_size)
// Restore the value previously held at typehash pointer location.
mstore(typeHashPtr, previousValue)
// Restore offer data pointer at the offer head pointer location.
mstore(offerHeadPtr, offerDataPtr)
// Restore consideration data pointer at the consideration head ptr.
mstore(considerationHeadPtr, considerationDataPtr)
// Restore consideration item length at the counter pointer.
mstore(counterPtr, originalConsiderationLength)
}
}
/**
* @dev Internal view function to derive the address of a given conduit
* using a corresponding conduit key.
*
* @param conduitKey A bytes32 value indicating what corresponding conduit,
* if any, to source token approvals from. This value is
* the "salt" parameter supplied by the deployer (i.e. the
* conduit controller) when deploying the given conduit.
*
* @return conduit The address of the conduit associated with the given
* conduit key.
*/
function _deriveConduit(bytes32 conduitKey)
internal
view
returns (address conduit)
{
// Read conduit controller address from runtime and place on the stack.
address conduitController = address(_CONDUIT_CONTROLLER);
// Read conduit creation code hash from runtime and place on the stack.
bytes32 conduitCreationCodeHash = _CONDUIT_CREATION_CODE_HASH;
// Leverage scratch space to perform an efficient hash.
assembly {
// Retrieve the free memory pointer; it will be replaced afterwards.
let freeMemoryPointer := mload(FreeMemoryPointerSlot)
// Place the control character and the conduit controller in scratch
// space; note that eleven bytes at the beginning are left unused.
mstore(0, or(MaskOverByteTwelve, conduitController))
// Place the conduit key in the next region of scratch space.
mstore(OneWord, conduitKey)
// Place conduit creation code hash in free memory pointer location.
mstore(TwoWords, conduitCreationCodeHash)
// Derive conduit by hashing and applying a mask over last 20 bytes.
conduit := and(
// Hash the relevant region.
keccak256(
// The region starts at memory pointer 11.
Create2AddressDerivation_ptr,
// The region is 85 bytes long (1 + 20 + 32 + 32).
Create2AddressDerivation_length
),
// The address equals the last twenty bytes of the hash.
MaskOverLastTwentyBytes
)
// Restore the free memory pointer.
mstore(FreeMemoryPointerSlot, freeMemoryPointer)
}
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator() internal view returns (bytes32) {
// prettier-ignore
return block.chainid == _CHAIN_ID
? _DOMAIN_SEPARATOR
: _deriveDomainSeparator();
}
/**
* @dev Internal view function to retrieve configuration information for
* this contract.
*
* @return version The contract version.
* @return domainSeparator The domain separator for this contract.
* @return conduitController The conduit Controller set for this contract.
*/
function _information()
internal
view
returns (
string memory version,
bytes32 domainSeparator,
address conduitController
)
{
// Derive the domain separator.
domainSeparator = _domainSeparator();
// Declare variable as immutables cannot be accessed within assembly.
conduitController = address(_CONDUIT_CONTROLLER);
// Allocate a string with the intended length.
version = new string(Version_length);
// Set the version as data on the newly allocated string.
assembly {
mstore(add(version, OneWord), shl(Version_shift, Version))
}
}
/**
* @dev Internal pure function to efficiently derive an digest to sign for
* an order in accordance with EIP-712.
*
* @param domainSeparator The domain separator.
* @param orderHash The order hash.
*
* @return value The hash.
*/
function _deriveEIP712Digest(bytes32 domainSeparator, bytes32 orderHash)
internal
pure
returns (bytes32 value)
{
// Leverage scratch space to perform an efficient hash.
assembly {
// Place the EIP-712 prefix at the start of scratch space.
mstore(0, EIP_712_PREFIX)
// Place the domain separator in the next region of scratch space.
mstore(EIP712_DomainSeparator_offset, domainSeparator)
// Place the order hash in scratch space, spilling into the first
// two bytes of the free memory pointer — this should never be set
// as memory cannot be expanded to that size, and will be zeroed out
// after the hash is performed.
mstore(EIP712_OrderHash_offset, orderHash)
// Hash the relevant region (65 bytes).
value := keccak256(0, EIP712_DigestPayload_size)
// Clear out the dirtied bits in the memory pointer.
mstore(EIP712_OrderHash_offset, 0)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
// prettier-ignore
import {
ConsiderationEventsAndErrors
} from "../interfaces/ConsiderationEventsAndErrors.sol";
import { ReentrancyGuard } from "./ReentrancyGuard.sol";
/**
* @title CounterManager
* @author 0age
* @notice CounterManager contains a storage mapping and related functionality
* for retrieving and incrementing a per-offerer counter.
*/
contract CounterManager is ConsiderationEventsAndErrors, ReentrancyGuard {
// Only orders signed using an offerer's current counter are fulfillable.
mapping(address => uint256) private _counters;
/**
* @dev Internal function to cancel all orders from a given offerer with a
* given zone in bulk by incrementing a counter. Note that only the
* offerer may increment the counter.
*
* @return newCounter The new counter.
*/
function _incrementCounter() internal returns (uint256 newCounter) {
// Ensure that the reentrancy guard is not currently set.
_assertNonReentrant();
// Skip overflow check as counter cannot be incremented that far.
unchecked {
// Increment current counter for the supplied offerer.
newCounter = ++_counters[msg.sender];
}
// Emit an event containing the new counter.
emit CounterIncremented(newCounter, msg.sender);
}
/**
* @dev Internal view function to retrieve the current counter for a given
* offerer.
*
* @param offerer The offerer in question.
*
* @return currentCounter The current counter.
*/
function _getCounter(address offerer)
internal
view
returns (uint256 currentCounter)
{
// Return the counter for the supplied offerer.
currentCounter = _counters[offerer];
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
// prettier-ignore
import {
ConduitControllerInterface
} from "../interfaces/ConduitControllerInterface.sol";
// prettier-ignore
import {
ConsiderationEventsAndErrors
} from "../interfaces/ConsiderationEventsAndErrors.sol";
import "./ConsiderationConstants.sol";
/**
* @title ConsiderationBase
* @author 0age
* @notice ConsiderationBase contains immutable constants and constructor logic.
*/
contract ConsiderationBase is ConsiderationEventsAndErrors {
// Precompute hashes, original chainId, and domain separator on deployment.
bytes32 internal immutable _NAME_HASH;
bytes32 internal immutable _VERSION_HASH;
bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH;
bytes32 internal immutable _OFFER_ITEM_TYPEHASH;
bytes32 internal immutable _CONSIDERATION_ITEM_TYPEHASH;
bytes32 internal immutable _ORDER_TYPEHASH;
uint256 internal immutable _CHAIN_ID;
bytes32 internal immutable _DOMAIN_SEPARATOR;
// Allow for interaction with the conduit controller.
ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER;
// Cache the conduit creation code hash used by the conduit controller.
bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) {
// Derive name and version hashes alongside required EIP-712 typehashes.
(
_NAME_HASH,
_VERSION_HASH,
_EIP_712_DOMAIN_TYPEHASH,
_OFFER_ITEM_TYPEHASH,
_CONSIDERATION_ITEM_TYPEHASH,
_ORDER_TYPEHASH
) = _deriveTypehashes();
// Store the current chainId and derive the current domain separator.
_CHAIN_ID = block.chainid;
_DOMAIN_SEPARATOR = _deriveDomainSeparator();
// Set the supplied conduit controller.
_CONDUIT_CONTROLLER = ConduitControllerInterface(conduitController);
// Retrieve the conduit creation code hash from the supplied controller.
(_CONDUIT_CREATION_CODE_HASH, ) = (
_CONDUIT_CONTROLLER.getConduitCodeHashes()
);
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return The derived domain separator.
*/
function _deriveDomainSeparator() internal view returns (bytes32) {
// prettier-ignore
return keccak256(
abi.encode(
_EIP_712_DOMAIN_TYPEHASH,
_NAME_HASH,
_VERSION_HASH,
block.chainid,
address(this)
)
);
}
/**
* @dev Internal pure function to retrieve the default name of this
* contract and return.
*
* @return The name of this contract.
*/
function _name() internal pure virtual returns (string memory) {
// Return the name of the contract.
assembly {
// First element is the offset for the returned string. Offset the
// value in memory by one word so that the free memory pointer will
// be overwritten by the next write.
mstore(OneWord, OneWord)
// Name is right padded, so it touches the length which is left
// padded. This enables writing both values at once. The free memory
// pointer will be overwritten in the process.
mstore(NameLengthPtr, NameWithLength)
// Standard ABI encoding pads returned data to the nearest word. Use
// the already empty zero slot memory region for this purpose and
// return the final name string, offset by the original single word.
return(OneWord, ThreeWords)
}
}
/**
* @dev Internal pure function to retrieve the default name of this contract
* as a string that can be used internally.
*
* @return The name of this contract.
*/
function _nameString() internal pure virtual returns (string memory) {
// Return the name of the contract.
return "Consideration";
}
/**
* @dev Internal pure function to derive required EIP-712 typehashes and
* other hashes during contract creation.
*
* @return nameHash The hash of the name of the contract.
* @return versionHash The hash of the version string of the
* contract.
* @return eip712DomainTypehash The primary EIP-712 domain typehash.
* @return offerItemTypehash The EIP-712 typehash for OfferItem
* types.
* @return considerationItemTypehash The EIP-712 typehash for
* ConsiderationItem types.
* @return orderTypehash The EIP-712 typehash for Order types.
*/
function _deriveTypehashes()
internal
pure
returns (
bytes32 nameHash,
bytes32 versionHash,
bytes32 eip712DomainTypehash,
bytes32 offerItemTypehash,
bytes32 considerationItemTypehash,
bytes32 orderTypehash
)
{
// Derive hash of the name of the contract.
nameHash = keccak256(bytes(_nameString()));
// Derive hash of the version string of the contract.
versionHash = keccak256(bytes("1.1"));
// Construct the OfferItem type string.
// prettier-ignore
bytes memory offerItemTypeString = abi.encodePacked(
"OfferItem(",
"uint8 itemType,",
"address token,",
"uint256 identifierOrCriteria,",
"uint256 startAmount,",
"uint256 endAmount",
")"
);
// Construct the ConsiderationItem type string.
// prettier-ignore
bytes memory considerationItemTypeString = abi.encodePacked(
"ConsiderationItem(",
"uint8 itemType,",
"address token,",
"uint256 identifierOrCriteria,",
"uint256 startAmount,",
"uint256 endAmount,",
"address recipient",
")"
);
// Construct the OrderComponents type string, not including the above.
// prettier-ignore
bytes memory orderComponentsPartialTypeString = abi.encodePacked(
"OrderComponents(",
"address offerer,",
"address zone,",
"OfferItem[] offer,",
"ConsiderationItem[] consideration,",
"uint8 orderType,",
"uint256 startTime,",
"uint256 endTime,",
"bytes32 zoneHash,",
"uint256 salt,",
"bytes32 conduitKey,",
"uint256 counter",
")"
);
// Construct the primary EIP-712 domain type string.
// prettier-ignore
eip712DomainTypehash = keccak256(
abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
);
// Derive the OfferItem type hash using the corresponding type string.
offerItemTypehash = keccak256(offerItemTypeString);
// Derive ConsiderationItem type hash using corresponding type string.
considerationItemTypehash = keccak256(considerationItemTypeString);
// Derive OrderItem type hash via combination of relevant type strings.
orderTypehash = keccak256(
abi.encodePacked(
orderComponentsPartialTypeString,
considerationItemTypeString,
offerItemTypeString
)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { SpentItem, ReceivedItem } from "../lib/ConsiderationStructs.sol";
/**
* @title ConsiderationEventsAndErrors
* @author 0age
* @notice ConsiderationEventsAndErrors contains all events and errors.
*/
interface ConsiderationEventsAndErrors {
/**
* @dev Emit an event whenever an order is successfully fulfilled.
*
* @param orderHash The hash of the fulfilled order.
* @param offerer The offerer of the fulfilled order.
* @param zone The zone of the fulfilled order.
* @param recipient The recipient of each spent item on the fulfilled
* order, or the null address if there is no specific
* fulfiller (i.e. the order is part of a group of
* orders). Defaults to the caller unless explicitly
* specified otherwise by the fulfiller.
* @param offer The offer items spent as part of the order.
* @param consideration The consideration items received as part of the
* order along with the recipients of each item.
*/
event OrderFulfilled(
bytes32 orderHash,
address indexed offerer,
address indexed zone,
address recipient,
SpentItem[] offer,
ReceivedItem[] consideration
);
/**
* @dev Emit an event whenever an order is successfully cancelled.
*
* @param orderHash The hash of the cancelled order.
* @param offerer The offerer of the cancelled order.
* @param zone The zone of the cancelled order.
*/
event OrderCancelled(
bytes32 orderHash,
address indexed offerer,
address indexed zone
);
/**
* @dev Emit an event whenever an order is explicitly validated. Note that
* this event will not be emitted on partial fills even though they do
* validate the order as part of partial fulfillment.
*
* @param orderHash The hash of the validated order.
* @param offerer The offerer of the validated order.
* @param zone The zone of the validated order.
*/
event OrderValidated(
bytes32 orderHash,
address indexed offerer,
address indexed zone
);
/**
* @dev Emit an event whenever a counter for a given offerer is incremented.
*
* @param newCounter The new counter for the offerer.
* @param offerer The offerer in question.
*/
event CounterIncremented(uint256 newCounter, address indexed offerer);
/**
* @dev Revert with an error when attempting to fill an order that has
* already been fully filled.
*
* @param orderHash The order hash on which a fill was attempted.
*/
error OrderAlreadyFilled(bytes32 orderHash);
/**
* @dev Revert with an error when attempting to fill an order outside the
* specified start time and end time.
*/
error InvalidTime();
/**
* @dev Revert with an error when attempting to fill an order referencing an
* invalid conduit (i.e. one that has not been deployed).
*/
error InvalidConduit(bytes32 conduitKey, address conduit);
/**
* @dev Revert with an error when an order is supplied for fulfillment with
* a consideration array that is shorter than the original array.
*/
error MissingOriginalConsiderationItems();
/**
* @dev Revert with an error when a call to a conduit fails with revert data
* that is too expensive to return.
*/
error InvalidCallToConduit(address conduit);
/**
* @dev Revert with an error if a consideration amount has not been fully
* zeroed out after applying all fulfillments.
*
* @param orderIndex The index of the order with the consideration
* item with a shortfall.
* @param considerationIndex The index of the consideration item on the
* order.
* @param shortfallAmount The unfulfilled consideration amount.
*/
error ConsiderationNotMet(
uint256 orderIndex,
uint256 considerationIndex,
uint256 shortfallAmount
);
/**
* @dev Revert with an error when insufficient ether is supplied as part of
* msg.value when fulfilling orders.
*/
error InsufficientEtherSupplied();
/**
* @dev Revert with an error when an ether transfer reverts.
*/
error EtherTransferGenericFailure(address account, uint256 amount);
/**
* @dev Revert with an error when a partial fill is attempted on an order
* that does not specify partial fill support in its order type.
*/
error PartialFillsNotEnabledForOrder();
/**
* @dev Revert with an error when attempting to fill an order that has been
* cancelled.
*
* @param orderHash The hash of the cancelled order.
*/
error OrderIsCancelled(bytes32 orderHash);
/**
* @dev Revert with an error when attempting to fill a basic order that has
* been partially filled.
*
* @param orderHash The hash of the partially used order.
*/
error OrderPartiallyFilled(bytes32 orderHash);
/**
* @dev Revert with an error when attempting to cancel an order as a caller
* other than the indicated offerer or zone.
*/
error InvalidCanceller();
/**
* @dev Revert with an error when supplying a fraction with a value of zero
* for the numerator or denominator, or one where the numerator exceeds
* the denominator.
*/
error BadFraction();
/**
* @dev Revert with an error when a caller attempts to supply callvalue to a
* non-payable basic order route or does not supply any callvalue to a
* payable basic order route.
*/
error InvalidMsgValue(uint256 value);
/**
* @dev Revert with an error when attempting to fill a basic order using
* calldata not produced by default ABI encoding.
*/
error InvalidBasicOrderParameterEncoding();
/**
* @dev Revert with an error when attempting to fulfill any number of
* available orders when none are fulfillable.
*/
error NoSpecifiedOrdersAvailable();
/**
* @dev Revert with an error when attempting to fulfill an order with an
* offer for ETH outside of matching orders.
*/
error InvalidNativeOfferItem();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ReentrancyErrors } from "../interfaces/ReentrancyErrors.sol";
import "./ConsiderationConstants.sol";
/**
* @title ReentrancyGuard
* @author 0age
* @notice ReentrancyGuard contains a storage variable and related functionality
* for protecting against reentrancy.
*/
contract ReentrancyGuard is ReentrancyErrors {
// Prevent reentrant calls on protected functions.
uint256 private _reentrancyGuard;
/**
* @dev Initialize the reentrancy guard during deployment.
*/
constructor() {
// Initialize the reentrancy guard in a cleared state.
_reentrancyGuard = _NOT_ENTERED;
}
/**
* @dev Internal function to ensure that the sentinel value for the
* reentrancy guard is not currently set and, if not, to set the
* sentinel value for the reentrancy guard.
*/
function _setReentrancyGuard() internal {
// Ensure that the reentrancy guard is not already set.
_assertNonReentrant();
// Set the reentrancy guard.
_reentrancyGuard = _ENTERED;
}
/**
* @dev Internal function to unset the reentrancy guard sentinel value.
*/
function _clearReentrancyGuard() internal {
// Clear the reentrancy guard.
_reentrancyGuard = _NOT_ENTERED;
}
/**
* @dev Internal view function to ensure that the sentinel value for the
reentrancy guard is not currently set.
*/
function _assertNonReentrant() internal view {
// Ensure that the reentrancy guard is not currently set.
if (_reentrancyGuard != _NOT_ENTERED) {
revert NoReentrantCalls();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title ReentrancyErrors
* @author 0age
* @notice ReentrancyErrors contains errors related to reentrancy.
*/
interface ReentrancyErrors {
/**
* @dev Revert with an error when a caller attempts to reenter a protected
* function.
*/
error NoReentrantCalls();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
interface EIP1271Interface {
function isValidSignature(bytes32 digest, bytes calldata signature)
external
view
returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title SignatureVerificationErrors
* @author 0age
* @notice SignatureVerificationErrors contains all errors related to signature
* verification.
*/
interface SignatureVerificationErrors {
/**
* @dev Revert with an error when a signature that does not contain a v
* value of 27 or 28 has been supplied.
*
* @param v The invalid v value.
*/
error BadSignatureV(uint8 v);
/**
* @dev Revert with an error when the signer recovered by the supplied
* signature does not match the offerer or an allowed EIP-1271 signer
* as specified by the offerer in the event they are a contract.
*/
error InvalidSigner();
/**
* @dev Revert with an error when a signer cannot be recovered from the
* supplied signature.
*/
error InvalidSignature();
/**
* @dev Revert with an error when an EIP-1271 call to an account fails.
*/
error BadContractSignature();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import "./ConsiderationConstants.sol";
/**
* @title LowLevelHelpers
* @author 0age
* @notice LowLevelHelpers contains logic for performing various low-level
* operations.
*/
contract LowLevelHelpers {
/**
* @dev Internal view function to staticcall an arbitrary target with given
* calldata. Note that no data is written to memory and no contract
* size check is performed.
*
* @param target The account to staticcall.
* @param callData The calldata to supply when staticcalling the target.
*
* @return success The status of the staticcall to the target.
*/
function _staticcall(address target, bytes memory callData)
internal
view
returns (bool success)
{
assembly {
// Perform the staticcall.
success := staticcall(
gas(),
target,
add(callData, OneWord),
mload(callData),
0,
0
)
}
}
/**
* @dev Internal view function to revert and pass along the revert reason if
* data was returned by the last call and that the size of that data
* does not exceed the currently allocated memory size.
*/
function _revertWithReasonIfOneIsReturned() internal view {
assembly {
// If it returned a message, bubble it up as long as sufficient gas
// remains to do so:
if returndatasize() {
// Ensure that sufficient gas is available to copy returndata
// while expanding memory where necessary. Start by computing
// the word size of returndata and allocated memory.
let returnDataWords := div(
add(returndatasize(), AlmostOneWord),
OneWord
)
// Note: use the free memory pointer in place of msize() to work
// around a Yul warning that prevents accessing msize directly
// when the IR pipeline is activated.
let msizeWords := div(mload(FreeMemoryPointerSlot), OneWord)
// Next, compute the cost of the returndatacopy.
let cost := mul(CostPerWord, returnDataWords)
// Then, compute cost of new memory allocation.
if gt(returnDataWords, msizeWords) {
cost := add(
cost,
add(
mul(sub(returnDataWords, msizeWords), CostPerWord),
div(
sub(
mul(returnDataWords, returnDataWords),
mul(msizeWords, msizeWords)
),
MemoryExpansionCoefficient
)
)
)
}
// Finally, add a small constant and compare to gas remaining;
// bubble up the revert data if enough gas is still available.
if lt(add(cost, ExtraGasBuffer), gas()) {
// Copy returndata to memory; overwrite existing memory.
returndatacopy(0, 0, returndatasize())
// Revert, specifying memory region with copied returndata.
revert(0, returndatasize())
}
}
}
}
/**
* @dev Internal pure function to determine if the first word of returndata
* matches an expected magic value.
*
* @param expected The expected magic value.
*
* @return A boolean indicating whether the expected value matches the one
* located in the first word of returndata.
*/
function _doesNotMatchMagic(bytes4 expected) internal pure returns (bool) {
// Declare a variable for the value held by the return data buffer.
bytes4 result;
// Utilize assembly in order to read directly from returndata buffer.
assembly {
// Only put result on stack if return data is exactly one word.
if eq(returndatasize(), OneWord) {
// Copy the word directly from return data into scratch space.
returndatacopy(0, 0, OneWord)
// Take value from scratch space and place it on the stack.
result := mload(0)
}
}
// Return a boolean indicating whether expected and located value match.
return result != expected;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ItemType, Side } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OfferItem,
ConsiderationItem,
ReceivedItem,
OrderParameters,
AdvancedOrder,
Execution,
FulfillmentComponent
} from "./ConsiderationStructs.sol";
import "./ConsiderationConstants.sol";
// prettier-ignore
import {
FulfillmentApplicationErrors
} from "../interfaces/FulfillmentApplicationErrors.sol";
/**
* @title FulfillmentApplier
* @author 0age
* @notice FulfillmentApplier contains logic related to applying fulfillments,
* both as part of order matching (where offer items are matched to
* consideration items) as well as fulfilling available orders (where
* order items and consideration items are independently aggregated).
*/
contract FulfillmentApplier is FulfillmentApplicationErrors {
/**
* @dev Internal pure function to match offer items to consideration items
* on a group of orders via a supplied fulfillment.
*
* @param advancedOrders The orders to match.
* @param offerComponents An array designating offer components to
* match to consideration components.
* @param considerationComponents An array designating consideration
* components to match to offer components.
* Note that each consideration amount must
* be zero in order for the match operation
* to be valid.
*
* @return execution The transfer performed as a result of the fulfillment.
*/
function _applyFulfillment(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] calldata offerComponents,
FulfillmentComponent[] calldata considerationComponents
) internal pure returns (Execution memory execution) {
// Ensure 1+ of both offer and consideration components are supplied.
if (
offerComponents.length == 0 || considerationComponents.length == 0
) {
revert OfferAndConsiderationRequiredOnFulfillment();
}
// Declare a new Execution struct.
Execution memory considerationExecution;
// Validate & aggregate consideration items to new Execution object.
_aggregateValidFulfillmentConsiderationItems(
advancedOrders,
considerationComponents,
considerationExecution
);
// Retrieve the consideration item from the execution struct.
ReceivedItem memory considerationItem = considerationExecution.item;
// Recipient does not need to be specified because it will always be set
// to that of the consideration.
// Validate & aggregate offer items to Execution object.
_aggregateValidFulfillmentOfferItems(
advancedOrders,
offerComponents,
execution
);
// Ensure offer and consideration share types, tokens and identifiers.
if (
execution.item.itemType != considerationItem.itemType ||
execution.item.token != considerationItem.token ||
execution.item.identifier != considerationItem.identifier
) {
revert MismatchedFulfillmentOfferAndConsiderationComponents();
}
// If total consideration amount exceeds the offer amount...
if (considerationItem.amount > execution.item.amount) {
// Retrieve the first consideration component from the fulfillment.
FulfillmentComponent memory targetComponent = (
considerationComponents[0]
);
// Skip underflow check as the conditional being true implies that
// considerationItem.amount > execution.item.amount.
unchecked {
// Add excess consideration item amount to original order array.
advancedOrders[targetComponent.orderIndex]
.parameters
.consideration[targetComponent.itemIndex]
.startAmount = (considerationItem.amount -
execution.item.amount);
}
// Reduce total consideration amount to equal the offer amount.
considerationItem.amount = execution.item.amount;
} else {
// Retrieve the first offer component from the fulfillment.
FulfillmentComponent memory targetComponent = offerComponents[0];
// Skip underflow check as the conditional being false implies that
// execution.item.amount >= considerationItem.amount.
unchecked {
// Add excess offer item amount to the original array of orders.
advancedOrders[targetComponent.orderIndex]
.parameters
.offer[targetComponent.itemIndex]
.startAmount = (execution.item.amount -
considerationItem.amount);
}
// Reduce total offer amount to equal the consideration amount.
execution.item.amount = considerationItem.amount;
}
// Reuse consideration recipient.
execution.item.recipient = considerationItem.recipient;
// Return the final execution that will be triggered for relevant items.
return execution; // Execution(considerationItem, offerer, conduitKey);
}
/**
* @dev Internal view function to aggregate offer or consideration items
* from a group of orders into a single execution via a supplied array
* of fulfillment components. Items that are not available to aggregate
* will not be included in the aggregated execution.
*
* @param advancedOrders The orders to aggregate.
* @param side The side (i.e. offer or consideration).
* @param fulfillmentComponents An array designating item components to
* aggregate if part of an available order.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token
* approvals from. The zero hash signifies that
* no conduit should be used, with approvals
* set directly on this contract.
* @param recipient The intended recipient for all received
* items.
*
* @return execution The transfer performed as a result of the fulfillment.
*/
function _aggregateAvailable(
AdvancedOrder[] memory advancedOrders,
Side side,
FulfillmentComponent[] memory fulfillmentComponents,
bytes32 fulfillerConduitKey,
address recipient
) internal view returns (Execution memory execution) {
// Skip overflow / underflow checks; conditions checked or unreachable.
unchecked {
// Retrieve fulfillment components array length and place on stack.
// Ensure at least one fulfillment component has been supplied.
if (fulfillmentComponents.length == 0) {
revert MissingFulfillmentComponentOnAggregation(side);
}
// If the fulfillment components are offer components...
if (side == Side.OFFER) {
// Set the supplied recipient on the execution item.
execution.item.recipient = payable(recipient);
// Return execution for aggregated items provided by offerer.
_aggregateValidFulfillmentOfferItems(
advancedOrders,
fulfillmentComponents,
execution
);
} else {
// Otherwise, fulfillment components are consideration
// components. Return execution for aggregated items provided by
// the fulfiller.
_aggregateValidFulfillmentConsiderationItems(
advancedOrders,
fulfillmentComponents,
execution
);
// Set the caller as the offerer on the execution.
execution.offerer = msg.sender;
// Set fulfiller conduit key as the conduit key on execution.
execution.conduitKey = fulfillerConduitKey;
}
// Set the offerer and recipient to null address if execution
// amount is zero. This will cause the execution item to be skipped.
if (execution.item.amount == 0) {
execution.offerer = address(0);
execution.item.recipient = payable(0);
}
}
}
/**
* @dev Internal pure function to aggregate a group of offer items using
* supplied directives on which component items are candidates for
* aggregation, skipping items on orders that are not available.
*
* @param advancedOrders The orders to aggregate offer items from.
* @param offerComponents An array of FulfillmentComponent structs
* indicating the order index and item index of each
* candidate offer item for aggregation.
* @param execution The execution to apply the aggregation to.
*/
function _aggregateValidFulfillmentOfferItems(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] memory offerComponents,
Execution memory execution
) internal pure {
assembly {
// Declare function for reverts on invalid fulfillment data.
function throwInvalidFulfillmentComponentData() {
// Store the InvalidFulfillmentComponentData error signature.
mstore(0, InvalidFulfillmentComponentData_error_signature)
// Return, supplying InvalidFulfillmentComponentData signature.
revert(0, InvalidFulfillmentComponentData_error_len)
}
// Declare function for reverts due to arithmetic overflows.
function throwOverflow() {
// Store the Panic error signature.
mstore(0, Panic_error_signature)
// Store the arithmetic (0x11) panic code as initial argument.
mstore(Panic_error_offset, Panic_arithmetic)
// Return, supplying Panic signature and arithmetic code.
revert(0, Panic_error_length)
}
// Get position in offerComponents head.
let fulfillmentHeadPtr := add(offerComponents, OneWord)
// Retrieve the order index using the fulfillment pointer.
let orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure that the order index is not out of range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Read advancedOrders[orderIndex] pointer from its array head.
let orderPtr := mload(
// Calculate head position of advancedOrders[orderIndex].
add(add(advancedOrders, OneWord), mul(orderIndex, OneWord))
)
// Read the pointer to OrderParameters from the AdvancedOrder.
let paramsPtr := mload(orderPtr)
// Load the offer array pointer.
let offerArrPtr := mload(
add(paramsPtr, OrderParameters_offer_head_offset)
)
// Retrieve item index using an offset of the fulfillment pointer.
let itemIndex := mload(
add(mload(fulfillmentHeadPtr), Fulfillment_itemIndex_offset)
)
// Only continue if the fulfillment is not invalid.
if iszero(lt(itemIndex, mload(offerArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using the item index.
let offerItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(offerArrPtr, OneWord),
// Calculate offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Declare a variable for the final aggregated item amount.
let amount := 0
// Create variable to track errors encountered with amount.
let errorBuffer := 0
// Only add offer amount to execution amount on a nonzero numerator.
if mload(add(orderPtr, AdvancedOrder_numerator_offset)) {
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(offerItemPtr, Common_amount_offset)
// Set the amount.
amount := mload(amountPtr)
// Zero out amount on item to indicate it is credited.
mstore(amountPtr, 0)
// Buffer indicating whether issues were found.
errorBuffer := iszero(amount)
}
// Retrieve the received item pointer.
let receivedItemPtr := mload(execution)
// Set the item type on the received item.
mstore(receivedItemPtr, mload(offerItemPtr))
// Set the token on the received item.
mstore(
add(receivedItemPtr, Common_token_offset),
mload(add(offerItemPtr, Common_token_offset))
)
// Set the identifier on the received item.
mstore(
add(receivedItemPtr, Common_identifier_offset),
mload(add(offerItemPtr, Common_identifier_offset))
)
// Set the offerer on returned execution using order pointer.
mstore(add(execution, Execution_offerer_offset), mload(paramsPtr))
// Set conduitKey on returned execution via offset of order pointer.
mstore(
add(execution, Execution_conduit_offset),
mload(add(paramsPtr, OrderParameters_conduit_offset))
)
// Calculate the hash of (itemType, token, identifier).
let dataHash := keccak256(
receivedItemPtr,
ReceivedItem_CommonParams_size
)
// Get position one word past last element in head of array.
let endPtr := add(
offerComponents,
mul(mload(offerComponents), OneWord)
)
// Iterate over remaining offer components.
// prettier-ignore
for {} lt(fulfillmentHeadPtr, endPtr) {} {
// Increment the pointer to the fulfillment head by one word.
fulfillmentHeadPtr := add(fulfillmentHeadPtr, OneWord)
// Get the order index using the fulfillment pointer.
orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure the order index is in range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Get pointer to AdvancedOrder element.
orderPtr := mload(
add(
add(advancedOrders, OneWord),
mul(orderIndex, OneWord)
)
)
// Only continue if numerator is not zero.
if iszero(mload(
add(orderPtr, AdvancedOrder_numerator_offset)
)) {
continue
}
// Read the pointer to OrderParameters from the AdvancedOrder.
paramsPtr := mload(orderPtr)
// Load offer array pointer.
offerArrPtr := mload(
add(
paramsPtr,
OrderParameters_offer_head_offset
)
)
// Get the item index using the fulfillment pointer.
itemIndex := mload(add(mload(fulfillmentHeadPtr), OneWord))
// Throw if itemIndex is out of the range of array.
if iszero(
lt(itemIndex, mload(offerArrPtr))
) {
throwInvalidFulfillmentComponentData()
}
// Retrieve offer item pointer using index.
offerItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(offerArrPtr, OneWord),
// Use offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Retrieve amount pointer using offer item pointer.
let amountPtr := add(
offerItemPtr,
Common_amount_offset
)
// Add offer amount to execution amount.
let newAmount := add(amount, mload(amountPtr))
// Update error buffer: 1 = zero amount, 2 = overflow, 3 = both.
errorBuffer := or(
errorBuffer,
or(
shl(1, lt(newAmount, amount)),
iszero(mload(amountPtr))
)
)
// Update the amount to the new, summed amount.
amount := newAmount
// Zero out amount on original item to indicate it is credited.
mstore(amountPtr, 0)
// Ensure the indicated item matches original item.
if iszero(
and(
and(
// The offerer must match on both items.
eq(
mload(paramsPtr),
mload(
add(execution, Execution_offerer_offset)
)
),
// The conduit key must match on both items.
eq(
mload(
add(
paramsPtr,
OrderParameters_conduit_offset
)
),
mload(
add(
execution,
Execution_conduit_offset
)
)
)
),
// The itemType, token, and identifier must match.
eq(
dataHash,
keccak256(
offerItemPtr,
ReceivedItem_CommonParams_size
)
)
)
) {
// Throw if any of the requirements are not met.
throwInvalidFulfillmentComponentData()
}
}
// Write final amount to execution.
mstore(add(mload(execution), Common_amount_offset), amount)
// Determine whether the error buffer contains a nonzero error code.
if errorBuffer {
// If errorBuffer is 1, an item had an amount of zero.
if eq(errorBuffer, 1) {
// Store the MissingItemAmount error signature.
mstore(0, MissingItemAmount_error_signature)
// Return, supplying MissingItemAmount signature.
revert(0, MissingItemAmount_error_len)
}
// If errorBuffer is not 1 or 0, the sum overflowed.
// Panic!
throwOverflow()
}
}
}
/**
* @dev Internal pure function to aggregate a group of consideration items
* using supplied directives on which component items are candidates
* for aggregation, skipping items on orders that are not available.
*
* @param advancedOrders The orders to aggregate consideration
* items from.
* @param considerationComponents An array of FulfillmentComponent structs
* indicating the order index and item index
* of each candidate consideration item for
* aggregation.
* @param execution The execution to apply the aggregation to.
*/
function _aggregateValidFulfillmentConsiderationItems(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] memory considerationComponents,
Execution memory execution
) internal pure {
// Utilize assembly in order to efficiently aggregate the items.
assembly {
// Declare function for reverts on invalid fulfillment data.
function throwInvalidFulfillmentComponentData() {
// Store the InvalidFulfillmentComponentData error signature.
mstore(0, InvalidFulfillmentComponentData_error_signature)
// Return, supplying InvalidFulfillmentComponentData signature.
revert(0, InvalidFulfillmentComponentData_error_len)
}
// Declare function for reverts due to arithmetic overflows.
function throwOverflow() {
// Store the Panic error signature.
mstore(0, Panic_error_signature)
// Store the arithmetic (0x11) panic code as initial argument.
mstore(Panic_error_offset, Panic_arithmetic)
// Return, supplying Panic signature and arithmetic code.
revert(0, Panic_error_length)
}
// Get position in considerationComponents head.
let fulfillmentHeadPtr := add(considerationComponents, OneWord)
// Retrieve the order index using the fulfillment pointer.
let orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure that the order index is not out of range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Read advancedOrders[orderIndex] pointer from its array head.
let orderPtr := mload(
// Calculate head position of advancedOrders[orderIndex].
add(add(advancedOrders, OneWord), mul(orderIndex, OneWord))
)
// Load consideration array pointer.
let considerationArrPtr := mload(
add(
// Read pointer to OrderParameters from the AdvancedOrder.
mload(orderPtr),
OrderParameters_consideration_head_offset
)
)
// Retrieve item index using an offset of the fulfillment pointer.
let itemIndex := mload(
add(mload(fulfillmentHeadPtr), Fulfillment_itemIndex_offset)
)
// Ensure that the order index is not out of range.
if iszero(lt(itemIndex, mload(considerationArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using the item index.
let considerationItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(considerationArrPtr, OneWord),
// Calculate offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Declare a variable for the final aggregated item amount.
let amount := 0
// Create variable to track errors encountered with amount.
let errorBuffer := 0
// Only add consideration amount to execution amount if numerator is
// greater than zero.
if mload(add(orderPtr, AdvancedOrder_numerator_offset)) {
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(considerationItemPtr, Common_amount_offset)
// Set the amount.
amount := mload(amountPtr)
// Set error bit if amount is zero.
errorBuffer := iszero(amount)
// Zero out amount on item to indicate it is credited.
mstore(amountPtr, 0)
}
// Retrieve ReceivedItem pointer from Execution.
let receivedItem := mload(execution)
// Set the item type on the received item.
mstore(receivedItem, mload(considerationItemPtr))
// Set the token on the received item.
mstore(
add(receivedItem, Common_token_offset),
mload(add(considerationItemPtr, Common_token_offset))
)
// Set the identifier on the received item.
mstore(
add(receivedItem, Common_identifier_offset),
mload(add(considerationItemPtr, Common_identifier_offset))
)
// Set the recipient on the received item.
mstore(
add(receivedItem, ReceivedItem_recipient_offset),
mload(
add(
considerationItemPtr,
ConsiderationItem_recipient_offset
)
)
)
// Calculate the hash of (itemType, token, identifier).
let dataHash := keccak256(
receivedItem,
ReceivedItem_CommonParams_size
)
// Get position one word past last element in head of array.
let endPtr := add(
considerationComponents,
mul(mload(considerationComponents), OneWord)
)
// Iterate over remaining offer components.
// prettier-ignore
for {} lt(fulfillmentHeadPtr, endPtr) {} {
// Increment position in considerationComponents head.
fulfillmentHeadPtr := add(fulfillmentHeadPtr, OneWord)
// Get the order index using the fulfillment pointer.
orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure the order index is in range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Get pointer to AdvancedOrder element.
orderPtr := mload(
add(
add(advancedOrders, OneWord),
mul(orderIndex, OneWord)
)
)
// Only continue if numerator is not zero.
if iszero(
mload(add(orderPtr, AdvancedOrder_numerator_offset))
) {
continue
}
// Load consideration array pointer from OrderParameters.
considerationArrPtr := mload(
add(
// Get pointer to OrderParameters from AdvancedOrder.
mload(orderPtr),
OrderParameters_consideration_head_offset
)
)
// Get the item index using the fulfillment pointer.
itemIndex := mload(add(mload(fulfillmentHeadPtr), OneWord))
// Check if itemIndex is within the range of array.
if iszero(lt(itemIndex, mload(considerationArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using index.
considerationItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(considerationArrPtr, OneWord),
// Use offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(
considerationItemPtr,
Common_amount_offset
)
// Add offer amount to execution amount.
let newAmount := add(amount, mload(amountPtr))
// Update error buffer: 1 = zero amount, 2 = overflow, 3 = both.
errorBuffer := or(
errorBuffer,
or(
shl(1, lt(newAmount, amount)),
iszero(mload(amountPtr))
)
)
// Update the amount to the new, summed amount.
amount := newAmount
// Zero out amount on original item to indicate it is credited.
mstore(amountPtr, 0)
// Ensure the indicated item matches original item.
if iszero(
and(
// Item recipients must match.
eq(
mload(
add(
considerationItemPtr,
ConsiderItem_recipient_offset
)
),
mload(
add(
receivedItem,
ReceivedItem_recipient_offset
)
)
),
// The itemType, token, identifier must match.
eq(
dataHash,
keccak256(
considerationItemPtr,
ReceivedItem_CommonParams_size
)
)
)
) {
// Throw if any of the requirements are not met.
throwInvalidFulfillmentComponentData()
}
}
// Write final amount to execution.
mstore(add(receivedItem, Common_amount_offset), amount)
// Determine whether the error buffer contains a nonzero error code.
if errorBuffer {
// If errorBuffer is 1, an item had an amount of zero.
if eq(errorBuffer, 1) {
// Store the MissingItemAmount error signature.
mstore(0, MissingItemAmount_error_signature)
// Return, supplying MissingItemAmount signature.
revert(0, MissingItemAmount_error_len)
}
// If errorBuffer is not 1 or 0, the sum overflowed.
// Panic!
throwOverflow()
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { Side } from "../lib/ConsiderationEnums.sol";
/**
* @title FulfillmentApplicationErrors
* @author 0age
* @notice FulfillmentApplicationErrors contains errors related to fulfillment
* application and aggregation.
*/
interface FulfillmentApplicationErrors {
/**
* @dev Revert with an error when a fulfillment is provided that does not
* declare at least one component as part of a call to fulfill
* available orders.
*/
error MissingFulfillmentComponentOnAggregation(Side side);
/**
* @dev Revert with an error when a fulfillment is provided that does not
* declare at least one offer component and at least one consideration
* component.
*/
error OfferAndConsiderationRequiredOnFulfillment();
/**
* @dev Revert with an error when the initial offer item named by a
* fulfillment component does not match the type, token, identifier,
* or conduit preference of the initial consideration item.
*/
error MismatchedFulfillmentOfferAndConsiderationComponents();
/**
* @dev Revert with an error when an order or item index are out of range
* or a fulfillment component does not match the type, token,
* identifier, or conduit preference of the initial consideration item.
*/
error InvalidFulfillmentComponentData();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ItemType, Side } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OfferItem,
ConsiderationItem,
OrderParameters,
AdvancedOrder,
CriteriaResolver
} from "./ConsiderationStructs.sol";
import "./ConsiderationConstants.sol";
// prettier-ignore
import {
CriteriaResolutionErrors
} from "../interfaces/CriteriaResolutionErrors.sol";
/**
* @title CriteriaResolution
* @author 0age
* @notice CriteriaResolution contains a collection of pure functions related to
* resolving criteria-based items.
*/
contract CriteriaResolution is CriteriaResolutionErrors {
/**
* @dev Internal pure function to apply criteria resolvers containing
* specific token identifiers and associated proofs to order items.
*
* @param advancedOrders The orders to apply criteria resolvers to.
* @param criteriaResolvers An array where each element contains a
* reference to a specific order as well as that
* order's offer or consideration, a token
* identifier, and a proof that the supplied token
* identifier is contained in the order's merkle
* root. Note that a root of zero indicates that
* any transferable token identifier is valid and
* that no proof needs to be supplied.
*/
function _applyCriteriaResolvers(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers
) internal pure {
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Retrieve length of criteria resolvers array and place on stack.
uint256 totalCriteriaResolvers = criteriaResolvers.length;
// Retrieve length of orders array and place on stack.
uint256 totalAdvancedOrders = advancedOrders.length;
// Iterate over each criteria resolver.
for (uint256 i = 0; i < totalCriteriaResolvers; ++i) {
// Retrieve the criteria resolver.
CriteriaResolver memory criteriaResolver = (
criteriaResolvers[i]
);
// Read the order index from memory and place it on the stack.
uint256 orderIndex = criteriaResolver.orderIndex;
// Ensure that the order index is in range.
if (orderIndex >= totalAdvancedOrders) {
revert OrderCriteriaResolverOutOfRange();
}
// Skip criteria resolution for order if not fulfilled.
if (advancedOrders[orderIndex].numerator == 0) {
continue;
}
// Retrieve the parameters for the order.
OrderParameters memory orderParameters = (
advancedOrders[orderIndex].parameters
);
// Read component index from memory and place it on the stack.
uint256 componentIndex = criteriaResolver.index;
// Declare values for item's type and criteria.
ItemType itemType;
uint256 identifierOrCriteria;
// If the criteria resolver refers to an offer item...
if (criteriaResolver.side == Side.OFFER) {
// Retrieve the offer.
OfferItem[] memory offer = orderParameters.offer;
// Ensure that the component index is in range.
if (componentIndex >= offer.length) {
revert OfferCriteriaResolverOutOfRange();
}
// Retrieve relevant item using the component index.
OfferItem memory offerItem = offer[componentIndex];
// Read item type and criteria from memory & place on stack.
itemType = offerItem.itemType;
identifierOrCriteria = offerItem.identifierOrCriteria;
// Optimistically update item type to remove criteria usage.
// Use assembly to operate on ItemType enum as a number.
ItemType newItemType;
assembly {
// Item type 4 becomes 2 and item type 5 becomes 3.
newItemType := sub(3, eq(itemType, 4))
}
offerItem.itemType = newItemType;
// Optimistically update identifier w/ supplied identifier.
offerItem.identifierOrCriteria = criteriaResolver
.identifier;
} else {
// Otherwise, the resolver refers to a consideration item.
ConsiderationItem[] memory consideration = (
orderParameters.consideration
);
// Ensure that the component index is in range.
if (componentIndex >= consideration.length) {
revert ConsiderationCriteriaResolverOutOfRange();
}
// Retrieve relevant item using order and component index.
ConsiderationItem memory considerationItem = (
consideration[componentIndex]
);
// Read item type and criteria from memory & place on stack.
itemType = considerationItem.itemType;
identifierOrCriteria = (
considerationItem.identifierOrCriteria
);
// Optimistically update item type to remove criteria usage.
// Use assembly to operate on ItemType enum as a number.
ItemType newItemType;
assembly {
// Item type 4 becomes 2 and item type 5 becomes 3.
newItemType := sub(3, eq(itemType, 4))
}
considerationItem.itemType = newItemType;
// Optimistically update identifier w/ supplied identifier.
considerationItem.identifierOrCriteria = (
criteriaResolver.identifier
);
}
// Ensure the specified item type indicates criteria usage.
if (!_isItemWithCriteria(itemType)) {
revert CriteriaNotEnabledForItem();
}
// If criteria is not 0 (i.e. a collection-wide offer)...
if (identifierOrCriteria != uint256(0)) {
// Verify identifier inclusion in criteria root using proof.
_verifyProof(
criteriaResolver.identifier,
identifierOrCriteria,
criteriaResolver.criteriaProof
);
}
}
// Iterate over each advanced order.
for (uint256 i = 0; i < totalAdvancedOrders; ++i) {
// Retrieve the advanced order.
AdvancedOrder memory advancedOrder = advancedOrders[i];
// Skip criteria resolution for order if not fulfilled.
if (advancedOrder.numerator == 0) {
continue;
}
// Retrieve the parameters for the order.
OrderParameters memory orderParameters = (
advancedOrder.parameters
);
// Read consideration length from memory and place on stack.
uint256 totalItems = orderParameters.consideration.length;
// Iterate over each consideration item on the order.
for (uint256 j = 0; j < totalItems; ++j) {
// Ensure item type no longer indicates criteria usage.
if (
_isItemWithCriteria(
orderParameters.consideration[j].itemType
)
) {
revert UnresolvedConsiderationCriteria();
}
}
// Read offer length from memory and place on stack.
totalItems = orderParameters.offer.length;
// Iterate over each offer item on the order.
for (uint256 j = 0; j < totalItems; ++j) {
// Ensure item type no longer indicates criteria usage.
if (
_isItemWithCriteria(orderParameters.offer[j].itemType)
) {
revert UnresolvedOfferCriteria();
}
}
}
}
}
/**
* @dev Internal pure function to check whether a given item type represents
* a criteria-based ERC721 or ERC1155 item (e.g. an item that can be
* resolved to one of a number of different identifiers at the time of
* order fulfillment).
*
* @param itemType The item type in question.
*
* @return withCriteria A boolean indicating that the item type in question
* represents a criteria-based item.
*/
function _isItemWithCriteria(ItemType itemType)
internal
pure
returns (bool withCriteria)
{
// ERC721WithCriteria is ItemType 4. ERC1155WithCriteria is ItemType 5.
assembly {
withCriteria := gt(itemType, 3)
}
}
/**
* @dev Internal pure function to ensure that a given element is contained
* in a merkle root via a supplied proof.
*
* @param leaf The element for which to prove inclusion.
* @param root The merkle root that inclusion will be proved against.
* @param proof The merkle proof.
*/
function _verifyProof(
uint256 leaf,
uint256 root,
bytes32[] memory proof
) internal pure {
// Declare a variable that will be used to determine proof validity.
bool isValid;
// Utilize assembly to efficiently verify the proof against the root.
assembly {
// Store the leaf at the beginning of scratch space.
mstore(0, leaf)
// Derive the hash of the leaf to use as the initial proof element.
let computedHash := keccak256(0, OneWord)
// Based on: https://github.com/Rari-Capital/solmate/blob/v7/src/utils/MerkleProof.sol
// Get memory start location of the first element in proof array.
let data := add(proof, OneWord)
// Iterate over each proof element to compute the root hash.
for {
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(data, shl(5, mload(proof)))
} lt(data, end) {
// Increment by one word at a time.
data := add(data, OneWord)
} {
// Get the proof element.
let loadedData := mload(data)
// Sort proof elements and place them in scratch space.
// Slot of `computedHash` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(5, gt(computedHash, loadedData))
// Store elements to hash contiguously in scratch space. Scratch
// space is 64 bytes (0x00 - 0x3f) & both elements are 32 bytes.
mstore(scratch, computedHash)
mstore(xor(scratch, OneWord), loadedData)
// Derive the updated hash.
computedHash := keccak256(0, TwoWords)
}
// Compare the final hash to the supplied root.
isValid := eq(computedHash, root)
}
// Revert if computed hash does not equal supplied root.
if (!isValid) {
revert InvalidProof();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title CriteriaResolutionErrors
* @author 0age
* @notice CriteriaResolutionErrors contains all errors related to criteria
* resolution.
*/
interface CriteriaResolutionErrors {
/**
* @dev Revert with an error when providing a criteria resolver that refers
* to an order that has not been supplied.
*/
error OrderCriteriaResolverOutOfRange();
/**
* @dev Revert with an error if an offer item still has unresolved criteria
* after applying all criteria resolvers.
*/
error UnresolvedOfferCriteria();
/**
* @dev Revert with an error if a consideration item still has unresolved
* criteria after applying all criteria resolvers.
*/
error UnresolvedConsiderationCriteria();
/**
* @dev Revert with an error when providing a criteria resolver that refers
* to an order with an offer item that has not been supplied.
*/
error OfferCriteriaResolverOutOfRange();
/**
* @dev Revert with an error when providing a criteria resolver that refers
* to an order with a consideration item that has not been supplied.
*/
error ConsiderationCriteriaResolverOutOfRange();
/**
* @dev Revert with an error when providing a criteria resolver that refers
* to an order with an item that does not expect a criteria to be
* resolved.
*/
error CriteriaNotEnabledForItem();
/**
* @dev Revert with an error when providing a criteria resolver that
* contains an invalid proof with respect to the given item and
* chosen identifier.
*/
error InvalidProof();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { ZoneInterface } from "../interfaces/ZoneInterface.sol";
// prettier-ignore
import {
AdvancedOrder,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
contract TestZone is ZoneInterface {
function isValidOrder(
bytes32 orderHash,
address caller,
address offerer,
bytes32 zoneHash
) external pure override returns (bytes4 validOrderMagicValue) {
orderHash;
caller;
offerer;
if (zoneHash == bytes32(uint256(1))) {
revert("Revert on zone hash 1");
} else if (zoneHash == bytes32(uint256(2))) {
assembly {
revert(0, 0)
}
}
validOrderMagicValue = zoneHash != bytes32(uint256(3))
? ZoneInterface.isValidOrder.selector
: bytes4(0xffffffff);
}
function isValidOrderIncludingExtraData(
bytes32 orderHash,
address caller,
AdvancedOrder calldata order,
bytes32[] calldata priorOrderHashes,
CriteriaResolver[] calldata criteriaResolvers
) external pure override returns (bytes4 validOrderMagicValue) {
orderHash;
caller;
order;
priorOrderHashes;
criteriaResolvers;
if (order.extraData.length == 4) {
revert("Revert on extraData length 4");
} else if (order.extraData.length == 5) {
assembly {
revert(0, 0)
}
}
validOrderMagicValue = order.parameters.zoneHash != bytes32(uint256(3))
? ZoneInterface.isValidOrder.selector
: bytes4(0xffffffff);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
AdvancedOrder,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
interface ZoneInterface {
// Called by Consideration whenever extraData is not provided by the caller.
function isValidOrder(
bytes32 orderHash,
address caller,
address offerer,
bytes32 zoneHash
) external view returns (bytes4 validOrderMagicValue);
// Called by Consideration whenever any extraData is provided by the caller.
function isValidOrderIncludingExtraData(
bytes32 orderHash,
address caller,
AdvancedOrder calldata order,
bytes32[] calldata priorOrderHashes,
CriteriaResolver[] calldata criteriaResolvers
) external view returns (bytes4 validOrderMagicValue);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ZoneInterface } from "../interfaces/ZoneInterface.sol";
import { OrderType } from "./ConsiderationEnums.sol";
// prettier-ignore
import { AdvancedOrder, CriteriaResolver } from "./ConsiderationStructs.sol";
import "./ConsiderationConstants.sol";
// prettier-ignore
import {
ZoneInteractionErrors
} from "../interfaces/ZoneInteractionErrors.sol";
import { LowLevelHelpers } from "./LowLevelHelpers.sol";
/**
* @title ZoneInteraction
* @author 0age
* @notice ZoneInteraction contains logic related to interacting with zones.
*/
contract ZoneInteraction is ZoneInteractionErrors, LowLevelHelpers {
/**
* @dev Internal view function to determine if an order has a restricted
* order type and, if so, to ensure that either the offerer or the zone
* are the fulfiller or that a staticcall to `isValidOrder` on the zone
* returns a magic value indicating that the order is currently valid.
*
* @param orderHash The hash of the order.
* @param zoneHash The hash to provide upon calling the zone.
* @param orderType The type of the order.
* @param offerer The offerer in question.
* @param zone The zone in question.
*/
function _assertRestrictedBasicOrderValidity(
bytes32 orderHash,
bytes32 zoneHash,
OrderType orderType,
address offerer,
address zone
) internal view {
// Order type 2-3 require zone or offerer be caller or zone to approve.
if (
uint256(orderType) > 1 &&
msg.sender != zone &&
msg.sender != offerer
) {
// Perform minimal staticcall to the zone.
_callIsValidOrder(zone, orderHash, offerer, zoneHash);
}
}
function _callIsValidOrder(
address zone,
bytes32 orderHash,
address offerer,
bytes32 zoneHash
) internal view {
// Perform minimal staticcall to the zone.
bool success = _staticcall(
zone,
abi.encodeWithSelector(
ZoneInterface.isValidOrder.selector,
orderHash,
msg.sender,
offerer,
zoneHash
)
);
// Ensure call was successful and returned the correct magic value.
_assertIsValidOrderStaticcallSuccess(success, orderHash);
}
/**
* @dev Internal view function to determine whether an order is a restricted
* order and, if so, to ensure that it was either submitted by the
* offerer or the zone for the order, or that the zone returns the
* expected magic value upon performing a staticcall to `isValidOrder`
* or `isValidOrderIncludingExtraData` depending on whether the order
* fulfillment specifies extra data or criteria resolvers.
*
* @param advancedOrder The advanced order in question.
* @param criteriaResolvers An array where each element contains a reference
* to a specific offer or consideration, a token
* identifier, and a proof that the supplied token
* identifier is contained in the order's merkle
* root. Note that a criteria of zero indicates
* that any (transferable) token identifier is
* valid and that no proof needs to be supplied.
* @param priorOrderHashes The order hashes of each order supplied prior to
* the current order as part of a "match" variety
* of order fulfillment (e.g. this array will be
* empty for single or "fulfill available").
* @param orderHash The hash of the order.
* @param zoneHash The hash to provide upon calling the zone.
* @param orderType The type of the order.
* @param offerer The offerer in question.
* @param zone The zone in question.
*/
function _assertRestrictedAdvancedOrderValidity(
AdvancedOrder memory advancedOrder,
CriteriaResolver[] memory criteriaResolvers,
bytes32[] memory priorOrderHashes,
bytes32 orderHash,
bytes32 zoneHash,
OrderType orderType,
address offerer,
address zone
) internal view {
// Order type 2-3 require zone or offerer be caller or zone to approve.
if (
uint256(orderType) > 1 &&
msg.sender != zone &&
msg.sender != offerer
) {
// If no extraData or criteria resolvers are supplied...
if (
advancedOrder.extraData.length == 0 &&
criteriaResolvers.length == 0
) {
// Perform minimal staticcall to the zone.
_callIsValidOrder(zone, orderHash, offerer, zoneHash);
} else {
// Otherwise, extra data or criteria resolvers were supplied; in
// that event, perform a more verbose staticcall to the zone.
bool success = _staticcall(
zone,
abi.encodeWithSelector(
ZoneInterface.isValidOrderIncludingExtraData.selector,
orderHash,
msg.sender,
advancedOrder,
priorOrderHashes,
criteriaResolvers
)
);
// Ensure call was successful and returned correct magic value.
_assertIsValidOrderStaticcallSuccess(success, orderHash);
}
}
}
/**
* @dev Internal view function to ensure that a staticcall to `isValidOrder`
* or `isValidOrderIncludingExtraData` as part of validating a
* restricted order that was not submitted by the named offerer or zone
* was successful and returned the required magic value.
*
* @param success A boolean indicating the status of the staticcall.
* @param orderHash The order hash of the order in question.
*/
function _assertIsValidOrderStaticcallSuccess(
bool success,
bytes32 orderHash
) internal view {
// If the call failed...
if (!success) {
// Revert and pass reason along if one was returned.
_revertWithReasonIfOneIsReturned();
// Otherwise, revert with a generic error message.
revert InvalidRestrictedOrder(orderHash);
}
// Ensure result was extracted and matches isValidOrder magic value.
if (_doesNotMatchMagic(ZoneInterface.isValidOrder.selector)) {
revert InvalidRestrictedOrder(orderHash);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title ZoneInteractionErrors
* @author 0age
* @notice ZoneInteractionErrors contains errors related to zone interaction.
*/
interface ZoneInteractionErrors {
/**
* @dev Revert with an error when attempting to fill an order that specifies
* a restricted submitter as its order type when not submitted by
* either the offerer or the order's zone or approved as valid by the
* zone in question via a staticcall to `isValidOrder`.
*
* @param orderHash The order hash for the invalid restricted order.
*/
error InvalidRestrictedOrder(bytes32 orderHash);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
// prettier-ignore
import {
OrderType,
ItemType,
BasicOrderRouteType
} from "./ConsiderationEnums.sol";
// prettier-ignore
import {
AdditionalRecipient,
BasicOrderParameters,
OfferItem,
ConsiderationItem,
SpentItem,
ReceivedItem
} from "./ConsiderationStructs.sol";
import { OrderValidator } from "./OrderValidator.sol";
import "./ConsiderationConstants.sol";
/**
* @title BasicOrderFulfiller
* @author 0age
* @notice BasicOrderFulfiller contains functionality for fulfilling "basic"
* orders with minimal overhead. See documentation for details on what
* qualifies as a basic order.
*/
contract BasicOrderFulfiller is OrderValidator {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) OrderValidator(conduitController) {}
/**
* @dev Internal function to fulfill an order offering an ERC20, ERC721, or
* ERC1155 item by supplying Ether (or other native tokens), ERC20
* tokens, an ERC721 item, or an ERC1155 item as consideration. Six
* permutations are supported: Native token to ERC721, Native token to
* ERC1155, ERC20 to ERC721, ERC20 to ERC1155, ERC721 to ERC20, and
* ERC1155 to ERC20 (with native tokens supplied as msg.value). For an
* order to be eligible for fulfillment via this method, it must
* contain a single offer item (though that item may have a greater
* amount if the item is not an ERC721). An arbitrary number of
* "additional recipients" may also be supplied which will each receive
* native tokens or ERC20 items from the fulfiller as consideration.
* Refer to the documentation for a more comprehensive summary of how
* to utilize this method and what orders are compatible with it.
*
* @param parameters Additional information on the fulfilled order. Note
* that the offerer and the fulfiller must first approve
* this contract (or their chosen conduit if indicated)
* before any tokens can be transferred. Also note that
* contract recipients of ERC1155 consideration items must
* implement `onERC1155Received` in order to receive those
* items.
*
* @return A boolean indicating whether the order has been fulfilled.
*/
function _validateAndFulfillBasicOrder(
BasicOrderParameters calldata parameters
) internal returns (bool) {
// Declare enums for order type & route to extract from basicOrderType.
BasicOrderRouteType route;
OrderType orderType;
// Declare additional recipient item type to derive from the route type.
ItemType additionalRecipientsItemType;
// Utilize assembly to extract the order type and the basic order route.
assembly {
// Read basicOrderType from calldata.
let basicOrderType := calldataload(BasicOrder_basicOrderType_cdPtr)
// Mask all but 2 least-significant bits to derive the order type.
orderType := and(basicOrderType, 3)
// Divide basicOrderType by four to derive the route.
route := shr(2, basicOrderType)
// If route > 1 additionalRecipient items are ERC20 (1) else Eth (0)
additionalRecipientsItemType := gt(route, 1)
}
{
// Declare temporary variable for enforcing payable status.
bool correctPayableStatus;
// Utilize assembly to compare the route to the callvalue.
assembly {
// route 0 and 1 are payable, otherwise route is not payable.
correctPayableStatus := eq(
additionalRecipientsItemType,
iszero(callvalue())
)
}
// Revert if msg.value has not been supplied as part of payable
// routes or has been supplied as part of non-payable routes.
if (!correctPayableStatus) {
revert InvalidMsgValue(msg.value);
}
}
// Declare more arguments that will be derived from route and calldata.
address additionalRecipientsToken;
ItemType offeredItemType;
bool offerTypeIsAdditionalRecipientsType;
// Declare scope for received item type to manage stack pressure.
{
ItemType receivedItemType;
// Utilize assembly to retrieve function arguments and cast types.
assembly {
// Check if offered item type == additional recipient item type.
offerTypeIsAdditionalRecipientsType := gt(route, 3)
// If route > 3 additionalRecipientsToken is at 0xc4 else 0x24.
additionalRecipientsToken := calldataload(
add(
BasicOrder_considerationToken_cdPtr,
mul(
offerTypeIsAdditionalRecipientsType,
BasicOrder_common_params_size
)
)
)
// If route > 2, receivedItemType is route - 2. If route is 2,
// the receivedItemType is ERC20 (1). Otherwise, it is Eth (0).
receivedItemType := add(
mul(sub(route, 2), gt(route, 2)),
eq(route, 2)
)
// If route > 3, offeredItemType is ERC20 (1). Route is 2 or 3,
// offeredItemType = route. Route is 0 or 1, it is route + 2.
offeredItemType := sub(
add(route, mul(iszero(additionalRecipientsItemType), 2)),
mul(
offerTypeIsAdditionalRecipientsType,
add(receivedItemType, 1)
)
)
}
// Derive & validate order using parameters and update order status.
_prepareBasicFulfillmentFromCalldata(
parameters,
orderType,
receivedItemType,
additionalRecipientsItemType,
additionalRecipientsToken,
offeredItemType
);
}
// Declare conduitKey argument used by transfer functions.
bytes32 conduitKey;
// Utilize assembly to derive conduit (if relevant) based on route.
assembly {
// use offerer conduit for routes 0-3, fulfiller conduit otherwise.
conduitKey := calldataload(
add(
BasicOrder_offererConduit_cdPtr,
mul(offerTypeIsAdditionalRecipientsType, OneWord)
)
)
}
// Transfer tokens based on the route.
if (additionalRecipientsItemType == ItemType.NATIVE) {
// Ensure neither the token nor the identifier parameters are set.
if (
(uint160(parameters.considerationToken) |
parameters.considerationIdentifier) != 0
) {
revert UnusedItemParameters();
}
// Transfer the ERC721 or ERC1155 item, bypassing the accumulator.
_transferIndividual721Or1155Item(
offeredItemType,
parameters.offerToken,
parameters.offerer,
msg.sender,
parameters.offerIdentifier,
parameters.offerAmount,
conduitKey
);
// Transfer native to recipients, return excess to caller & wrap up.
_transferEthAndFinalize(
parameters.considerationAmount,
parameters.offerer,
parameters.additionalRecipients
);
} else {
// Initialize an accumulator array. From this point forward, no new
// memory regions can be safely allocated until the accumulator is
// no longer being utilized, as the accumulator operates in an
// open-ended fashion from this memory pointer; existing memory may
// still be accessed and modified, however.
bytes memory accumulator = new bytes(AccumulatorDisarmed);
// Choose transfer method for ERC721 or ERC1155 item based on route.
if (route == BasicOrderRouteType.ERC20_TO_ERC721) {
// Transfer ERC721 to caller using offerer's conduit preference.
_transferERC721(
parameters.offerToken,
parameters.offerer,
msg.sender,
parameters.offerIdentifier,
parameters.offerAmount,
conduitKey,
accumulator
);
} else if (route == BasicOrderRouteType.ERC20_TO_ERC1155) {
// Transfer ERC1155 to caller with offerer's conduit preference.
_transferERC1155(
parameters.offerToken,
parameters.offerer,
msg.sender,
parameters.offerIdentifier,
parameters.offerAmount,
conduitKey,
accumulator
);
} else if (route == BasicOrderRouteType.ERC721_TO_ERC20) {
// Transfer ERC721 to offerer using caller's conduit preference.
_transferERC721(
parameters.considerationToken,
msg.sender,
parameters.offerer,
parameters.considerationIdentifier,
parameters.considerationAmount,
conduitKey,
accumulator
);
} else {
// route == BasicOrderRouteType.ERC1155_TO_ERC20
// Transfer ERC1155 to offerer with caller's conduit preference.
_transferERC1155(
parameters.considerationToken,
msg.sender,
parameters.offerer,
parameters.considerationIdentifier,
parameters.considerationAmount,
conduitKey,
accumulator
);
}
// Transfer ERC20 tokens to all recipients and wrap up.
_transferERC20AndFinalize(
parameters.offerer,
parameters,
offerTypeIsAdditionalRecipientsType,
accumulator
);
// Trigger any remaining accumulated transfers via call to conduit.
_triggerIfArmed(accumulator);
}
// Clear the reentrancy guard.
_clearReentrancyGuard();
return true;
}
/**
* @dev Internal function to prepare fulfillment of a basic order with
* manual calldata and memory access. This calculates the order hash,
* emits an OrderFulfilled event, and asserts basic order validity.
* Note that calldata offsets must be validated as this function
* accesses constant calldata pointers for dynamic types that match
* default ABI encoding, but valid ABI encoding can use arbitrary
* offsets. Checking that the offsets were produced by default encoding
* will ensure that other functions using Solidity's calldata accessors
* (which calculate pointers from the stored offsets) are reading the
* same data as the order hash is derived from. Also note that This
* function accesses memory directly. It does not clear the expanded
* memory regions used, nor does it update the free memory pointer, so
* other direct memory access must not assume that unused memory is
* empty.
*
* @param parameters The parameters of the basic order.
* @param orderType The order type.
* @param receivedItemType The item type of the initial
* consideration item on the order.
* @param additionalRecipientsItemType The item type of any additional
* consideration item on the order.
* @param additionalRecipientsToken The ERC20 token contract address (if
* applicable) for any additional
* consideration item on the order.
* @param offeredItemType The item type of the offered item on
* the order.
*/
function _prepareBasicFulfillmentFromCalldata(
BasicOrderParameters calldata parameters,
OrderType orderType,
ItemType receivedItemType,
ItemType additionalRecipientsItemType,
address additionalRecipientsToken,
ItemType offeredItemType
) internal {
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard();
// Ensure current timestamp falls between order start time and end time.
_verifyTime(parameters.startTime, parameters.endTime, true);
// Verify that calldata offsets for all dynamic types were produced by
// default encoding. This ensures that the constants we use for calldata
// pointers to dynamic types are the same as those calculated by
// Solidity using their offsets. Also verify that the basic order type
// is within range.
_assertValidBasicOrderParameters();
// Ensure supplied consideration array length is not less than original.
_assertConsiderationLengthIsNotLessThanOriginalConsiderationLength(
parameters.additionalRecipients.length,
parameters.totalOriginalAdditionalRecipients
);
// Declare stack element for the order hash.
bytes32 orderHash;
{
/**
* First, handle consideration items. Memory Layout:
* 0x60: final hash of the array of consideration item hashes
* 0x80-0x160: reused space for EIP712 hashing of each item
* - 0x80: ConsiderationItem EIP-712 typehash (constant)
* - 0xa0: itemType
* - 0xc0: token
* - 0xe0: identifier
* - 0x100: startAmount
* - 0x120: endAmount
* - 0x140: recipient
* 0x160-END_ARR: array of consideration item hashes
* - 0x160: primary consideration item EIP712 hash
* - 0x180-END_ARR: additional recipient item EIP712 hashes
* END_ARR: beginning of data for OrderFulfilled event
* - END_ARR + 0x120: length of ReceivedItem array
* - END_ARR + 0x140: beginning of data for first ReceivedItem
* (Note: END_ARR = 0x180 + RECIPIENTS_LENGTH * 0x20)
*/
// Load consideration item typehash from runtime and place on stack.
bytes32 typeHash = _CONSIDERATION_ITEM_TYPEHASH;
// Utilize assembly to enable reuse of memory regions and use
// constant pointers when possible.
assembly {
/*
* 1. Calculate the EIP712 ConsiderationItem hash for the
* primary consideration item of the basic order.
*/
// Write ConsiderationItem type hash and item type to memory.
mstore(BasicOrder_considerationItem_typeHash_ptr, typeHash)
mstore(
BasicOrder_considerationItem_itemType_ptr,
receivedItemType
)
// Copy calldata region with (token, identifier, amount) from
// BasicOrderParameters to ConsiderationItem. The
// considerationAmount is written to startAmount and endAmount
// as basic orders do not have dynamic amounts.
calldatacopy(
BasicOrder_considerationItem_token_ptr,
BasicOrder_considerationToken_cdPtr,
ThreeWords
)
// Copy calldata region with considerationAmount and offerer
// from BasicOrderParameters to endAmount and recipient in
// ConsiderationItem.
calldatacopy(
BasicOrder_considerationItem_endAmount_ptr,
BasicOrder_considerationAmount_cdPtr,
TwoWords
)
// Calculate EIP712 ConsiderationItem hash and store it in the
// array of EIP712 consideration hashes.
mstore(
BasicOrder_considerationHashesArray_ptr,
keccak256(
BasicOrder_considerationItem_typeHash_ptr,
EIP712_ConsiderationItem_size
)
)
/*
* 2. Write a ReceivedItem struct for the primary consideration
* item to the consideration array in OrderFulfilled.
*/
// Get the length of the additional recipients array.
let totalAdditionalRecipients := calldataload(
BasicOrder_additionalRecipients_length_cdPtr
)
// Calculate pointer to length of OrderFulfilled consideration
// array.
let eventConsiderationArrPtr := add(
OrderFulfilled_consideration_length_baseOffset,
mul(totalAdditionalRecipients, OneWord)
)
// Set the length of the consideration array to the number of
// additional recipients, plus one for the primary consideration
// item.
mstore(
eventConsiderationArrPtr,
add(
calldataload(
BasicOrder_additionalRecipients_length_cdPtr
),
1
)
)
// Overwrite the consideration array pointer so it points to the
// body of the first element
eventConsiderationArrPtr := add(
eventConsiderationArrPtr,
OneWord
)
// Set itemType at start of the ReceivedItem memory region.
mstore(eventConsiderationArrPtr, receivedItemType)
// Copy calldata region (token, identifier, amount & recipient)
// from BasicOrderParameters to ReceivedItem memory.
calldatacopy(
add(eventConsiderationArrPtr, Common_token_offset),
BasicOrder_considerationToken_cdPtr,
FourWords
)
/*
* 3. Calculate EIP712 ConsiderationItem hashes for original
* additional recipients and add a ReceivedItem for each to the
* consideration array in the OrderFulfilled event. The original
* additional recipients are all the considerations signed by
* the offerer aside from the primary consideration of the
* order. Uses memory region from 0x80-0x160 as a buffer for
* calculating EIP712 ConsiderationItem hashes.
*/
// Put pointer to consideration hashes array on the stack.
// This will be updated as each additional recipient is hashed
let
considerationHashesPtr
:= BasicOrder_considerationHashesArray_ptr
// Write item type, token, & identifier for additional recipient
// to memory region for hashing EIP712 ConsiderationItem; these
// values will be reused for each recipient.
mstore(
BasicOrder_considerationItem_itemType_ptr,
additionalRecipientsItemType
)
mstore(
BasicOrder_considerationItem_token_ptr,
additionalRecipientsToken
)
mstore(BasicOrder_considerationItem_identifier_ptr, 0)
// Read length of the additionalRecipients array from calldata
// and iterate.
totalAdditionalRecipients := calldataload(
BasicOrder_totalOriginalAdditionalRecipients_cdPtr
)
let i := 0
// prettier-ignore
for {} lt(i, totalAdditionalRecipients) {
i := add(i, 1)
} {
/*
* Calculate EIP712 ConsiderationItem hash for recipient.
*/
// Retrieve calldata pointer for additional recipient.
let additionalRecipientCdPtr := add(
BasicOrder_additionalRecipients_data_cdPtr,
mul(AdditionalRecipients_size, i)
)
// Copy startAmount from calldata to the ConsiderationItem
// struct.
calldatacopy(
BasicOrder_considerationItem_startAmount_ptr,
additionalRecipientCdPtr,
OneWord
)
// Copy endAmount and recipient from calldata to the
// ConsiderationItem struct.
calldatacopy(
BasicOrder_considerationItem_endAmount_ptr,
additionalRecipientCdPtr,
AdditionalRecipients_size
)
// Add 1 word to the pointer as part of each loop to reduce
// operations needed to get local offset into the array.
considerationHashesPtr := add(
considerationHashesPtr,
OneWord
)
// Calculate EIP712 ConsiderationItem hash and store it in
// the array of consideration hashes.
mstore(
considerationHashesPtr,
keccak256(
BasicOrder_considerationItem_typeHash_ptr,
EIP712_ConsiderationItem_size
)
)
/*
* Write ReceivedItem to OrderFulfilled data.
*/
// At this point, eventConsiderationArrPtr points to the
// beginning of the ReceivedItem struct of the previous
// element in the array. Increase it by the size of the
// struct to arrive at the pointer for the current element.
eventConsiderationArrPtr := add(
eventConsiderationArrPtr,
ReceivedItem_size
)
// Write itemType to the ReceivedItem struct.
mstore(
eventConsiderationArrPtr,
additionalRecipientsItemType
)
// Write token to the next word of the ReceivedItem struct.
mstore(
add(eventConsiderationArrPtr, OneWord),
additionalRecipientsToken
)
// Copy endAmount & recipient words to ReceivedItem struct.
calldatacopy(
add(
eventConsiderationArrPtr,
ReceivedItem_amount_offset
),
additionalRecipientCdPtr,
TwoWords
)
}
/*
* 4. Hash packed array of ConsiderationItem EIP712 hashes:
* `keccak256(abi.encodePacked(receivedItemHashes))`
* Note that it is set at 0x60 — all other memory begins at
* 0x80. 0x60 is the "zero slot" and will be restored at the end
* of the assembly section and before required by the compiler.
*/
mstore(
receivedItemsHash_ptr,
keccak256(
BasicOrder_considerationHashesArray_ptr,
mul(add(totalAdditionalRecipients, 1), OneWord)
)
)
/*
* 5. Add a ReceivedItem for each tip to the consideration array
* in the OrderFulfilled event. The tips are all the
* consideration items that were not signed by the offerer and
* were provided by the fulfiller.
*/
// Overwrite length to length of the additionalRecipients array.
totalAdditionalRecipients := calldataload(
BasicOrder_additionalRecipients_length_cdPtr
)
// prettier-ignore
for {} lt(i, totalAdditionalRecipients) {
i := add(i, 1)
} {
// Retrieve calldata pointer for additional recipient.
let additionalRecipientCdPtr := add(
BasicOrder_additionalRecipients_data_cdPtr,
mul(AdditionalRecipients_size, i)
)
// At this point, eventConsiderationArrPtr points to the
// beginning of the ReceivedItem struct of the previous
// element in the array. Increase it by the size of the
// struct to arrive at the pointer for the current element.
eventConsiderationArrPtr := add(
eventConsiderationArrPtr,
ReceivedItem_size
)
// Write itemType to the ReceivedItem struct.
mstore(
eventConsiderationArrPtr,
additionalRecipientsItemType
)
// Write token to the next word of the ReceivedItem struct.
mstore(
add(eventConsiderationArrPtr, OneWord),
additionalRecipientsToken
)
// Copy endAmount & recipient words to ReceivedItem struct.
calldatacopy(
add(
eventConsiderationArrPtr,
ReceivedItem_amount_offset
),
additionalRecipientCdPtr,
TwoWords
)
}
}
}
{
/**
* Next, handle offered items. Memory Layout:
* EIP712 data for OfferItem
* - 0x80: OfferItem EIP-712 typehash (constant)
* - 0xa0: itemType
* - 0xc0: token
* - 0xe0: identifier (reused for offeredItemsHash)
* - 0x100: startAmount
* - 0x120: endAmount
*/
// Place offer item typehash on the stack.
bytes32 typeHash = _OFFER_ITEM_TYPEHASH;
// Utilize assembly to enable reuse of memory regions when possible.
assembly {
/*
* 1. Calculate OfferItem EIP712 hash
*/
// Write the OfferItem typeHash to memory.
mstore(BasicOrder_offerItem_typeHash_ptr, typeHash)
// Write the OfferItem item type to memory.
mstore(BasicOrder_offerItem_itemType_ptr, offeredItemType)
// Copy calldata region with (offerToken, offerIdentifier,
// offerAmount) from OrderParameters to (token, identifier,
// startAmount) in OfferItem struct. The offerAmount is written
// to startAmount and endAmount as basic orders do not have
// dynamic amounts.
calldatacopy(
BasicOrder_offerItem_token_ptr,
BasicOrder_offerToken_cdPtr,
ThreeWords
)
// Copy offerAmount from calldata to endAmount in OfferItem
// struct.
calldatacopy(
BasicOrder_offerItem_endAmount_ptr,
BasicOrder_offerAmount_cdPtr,
OneWord
)
// Compute EIP712 OfferItem hash, write result to scratch space:
// `keccak256(abi.encode(offeredItem))`
mstore(
0,
keccak256(
BasicOrder_offerItem_typeHash_ptr,
EIP712_OfferItem_size
)
)
/*
* 2. Calculate hash of array of EIP712 hashes and write the
* result to the corresponding OfferItem struct:
* `keccak256(abi.encodePacked(offerItemHashes))`
*/
mstore(BasicOrder_order_offerHashes_ptr, keccak256(0, OneWord))
/*
* 3. Write SpentItem to offer array in OrderFulfilled event.
*/
let eventConsiderationArrPtr := add(
OrderFulfilled_offer_length_baseOffset,
mul(
calldataload(
BasicOrder_additionalRecipients_length_cdPtr
),
OneWord
)
)
// Set a length of 1 for the offer array.
mstore(eventConsiderationArrPtr, 1)
// Write itemType to the SpentItem struct.
mstore(add(eventConsiderationArrPtr, OneWord), offeredItemType)
// Copy calldata region with (offerToken, offerIdentifier,
// offerAmount) from OrderParameters to (token, identifier,
// amount) in SpentItem struct.
calldatacopy(
add(eventConsiderationArrPtr, AdditionalRecipients_size),
BasicOrder_offerToken_cdPtr,
ThreeWords
)
}
}
{
/**
* Once consideration items and offer items have been handled,
* derive the final order hash. Memory Layout:
* 0x80-0x1c0: EIP712 data for order
* - 0x80: Order EIP-712 typehash (constant)
* - 0xa0: orderParameters.offerer
* - 0xc0: orderParameters.zone
* - 0xe0: keccak256(abi.encodePacked(offerHashes))
* - 0x100: keccak256(abi.encodePacked(considerationHashes))
* - 0x120: orderParameters.basicOrderType (% 4 = orderType)
* - 0x140: orderParameters.startTime
* - 0x160: orderParameters.endTime
* - 0x180: orderParameters.zoneHash
* - 0x1a0: orderParameters.salt
* - 0x1c0: orderParameters.conduitKey
* - 0x1e0: _counters[orderParameters.offerer] (from storage)
*/
// Read the offerer from calldata and place on the stack.
address offerer;
assembly {
offerer := calldataload(BasicOrder_offerer_cdPtr)
}
// Read offerer's current counter from storage and place on stack.
uint256 counter = _getCounter(offerer);
// Load order typehash from runtime code and place on stack.
bytes32 typeHash = _ORDER_TYPEHASH;
assembly {
// Set the OrderItem typeHash in memory.
mstore(BasicOrder_order_typeHash_ptr, typeHash)
// Copy offerer and zone from OrderParameters in calldata to the
// Order struct.
calldatacopy(
BasicOrder_order_offerer_ptr,
BasicOrder_offerer_cdPtr,
TwoWords
)
// Copy receivedItemsHash from zero slot to the Order struct.
mstore(
BasicOrder_order_considerationHashes_ptr,
mload(receivedItemsHash_ptr)
)
// Write the supplied orderType to the Order struct.
mstore(BasicOrder_order_orderType_ptr, orderType)
// Copy startTime, endTime, zoneHash, salt & conduit from
// calldata to the Order struct.
calldatacopy(
BasicOrder_order_startTime_ptr,
BasicOrder_startTime_cdPtr,
FiveWords
)
// Write offerer's counter, retrieved from storage, to struct.
mstore(BasicOrder_order_counter_ptr, counter)
// Compute the EIP712 Order hash.
orderHash := keccak256(
BasicOrder_order_typeHash_ptr,
EIP712_Order_size
)
}
}
assembly {
/**
* After the order hash has been derived, emit OrderFulfilled event:
* event OrderFulfilled(
* bytes32 orderHash,
* address indexed offerer,
* address indexed zone,
* address fulfiller,
* SpentItem[] offer,
* > (itemType, token, id, amount)
* ReceivedItem[] consideration
* > (itemType, token, id, amount, recipient)
* )
* topic0 - OrderFulfilled event signature
* topic1 - offerer
* topic2 - zone
* data:
* - 0x00: orderHash
* - 0x20: fulfiller
* - 0x40: offer arr ptr (0x80)
* - 0x60: consideration arr ptr (0x120)
* - 0x80: offer arr len (1)
* - 0xa0: offer.itemType
* - 0xc0: offer.token
* - 0xe0: offer.identifier
* - 0x100: offer.amount
* - 0x120: 1 + recipients.length
* - 0x140: recipient 0
*/
// Derive pointer to start of OrderFulfilled event data
let eventDataPtr := add(
OrderFulfilled_baseOffset,
mul(
calldataload(BasicOrder_additionalRecipients_length_cdPtr),
OneWord
)
)
// Write the order hash to the head of the event's data region.
mstore(eventDataPtr, orderHash)
// Write the fulfiller (i.e. the caller) next for receiver argument.
mstore(add(eventDataPtr, OrderFulfilled_fulfiller_offset), caller())
// Write the SpentItem and ReceivedItem array offsets (constants).
mstore(
// SpentItem array offset
add(eventDataPtr, OrderFulfilled_offer_head_offset),
OrderFulfilled_offer_body_offset
)
mstore(
// ReceivedItem array offset
add(eventDataPtr, OrderFulfilled_consideration_head_offset),
OrderFulfilled_consideration_body_offset
)
// Derive total data size including SpentItem and ReceivedItem data.
// SpentItem portion is already included in the baseSize constant,
// as there can only be one element in the array.
let dataSize := add(
OrderFulfilled_baseSize,
mul(
calldataload(BasicOrder_additionalRecipients_length_cdPtr),
ReceivedItem_size
)
)
// Emit OrderFulfilled log with three topics (the event signature
// as well as the two indexed arguments, the offerer and the zone).
log3(
// Supply the pointer for event data in memory.
eventDataPtr,
// Supply the size of event data in memory.
dataSize,
// Supply the OrderFulfilled event signature.
OrderFulfilled_selector,
// Supply the first topic (the offerer).
calldataload(BasicOrder_offerer_cdPtr),
// Supply the second topic (the zone).
calldataload(BasicOrder_zone_cdPtr)
)
// Restore the zero slot.
mstore(ZeroSlot, 0)
}
// Determine whether order is restricted and, if so, that it is valid.
_assertRestrictedBasicOrderValidity(
orderHash,
parameters.zoneHash,
orderType,
parameters.offerer,
parameters.zone
);
// Verify and update the status of the derived order.
_validateBasicOrderAndUpdateStatus(
orderHash,
parameters.offerer,
parameters.signature
);
}
/**
* @dev Internal function to transfer Ether (or other native tokens) to a
* given recipient as part of basic order fulfillment. Note that
* conduits are not utilized for native tokens as the transferred
* amount must be provided as msg.value.
*
* @param amount The amount to transfer.
* @param to The recipient of the native token transfer.
* @param additionalRecipients The additional recipients of the order.
*/
function _transferEthAndFinalize(
uint256 amount,
address payable to,
AdditionalRecipient[] calldata additionalRecipients
) internal {
// Put ether value supplied by the caller on the stack.
uint256 etherRemaining = msg.value;
// Retrieve total number of additional recipients and place on stack.
uint256 totalAdditionalRecipients = additionalRecipients.length;
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
// Iterate over each additional recipient.
for (uint256 i = 0; i < totalAdditionalRecipients; ++i) {
// Retrieve the additional recipient.
AdditionalRecipient calldata additionalRecipient = (
additionalRecipients[i]
);
// Read ether amount to transfer to recipient & place on stack.
uint256 additionalRecipientAmount = additionalRecipient.amount;
// Ensure that sufficient Ether is available.
if (additionalRecipientAmount > etherRemaining) {
revert InsufficientEtherSupplied();
}
// Transfer Ether to the additional recipient.
_transferEth(
additionalRecipient.recipient,
additionalRecipientAmount
);
// Reduce ether value available. Skip underflow check as
// subtracted value is confirmed above as less than remaining.
etherRemaining -= additionalRecipientAmount;
}
}
// Ensure that sufficient Ether is still available.
if (amount > etherRemaining) {
revert InsufficientEtherSupplied();
}
// Transfer Ether to the offerer.
_transferEth(to, amount);
// If any Ether remains after transfers, return it to the caller.
if (etherRemaining > amount) {
// Skip underflow check as etherRemaining > amount.
unchecked {
// Transfer remaining Ether to the caller.
_transferEth(payable(msg.sender), etherRemaining - amount);
}
}
}
/**
* @dev Internal function to transfer ERC20 tokens to a given recipient as
* part of basic order fulfillment.
*
* @param offerer The offerer of the fulfiller order.
* @param parameters The basic order parameters.
* @param fromOfferer A boolean indicating whether to decrement amount from
* the offered amount.
* @param accumulator An open-ended array that collects transfers to execute
* against a given conduit in a single call.
*/
function _transferERC20AndFinalize(
address offerer,
BasicOrderParameters calldata parameters,
bool fromOfferer,
bytes memory accumulator
) internal {
// Declare from and to variables determined by fromOfferer value.
address from;
address to;
// Declare token and amount variables determined by fromOfferer value.
address token;
uint256 amount;
// Declare and check identifier variable within an isolated scope.
{
// Declare identifier variable determined by fromOfferer value.
uint256 identifier;
// Set ERC20 token transfer variables based on fromOfferer boolean.
if (fromOfferer) {
// Use offerer as from value and msg.sender as to value.
from = offerer;
to = msg.sender;
// Use offer token and related values if token is from offerer.
token = parameters.offerToken;
identifier = parameters.offerIdentifier;
amount = parameters.offerAmount;
} else {
// Use msg.sender as from value and offerer as to value.
from = msg.sender;
to = offerer;
// Otherwise, use consideration token and related values.
token = parameters.considerationToken;
identifier = parameters.considerationIdentifier;
amount = parameters.considerationAmount;
}
// Ensure that no identifier is supplied.
if (identifier != 0) {
revert UnusedItemParameters();
}
}
// Determine the appropriate conduit to utilize.
bytes32 conduitKey;
// Utilize assembly to derive conduit (if relevant) based on route.
assembly {
// Use offerer conduit if fromOfferer, fulfiller conduit otherwise.
conduitKey := calldataload(
sub(
BasicOrder_fulfillerConduit_cdPtr,
mul(fromOfferer, OneWord)
)
)
}
// Retrieve total number of additional recipients and place on stack.
uint256 totalAdditionalRecipients = (
parameters.additionalRecipients.length
);
// Iterate over each additional recipient.
for (uint256 i = 0; i < totalAdditionalRecipients; ) {
// Retrieve the additional recipient.
AdditionalRecipient calldata additionalRecipient = (
parameters.additionalRecipients[i]
);
uint256 additionalRecipientAmount = additionalRecipient.amount;
// Decrement the amount to transfer to fulfiller if indicated.
if (fromOfferer) {
amount -= additionalRecipientAmount;
}
// Transfer ERC20 tokens to additional recipient given approval.
_transferERC20(
token,
from,
additionalRecipient.recipient,
additionalRecipientAmount,
conduitKey,
accumulator
);
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
++i;
}
}
// Transfer ERC20 token amount (from account must have proper approval).
_transferERC20(token, from, to, amount, conduitKey, accumulator);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { OrderType } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OrderParameters,
Order,
AdvancedOrder,
OrderComponents,
OrderStatus,
CriteriaResolver
} from "./ConsiderationStructs.sol";
import "./ConsiderationConstants.sol";
import { Executor } from "./Executor.sol";
import { ZoneInteraction } from "./ZoneInteraction.sol";
/**
* @title OrderValidator
* @author 0age
* @notice OrderValidator contains functionality related to validating orders
* and updating their status.
*/
contract OrderValidator is Executor, ZoneInteraction {
// Track status of each order (validated, cancelled, and fraction filled).
mapping(bytes32 => OrderStatus) private _orderStatus;
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) Executor(conduitController) {}
/**
* @dev Internal function to verify and update the status of a basic order.
*
* @param orderHash The hash of the order.
* @param offerer The offerer of the order.
* @param signature A signature from the offerer indicating that the order
* has been approved.
*/
function _validateBasicOrderAndUpdateStatus(
bytes32 orderHash,
address offerer,
bytes memory signature
) internal {
// Retrieve the order status for the given order hash.
OrderStatus storage orderStatus = _orderStatus[orderHash];
// Ensure order is fillable and is not cancelled.
_verifyOrderStatus(
orderHash,
orderStatus,
true, // Only allow unused orders when fulfilling basic orders.
true // Signifies to revert if the order is invalid.
);
// If the order is not already validated, verify the supplied signature.
if (!orderStatus.isValidated) {
_verifySignature(offerer, orderHash, signature);
}
// Update order status as fully filled, packing struct values.
orderStatus.isValidated = true;
orderStatus.isCancelled = false;
orderStatus.numerator = 1;
orderStatus.denominator = 1;
}
/**
* @dev Internal function to validate an order, determine what portion to
* fill, and update its status. The desired fill amount is supplied as
* a fraction, as is the returned amount to fill.
*
* @param advancedOrder The order to fulfill as well as the fraction to
* fill. Note that all offer and consideration
* amounts must divide with no remainder in order
* for a partial fill to be valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific offer or consideration, a token
* identifier, and a proof that the supplied token
* identifier is contained in the order's merkle
* root. Note that a criteria of zero indicates
* that any (transferable) token identifier is
* valid and that no proof needs to be supplied.
* @param revertOnInvalid A boolean indicating whether to revert if the
* order is invalid due to the time or status.
* @param priorOrderHashes The order hashes of each order supplied prior to
* the current order as part of a "match" variety
* of order fulfillment (e.g. this array will be
* empty for single or "fulfill available").
*
* @return orderHash The order hash.
* @return newNumerator A value indicating the portion of the order that
* will be filled.
* @return newDenominator A value indicating the total size of the order.
*/
function _validateOrderAndUpdateStatus(
AdvancedOrder memory advancedOrder,
CriteriaResolver[] memory criteriaResolvers,
bool revertOnInvalid,
bytes32[] memory priorOrderHashes
)
internal
returns (
bytes32 orderHash,
uint256 newNumerator,
uint256 newDenominator
)
{
// Retrieve the parameters for the order.
OrderParameters memory orderParameters = advancedOrder.parameters;
// Ensure current timestamp falls between order start time and end time.
if (
!_verifyTime(
orderParameters.startTime,
orderParameters.endTime,
revertOnInvalid
)
) {
// Assuming an invalid time and no revert, return zeroed out values.
return (bytes32(0), 0, 0);
}
// Read numerator and denominator from memory and place on the stack.
uint256 numerator = uint256(advancedOrder.numerator);
uint256 denominator = uint256(advancedOrder.denominator);
// Ensure that the supplied numerator and denominator are valid.
if (numerator > denominator || numerator == 0) {
revert BadFraction();
}
// If attempting partial fill (n < d) check order type & ensure support.
if (
numerator < denominator &&
_doesNotSupportPartialFills(orderParameters.orderType)
) {
// Revert if partial fill was attempted on an unsupported order.
revert PartialFillsNotEnabledForOrder();
}
// Retrieve current counter & use it w/ parameters to derive order hash.
orderHash = _assertConsiderationLengthAndGetOrderHash(orderParameters);
// Ensure restricted orders have a valid submitter or pass a zone check.
_assertRestrictedAdvancedOrderValidity(
advancedOrder,
criteriaResolvers,
priorOrderHashes,
orderHash,
orderParameters.zoneHash,
orderParameters.orderType,
orderParameters.offerer,
orderParameters.zone
);
// Retrieve the order status using the derived order hash.
OrderStatus storage orderStatus = _orderStatus[orderHash];
// Ensure order is fillable and is not cancelled.
if (
!_verifyOrderStatus(
orderHash,
orderStatus,
false, // Allow partially used orders to be filled.
revertOnInvalid
)
) {
// Assuming an invalid order status and no revert, return zero fill.
return (orderHash, 0, 0);
}
// If the order is not already validated, verify the supplied signature.
if (!orderStatus.isValidated) {
_verifySignature(
orderParameters.offerer,
orderHash,
advancedOrder.signature
);
}
// Read filled amount as numerator and denominator and put on the stack.
uint256 filledNumerator = orderStatus.numerator;
uint256 filledDenominator = orderStatus.denominator;
// If order (orderStatus) currently has a non-zero denominator it is
// partially filled.
if (filledDenominator != 0) {
// If denominator of 1 supplied, fill all remaining amount on order.
if (denominator == 1) {
// Scale numerator & denominator to match current denominator.
numerator = filledDenominator;
denominator = filledDenominator;
}
// Otherwise, if supplied denominator differs from current one...
else if (filledDenominator != denominator) {
// scale current numerator by the supplied denominator, then...
filledNumerator *= denominator;
// the supplied numerator & denominator by current denominator.
numerator *= filledDenominator;
denominator *= filledDenominator;
}
// Once adjusted, if current+supplied numerator exceeds denominator:
if (filledNumerator + numerator > denominator) {
// Skip underflow check: denominator >= orderStatus.numerator
unchecked {
// Reduce current numerator so it + supplied = denominator.
numerator = denominator - filledNumerator;
}
}
// Increment the filled numerator by the new numerator.
filledNumerator += numerator;
// Use assembly to ensure fractional amounts are below max uint120.
assembly {
// Check filledNumerator and denominator for uint120 overflow.
if or(
gt(filledNumerator, MaxUint120),
gt(denominator, MaxUint120)
) {
// Derive greatest common divisor using euclidean algorithm.
function gcd(_a, _b) -> out {
for {
} _b {
} {
let _c := _b
_b := mod(_a, _c)
_a := _c
}
out := _a
}
let scaleDown := gcd(
numerator,
gcd(filledNumerator, denominator)
)
// Ensure that the divisor is at least one.
let safeScaleDown := add(scaleDown, iszero(scaleDown))
// Scale all fractional values down by gcd.
numerator := div(numerator, safeScaleDown)
filledNumerator := div(filledNumerator, safeScaleDown)
denominator := div(denominator, safeScaleDown)
// Perform the overflow check a second time.
if or(
gt(filledNumerator, MaxUint120),
gt(denominator, MaxUint120)
) {
// Store the Panic error signature.
mstore(0, Panic_error_signature)
// Set arithmetic (0x11) panic code as initial argument.
mstore(Panic_error_offset, Panic_arithmetic)
// Return, supplying Panic signature & arithmetic code.
revert(0, Panic_error_length)
}
}
}
// Skip overflow check: checked above unless numerator is reduced.
unchecked {
// Update order status and fill amount, packing struct values.
orderStatus.isValidated = true;
orderStatus.isCancelled = false;
orderStatus.numerator = uint120(filledNumerator);
orderStatus.denominator = uint120(denominator);
}
} else {
// Update order status and fill amount, packing struct values.
orderStatus.isValidated = true;
orderStatus.isCancelled = false;
orderStatus.numerator = uint120(numerator);
orderStatus.denominator = uint120(denominator);
}
// Return order hash, a modified numerator, and a modified denominator.
return (orderHash, numerator, denominator);
}
/**
* @dev Internal function to cancel an arbitrary number of orders. Note that
* only the offerer or the zone of a given order may cancel it. Callers
* should ensure that the intended order was cancelled by calling
* `getOrderStatus` and confirming that `isCancelled` returns `true`.
*
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders were
* successfully cancelled.
*/
function _cancel(OrderComponents[] calldata orders)
internal
returns (bool cancelled)
{
// Ensure that the reentrancy guard is not currently set.
_assertNonReentrant();
// Declare variables outside of the loop.
OrderStatus storage orderStatus;
address offerer;
address zone;
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
// Read length of the orders array from memory and place on stack.
uint256 totalOrders = orders.length;
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ) {
// Retrieve the order.
OrderComponents calldata order = orders[i];
offerer = order.offerer;
zone = order.zone;
// Ensure caller is either offerer or zone of the order.
if (msg.sender != offerer && msg.sender != zone) {
revert InvalidCanceller();
}
// Derive order hash using the order parameters and the counter.
bytes32 orderHash = _deriveOrderHash(
OrderParameters(
offerer,
zone,
order.offer,
order.consideration,
order.orderType,
order.startTime,
order.endTime,
order.zoneHash,
order.salt,
order.conduitKey,
order.consideration.length
),
order.counter
);
// Retrieve the order status using the derived order hash.
orderStatus = _orderStatus[orderHash];
// Update the order status as not valid and cancelled.
orderStatus.isValidated = false;
orderStatus.isCancelled = true;
// Emit an event signifying that the order has been cancelled.
emit OrderCancelled(orderHash, offerer, zone);
// Increment counter inside body of loop for gas efficiency.
++i;
}
}
// Return a boolean indicating that orders were successfully cancelled.
cancelled = true;
}
/**
* @dev Internal function to validate an arbitrary number of orders, thereby
* registering their signatures as valid and allowing the fulfiller to
* skip signature verification on fulfillment. Note that validated
* orders may still be unfulfillable due to invalid item amounts or
* other factors; callers should determine whether validated orders are
* fulfillable by simulating the fulfillment call prior to execution.
* Also note that anyone can validate a signed order, but only the
* offerer can validate an order without supplying a signature.
*
* @param orders The orders to validate.
*
* @return validated A boolean indicating whether the supplied orders were
* successfully validated.
*/
function _validate(Order[] calldata orders)
internal
returns (bool validated)
{
// Ensure that the reentrancy guard is not currently set.
_assertNonReentrant();
// Declare variables outside of the loop.
OrderStatus storage orderStatus;
bytes32 orderHash;
address offerer;
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
// Read length of the orders array from memory and place on stack.
uint256 totalOrders = orders.length;
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ) {
// Retrieve the order.
Order calldata order = orders[i];
// Retrieve the order parameters.
OrderParameters calldata orderParameters = order.parameters;
// Move offerer from memory to the stack.
offerer = orderParameters.offerer;
// Get current counter & use it w/ params to derive order hash.
orderHash = _assertConsiderationLengthAndGetOrderHash(
orderParameters
);
// Retrieve the order status using the derived order hash.
orderStatus = _orderStatus[orderHash];
// Ensure order is fillable and retrieve the filled amount.
_verifyOrderStatus(
orderHash,
orderStatus,
false, // Signifies that partially filled orders are valid.
true // Signifies to revert if the order is invalid.
);
// If the order has not already been validated...
if (!orderStatus.isValidated) {
// Verify the supplied signature.
_verifySignature(offerer, orderHash, order.signature);
// Update order status to mark the order as valid.
orderStatus.isValidated = true;
// Emit an event signifying the order has been validated.
emit OrderValidated(
orderHash,
offerer,
orderParameters.zone
);
}
// Increment counter inside body of the loop for gas efficiency.
++i;
}
}
// Return a boolean indicating that orders were successfully validated.
validated = true;
}
/**
* @dev Internal view function to retrieve the status of a given order by
* hash, including whether the order has been cancelled or validated
* and the fraction of the order that has been filled.
*
* @param orderHash The order hash in question.
*
* @return isValidated A boolean indicating whether the order in question
* has been validated (i.e. previously approved or
* partially filled).
* @return isCancelled A boolean indicating whether the order in question
* has been cancelled.
* @return totalFilled The total portion of the order that has been filled
* (i.e. the "numerator").
* @return totalSize The total size of the order that is either filled or
* unfilled (i.e. the "denominator").
*/
function _getOrderStatus(bytes32 orderHash)
internal
view
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
)
{
// Retrieve the order status using the order hash.
OrderStatus storage orderStatus = _orderStatus[orderHash];
// Return the fields on the order status.
return (
orderStatus.isValidated,
orderStatus.isCancelled,
orderStatus.numerator,
orderStatus.denominator
);
}
/**
* @dev Internal pure function to check whether a given order type indicates
* that partial fills are not supported (e.g. only "full fills" are
* allowed for the order in question).
*
* @param orderType The order type in question.
*
* @return isFullOrder A boolean indicating whether the order type only
* supports full fills.
*/
function _doesNotSupportPartialFills(OrderType orderType)
internal
pure
returns (bool isFullOrder)
{
// The "full" order types are even, while "partial" order types are odd.
// Bitwise and by 1 is equivalent to modulo by 2, but 2 gas cheaper.
assembly {
// Equivalent to `uint256(orderType) & 1 == 0`.
isFullOrder := iszero(and(orderType, 1))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { ItemType } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OfferItem,
ConsiderationItem,
SpentItem,
ReceivedItem,
OrderParameters,
Order,
AdvancedOrder,
CriteriaResolver
} from "./ConsiderationStructs.sol";
import { BasicOrderFulfiller } from "./BasicOrderFulfiller.sol";
import { CriteriaResolution } from "./CriteriaResolution.sol";
import { AmountDeriver } from "./AmountDeriver.sol";
import "./ConsiderationConstants.sol";
/**
* @title OrderFulfiller
* @author 0age
* @notice OrderFulfiller contains logic related to order fulfillment where a
* single order is being fulfilled and where basic order fulfillment is
* not available as an option.
*/
contract OrderFulfiller is
BasicOrderFulfiller,
CriteriaResolution,
AmountDeriver
{
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController)
BasicOrderFulfiller(conduitController)
{}
/**
* @dev Internal function to validate an order and update its status, adjust
* prices based on current time, apply criteria resolvers, determine
* what portion to fill, and transfer relevant tokens.
*
* @param advancedOrder The order to fulfill as well as the fraction
* to fill. Note that all offer and consideration
* components must divide with no remainder for
* the partial fill to be valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a proof
* that the supplied token identifier is
* contained in the order's merkle root. Note
* that a criteria of zero indicates that any
* (transferable) token identifier is valid and
* that no proof needs to be supplied.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Consideration.
* @param recipient The intended recipient for all received items.
*
* @return A boolean indicating whether the order has been fulfilled.
*/
function _validateAndFulfillAdvancedOrder(
AdvancedOrder memory advancedOrder,
CriteriaResolver[] memory criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) internal returns (bool) {
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard();
// Declare empty bytes32 array (unused, will remain empty).
bytes32[] memory priorOrderHashes;
// Validate order, update status, and determine fraction to fill.
(
bytes32 orderHash,
uint256 fillNumerator,
uint256 fillDenominator
) = _validateOrderAndUpdateStatus(
advancedOrder,
criteriaResolvers,
true,
priorOrderHashes
);
// Create an array with length 1 containing the order.
AdvancedOrder[] memory advancedOrders = new AdvancedOrder[](1);
// Populate the order as the first and only element of the new array.
advancedOrders[0] = advancedOrder;
// Apply criteria resolvers using generated orders and details arrays.
_applyCriteriaResolvers(advancedOrders, criteriaResolvers);
// Retrieve the order parameters after applying criteria resolvers.
OrderParameters memory orderParameters = advancedOrders[0].parameters;
// Perform each item transfer with the appropriate fractional amount.
_applyFractionsAndTransferEach(
orderParameters,
fillNumerator,
fillDenominator,
fulfillerConduitKey,
recipient
);
// Emit an event signifying that the order has been fulfilled.
_emitOrderFulfilledEvent(
orderHash,
orderParameters.offerer,
orderParameters.zone,
recipient,
orderParameters.offer,
orderParameters.consideration
);
// Clear the reentrancy guard.
_clearReentrancyGuard();
return true;
}
/**
* @dev Internal function to transfer each item contained in a given single
* order fulfillment after applying a respective fraction to the amount
* being transferred.
*
* @param orderParameters The parameters for the fulfilled order.
* @param numerator A value indicating the portion of the order
* that should be filled.
* @param denominator A value indicating the total order size.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Consideration.
* @param recipient The intended recipient for all received items.
*/
function _applyFractionsAndTransferEach(
OrderParameters memory orderParameters,
uint256 numerator,
uint256 denominator,
bytes32 fulfillerConduitKey,
address recipient
) internal {
// Read start time & end time from order parameters and place on stack.
uint256 startTime = orderParameters.startTime;
uint256 endTime = orderParameters.endTime;
// Initialize an accumulator array. From this point forward, no new
// memory regions can be safely allocated until the accumulator is no
// longer being utilized, as the accumulator operates in an open-ended
// fashion from this memory pointer; existing memory may still be
// accessed and modified, however.
bytes memory accumulator = new bytes(AccumulatorDisarmed);
// As of solidity 0.6.0, inline assembly cannot directly access function
// definitions, but can still access locally scoped function variables.
// This means that in order to recast the type of a function, we need to
// create a local variable to reference the internal function definition
// (using the same type) and a local variable with the desired type,
// and then cast the original function pointer to the desired type.
/**
* Repurpose existing OfferItem memory regions on the offer array for
* the order by overriding the _transfer function pointer to accept a
* modified OfferItem argument in place of the usual ReceivedItem:
*
* ========= OfferItem ========== ====== ReceivedItem ======
* ItemType itemType; ------------> ItemType itemType;
* address token; ----------------> address token;
* uint256 identifierOrCriteria; -> uint256 identifier;
* uint256 startAmount; ----------> uint256 amount;
* uint256 endAmount; ------------> address recipient;
*/
// Declare a nested scope to minimize stack depth.
unchecked {
// Declare a virtual function pointer taking an OfferItem argument.
function(OfferItem memory, address, bytes32, bytes memory)
internal _transferOfferItem;
{
// Assign _transfer function to a new function pointer (it takes
// a ReceivedItem as its initial argument)
function(ReceivedItem memory, address, bytes32, bytes memory)
internal _transferReceivedItem = _transfer;
// Utilize assembly to override the virtual function pointer.
assembly {
// Cast initial ReceivedItem type to an OfferItem type.
_transferOfferItem := _transferReceivedItem
}
}
// Read offer array length from memory and place on stack.
uint256 totalOfferItems = orderParameters.offer.length;
// Iterate over each offer on the order.
// Skip overflow check as for loop is indexed starting at zero.
for (uint256 i = 0; i < totalOfferItems; ++i) {
// Retrieve the offer item.
OfferItem memory offerItem = orderParameters.offer[i];
// Offer items for the native token can not be received
// outside of a match order function.
if (offerItem.itemType == ItemType.NATIVE) {
revert InvalidNativeOfferItem();
}
// Declare an additional nested scope to minimize stack depth.
{
// Apply fill fraction to get offer item amount to transfer.
uint256 amount = _applyFraction(
offerItem.startAmount,
offerItem.endAmount,
numerator,
denominator,
startTime,
endTime,
false
);
// Utilize assembly to set overloaded offerItem arguments.
assembly {
// Write new fractional amount to startAmount as amount.
mstore(
add(offerItem, ReceivedItem_amount_offset),
amount
)
// Write recipient to endAmount.
mstore(
add(offerItem, ReceivedItem_recipient_offset),
recipient
)
}
}
// Transfer the item from the offerer to the recipient.
_transferOfferItem(
offerItem,
orderParameters.offerer,
orderParameters.conduitKey,
accumulator
);
}
}
// Put ether value supplied by the caller on the stack.
uint256 etherRemaining = msg.value;
/**
* Repurpose existing ConsiderationItem memory regions on the
* consideration array for the order by overriding the _transfer
* function pointer to accept a modified ConsiderationItem argument in
* place of the usual ReceivedItem:
*
* ====== ConsiderationItem ===== ====== ReceivedItem ======
* ItemType itemType; ------------> ItemType itemType;
* address token; ----------------> address token;
* uint256 identifierOrCriteria;--> uint256 identifier;
* uint256 startAmount; ----------> uint256 amount;
* uint256 endAmount; /----> address recipient;
* address recipient; ------/
*/
// Declare a nested scope to minimize stack depth.
unchecked {
// Declare virtual function pointer with ConsiderationItem argument.
function(ConsiderationItem memory, address, bytes32, bytes memory)
internal _transferConsiderationItem;
{
// Reassign _transfer function to a new function pointer (it
// takes a ReceivedItem as its initial argument).
function(ReceivedItem memory, address, bytes32, bytes memory)
internal _transferReceivedItem = _transfer;
// Utilize assembly to override the virtual function pointer.
assembly {
// Cast ReceivedItem type to ConsiderationItem type.
_transferConsiderationItem := _transferReceivedItem
}
}
// Read consideration array length from memory and place on stack.
uint256 totalConsiderationItems = orderParameters
.consideration
.length;
// Iterate over each consideration item on the order.
// Skip overflow check as for loop is indexed starting at zero.
for (uint256 i = 0; i < totalConsiderationItems; ++i) {
// Retrieve the consideration item.
ConsiderationItem memory considerationItem = (
orderParameters.consideration[i]
);
// Apply fraction & derive considerationItem amount to transfer.
uint256 amount = _applyFraction(
considerationItem.startAmount,
considerationItem.endAmount,
numerator,
denominator,
startTime,
endTime,
true
);
// Use assembly to set overloaded considerationItem arguments.
assembly {
// Write derived fractional amount to startAmount as amount.
mstore(
add(considerationItem, ReceivedItem_amount_offset),
amount
)
// Write original recipient to endAmount as recipient.
mstore(
add(considerationItem, ReceivedItem_recipient_offset),
mload(
add(
considerationItem,
ConsiderationItem_recipient_offset
)
)
)
}
// Reduce available value if offer spent ETH or a native token.
if (considerationItem.itemType == ItemType.NATIVE) {
// Ensure that sufficient native tokens are still available.
if (amount > etherRemaining) {
revert InsufficientEtherSupplied();
}
// Skip underflow check as a comparison has just been made.
etherRemaining -= amount;
}
// Transfer item from caller to recipient specified by the item.
_transferConsiderationItem(
considerationItem,
msg.sender,
fulfillerConduitKey,
accumulator
);
}
}
// Trigger any remaining accumulated transfers via call to the conduit.
_triggerIfArmed(accumulator);
// If any ether remains after fulfillments...
if (etherRemaining != 0) {
// return it to the caller.
_transferEth(payable(msg.sender), etherRemaining);
}
}
/**
* @dev Internal function to emit an OrderFulfilled event. OfferItems are
* translated into SpentItems and ConsiderationItems are translated
* into ReceivedItems.
*
* @param orderHash The order hash.
* @param offerer The offerer for the order.
* @param zone The zone for the order.
* @param fulfiller The fulfiller of the order, or the null address if
* the order was fulfilled via order matching.
* @param offer The offer items for the order.
* @param consideration The consideration items for the order.
*/
function _emitOrderFulfilledEvent(
bytes32 orderHash,
address offerer,
address zone,
address fulfiller,
OfferItem[] memory offer,
ConsiderationItem[] memory consideration
) internal {
// Cast already-modified offer memory region as spent items.
SpentItem[] memory spentItems;
assembly {
spentItems := offer
}
// Cast already-modified consideration memory region as received items.
ReceivedItem[] memory receivedItems;
assembly {
receivedItems := consideration
}
// Emit an event signifying that the order has been fulfilled.
emit OrderFulfilled(
orderHash,
offerer,
zone,
fulfiller,
spentItems,
receivedItems
);
}
/**
* @dev Internal pure function to convert an order to an advanced order with
* numerator and denominator of 1 and empty extraData.
*
* @param order The order to convert.
*
* @return advancedOrder The new advanced order.
*/
function _convertOrderToAdvanced(Order calldata order)
internal
pure
returns (AdvancedOrder memory advancedOrder)
{
// Convert to partial order (1/1 or full fill) and return new value.
advancedOrder = AdvancedOrder(
order.parameters,
1,
1,
order.signature,
""
);
}
/**
* @dev Internal pure function to convert an array of orders to an array of
* advanced orders with numerator and denominator of 1.
*
* @param orders The orders to convert.
*
* @return advancedOrders The new array of partial orders.
*/
function _convertOrdersToAdvanced(Order[] calldata orders)
internal
pure
returns (AdvancedOrder[] memory advancedOrders)
{
// Read the number of orders from calldata and place on the stack.
uint256 totalOrders = orders.length;
// Allocate new empty array for each partial order in memory.
advancedOrders = new AdvancedOrder[](totalOrders);
// Skip overflow check as the index for the loop starts at zero.
unchecked {
// Iterate over the given orders.
for (uint256 i = 0; i < totalOrders; ++i) {
// Convert to partial order (1/1 or full fill) and update array.
advancedOrders[i] = _convertOrderToAdvanced(orders[i]);
}
}
// Return the array of advanced orders.
return advancedOrders;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
// prettier-ignore
import {
AmountDerivationErrors
} from "../interfaces/AmountDerivationErrors.sol";
import "./ConsiderationConstants.sol";
/**
* @title AmountDeriver
* @author 0age
* @notice AmountDeriver contains view and pure functions related to deriving
* item amounts based on partial fill quantity and on linear
* interpolation based on current time when the start amount and end
* amount differ.
*/
contract AmountDeriver is AmountDerivationErrors {
/**
* @dev Internal view function to derive the current amount of a given item
* based on the current price, the starting price, and the ending
* price. If the start and end prices differ, the current price will be
* interpolated on a linear basis. Note that this function expects that
* the startTime parameter of orderParameters is not greater than the
* current block timestamp and that the endTime parameter is greater
* than the current block timestamp. If this condition is not upheld,
* duration / elapsed / remaining variables will underflow.
*
* @param startAmount The starting amount of the item.
* @param endAmount The ending amount of the item.
* @param startTime The starting time of the order.
* @param endTime The end time of the order.
* @param roundUp A boolean indicating whether the resultant amount
* should be rounded up or down.
*
* @return amount The current amount.
*/
function _locateCurrentAmount(
uint256 startAmount,
uint256 endAmount,
uint256 startTime,
uint256 endTime,
bool roundUp
) internal view returns (uint256 amount) {
// Only modify end amount if it doesn't already equal start amount.
if (startAmount != endAmount) {
// Declare variables to derive in the subsequent unchecked scope.
uint256 duration;
uint256 elapsed;
uint256 remaining;
// Skip underflow checks as startTime <= block.timestamp < endTime.
unchecked {
// Derive the duration for the order and place it on the stack.
duration = endTime - startTime;
// Derive time elapsed since the order started & place on stack.
elapsed = block.timestamp - startTime;
// Derive time remaining until order expires and place on stack.
remaining = duration - elapsed;
}
// Aggregate new amounts weighted by time with rounding factor.
uint256 totalBeforeDivision = ((startAmount * remaining) +
(endAmount * elapsed));
// Use assembly to combine operations and skip divide-by-zero check.
assembly {
// Multiply by iszero(iszero(totalBeforeDivision)) to ensure
// amount is set to zero if totalBeforeDivision is zero,
// as intermediate overflow can occur if it is zero.
amount := mul(
iszero(iszero(totalBeforeDivision)),
// Subtract 1 from the numerator and add 1 to the result if
// roundUp is true to get the proper rounding direction.
// Division is performed with no zero check as duration
// cannot be zero as long as startTime < endTime.
add(
div(sub(totalBeforeDivision, roundUp), duration),
roundUp
)
)
}
// Return the current amount.
return amount;
}
// Return the original amount as startAmount == endAmount.
return endAmount;
}
/**
* @dev Internal pure function to return a fraction of a given value and to
* ensure the resultant value does not have any fractional component.
* Note that this function assumes that zero will never be supplied as
* the denominator parameter; invalid / undefined behavior will result
* should a denominator of zero be provided.
*
* @param numerator A value indicating the portion of the order that
* should be filled.
* @param denominator A value indicating the total size of the order. Note
* that this value cannot be equal to zero.
* @param value The value for which to compute the fraction.
*
* @return newValue The value after applying the fraction.
*/
function _getFraction(
uint256 numerator,
uint256 denominator,
uint256 value
) internal pure returns (uint256 newValue) {
// Return value early in cases where the fraction resolves to 1.
if (numerator == denominator) {
return value;
}
// Ensure fraction can be applied to the value with no remainder. Note
// that the denominator cannot be zero.
assembly {
// Ensure new value contains no remainder via mulmod operator.
// Credit to @hrkrshnn + @axic for proposing this optimal solution.
if mulmod(value, numerator, denominator) {
mstore(0, InexactFraction_error_signature)
revert(0, InexactFraction_error_len)
}
}
// Multiply the numerator by the value and ensure no overflow occurs.
uint256 valueTimesNumerator = value * numerator;
// Divide and check for remainder. Note that denominator cannot be zero.
assembly {
// Perform division without zero check.
newValue := div(valueTimesNumerator, denominator)
}
}
/**
* @dev Internal view function to apply a fraction to a consideration
* or offer item.
*
* @param startAmount The starting amount of the item.
* @param endAmount The ending amount of the item.
* @param numerator A value indicating the portion of the order that
* should be filled.
* @param denominator A value indicating the total size of the order.
* @param startTime The starting time of the order.
* @param endTime The end time of the order.
* @param roundUp A boolean indicating whether the resultant
* amount should be rounded up or down.
*
* @return amount The received item to transfer with the final amount.
*/
function _applyFraction(
uint256 startAmount,
uint256 endAmount,
uint256 numerator,
uint256 denominator,
uint256 startTime,
uint256 endTime,
bool roundUp
) internal view returns (uint256 amount) {
// If start amount equals end amount, apply fraction to end amount.
if (startAmount == endAmount) {
// Apply fraction to end amount.
amount = _getFraction(numerator, denominator, endAmount);
} else {
// Otherwise, apply fraction to both and interpolated final amount.
amount = _locateCurrentAmount(
_getFraction(numerator, denominator, startAmount),
_getFraction(numerator, denominator, endAmount),
startTime,
endTime,
roundUp
);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title AmountDerivationErrors
* @author 0age
* @notice AmountDerivationErrors contains errors related to amount derivation.
*/
interface AmountDerivationErrors {
/**
* @dev Revert with an error when attempting to apply a fraction as part of
* a partial fill that does not divide the target amount cleanly.
*/
error InexactFraction();
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import { Side, ItemType } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OfferItem,
ConsiderationItem,
ReceivedItem,
OrderParameters,
Fulfillment,
FulfillmentComponent,
Execution,
Order,
AdvancedOrder,
CriteriaResolver
} from "./ConsiderationStructs.sol";
import { OrderFulfiller } from "./OrderFulfiller.sol";
import { FulfillmentApplier } from "./FulfillmentApplier.sol";
import "./ConsiderationConstants.sol";
/**
* @title OrderCombiner
* @author 0age
* @notice OrderCombiner contains logic for fulfilling combinations of orders,
* either by matching offer items to consideration items or by
* fulfilling orders where available.
*/
contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) OrderFulfiller(conduitController) {}
/**
* @notice Internal function to attempt to fill a group of orders, fully or
* partially, with an arbitrary number of items for offer and
* consideration per order alongside criteria resolvers containing
* specific token identifiers and associated proofs. Any order that
* is not currently active, has already been fully filled, or has
* been cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or a conduit if indicated by
* the order) to transfer any relevant
* tokens on their behalf and that
* contracts must implement
* `onERC1155Received` in order to receive
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used (and
* direct approvals set on Consideration).
* @param recipient The intended recipient for all received
* items.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _fulfillAvailableAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
)
internal
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Validate orders, apply amounts, & determine if they utilize conduits.
_validateOrdersAndPrepareToFulfill(
advancedOrders,
criteriaResolvers,
false, // Signifies that invalid orders should NOT revert.
maximumFulfilled,
recipient
);
// Aggregate used offer and consideration items and execute transfers.
(availableOrders, executions) = _executeAvailableFulfillments(
advancedOrders,
offerFulfillments,
considerationFulfillments,
fulfillerConduitKey,
recipient
);
// Return order fulfillment details and executions.
return (availableOrders, executions);
}
/**
* @dev Internal function to validate a group of orders, update their
* statuses, reduce amounts by their previously filled fractions, apply
* criteria resolvers, and emit OrderFulfilled events.
*
* @param advancedOrders The advanced orders to validate and reduce by
* their previously filled amounts.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* a root of zero indicates that any transferable
* token identifier is valid and that no proof
* needs to be supplied.
* @param revertOnInvalid A boolean indicating whether to revert on any
* order being invalid; setting this to false will
* instead cause the invalid order to be skipped.
* @param maximumFulfilled The maximum number of orders to fulfill.
* @param recipient The intended recipient for all received items.
*/
function _validateOrdersAndPrepareToFulfill(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
bool revertOnInvalid,
uint256 maximumFulfilled,
address recipient
) internal {
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard();
// Read length of orders array and place on the stack.
uint256 totalOrders = advancedOrders.length;
// Track the order hash for each order being fulfilled.
bytes32[] memory orderHashes = new bytes32[](totalOrders);
// Override orderHashes length to zero after memory has been allocated.
assembly {
mstore(orderHashes, 0)
}
// Declare an error buffer indicating status of any native offer items.
// {00} == 0 => In a match function, no native offer items: allow.
// {01} == 1 => In a match function, some native offer items: allow.
// {10} == 2 => Not in a match function, no native offer items: allow.
// {11} == 3 => Not in a match function, some native offer items: THROW.
uint256 invalidNativeOfferItemErrorBuffer;
// Use assembly to set the value for the second bit of the error buffer.
assembly {
// Use the second bit of the error buffer to indicate whether the
// current function is not matchAdvancedOrders or matchOrders.
invalidNativeOfferItemErrorBuffer := shl(
1,
gt(
// Take the remainder of the selector modulo a magic value.
mod(
shr(NumBitsAfterSelector, calldataload(0)),
NonMatchSelector_MagicModulus
),
// Check if remainder is higher than the greatest remainder
// of the two match selectors modulo the magic value.
NonMatchSelector_MagicRemainder
)
)
}
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ++i) {
// Retrieve the current order.
AdvancedOrder memory advancedOrder = advancedOrders[i];
// Determine if max number orders have already been fulfilled.
if (maximumFulfilled == 0) {
// Mark fill fraction as zero as the order will not be used.
advancedOrder.numerator = 0;
// Update the length of the orderHashes array.
assembly {
mstore(orderHashes, add(i, 1))
}
// Continue iterating through the remaining orders.
continue;
}
// Validate it, update status, and determine fraction to fill.
(
bytes32 orderHash,
uint256 numerator,
uint256 denominator
) = _validateOrderAndUpdateStatus(
advancedOrder,
criteriaResolvers,
revertOnInvalid,
orderHashes
);
// Update the length of the orderHashes array.
assembly {
mstore(orderHashes, add(i, 1))
}
// Do not track hash or adjust prices if order is not fulfilled.
if (numerator == 0) {
// Mark fill fraction as zero if the order is not fulfilled.
advancedOrder.numerator = 0;
// Continue iterating through the remaining orders.
continue;
}
// Otherwise, track the order hash in question.
orderHashes[i] = orderHash;
// Decrement the number of fulfilled orders.
// Skip underflow check as the condition before
// implies that maximumFulfilled > 0.
maximumFulfilled--;
// Place the start time for the order on the stack.
uint256 startTime = advancedOrder.parameters.startTime;
// Place the end time for the order on the stack.
uint256 endTime = advancedOrder.parameters.endTime;
// Retrieve array of offer items for the order in question.
OfferItem[] memory offer = advancedOrder.parameters.offer;
// Read length of offer array and place on the stack.
uint256 totalOfferItems = offer.length;
// Iterate over each offer item on the order.
for (uint256 j = 0; j < totalOfferItems; ++j) {
// Retrieve the offer item.
OfferItem memory offerItem = offer[j];
assembly {
// If the offer item is for the native token, set the
// first bit of the error buffer to true.
invalidNativeOfferItemErrorBuffer := or(
invalidNativeOfferItemErrorBuffer,
iszero(mload(offerItem))
)
}
// Apply order fill fraction to offer item end amount.
uint256 endAmount = _getFraction(
numerator,
denominator,
offerItem.endAmount
);
// Reuse same fraction if start and end amounts are equal.
if (offerItem.startAmount == offerItem.endAmount) {
// Apply derived amount to both start and end amount.
offerItem.startAmount = endAmount;
} else {
// Apply order fill fraction to offer item start amount.
offerItem.startAmount = _getFraction(
numerator,
denominator,
offerItem.startAmount
);
}
// Update end amount in memory to match the derived amount.
offerItem.endAmount = endAmount;
// Adjust offer amount using current time; round down.
offerItem.startAmount = _locateCurrentAmount(
offerItem.startAmount,
offerItem.endAmount,
startTime,
endTime,
false // round down
);
}
// Retrieve array of consideration items for order in question.
ConsiderationItem[] memory consideration = (
advancedOrder.parameters.consideration
);
// Read length of consideration array and place on the stack.
uint256 totalConsiderationItems = consideration.length;
// Iterate over each consideration item on the order.
for (uint256 j = 0; j < totalConsiderationItems; ++j) {
// Retrieve the consideration item.
ConsiderationItem memory considerationItem = (
consideration[j]
);
// Apply fraction to consideration item end amount.
uint256 endAmount = _getFraction(
numerator,
denominator,
considerationItem.endAmount
);
// Reuse same fraction if start and end amounts are equal.
if (
considerationItem.startAmount ==
considerationItem.endAmount
) {
// Apply derived amount to both start and end amount.
considerationItem.startAmount = endAmount;
} else {
// Apply fraction to consideration item start amount.
considerationItem.startAmount = _getFraction(
numerator,
denominator,
considerationItem.startAmount
);
}
// Update end amount in memory to match the derived amount.
considerationItem.endAmount = endAmount;
// Adjust consideration amount using current time; round up.
considerationItem.startAmount = (
_locateCurrentAmount(
considerationItem.startAmount,
considerationItem.endAmount,
startTime,
endTime,
true // round up
)
);
// Utilize assembly to manually "shift" the recipient value.
assembly {
// Write recipient to endAmount, as endAmount is not
// used from this point on and can be repurposed to fit
// the layout of a ReceivedItem.
mstore(
add(
considerationItem,
ReceivedItem_recipient_offset // old endAmount
),
mload(
add(
considerationItem,
ConsiderationItem_recipient_offset
)
)
)
}
}
}
}
// If the first bit is set, a native offer item was encountered. If the
// second bit is set in the error buffer, the current function is not
// matchOrders or matchAdvancedOrders. If the value is three, both the
// first and second bits were set; in that case, revert with an error.
if (invalidNativeOfferItemErrorBuffer == 3) {
revert InvalidNativeOfferItem();
}
// Apply criteria resolvers to each order as applicable.
_applyCriteriaResolvers(advancedOrders, criteriaResolvers);
// Emit an event for each order signifying that it has been fulfilled.
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ++i) {
// Do not emit an event if no order hash is present.
if (orderHashes[i] == bytes32(0)) {
continue;
}
// Retrieve parameters for the order in question.
OrderParameters memory orderParameters = (
advancedOrders[i].parameters
);
// Emit an OrderFulfilled event.
_emitOrderFulfilledEvent(
orderHashes[i],
orderParameters.offerer,
orderParameters.zone,
recipient,
orderParameters.offer,
orderParameters.consideration
);
}
}
}
/**
* @dev Internal function to fulfill a group of validated orders, fully or
* partially, with an arbitrary number of items for offer and
* consideration per order and to execute transfers. Any order that is
* not currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration items
* will then be aggregated where possible as indicated by the supplied
* offer and consideration component arrays and aggregated items will
* be transferred to the fulfiller or to each intended recipient,
* respectively. Note that a failing item transfer or an issue with
* order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or the conduit if indicated by
* the order) to transfer any relevant
* tokens on their behalf and that
* contracts must implement
* `onERC1155Received` in order to receive
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on Consideration.
* @param recipient The intended recipient for all received
* items.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _executeAvailableFulfillments(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[][] memory offerFulfillments,
FulfillmentComponent[][] memory considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient
)
internal
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Retrieve length of offer fulfillments array and place on the stack.
uint256 totalOfferFulfillments = offerFulfillments.length;
// Retrieve length of consideration fulfillments array & place on stack.
uint256 totalConsiderationFulfillments = (
considerationFulfillments.length
);
// Allocate an execution for each offer and consideration fulfillment.
executions = new Execution[](
totalOfferFulfillments + totalConsiderationFulfillments
);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Track number of filtered executions.
uint256 totalFilteredExecutions = 0;
// Iterate over each offer fulfillment.
for (uint256 i = 0; i < totalOfferFulfillments; ++i) {
/// Retrieve the offer fulfillment components in question.
FulfillmentComponent[] memory components = (
offerFulfillments[i]
);
// Derive aggregated execution corresponding with fulfillment.
Execution memory execution = _aggregateAvailable(
advancedOrders,
Side.OFFER,
components,
fulfillerConduitKey,
recipient
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
// Otherwise, assign the execution to the executions array.
executions[i - totalFilteredExecutions] = execution;
}
}
// Iterate over each consideration fulfillment.
for (uint256 i = 0; i < totalConsiderationFulfillments; ++i) {
/// Retrieve consideration fulfillment components in question.
FulfillmentComponent[] memory components = (
considerationFulfillments[i]
);
// Derive aggregated execution corresponding with fulfillment.
Execution memory execution = _aggregateAvailable(
advancedOrders,
Side.CONSIDERATION,
components,
fulfillerConduitKey,
address(0) // unused
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
// Otherwise, assign the execution to the executions array.
executions[
i + totalOfferFulfillments - totalFilteredExecutions
] = execution;
}
}
// If some number of executions have been filtered...
if (totalFilteredExecutions != 0) {
// reduce the total length of the executions array.
assembly {
mstore(
executions,
sub(mload(executions), totalFilteredExecutions)
)
}
}
}
// Revert if no orders are available.
if (executions.length == 0) {
revert NoSpecifiedOrdersAvailable();
}
// Perform final checks and return.
availableOrders = _performFinalChecksAndExecuteOrders(
advancedOrders,
executions
);
return (availableOrders, executions);
}
/**
* @dev Internal function to perform a final check that each consideration
* item for an arbitrary number of fulfilled orders has been met and to
* trigger associated executions, transferring the respective items.
*
* @param advancedOrders The orders to check and perform executions for.
* @param executions An array of elements indicating the sequence of
* transfers to perform when fulfilling the given
* orders.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
*/
function _performFinalChecksAndExecuteOrders(
AdvancedOrder[] memory advancedOrders,
Execution[] memory executions
) internal returns (bool[] memory availableOrders) {
// Retrieve the length of the advanced orders array and place on stack.
uint256 totalOrders = advancedOrders.length;
// Initialize array for tracking available orders.
availableOrders = new bool[](totalOrders);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over orders to ensure all considerations are met.
for (uint256 i = 0; i < totalOrders; ++i) {
// Retrieve the order in question.
AdvancedOrder memory advancedOrder = advancedOrders[i];
// Skip consideration item checks for order if not fulfilled.
if (advancedOrder.numerator == 0) {
// Note: orders do not need to be marked as unavailable as a
// new memory region has been allocated. Review carefully if
// altering compiler version or managing memory manually.
continue;
}
// Mark the order as available.
availableOrders[i] = true;
// Retrieve consideration items to ensure they are fulfilled.
ConsiderationItem[] memory consideration = (
advancedOrder.parameters.consideration
);
// Read length of consideration array and place on the stack.
uint256 totalConsiderationItems = consideration.length;
// Iterate over each consideration item to ensure it is met.
for (uint256 j = 0; j < totalConsiderationItems; ++j) {
// Retrieve remaining amount on the consideration item.
uint256 unmetAmount = consideration[j].startAmount;
// Revert if the remaining amount is not zero.
if (unmetAmount != 0) {
revert ConsiderationNotMet(i, j, unmetAmount);
}
}
}
}
// Put ether value supplied by the caller on the stack.
uint256 etherRemaining = msg.value;
// Initialize an accumulator array. From this point forward, no new
// memory regions can be safely allocated until the accumulator is no
// longer being utilized, as the accumulator operates in an open-ended
// fashion from this memory pointer; existing memory may still be
// accessed and modified, however.
bytes memory accumulator = new bytes(AccumulatorDisarmed);
// Retrieve the length of the executions array and place on stack.
uint256 totalExecutions = executions.length;
// Iterate over each execution.
for (uint256 i = 0; i < totalExecutions; ) {
// Retrieve the execution and the associated received item.
Execution memory execution = executions[i];
ReceivedItem memory item = execution.item;
// If execution transfers native tokens, reduce value available.
if (item.itemType == ItemType.NATIVE) {
// Ensure that sufficient native tokens are still available.
if (item.amount > etherRemaining) {
revert InsufficientEtherSupplied();
}
// Skip underflow check as amount is less than ether remaining.
unchecked {
etherRemaining -= item.amount;
}
}
// Transfer the item specified by the execution.
_transfer(
item,
execution.offerer,
execution.conduitKey,
accumulator
);
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
++i;
}
}
// Trigger any remaining accumulated transfers via call to the conduit.
_triggerIfArmed(accumulator);
// If any ether remains after fulfillments, return it to the caller.
if (etherRemaining != 0) {
_transferEth(payable(msg.sender), etherRemaining);
}
// Clear the reentrancy guard.
_clearReentrancyGuard();
// Return the array containing available orders.
return (availableOrders);
}
/**
* @dev Internal function to match an arbitrary number of full or partial
* orders, each with an arbitrary number of items for offer and
* consideration, supplying criteria resolvers containing specific
* token identifiers and associated proofs as well as fulfillments
* allocating offer components to consideration components.
*
* @param advancedOrders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or their conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _matchAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
Fulfillment[] calldata fulfillments
) internal returns (Execution[] memory executions) {
// Validate orders, update order status, and determine item amounts.
_validateOrdersAndPrepareToFulfill(
advancedOrders,
criteriaResolvers,
true, // Signifies that invalid orders should revert.
advancedOrders.length,
address(0) // OrderFulfilled event has no recipient when matching.
);
// Fulfill the orders using the supplied fulfillments.
return _fulfillAdvancedOrders(advancedOrders, fulfillments);
}
/**
* @dev Internal function to fulfill an arbitrary number of orders, either
* full or partial, after validating, adjusting amounts, and applying
* criteria resolvers.
*
* @param advancedOrders The orders to match, including a fraction to
* attempt to fill for each order.
* @param fulfillments An array of elements allocating offer
* components to consideration components. Note
* that the final amount of each consideration
* component must be zero for a match operation to
* be considered valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _fulfillAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
Fulfillment[] calldata fulfillments
) internal returns (Execution[] memory executions) {
// Retrieve fulfillments array length and place on the stack.
uint256 totalFulfillments = fulfillments.length;
// Allocate executions by fulfillment and apply them to each execution.
executions = new Execution[](totalFulfillments);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Track number of filtered executions.
uint256 totalFilteredExecutions = 0;
// Iterate over each fulfillment.
for (uint256 i = 0; i < totalFulfillments; ++i) {
/// Retrieve the fulfillment in question.
Fulfillment calldata fulfillment = fulfillments[i];
// Derive the execution corresponding with the fulfillment.
Execution memory execution = _applyFulfillment(
advancedOrders,
fulfillment.offerComponents,
fulfillment.considerationComponents
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// Increment total filtered executions.
++totalFilteredExecutions;
} else {
// Otherwise, assign the execution to the executions array.
executions[i - totalFilteredExecutions] = execution;
}
}
// If some number of executions have been filtered...
if (totalFilteredExecutions != 0) {
// reduce the total length of the executions array.
assembly {
mstore(
executions,
sub(mload(executions), totalFilteredExecutions)
)
}
}
}
// Perform final checks and execute orders.
_performFinalChecksAndExecuteOrders(advancedOrders, executions);
// Return the executions array.
return (executions);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
// prettier-ignore
import {
ConsiderationInterface
} from "../interfaces/ConsiderationInterface.sol";
// prettier-ignore
import {
OrderComponents,
BasicOrderParameters,
OrderParameters,
Order,
AdvancedOrder,
OrderStatus,
CriteriaResolver,
Fulfillment,
FulfillmentComponent,
Execution
} from "./ConsiderationStructs.sol";
import { OrderCombiner } from "./OrderCombiner.sol";
/**
* @title Consideration
* @author 0age
* @custom:coauthor d1ll0n
* @custom:coauthor transmissions11
* @custom:version 1.1
* @notice Consideration is a generalized ETH/ERC20/ERC721/ERC1155 marketplace.
* It minimizes external calls to the greatest extent possible and
* provides lightweight methods for common routes as well as more
* flexible methods for composing advanced orders or groups of orders.
* Each order contains an arbitrary number of items that may be spent
* (the "offer") along with an arbitrary number of items that must be
* received back by the indicated recipients (the "consideration").
*/
contract Consideration is ConsiderationInterface, OrderCombiner {
/**
* @notice Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) OrderCombiner(conduitController) {}
/**
* @notice Fulfill an order offering an ERC20, ERC721, or ERC1155 item by
* supplying Ether (or other native tokens), ERC20 tokens, an ERC721
* item, or an ERC1155 item as consideration. Six permutations are
* supported: Native token to ERC721, Native token to ERC1155, ERC20
* to ERC721, ERC20 to ERC1155, ERC721 to ERC20, and ERC1155 to
* ERC20 (with native tokens supplied as msg.value). For an order to
* be eligible for fulfillment via this method, it must contain a
* single offer item (though that item may have a greater amount if
* the item is not an ERC721). An arbitrary number of "additional
* recipients" may also be supplied which will each receive native
* tokens or ERC20 items from the fulfiller as consideration. Refer
* to the documentation for a more comprehensive summary of how to
* utilize this method and what orders are compatible with it.
*
* @param parameters Additional information on the fulfilled order. Note
* that the offerer and the fulfiller must first approve
* this contract (or their chosen conduit if indicated)
* before any tokens can be transferred. Also note that
* contract recipients of ERC1155 consideration items must
* implement `onERC1155Received` in order to receive those
* items.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillBasicOrder(BasicOrderParameters calldata parameters)
external
payable
override
returns (bool fulfilled)
{
// Validate and fulfill the basic order.
fulfilled = _validateAndFulfillBasicOrder(parameters);
}
/**
* @notice Fulfill an order with an arbitrary number of items for offer and
* consideration. Note that this function does not support
* criteria-based orders or partial filling of orders (though
* filling the remainder of a partially-filled order is supported).
*
* @param order The order to fulfill. Note that both the
* offerer and the fulfiller must first approve
* this contract (or the corresponding conduit if
* indicated) to transfer any relevant tokens on
* their behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155 tokens
* as consideration.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used (and direct approvals set on
* Consideration).
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey)
external
payable
override
returns (bool fulfilled)
{
// Convert order to "advanced" order, then validate and fulfill it.
fulfilled = _validateAndFulfillAdvancedOrder(
_convertOrderToAdvanced(order),
new CriteriaResolver[](0), // No criteria resolvers supplied.
fulfillerConduitKey,
msg.sender
);
}
/**
* @notice Fill an order, fully or partially, with an arbitrary number of
* items for offer and consideration alongside criteria resolvers
* containing specific token identifiers and associated proofs.
*
* @param advancedOrder The order to fulfill along with the fraction
* of the order to attempt to fill. Note that
* both the offerer and the fulfiller must first
* approve this contract (or their conduit if
* indicated by the order) to transfer any
* relevant tokens on their behalf and that
* contracts must implement `onERC1155Received`
* to receive ERC1155 tokens as consideration.
* Also note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount with
* the supplied fraction for the partial fill to
* be considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a proof
* that the supplied token identifier is
* contained in the merkle root held by the item
* in question's criteria element. Note that an
* empty criteria indicates that any
* (transferable) token identifier on the token
* in question is valid and that no associated
* proof needs to be supplied.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used (and direct approvals set on
* Consideration).
* @param recipient The intended recipient for all received items,
* with `address(0)` indicating that the caller
* should receive the items.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillAdvancedOrder(
AdvancedOrder calldata advancedOrder,
CriteriaResolver[] calldata criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) external payable override returns (bool fulfilled) {
// Validate and fulfill the order.
fulfilled = _validateAndFulfillAdvancedOrder(
advancedOrder,
criteriaResolvers,
fulfillerConduitKey,
recipient == address(0) ? msg.sender : recipient
);
}
/**
* @notice Attempt to fill a group of orders, each with an arbitrary number
* of items for offer and consideration. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
* Note that this function does not support criteria-based orders or
* partial filling of orders (though filling the remainder of a
* partially-filled order is supported).
*
* @param orders The orders to fulfill. Note that both
* the offerer and the fulfiller must first
* approve this contract (or the
* corresponding conduit if indicated) to
* transfer any relevant tokens on their
* behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155
* tokens as consideration.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used (and
* direct approvals set on Consideration).
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableOrders(
Order[] calldata orders,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
uint256 maximumFulfilled
)
external
payable
override
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Convert orders to "advanced" orders and fulfill all available orders.
return
_fulfillAvailableAdvancedOrders(
_convertOrdersToAdvanced(orders), // Convert to advanced orders.
new CriteriaResolver[](0), // No criteria resolvers supplied.
offerFulfillments,
considerationFulfillments,
fulfillerConduitKey,
msg.sender,
maximumFulfilled
);
}
/**
* @notice Attempt to fill a group of orders, fully or partially, with an
* arbitrary number of items for offer and consideration per order
* alongside criteria resolvers containing specific token
* identifiers and associated proofs. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or their conduit if indicated
* by the order) to transfer any relevant
* tokens on their behalf and that
* contracts must implement
* `onERC1155Received` in order to receive
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used (and
* direct approvals set on Consideration).
* @param recipient The intended recipient for all received
* items, with `address(0)` indicating that
* the caller should receive the items.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
)
external
payable
override
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Fulfill all available orders.
return
_fulfillAvailableAdvancedOrders(
advancedOrders,
criteriaResolvers,
offerFulfillments,
considerationFulfillments,
fulfillerConduitKey,
recipient == address(0) ? msg.sender : recipient,
maximumFulfilled
);
}
/**
* @notice Match an arbitrary number of orders, each with an arbitrary
* number of items for offer and consideration along with a set of
* fulfillments allocating offer components to consideration
* components. Note that this function does not support
* criteria-based or partial filling of orders (though filling the
* remainder of a partially-filled order is supported).
*
* @param orders The orders to match. Note that both the offerer
* and fulfiller on each order must first approve
* this contract (or their conduit if indicated by
* the order) to transfer any relevant tokens on
* their behalf and each consideration recipient
* must implement `onERC1155Received` in order to
* receive ERC1155 tokens.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchOrders(
Order[] calldata orders,
Fulfillment[] calldata fulfillments
) external payable override returns (Execution[] memory executions) {
// Convert to advanced, validate, and match orders using fulfillments.
return
_matchAdvancedOrders(
_convertOrdersToAdvanced(orders),
new CriteriaResolver[](0), // No criteria resolvers supplied.
fulfillments
);
}
/**
* @notice Match an arbitrary number of full or partial orders, each with an
* arbitrary number of items for offer and consideration, supplying
* criteria resolvers containing specific token identifiers and
* associated proofs as well as fulfillments allocating offer
* components to consideration components.
*
* @param advancedOrders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or their conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
) external payable override returns (Execution[] memory executions) {
// Validate and match the advanced orders using supplied fulfillments.
return
_matchAdvancedOrders(
advancedOrders,
criteriaResolvers,
fulfillments
);
}
/**
* @notice Cancel an arbitrary number of orders. Note that only the offerer
* or the zone of a given order may cancel it. Callers should ensure
* that the intended order was cancelled by calling `getOrderStatus`
* and confirming that `isCancelled` returns `true`.
*
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancel(OrderComponents[] calldata orders)
external
override
returns (bool cancelled)
{
// Cancel the orders.
cancelled = _cancel(orders);
}
/**
* @notice Validate an arbitrary number of orders, thereby registering their
* signatures as valid and allowing the fulfiller to skip signature
* verification on fulfillment. Note that validated orders may still
* be unfulfillable due to invalid item amounts or other factors;
* callers should determine whether validated orders are fulfillable
* by simulating the fulfillment call prior to execution. Also note
* that anyone can validate a signed order, but only the offerer can
* validate an order without supplying a signature.
*
* @param orders The orders to validate.
*
* @return validated A boolean indicating whether the supplied orders have
* been successfully validated.
*/
function validate(Order[] calldata orders)
external
override
returns (bool validated)
{
// Validate the orders.
validated = _validate(orders);
}
/**
* @notice Cancel all orders from a given offerer with a given zone in bulk
* by incrementing a counter. Note that only the offerer may
* increment the counter.
*
* @return newCounter The new counter.
*/
function incrementCounter() external override returns (uint256 newCounter) {
// Increment current counter for the supplied offerer.
newCounter = _incrementCounter();
}
/**
* @notice Retrieve the order hash for a given order.
*
* @param order The components of the order.
*
* @return orderHash The order hash.
*/
function getOrderHash(OrderComponents calldata order)
external
view
override
returns (bytes32 orderHash)
{
// Derive order hash by supplying order parameters along with counter.
orderHash = _deriveOrderHash(
OrderParameters(
order.offerer,
order.zone,
order.offer,
order.consideration,
order.orderType,
order.startTime,
order.endTime,
order.zoneHash,
order.salt,
order.conduitKey,
order.consideration.length
),
order.counter
);
}
/**
* @notice Retrieve the status of a given order by hash, including whether
* the order has been cancelled or validated and the fraction of the
* order that has been filled.
*
* @param orderHash The order hash in question.
*
* @return isValidated A boolean indicating whether the order in question
* has been validated (i.e. previously approved or
* partially filled).
* @return isCancelled A boolean indicating whether the order in question
* has been cancelled.
* @return totalFilled The total portion of the order that has been filled
* (i.e. the "numerator").
* @return totalSize The total size of the order that is either filled or
* unfilled (i.e. the "denominator").
*/
function getOrderStatus(bytes32 orderHash)
external
view
override
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
)
{
// Retrieve the order status using the order hash.
return _getOrderStatus(orderHash);
}
/**
* @notice Retrieve the current counter for a given offerer.
*
* @param offerer The offerer in question.
*
* @return counter The current counter.
*/
function getCounter(address offerer)
external
view
override
returns (uint256 counter)
{
// Return the counter for the supplied offerer.
counter = _getCounter(offerer);
}
/**
* @notice Retrieve configuration information for this contract.
*
* @return version The contract version.
* @return domainSeparator The domain separator for this contract.
* @return conduitController The conduit Controller set for this contract.
*/
function information()
external
view
override
returns (
string memory version,
bytes32 domainSeparator,
address conduitController
)
{
// Return the information for this contract.
return _information();
}
/**
* @notice Retrieve the name of this contract.
*
* @return contractName The name of this contract.
*/
function name()
external
pure
override
returns (string memory contractName)
{
// Return the name of the contract.
contractName = _name();
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
BasicOrderParameters,
OrderComponents,
Fulfillment,
FulfillmentComponent,
Execution,
Order,
AdvancedOrder,
OrderStatus,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
/**
* @title ConsiderationInterface
* @author 0age
* @custom:version 1.1
* @notice Consideration is a generalized ETH/ERC20/ERC721/ERC1155 marketplace.
* It minimizes external calls to the greatest extent possible and
* provides lightweight methods for common routes as well as more
* flexible methods for composing advanced orders.
*
* @dev ConsiderationInterface contains all external function interfaces for
* Consideration.
*/
interface ConsiderationInterface {
/**
* @notice Fulfill an order offering an ERC721 token by supplying Ether (or
* the native token for the given chain) as consideration for the
* order. An arbitrary number of "additional recipients" may also be
* supplied which will each receive native tokens from the fulfiller
* as consideration.
*
* @param parameters Additional information on the fulfilled order. Note
* that the offerer must first approve this contract (or
* their preferred conduit if indicated by the order) for
* their offered ERC721 token to be transferred.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillBasicOrder(BasicOrderParameters calldata parameters)
external
payable
returns (bool fulfilled);
/**
* @notice Fulfill an order with an arbitrary number of items for offer and
* consideration. Note that this function does not support
* criteria-based orders or partial filling of orders (though
* filling the remainder of a partially-filled order is supported).
*
* @param order The order to fulfill. Note that both the
* offerer and the fulfiller must first approve
* this contract (or the corresponding conduit if
* indicated) to transfer any relevant tokens on
* their behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155 tokens
* as consideration.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Consideration.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey)
external
payable
returns (bool fulfilled);
/**
* @notice Fill an order, fully or partially, with an arbitrary number of
* items for offer and consideration alongside criteria resolvers
* containing specific token identifiers and associated proofs.
*
* @param advancedOrder The order to fulfill along with the fraction
* of the order to attempt to fill. Note that
* both the offerer and the fulfiller must first
* approve this contract (or their preferred
* conduit if indicated by the order) to transfer
* any relevant tokens on their behalf and that
* contracts must implement `onERC1155Received`
* to receive ERC1155 tokens as consideration.
* Also note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount with
* the supplied fraction for the partial fill to
* be considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a proof
* that the supplied token identifier is
* contained in the merkle root held by the item
* in question's criteria element. Note that an
* empty criteria indicates that any
* (transferable) token identifier on the token
* in question is valid and that no associated
* proof needs to be supplied.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Consideration.
* @param recipient The intended recipient for all received items,
* with `address(0)` indicating that the caller
* should receive the items.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillAdvancedOrder(
AdvancedOrder calldata advancedOrder,
CriteriaResolver[] calldata criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) external payable returns (bool fulfilled);
/**
* @notice Attempt to fill a group of orders, each with an arbitrary number
* of items for offer and consideration. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
* Note that this function does not support criteria-based orders or
* partial filling of orders (though filling the remainder of a
* partially-filled order is supported).
*
* @param orders The orders to fulfill. Note that both
* the offerer and the fulfiller must first
* approve this contract (or the
* corresponding conduit if indicated) to
* transfer any relevant tokens on their
* behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155
* tokens as consideration.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableOrders(
Order[] calldata orders,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Attempt to fill a group of orders, fully or partially, with an
* arbitrary number of items for offer and consideration per order
* alongside criteria resolvers containing specific token
* identifiers and associated proofs. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or their preferred conduit if
* indicated by the order) to transfer any
* relevant tokens on their behalf and that
* contracts must implement
* `onERC1155Received` to enable receipt of
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param recipient The intended recipient for all received
* items, with `address(0)` indicating that
* the caller should receive the items.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableAdvancedOrders(
AdvancedOrder[] calldata advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Match an arbitrary number of orders, each with an arbitrary
* number of items for offer and consideration along with as set of
* fulfillments allocating offer components to consideration
* components. Note that this function does not support
* criteria-based or partial filling of orders (though filling the
* remainder of a partially-filled order is supported).
*
* @param orders The orders to match. Note that both the offerer and
* fulfiller on each order must first approve this
* contract (or their conduit if indicated by the order)
* to transfer any relevant tokens on their behalf and
* each consideration recipient must implement
* `onERC1155Received` to enable ERC1155 token receipt.
* @param fulfillments An array of elements allocating offer components to
* consideration components. Note that each
* consideration component must be fully met for the
* match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchOrders(
Order[] calldata orders,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Match an arbitrary number of full or partial orders, each with an
* arbitrary number of items for offer and consideration, supplying
* criteria resolvers containing specific token identifiers and
* associated proofs as well as fulfillments allocating offer
* components to consideration components.
*
* @param orders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or a preferred conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchAdvancedOrders(
AdvancedOrder[] calldata orders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Cancel an arbitrary number of orders. Note that only the offerer
* or the zone of a given order may cancel it. Callers should ensure
* that the intended order was cancelled by calling `getOrderStatus`
* and confirming that `isCancelled` returns `true`.
*
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancel(OrderComponents[] calldata orders)
external
returns (bool cancelled);
/**
* @notice Validate an arbitrary number of orders, thereby registering their
* signatures as valid and allowing the fulfiller to skip signature
* verification on fulfillment. Note that validated orders may still
* be unfulfillable due to invalid item amounts or other factors;
* callers should determine whether validated orders are fulfillable
* by simulating the fulfillment call prior to execution. Also note
* that anyone can validate a signed order, but only the offerer can
* validate an order without supplying a signature.
*
* @param orders The orders to validate.
*
* @return validated A boolean indicating whether the supplied orders have
* been successfully validated.
*/
function validate(Order[] calldata orders)
external
returns (bool validated);
/**
* @notice Cancel all orders from a given offerer with a given zone in bulk
* by incrementing a counter. Note that only the offerer may
* increment the counter.
*
* @return newCounter The new counter.
*/
function incrementCounter() external returns (uint256 newCounter);
/**
* @notice Retrieve the order hash for a given order.
*
* @param order The components of the order.
*
* @return orderHash The order hash.
*/
function getOrderHash(OrderComponents calldata order)
external
view
returns (bytes32 orderHash);
/**
* @notice Retrieve the status of a given order by hash, including whether
* the order has been cancelled or validated and the fraction of the
* order that has been filled.
*
* @param orderHash The order hash in question.
*
* @return isValidated A boolean indicating whether the order in question
* has been validated (i.e. previously approved or
* partially filled).
* @return isCancelled A boolean indicating whether the order in question
* has been cancelled.
* @return totalFilled The total portion of the order that has been filled
* (i.e. the "numerator").
* @return totalSize The total size of the order that is either filled or
* unfilled (i.e. the "denominator").
*/
function getOrderStatus(bytes32 orderHash)
external
view
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
);
/**
* @notice Retrieve the current counter for a given offerer.
*
* @param offerer The offerer in question.
*
* @return counter The current counter.
*/
function getCounter(address offerer)
external
view
returns (uint256 counter);
/**
* @notice Retrieve configuration information for this contract.
*
* @return version The contract version.
* @return domainSeparator The domain separator for this contract.
* @return conduitController The conduit Controller set for this contract.
*/
function information()
external
view
returns (
string memory version,
bytes32 domainSeparator,
address conduitController
);
/**
* @notice Retrieve the name of this contract.
*
* @return contractName The name of this contract.
*/
function name() external view returns (string memory contractName);
}
File 2 of 5: Feetpix
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "operator-filter-registry/src/OperatorFilterer.sol";
import "./PaymentMinimum.sol";
// __ _ _
// / _| | | (_)
// | |_ ___ ___ | |_ _ __ _ __ __
// | _| / _ \\ / _ \\ | __| | '_ \\ | | \\ \\/ /
// | | | __/ | __/ | |_ | |_) | | | > <
// |_| \\___| \\___| \\__| | .__/ |_| /_/\\_\\
// | |
// |_|
contract Feetpix is ERC721A, Ownable, PaymentMinimum, OperatorFilterer {
/** Maximum number of tokens per wallet */
uint256 public constant MAX_WALLET = 10;
/** Maximum amount of tokens in collection */
uint256 public constant MAX_SUPPLY = 10000;
/** Price per token */
uint256 public cost = 0.0039 ether;
/** Max free */
uint256 public free = 7000;
/** Base URI */
string public baseURI;
/** Burn service */
address public authorizedOperator;
/** Whitelist max per wallet */
uint256 public constant MAX_PER_WHITELIST = 10;
/** Public sale state */
bool public saleActive = false;
/** Presale state */
bool public presaleActive = false;
/** Notify on sale state change */
event SaleStateChanged(bool _val);
/** Notify on presale state change */
event PresaleStateChanged(bool _val);
/** Notify on total supply change */
event TotalSupplyChanged(uint256 _val);
constructor(
string memory _name,
string memory _symbol,
uint256 _minimum,
address _builder,
address[] memory _shareholders,
uint256[] memory _shares
)
ERC721A(_name, _symbol)
PaymentMinimum(_minimum, _builder, _shareholders, _shares)
OperatorFilterer(address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6), false) {}
/// @notice Returns the base URI
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
/// @notice Gets cost to mint n amount of NFTs, taking account for first one free
/// @param _numberMinted How many a given wallet has already minted
/// @param _numberToMint How many a given wallet is planning to mint
/// @param _costPerMint Price of one nft
function subtotal(uint256 _numberMinted, uint256 _numberToMint, uint256 _costPerMint) public pure returns (uint256) {
return _numberToMint * _costPerMint - (_numberMinted > 0 ? 0 : _costPerMint);
}
/// @notice Gets number of NFTs minted for a given wallet
/// @param _wallet Wallet to check
function numberMinted(address _wallet) external view returns (uint256) {
return _numberMinted(_wallet);
}
/// @notice Sets public sale state
/// @param _val New sale state
function setSaleState(bool _val) external onlyOwner {
saleActive = _val;
emit SaleStateChanged(_val);
}
/// @notice Sets presale state
/// @param _val New presale state
function setPresaleState(bool _val) external onlyOwner {
presaleActive = _val;
emit PresaleStateChanged(_val);
}
/// @notice Sets the price
/// @param _val New price
function setCost(uint256 _val) external onlyOwner {
cost = _val;
}
/// @notice Sets the base metadata URI
/// @param _val The new URI
function setBaseURI(string calldata _val) external onlyOwner {
baseURI = _val;
}
/// @notice Sets the authorized operator
/// @param _val The new operator
/// @dev Allows staking service to transfer nfts
function setAuthorizedOperator(address _val) external onlyOwner {
authorizedOperator = _val;
}
/// @notice Reserves a set of NFTs for collection owner (giveaways, etc)
/// @param _amt The amount to reserve
function reserve(uint256 _amt) external onlyOwner {
require(free >= _amt, "Cannot exceed free mint limit.");
free -= _amt;
_mint(msg.sender, _amt);
emit TotalSupplyChanged(totalSupply());
}
/// @notice Mints a new token in presale
/// @param _amt The number of tokens to mint
/// @dev Must send cost * amt in ETH
function preMint(uint256 _amt) external payable {
require(presaleActive, "Presale is not active.");
require(_amt <= MAX_PER_WHITELIST, "Amount of tokens exceeds transaction limit.");
require(_amt <= free, "Amount exceeds supply.");
require(subtotal(_numberMinted(msg.sender), _amt, cost - 0.0019 ether) == msg.value, "ETH sent not equal to cost.");
require(free > 0, "Presale has ended.");
free -= _amt;
// Switch to public once all free mints are gone
if (free == 0) { presaleActive = false; saleActive = true; }
_safeMint(msg.sender, _amt);
emit TotalSupplyChanged(totalSupply());
}
/// @notice Mints a new token in public sale
/// @param _amt The number of tokens to mint
/// @dev Must send cost * amt in ETH
function mint(uint256 _amt) external payable {
require(saleActive, "Sale is not active.");
require(_amt <= MAX_WALLET, "Amount of tokens exceeds transaction limit.");
require(totalSupply() + _amt <= MAX_SUPPLY, "Amount exceeds supply.");
require(cost * _amt == msg.value, "ETH sent not equal to cost.");
_safeMint(msg.sender, _amt);
emit TotalSupplyChanged(totalSupply());
}
/// @dev Override to use filter operator
function transferFrom(address from, address to, uint256 tokenId) public payable override onlyAllowedOperator(from) {
super.transferFrom(from, to, tokenId);
}
/// @dev Override to use filter operator
/// @dev We give the staking/other service direct access as a convenience to the consumer (one less transaction, gas)
function safeTransferFrom(address from, address to, uint256 tokenId) public payable override onlyAllowedOperator(from) {
if (authorizedOperator != _msgSender()) {
require(from == _msgSenderERC721A() || isApprovedForAll(from, _msgSenderERC721A()), "ERC721A: transfer caller is not owner nor approved");
}
super.safeTransferFrom(from, to, tokenId);
}
/// @dev Override to use filter operator
/// @dev We give the staking/other service direct access as a convenience to the consumer (one less transaction, gas)
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public payable override onlyAllowedOperator(from) {
if (authorizedOperator != _msgSender()) {
require(from == _msgSenderERC721A() || isApprovedForAll(from, _msgSenderERC721A()), "ERC721A: transfer caller is not owner nor approved");
}
super.safeTransferFrom(from, to, tokenId, data);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
/// @title Minimum amount payment splitter
contract PaymentMinimum is PaymentSplitter {
address private immutable AGENCY;
uint256 private _minimumPayment;
constructor(
uint256 _min,
address _agency,
address[] memory _shareholders,
uint256[] memory _shares
) PaymentSplitter(_shareholders, _shares) {
_minimumPayment = _min;
AGENCY = _agency;
}
/// @notice Only agency can call
modifier onlyAgency(address _account) {
require(AGENCY == _account, "PaymentMinimum: Account is not agency.");
_;
}
/// @notice Only after minimum payment is met
modifier onlyAfterMinimum() {
require(_minimumPayment == 0, "PaymentMinimum: Minimum payment is not met.");
_;
}
/// @notice Function for agency to release minimum funds
/// @param _account Agency account
function agencyRelease(address payable _account) public virtual onlyAgency(_account) {
uint256 _balance = address(this).balance;
uint256 _remaining = _minimumPayment;
require(_remaining > 0, "PaymentMinimum: Minimum payment is already met.");
require (_balance > 0, "PaymentMinimum: Contract has no funds.");
uint256 _payment = _balance >= _remaining ? _remaining : _balance;
_minimumPayment -= _payment;
Address.sendValue(_account, _payment);
}
/// @dev Require minimum to be met before allowing a call
function release(address payable account) public virtual override onlyAfterMinimum() {
super.release(account);
}
/// @dev require minimum to be met before allowing a call
function release(IERC20 token, address account) public virtual override onlyAfterMinimum() {
super.release(token, account);
}
}// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs
pragma solidity ^0.8.4;
import './IERC721A.sol';
/**
* @dev Interface of ERC721 token receiver.
*/
interface ERC721A__IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
/**
* @title ERC721A
*
* @dev Implementation of the [ERC721](https://eips.ethereum.org/EIPS/eip-721)
* Non-Fungible Token Standard, including the Metadata extension.
* Optimized for lower gas during batch mints.
*
* Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
* starting from `_startTokenId()`.
*
* Assumptions:
*
* - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
* - The maximum token ID cannot exceed 2**256 - 1 (max value of uint256).
*/
contract ERC721A is IERC721A {
// Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
struct TokenApprovalRef {
address value;
}
// =============================================================
// CONSTANTS
// =============================================================
// Mask of an entry in packed address data.
uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;
// The bit position of `numberMinted` in packed address data.
uint256 private constant _BITPOS_NUMBER_MINTED = 64;
// The bit position of `numberBurned` in packed address data.
uint256 private constant _BITPOS_NUMBER_BURNED = 128;
// The bit position of `aux` in packed address data.
uint256 private constant _BITPOS_AUX = 192;
// Mask of all 256 bits in packed address data except the 64 bits for `aux`.
uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;
// The bit position of `startTimestamp` in packed ownership.
uint256 private constant _BITPOS_START_TIMESTAMP = 160;
// The bit mask of the `burned` bit in packed ownership.
uint256 private constant _BITMASK_BURNED = 1 << 224;
// The bit position of the `nextInitialized` bit in packed ownership.
uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;
// The bit mask of the `nextInitialized` bit in packed ownership.
uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;
// The bit position of `extraData` in packed ownership.
uint256 private constant _BITPOS_EXTRA_DATA = 232;
// Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;
// The mask of the lower 160 bits for addresses.
uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;
// The maximum `quantity` that can be minted with {_mintERC2309}.
// This limit is to prevent overflows on the address data entries.
// For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
// is required to cause an overflow, which is unrealistic.
uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;
// The `Transfer` event signature is given by:
// `keccak256(bytes("Transfer(address,address,uint256)"))`.
bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// =============================================================
// STORAGE
// =============================================================
// The next token ID to be minted.
uint256 private _currentIndex;
// The number of tokens burned.
uint256 private _burnCounter;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to ownership details
// An empty struct value does not necessarily mean the token is unowned.
// See {_packedOwnershipOf} implementation for details.
//
// Bits Layout:
// - [0..159] `addr`
// - [160..223] `startTimestamp`
// - [224] `burned`
// - [225] `nextInitialized`
// - [232..255] `extraData`
mapping(uint256 => uint256) private _packedOwnerships;
// Mapping owner address to address data.
//
// Bits Layout:
// - [0..63] `balance`
// - [64..127] `numberMinted`
// - [128..191] `numberBurned`
// - [192..255] `aux`
mapping(address => uint256) private _packedAddressData;
// Mapping from token ID to approved address.
mapping(uint256 => TokenApprovalRef) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// =============================================================
// CONSTRUCTOR
// =============================================================
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
_currentIndex = _startTokenId();
}
// =============================================================
// TOKEN COUNTING OPERATIONS
// =============================================================
/**
* @dev Returns the starting token ID.
* To change the starting token ID, please override this function.
*/
function _startTokenId() internal view virtual returns (uint256) {
return 0;
}
/**
* @dev Returns the next token ID to be minted.
*/
function _nextTokenId() internal view virtual returns (uint256) {
return _currentIndex;
}
/**
* @dev Returns the total number of tokens in existence.
* Burned tokens will reduce the count.
* To get the total number of tokens minted, please see {_totalMinted}.
*/
function totalSupply() public view virtual override returns (uint256) {
// Counter underflow is impossible as _burnCounter cannot be incremented
// more than `_currentIndex - _startTokenId()` times.
unchecked {
return _currentIndex - _burnCounter - _startTokenId();
}
}
/**
* @dev Returns the total amount of tokens minted in the contract.
*/
function _totalMinted() internal view virtual returns (uint256) {
// Counter underflow is impossible as `_currentIndex` does not decrement,
// and it is initialized to `_startTokenId()`.
unchecked {
return _currentIndex - _startTokenId();
}
}
/**
* @dev Returns the total number of tokens burned.
*/
function _totalBurned() internal view virtual returns (uint256) {
return _burnCounter;
}
// =============================================================
// ADDRESS DATA OPERATIONS
// =============================================================
/**
* @dev Returns the number of tokens in `owner`'s account.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
if (owner == address(0)) revert BalanceQueryForZeroAddress();
return _packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
}
/**
* Returns the number of tokens minted by `owner`.
*/
function _numberMinted(address owner) internal view returns (uint256) {
return (_packedAddressData[owner] >> _BITPOS_NUMBER_MINTED) & _BITMASK_ADDRESS_DATA_ENTRY;
}
/**
* Returns the number of tokens burned by or on behalf of `owner`.
*/
function _numberBurned(address owner) internal view returns (uint256) {
return (_packedAddressData[owner] >> _BITPOS_NUMBER_BURNED) & _BITMASK_ADDRESS_DATA_ENTRY;
}
/**
* Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
*/
function _getAux(address owner) internal view returns (uint64) {
return uint64(_packedAddressData[owner] >> _BITPOS_AUX);
}
/**
* Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
* If there are multiple variables, please pack them into a uint64.
*/
function _setAux(address owner, uint64 aux) internal virtual {
uint256 packed = _packedAddressData[owner];
uint256 auxCasted;
// Cast `aux` with assembly to avoid redundant masking.
assembly {
auxCasted := aux
}
packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
_packedAddressData[owner] = packed;
}
// =============================================================
// IERC165
// =============================================================
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
* to learn more about how these ids are created.
*
* This function call must use less than 30000 gas.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
// The interface IDs are constants representing the first 4 bytes
// of the XOR of all function selectors in the interface.
// See: [ERC165](https://eips.ethereum.org/EIPS/eip-165)
// (e.g. `bytes4(i.functionA.selector ^ i.functionB.selector ^ ...)`)
return
interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
interfaceId == 0x5b5e139f; // ERC165 interface ID for ERC721Metadata.
}
// =============================================================
// IERC721Metadata
// =============================================================
/**
* @dev Returns the token collection name.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the token collection symbol.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = _baseURI();
return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId))) : '';
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, it can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return '';
}
// =============================================================
// OWNERSHIPS OPERATIONS
// =============================================================
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
return address(uint160(_packedOwnershipOf(tokenId)));
}
/**
* @dev Gas spent here starts off proportional to the maximum mint batch size.
* It gradually moves to O(1) as tokens get transferred around over time.
*/
function _ownershipOf(uint256 tokenId) internal view virtual returns (TokenOwnership memory) {
return _unpackedOwnership(_packedOwnershipOf(tokenId));
}
/**
* @dev Returns the unpacked `TokenOwnership` struct at `index`.
*/
function _ownershipAt(uint256 index) internal view virtual returns (TokenOwnership memory) {
return _unpackedOwnership(_packedOwnerships[index]);
}
/**
* @dev Initializes the ownership slot minted at `index` for efficiency purposes.
*/
function _initializeOwnershipAt(uint256 index) internal virtual {
if (_packedOwnerships[index] == 0) {
_packedOwnerships[index] = _packedOwnershipOf(index);
}
}
/**
* Returns the packed ownership data of `tokenId`.
*/
function _packedOwnershipOf(uint256 tokenId) private view returns (uint256) {
uint256 curr = tokenId;
unchecked {
if (_startTokenId() <= curr)
if (curr < _currentIndex) {
uint256 packed = _packedOwnerships[curr];
// If not burned.
if (packed & _BITMASK_BURNED == 0) {
// Invariant:
// There will always be an initialized ownership slot
// (i.e. `ownership.addr != address(0) && ownership.burned == false`)
// before an unintialized ownership slot
// (i.e. `ownership.addr == address(0) && ownership.burned == false`)
// Hence, `curr` will not underflow.
//
// We can directly compare the packed value.
// If the address is zero, packed will be zero.
while (packed == 0) {
packed = _packedOwnerships[--curr];
}
return packed;
}
}
}
revert OwnerQueryForNonexistentToken();
}
/**
* @dev Returns the unpacked `TokenOwnership` struct from `packed`.
*/
function _unpackedOwnership(uint256 packed) private pure returns (TokenOwnership memory ownership) {
ownership.addr = address(uint160(packed));
ownership.startTimestamp = uint64(packed >> _BITPOS_START_TIMESTAMP);
ownership.burned = packed & _BITMASK_BURNED != 0;
ownership.extraData = uint24(packed >> _BITPOS_EXTRA_DATA);
}
/**
* @dev Packs ownership data into a single uint256.
*/
function _packOwnershipData(address owner, uint256 flags) private view returns (uint256 result) {
assembly {
// Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
owner := and(owner, _BITMASK_ADDRESS)
// `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
result := or(owner, or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags))
}
}
/**
* @dev Returns the `nextInitialized` flag set if `quantity` equals 1.
*/
function _nextInitializedFlag(uint256 quantity) private pure returns (uint256 result) {
// For branchless setting of the `nextInitialized` flag.
assembly {
// `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
}
}
// =============================================================
// APPROVAL OPERATIONS
// =============================================================
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the
* zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) public payable virtual override {
address owner = ownerOf(tokenId);
if (_msgSenderERC721A() != owner)
if (!isApprovedForAll(owner, _msgSenderERC721A())) {
revert ApprovalCallerNotOwnerNorApproved();
}
_tokenApprovals[tokenId].value = to;
emit Approval(owner, to, tokenId);
}
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken();
return _tokenApprovals[tokenId].value;
}
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom}
* for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_operatorApprovals[_msgSenderERC721A()][operator] = approved;
emit ApprovalForAll(_msgSenderERC721A(), operator, approved);
}
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted. See {_mint}.
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return
_startTokenId() <= tokenId &&
tokenId < _currentIndex && // If within bounds,
_packedOwnerships[tokenId] & _BITMASK_BURNED == 0; // and not burned.
}
/**
* @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
*/
function _isSenderApprovedOrOwner(
address approvedAddress,
address owner,
address msgSender
) private pure returns (bool result) {
assembly {
// Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
owner := and(owner, _BITMASK_ADDRESS)
// Mask `msgSender` to the lower 160 bits, in case the upper bits somehow aren't clean.
msgSender := and(msgSender, _BITMASK_ADDRESS)
// `msgSender == owner || msgSender == approvedAddress`.
result := or(eq(msgSender, owner), eq(msgSender, approvedAddress))
}
}
/**
* @dev Returns the storage slot and value for the approved address of `tokenId`.
*/
function _getApprovedSlotAndAddress(uint256 tokenId)
private
view
returns (uint256 approvedAddressSlot, address approvedAddress)
{
TokenApprovalRef storage tokenApproval = _tokenApprovals[tokenId];
// The following is equivalent to `approvedAddress = _tokenApprovals[tokenId].value`.
assembly {
approvedAddressSlot := tokenApproval.slot
approvedAddress := sload(approvedAddressSlot)
}
}
// =============================================================
// TRANSFER OPERATIONS
// =============================================================
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token
* by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public payable virtual override {
uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();
(uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);
// The nested ifs save around 20+ gas over a compound boolean condition.
if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
if (to == address(0)) revert TransferToZeroAddress();
_beforeTokenTransfers(from, to, tokenId, 1);
// Clear approvals from the previous owner.
assembly {
if approvedAddress {
// This is equivalent to `delete _tokenApprovals[tokenId]`.
sstore(approvedAddressSlot, 0)
}
}
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
unchecked {
// We can directly increment and decrement the balances.
--_packedAddressData[from]; // Updates: `balance -= 1`.
++_packedAddressData[to]; // Updates: `balance += 1`.
// Updates:
// - `address` to the next owner.
// - `startTimestamp` to the timestamp of transfering.
// - `burned` to `false`.
// - `nextInitialized` to `true`.
_packedOwnerships[tokenId] = _packOwnershipData(
to,
_BITMASK_NEXT_INITIALIZED | _nextExtraData(from, to, prevOwnershipPacked)
);
// If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
uint256 nextTokenId = tokenId + 1;
// If the next slot's address is zero and not burned (i.e. packed value is zero).
if (_packedOwnerships[nextTokenId] == 0) {
// If the next slot is within bounds.
if (nextTokenId != _currentIndex) {
// Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
_packedOwnerships[nextTokenId] = prevOwnershipPacked;
}
}
}
}
emit Transfer(from, to, tokenId);
_afterTokenTransfers(from, to, tokenId, 1);
}
/**
* @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public payable virtual override {
safeTransferFrom(from, to, tokenId, '');
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token
* by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) public payable virtual override {
transferFrom(from, to, tokenId);
if (to.code.length != 0)
if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
revert TransferToNonERC721ReceiverImplementer();
}
}
/**
* @dev Hook that is called before a set of serially-ordered token IDs
* are about to be transferred. This includes minting.
* And also called before burning one token.
*
* `startTokenId` - the first token ID to be transferred.
* `quantity` - the amount to be transferred.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, `tokenId` will be burned by `from`.
* - `from` and `to` are never both zero.
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual {}
/**
* @dev Hook that is called after a set of serially-ordered token IDs
* have been transferred. This includes minting.
* And also called after one token has been burned.
*
* `startTokenId` - the first token ID to be transferred.
* `quantity` - the amount to be transferred.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
* transferred to `to`.
* - When `from` is zero, `tokenId` has been minted for `to`.
* - When `to` is zero, `tokenId` has been burned by `from`.
* - `from` and `to` are never both zero.
*/
function _afterTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual {}
/**
* @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
*
* `from` - Previous owner of the given token ID.
* `to` - Target address that will receive the token.
* `tokenId` - Token ID to be transferred.
* `_data` - Optional data to send along with the call.
*
* Returns whether the call correctly returned the expected magic value.
*/
function _checkContractOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory _data
) private returns (bool) {
try ERC721A__IERC721Receiver(to).onERC721Received(_msgSenderERC721A(), from, tokenId, _data) returns (
bytes4 retval
) {
return retval == ERC721A__IERC721Receiver(to).onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert TransferToNonERC721ReceiverImplementer();
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
// =============================================================
// MINT OPERATIONS
// =============================================================
/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {Transfer} event for each mint.
*/
function _mint(address to, uint256 quantity) internal virtual {
uint256 startTokenId = _currentIndex;
if (quantity == 0) revert MintZeroQuantity();
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are incredibly unrealistic.
// `balance` and `numberMinted` have a maximum limit of 2**64.
// `tokenId` has a maximum limit of 2**256.
unchecked {
// Updates:
// - `balance += quantity`.
// - `numberMinted += quantity`.
//
// We can directly add to the `balance` and `numberMinted`.
_packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);
// Updates:
// - `address` to the owner.
// - `startTimestamp` to the timestamp of minting.
// - `burned` to `false`.
// - `nextInitialized` to `quantity == 1`.
_packedOwnerships[startTokenId] = _packOwnershipData(
to,
_nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
);
uint256 toMasked;
uint256 end = startTokenId + quantity;
// Use assembly to loop and emit the `Transfer` event for gas savings.
// The duplicated `log4` removes an extra check and reduces stack juggling.
// The assembly, together with the surrounding Solidity code, have been
// delicately arranged to nudge the compiler into producing optimized opcodes.
assembly {
// Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
toMasked := and(to, _BITMASK_ADDRESS)
// Emit the `Transfer` event.
log4(
0, // Start of data (0, since no data).
0, // End of data (0, since no data).
_TRANSFER_EVENT_SIGNATURE, // Signature.
0, // `address(0)`.
toMasked, // `to`.
startTokenId // `tokenId`.
)
// The `iszero(eq(,))` check ensures that large values of `quantity`
// that overflows uint256 will make the loop run out of gas.
// The compiler will optimize the `iszero` away for performance.
for {
let tokenId := add(startTokenId, 1)
} iszero(eq(tokenId, end)) {
tokenId := add(tokenId, 1)
} {
// Emit the `Transfer` event. Similar to above.
log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
}
}
if (toMasked == 0) revert MintToZeroAddress();
_currentIndex = end;
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* This function is intended for efficient minting only during contract creation.
*
* It emits only one {ConsecutiveTransfer} as defined in
* [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
* instead of a sequence of {Transfer} event(s).
*
* Calling this function outside of contract creation WILL make your contract
* non-compliant with the ERC721 standard.
* For full ERC721 compliance, substituting ERC721 {Transfer} event(s) with the ERC2309
* {ConsecutiveTransfer} event is only permissible during contract creation.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {ConsecutiveTransfer} event.
*/
function _mintERC2309(address to, uint256 quantity) internal virtual {
uint256 startTokenId = _currentIndex;
if (to == address(0)) revert MintToZeroAddress();
if (quantity == 0) revert MintZeroQuantity();
if (quantity > _MAX_MINT_ERC2309_QUANTITY_LIMIT) revert MintERC2309QuantityExceedsLimit();
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are unrealistic due to the above check for `quantity` to be below the limit.
unchecked {
// Updates:
// - `balance += quantity`.
// - `numberMinted += quantity`.
//
// We can directly add to the `balance` and `numberMinted`.
_packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);
// Updates:
// - `address` to the owner.
// - `startTimestamp` to the timestamp of minting.
// - `burned` to `false`.
// - `nextInitialized` to `quantity == 1`.
_packedOwnerships[startTokenId] = _packOwnershipData(
to,
_nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
);
emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);
_currentIndex = startTokenId + quantity;
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Safely mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called for each safe transfer.
* - `quantity` must be greater than 0.
*
* See {_mint}.
*
* Emits a {Transfer} event for each mint.
*/
function _safeMint(
address to,
uint256 quantity,
bytes memory _data
) internal virtual {
_mint(to, quantity);
unchecked {
if (to.code.length != 0) {
uint256 end = _currentIndex;
uint256 index = end - quantity;
do {
if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
revert TransferToNonERC721ReceiverImplementer();
}
} while (index < end);
// Reentrancy protection.
if (_currentIndex != end) revert();
}
}
}
/**
* @dev Equivalent to `_safeMint(to, quantity, '')`.
*/
function _safeMint(address to, uint256 quantity) internal virtual {
_safeMint(to, quantity, '');
}
// =============================================================
// BURN OPERATIONS
// =============================================================
/**
* @dev Equivalent to `_burn(tokenId, false)`.
*/
function _burn(uint256 tokenId) internal virtual {
_burn(tokenId, false);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId, bool approvalCheck) internal virtual {
uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
address from = address(uint160(prevOwnershipPacked));
(uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);
if (approvalCheck) {
// The nested ifs save around 20+ gas over a compound boolean condition.
if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
}
_beforeTokenTransfers(from, address(0), tokenId, 1);
// Clear approvals from the previous owner.
assembly {
if approvedAddress {
// This is equivalent to `delete _tokenApprovals[tokenId]`.
sstore(approvedAddressSlot, 0)
}
}
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
unchecked {
// Updates:
// - `balance -= 1`.
// - `numberBurned += 1`.
//
// We can directly decrement the balance, and increment the number burned.
// This is equivalent to `packed -= 1; packed += 1 << _BITPOS_NUMBER_BURNED;`.
_packedAddressData[from] += (1 << _BITPOS_NUMBER_BURNED) - 1;
// Updates:
// - `address` to the last owner.
// - `startTimestamp` to the timestamp of burning.
// - `burned` to `true`.
// - `nextInitialized` to `true`.
_packedOwnerships[tokenId] = _packOwnershipData(
from,
(_BITMASK_BURNED | _BITMASK_NEXT_INITIALIZED) | _nextExtraData(from, address(0), prevOwnershipPacked)
);
// If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
uint256 nextTokenId = tokenId + 1;
// If the next slot's address is zero and not burned (i.e. packed value is zero).
if (_packedOwnerships[nextTokenId] == 0) {
// If the next slot is within bounds.
if (nextTokenId != _currentIndex) {
// Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
_packedOwnerships[nextTokenId] = prevOwnershipPacked;
}
}
}
}
emit Transfer(from, address(0), tokenId);
_afterTokenTransfers(from, address(0), tokenId, 1);
// Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
unchecked {
_burnCounter++;
}
}
// =============================================================
// EXTRA DATA OPERATIONS
// =============================================================
/**
* @dev Directly sets the extra data for the ownership data `index`.
*/
function _setExtraDataAt(uint256 index, uint24 extraData) internal virtual {
uint256 packed = _packedOwnerships[index];
if (packed == 0) revert OwnershipNotInitializedForExtraData();
uint256 extraDataCasted;
// Cast `extraData` with assembly to avoid redundant masking.
assembly {
extraDataCasted := extraData
}
packed = (packed & _BITMASK_EXTRA_DATA_COMPLEMENT) | (extraDataCasted << _BITPOS_EXTRA_DATA);
_packedOwnerships[index] = packed;
}
/**
* @dev Called during each token transfer to set the 24bit `extraData` field.
* Intended to be overridden by the cosumer contract.
*
* `previousExtraData` - the value of `extraData` before transfer.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, `tokenId` will be burned by `from`.
* - `from` and `to` are never both zero.
*/
function _extraData(
address from,
address to,
uint24 previousExtraData
) internal view virtual returns (uint24) {}
/**
* @dev Returns the next extra data for the packed ownership data.
* The returned result is shifted into position.
*/
function _nextExtraData(
address from,
address to,
uint256 prevOwnershipPacked
) private view returns (uint256) {
uint24 extraData = uint24(prevOwnershipPacked >> _BITPOS_EXTRA_DATA);
return uint256(_extraData(from, to, extraData)) << _BITPOS_EXTRA_DATA;
}
// =============================================================
// OTHER OPERATIONS
// =============================================================
/**
* @dev Returns the message sender (defaults to `msg.sender`).
*
* If you are writing GSN compatible contracts, you need to override this function.
*/
function _msgSenderERC721A() internal view virtual returns (address) {
return msg.sender;
}
/**
* @dev Converts a uint256 to its ASCII string decimal representation.
*/
function _toString(uint256 value) internal pure virtual returns (string memory str) {
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
let m := add(mload(0x40), 0xa0)
// Update the free memory pointer to allocate.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
// prettier-ignore
for { let temp := value } 1 {} {
str := sub(str, 1)
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
// prettier-ignore
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
}
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs
pragma solidity ^0.8.4;
/**
* @dev Interface of ERC721A.
*/
interface IERC721A {
/**
* The caller must own the token or be an approved operator.
*/
error ApprovalCallerNotOwnerNorApproved();
/**
* The token does not exist.
*/
error ApprovalQueryForNonexistentToken();
/**
* Cannot query the balance for the zero address.
*/
error BalanceQueryForZeroAddress();
/**
* Cannot mint to the zero address.
*/
error MintToZeroAddress();
/**
* The quantity of tokens minted must be more than zero.
*/
error MintZeroQuantity();
/**
* The token does not exist.
*/
error OwnerQueryForNonexistentToken();
/**
* The caller must own the token or be an approved operator.
*/
error TransferCallerNotOwnerNorApproved();
/**
* The token must be owned by `from`.
*/
error TransferFromIncorrectOwner();
/**
* Cannot safely transfer to a contract that does not implement the
* ERC721Receiver interface.
*/
error TransferToNonERC721ReceiverImplementer();
/**
* Cannot transfer to the zero address.
*/
error TransferToZeroAddress();
/**
* The token does not exist.
*/
error URIQueryForNonexistentToken();
/**
* The `quantity` minted with ERC2309 exceeds the safety limit.
*/
error MintERC2309QuantityExceedsLimit();
/**
* The `extraData` cannot be set on an unintialized ownership slot.
*/
error OwnershipNotInitializedForExtraData();
// =============================================================
// STRUCTS
// =============================================================
struct TokenOwnership {
// The address of the owner.
address addr;
// Stores the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;
// Whether the token has been burned.
bool burned;
// Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
uint24 extraData;
}
// =============================================================
// TOKEN COUNTERS
// =============================================================
/**
* @dev Returns the total number of tokens in existence.
* Burned tokens will reduce the count.
* To get the total number of tokens minted, please see {_totalMinted}.
*/
function totalSupply() external view returns (uint256);
// =============================================================
// IERC165
// =============================================================
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
* to learn more about how these ids are created.
*
* This function call must use less than 30000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
// =============================================================
// IERC721
// =============================================================
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables
* (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in `owner`'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`,
* checking first that contract recipients are aware of the ERC721 protocol
* to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move
* this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external payable;
/**
* @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom}
* whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token
* by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the
* zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external payable;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom}
* for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
// =============================================================
// IERC721Metadata
// =============================================================
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
// =============================================================
// IERC2309
// =============================================================
/**
* @dev Emitted when tokens in `fromTokenId` to `toTokenId`
* (inclusive) is transferred from `from` to `to`, as defined in the
* [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
*
* See {_mintERC2309} for more details.
*/
event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
/**
* @title OperatorFilterer
* @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
* registrant's entries in the OperatorFilterRegistry.
* @dev This smart contract is meant to be inherited by token contracts so they can use the following:
* - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
* - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
*/
abstract contract OperatorFilterer {
error OperatorNotAllowed(address operator);
IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E);
constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
// If an inheriting token contract is deployed to a network without the registry deployed, the modifier
// will not revert, but the contract will need to be registered with the registry once it is deployed in
// order for the modifier to filter addresses.
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
if (subscribe) {
OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
} else {
if (subscriptionOrRegistrantToCopy != address(0)) {
OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
} else {
OPERATOR_FILTER_REGISTRY.register(address(this));
}
}
}
}
modifier onlyAllowedOperator(address from) virtual {
// Allow spending tokens from addresses with balance
// Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
// from an EOA.
if (from != msg.sender) {
_checkFilterOperator(msg.sender);
}
_;
}
modifier onlyAllowedOperatorApproval(address operator) virtual {
_checkFilterOperator(operator);
_;
}
function _checkFilterOperator(address operator) internal view virtual {
// Check registry code length to facilitate testing in environments without a deployed registry.
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
revert OperatorNotAllowed(operator);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IOperatorFilterRegistry {
function isOperatorAllowed(address registrant, address operator) external view returns (bool);
function register(address registrant) external;
function registerAndSubscribe(address registrant, address subscription) external;
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
function unregister(address addr) external;
function updateOperator(address registrant, address operator, bool filtered) external;
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
function subscribe(address registrant, address registrantToSubscribe) external;
function unsubscribe(address registrant, bool copyExistingEntries) external;
function subscriptionOf(address addr) external returns (address registrant);
function subscribers(address registrant) external returns (address[] memory);
function subscriberAt(address registrant, uint256 index) external returns (address);
function copyEntriesOf(address registrant, address registrantToCopy) external;
function isOperatorFiltered(address registrant, address operator) external returns (bool);
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
function filteredOperators(address addr) external returns (address[] memory);
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
function isRegistered(address addr) external returns (bool);
function codeHashOf(address addr) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (finance/PaymentSplitter.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/utils/SafeERC20.sol";
import "../utils/Address.sol";
import "../utils/Context.sol";
/**
* @title PaymentSplitter
* @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware
* that the Ether will be split in this way, since it is handled transparently by the contract.
*
* The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each
* account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim
* an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the
* time of contract deployment and can't be updated thereafter.
*
* `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the
* accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
* function.
*
* NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and
* tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you
* to run tests before sending real value to this contract.
*/
contract PaymentSplitter is Context {
event PayeeAdded(address account, uint256 shares);
event PaymentReleased(address to, uint256 amount);
event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
event PaymentReceived(address from, uint256 amount);
uint256 private _totalShares;
uint256 private _totalReleased;
mapping(address => uint256) private _shares;
mapping(address => uint256) private _released;
address[] private _payees;
mapping(IERC20 => uint256) private _erc20TotalReleased;
mapping(IERC20 => mapping(address => uint256)) private _erc20Released;
/**
* @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at
* the matching position in the `shares` array.
*
* All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no
* duplicates in `payees`.
*/
constructor(address[] memory payees, uint256[] memory shares_) payable {
require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
require(payees.length > 0, "PaymentSplitter: no payees");
for (uint256 i = 0; i < payees.length; i++) {
_addPayee(payees[i], shares_[i]);
}
}
/**
* @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully
* reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the
* reliability of the events, and not the actual splitting of Ether.
*
* To learn more about this see the Solidity documentation for
* https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback
* functions].
*/
receive() external payable virtual {
emit PaymentReceived(_msgSender(), msg.value);
}
/**
* @dev Getter for the total shares held by payees.
*/
function totalShares() public view returns (uint256) {
return _totalShares;
}
/**
* @dev Getter for the total amount of Ether already released.
*/
function totalReleased() public view returns (uint256) {
return _totalReleased;
}
/**
* @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20
* contract.
*/
function totalReleased(IERC20 token) public view returns (uint256) {
return _erc20TotalReleased[token];
}
/**
* @dev Getter for the amount of shares held by an account.
*/
function shares(address account) public view returns (uint256) {
return _shares[account];
}
/**
* @dev Getter for the amount of Ether already released to a payee.
*/
function released(address account) public view returns (uint256) {
return _released[account];
}
/**
* @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an
* IERC20 contract.
*/
function released(IERC20 token, address account) public view returns (uint256) {
return _erc20Released[token][account];
}
/**
* @dev Getter for the address of the payee number `index`.
*/
function payee(uint256 index) public view returns (address) {
return _payees[index];
}
/**
* @dev Getter for the amount of payee's releasable Ether.
*/
function releasable(address account) public view returns (uint256) {
uint256 totalReceived = address(this).balance + totalReleased();
return _pendingPayment(account, totalReceived, released(account));
}
/**
* @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an
* IERC20 contract.
*/
function releasable(IERC20 token, address account) public view returns (uint256) {
uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
return _pendingPayment(account, totalReceived, released(token, account));
}
/**
* @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
* total shares and their previous withdrawals.
*/
function release(address payable account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");
uint256 payment = releasable(account);
require(payment != 0, "PaymentSplitter: account is not due payment");
// _totalReleased is the sum of all values in _released.
// If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow.
_totalReleased += payment;
unchecked {
_released[account] += payment;
}
Address.sendValue(account, payment);
emit PaymentReleased(account, payment);
}
/**
* @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their
* percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20
* contract.
*/
function release(IERC20 token, address account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");
uint256 payment = releasable(token, account);
require(payment != 0, "PaymentSplitter: account is not due payment");
// _erc20TotalReleased[token] is the sum of all values in _erc20Released[token].
// If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment"
// cannot overflow.
_erc20TotalReleased[token] += payment;
unchecked {
_erc20Released[token][account] += payment;
}
SafeERC20.safeTransfer(token, account, payment);
emit ERC20PaymentReleased(token, account, payment);
}
/**
* @dev internal logic for computing the pending payment of an `account` given the token historical balances and
* already released amounts.
*/
function _pendingPayment(
address account,
uint256 totalReceived,
uint256 alreadyReleased
) private view returns (uint256) {
return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
}
/**
* @dev Add a new payee to the contract.
* @param account The address of the payee to add.
* @param shares_ The number of shares owned by the payee.
*/
function _addPayee(address account, uint256 shares_) private {
require(account != address(0), "PaymentSplitter: account is the zero address");
require(shares_ > 0, "PaymentSplitter: shares are 0");
require(_shares[account] == 0, "PaymentSplitter: account already has shares");
_payees.push(account);
_shares[account] = shares_;
_totalShares = _totalShares + shares_;
emit PayeeAdded(account, shares_);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
File 3 of 5: PausableZone
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { ZoneInterface } from "../interfaces/ZoneInterface.sol";
import { ZoneInteractionErrors } from "../interfaces/ZoneInteractionErrors.sol";
// prettier-ignore
import {
PausableZoneEventsAndErrors
} from "./interfaces/PausableZoneEventsAndErrors.sol";
import { SeaportInterface } from "../interfaces/SeaportInterface.sol";
// prettier-ignore
import {
AdvancedOrder,
CriteriaResolver,
Order,
OrderComponents,
Fulfillment,
Execution
} from "../lib/ConsiderationStructs.sol";
import { PausableZoneInterface } from "./interfaces/PausableZoneInterface.sol";
/**
* @title PausableZone
* @author cupOJoseph, BCLeFevre, ryanio
* @notice PausableZone is a simple zone implementation that approves every
* order. It can be self-destructed by its controller to pause
* restricted orders that have it set as their zone.
*/
contract PausableZone is
PausableZoneEventsAndErrors,
ZoneInterface,
PausableZoneInterface
{
// Set an immutable controller that can pause the zone & update an operator.
address internal immutable _controller;
// Set an operator that can instruct the zone to cancel or execute orders.
address public operator;
/**
* @dev Ensure that the caller is either the operator or controller.
*/
modifier isOperator() {
// Ensure that the caller is either the operator or the controller.
if (msg.sender != operator && msg.sender != _controller) {
revert InvalidOperator();
}
// Continue with function execution.
_;
}
/**
* @dev Ensure that the caller is the controller.
*/
modifier isController() {
// Ensure that the caller is the controller.
if (msg.sender != _controller) {
revert InvalidController();
}
// Continue with function execution.
_;
}
/**
* @notice Set the deployer as the controller of the zone.
*/
constructor() {
// Set the controller to the deployer.
_controller = msg.sender;
// Emit an event signifying that the zone is unpaused.
emit Unpaused();
}
/**
* @notice Check if a given order is currently valid.
*
* @dev This function is called by Seaport whenever extraData is not
* provided by the caller.
*
* @param orderHash The hash of the order.
* @param caller The caller in question.
* @param offerer The offerer in question.
* @param zoneHash The hash to provide upon calling the zone.
*
* @return validOrderMagicValue A magic value indicating if the order is
* currently valid.
*/
function isValidOrder(
bytes32 orderHash,
address caller,
address offerer,
bytes32 zoneHash
) external pure override returns (bytes4 validOrderMagicValue) {
orderHash;
caller;
offerer;
zoneHash;
// Return the selector of isValidOrder as the magic value.
validOrderMagicValue = ZoneInterface.isValidOrder.selector;
}
/**
* @notice Check if a given order including extraData is currently valid.
*
* @dev This function is called by Seaport whenever any extraData is
* provided by the caller.
*
* @param orderHash The hash of the order.
* @param caller The caller in question.
* @param order The order in question.
* @param priorOrderHashes The order hashes of each order supplied prior to
* the current order as part of a "match" variety
* of order fulfillment.
* @param criteriaResolvers The criteria resolvers corresponding to
* the order.
*
* @return validOrderMagicValue A magic value indicating if the order is
* currently valid.
*/
function isValidOrderIncludingExtraData(
bytes32 orderHash,
address caller,
AdvancedOrder calldata order,
bytes32[] calldata priorOrderHashes,
CriteriaResolver[] calldata criteriaResolvers
) external pure override returns (bytes4 validOrderMagicValue) {
orderHash;
caller;
order;
priorOrderHashes;
criteriaResolvers;
// Return the selector of isValidOrder as the magic value.
validOrderMagicValue = ZoneInterface.isValidOrder.selector;
}
/**
* @notice Cancel an arbitrary number of orders that have agreed to use the
* contract as their zone.
*
* @param seaport The Seaport address.
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancelOrders(
SeaportInterface seaport,
OrderComponents[] calldata orders
) external override isOperator returns (bool cancelled) {
// Call cancel on Seaport and return its boolean value.
cancelled = seaport.cancel(orders);
}
/**
* @notice Execute an arbitrary number of matched orders, each with
* an arbitrary number of items for offer and consideration
* along with a set of fulfillments allocating offer components
* to consideration components.
*
* @param seaport The Seaport address.
* @param orders The orders to match.
* @param fulfillments An array of elements allocating offer components
* to consideration components.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function executeMatchOrders(
SeaportInterface seaport,
Order[] calldata orders,
Fulfillment[] calldata fulfillments
)
external
payable
override
isOperator
returns (Execution[] memory executions)
{
// Call matchOrders on Seaport and return the sequence of transfers
// performed as part of matching the given orders.
executions = seaport.matchOrders{ value: msg.value }(
orders,
fulfillments
);
}
/**
* @notice Execute an arbitrary number of matched advanced orders,
* each with an arbitrary number of items for offer and
* consideration along with a set of fulfillments allocating
* offer components to consideration components.
*
* @param seaport The Seaport address.
* @param orders The orders to match.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root.
* @param fulfillments An array of elements allocating offer components
* to consideration components.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function executeMatchAdvancedOrders(
SeaportInterface seaport,
AdvancedOrder[] calldata orders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
)
external
payable
override
isOperator
returns (Execution[] memory executions)
{
// Call matchAdvancedOrders on Seaport and return the sequence of
// transfers performed as part of matching the given orders.
executions = seaport.matchAdvancedOrders{ value: msg.value }(
orders,
criteriaResolvers,
fulfillments
);
}
/**
* @notice Pause this contract, safely stopping orders from using
* the contract as a zone. Restricted orders with this address as a
* zone will not be fulfillable unless the zone is redeployed to the
* same address.
*/
function pause() external override isController {
// Emit an event signifying that the zone is paused.
emit Paused();
// Destroy the zone, sending any ether to the transaction submitter.
selfdestruct(payable(tx.origin));
}
/**
* @notice Assign the given address with the ability to operate the zone.
*
* @param operatorToAssign The address to assign as the operator.
*/
function assignOperator(address operatorToAssign)
external
override
isController
{
// Ensure the operator being assigned is not the null address.
require(
operatorToAssign != address(0),
"Operator can not be set to the null address"
);
// Set the given address as the new operator.
operator = operatorToAssign;
// Emit an event indicating the operator has been updated.
emit OperatorUpdated(operator);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
AdvancedOrder,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
interface ZoneInterface {
/**
* @dev Emit an event whenever a zone is successfully paused.
*/
event Paused();
/**
* @dev Emit an event whenever a zone is successfully unpaused (created).
*/
event Unpaused();
// Called by Consideration whenever extraData is not provided by the caller.
function isValidOrder(
bytes32 orderHash,
address caller,
address offerer,
bytes32 zoneHash
) external view returns (bytes4 validOrderMagicValue);
// Called by Consideration whenever any extraData is provided by the caller.
function isValidOrderIncludingExtraData(
bytes32 orderHash,
address caller,
AdvancedOrder calldata order,
bytes32[] calldata priorOrderHashes,
CriteriaResolver[] calldata criteriaResolvers
) external view returns (bytes4 validOrderMagicValue);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @title ZoneInteractionErrors
* @author 0age
* @notice ZoneInteractionErrors contains errors related to zone interaction.
*/
interface ZoneInteractionErrors {
/**
* @dev Revert with an error when attempting to fill an order that specifies
* a restricted submitter as its order type when not submitted by
* either the offerer or the order's zone or approved as valid by the
* zone in question via a staticcall to `isValidOrder`.
*
* @param orderHash The order hash for the invalid restricted order.
*/
error InvalidRestrictedOrder(bytes32 orderHash);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
/**
* @notice PausableZoneEventsAndErrors contains errors and events
* related to zone interaction.
*/
interface PausableZoneEventsAndErrors {
/**
* @dev Emit an event whenever a zone owner registers a new potential
* owner for that zone.
*
* @param newPotentialOwner The new potential owner of the zone.
*/
event PotentialOwnerUpdated(address newPotentialOwner);
/**
* @dev Emit an event whenever zone ownership is transferred.
*
* @param previousOwner The previous owner of the zone.
* @param newOwner The new owner of the zone.
*/
event OwnershipTransferred(address previousOwner, address newOwner);
/**
* @dev Emit an event whenever a new zone is created.
*
* @param zone The address of the zone.
* @param salt The salt used to deploy the zone.
*/
event ZoneCreated(address zone, bytes32 salt);
/**
* @dev Emit an event whenever a zone owner assigns a new pauser
*
* @param newPauser The new pausear of the zone.
*/
event PauserUpdated(address newPauser);
/**
* @dev Emit an event whenever a zone owner assigns a new operator
*
* @param newOperator The new operator of the zone.
*/
event OperatorUpdated(address newOperator);
/**
* @dev Revert with an error when attempting to pause the zone
* while the caller is not the owner or pauser of the zone.
*/
error InvalidPauser();
/**
* @dev Revert with an error when attempting to call an operation
* while the caller is not the controller or operator of the zone.
*/
error InvalidOperator();
/**
* @dev Revert with an error when attempting to pause the zone or update the
* operator while the caller is not the controller of the zone.
*/
error InvalidController();
/**
* @dev Revert with an error when attempting to deploy a zone that is
* currently deployed.
*/
error ZoneAlreadyExists(address zone);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
BasicOrderParameters,
OrderComponents,
Fulfillment,
FulfillmentComponent,
Execution,
Order,
AdvancedOrder,
OrderStatus,
CriteriaResolver
} from "../lib/ConsiderationStructs.sol";
/**
* @title SeaportInterface
* @author 0age
* @custom:version 1.1
* @notice Seaport is a generalized ETH/ERC20/ERC721/ERC1155 marketplace. It
* minimizes external calls to the greatest extent possible and provides
* lightweight methods for common routes as well as more flexible
* methods for composing advanced orders.
*
* @dev SeaportInterface contains all external function interfaces for Seaport.
*/
interface SeaportInterface {
/**
* @notice Fulfill an order offering an ERC721 token by supplying Ether (or
* the native token for the given chain) as consideration for the
* order. An arbitrary number of "additional recipients" may also be
* supplied which will each receive native tokens from the fulfiller
* as consideration.
*
* @param parameters Additional information on the fulfilled order. Note
* that the offerer must first approve this contract (or
* their preferred conduit if indicated by the order) for
* their offered ERC721 token to be transferred.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillBasicOrder(BasicOrderParameters calldata parameters)
external
payable
returns (bool fulfilled);
/**
* @notice Fulfill an order with an arbitrary number of items for offer and
* consideration. Note that this function does not support
* criteria-based orders or partial filling of orders (though
* filling the remainder of a partially-filled order is supported).
*
* @param order The order to fulfill. Note that both the
* offerer and the fulfiller must first approve
* this contract (or the corresponding conduit if
* indicated) to transfer any relevant tokens on
* their behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155 tokens
* as consideration.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Seaport.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey)
external
payable
returns (bool fulfilled);
/**
* @notice Fill an order, fully or partially, with an arbitrary number of
* items for offer and consideration alongside criteria resolvers
* containing specific token identifiers and associated proofs.
*
* @param advancedOrder The order to fulfill along with the fraction
* of the order to attempt to fill. Note that
* both the offerer and the fulfiller must first
* approve this contract (or their preferred
* conduit if indicated by the order) to transfer
* any relevant tokens on their behalf and that
* contracts must implement `onERC1155Received`
* to receive ERC1155 tokens as consideration.
* Also note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount with
* the supplied fraction for the partial fill to
* be considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a proof
* that the supplied token identifier is
* contained in the merkle root held by the item
* in question's criteria element. Note that an
* empty criteria indicates that any
* (transferable) token identifier on the token
* in question is valid and that no associated
* proof needs to be supplied.
* @param fulfillerConduitKey A bytes32 value indicating what conduit, if
* any, to source the fulfiller's token approvals
* from. The zero hash signifies that no conduit
* should be used, with direct approvals set on
* Seaport.
* @param recipient The intended recipient for all received items,
* with `address(0)` indicating that the caller
* should receive the items.
*
* @return fulfilled A boolean indicating whether the order has been
* successfully fulfilled.
*/
function fulfillAdvancedOrder(
AdvancedOrder calldata advancedOrder,
CriteriaResolver[] calldata criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) external payable returns (bool fulfilled);
/**
* @notice Attempt to fill a group of orders, each with an arbitrary number
* of items for offer and consideration. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
* Note that this function does not support criteria-based orders or
* partial filling of orders (though filling the remainder of a
* partially-filled order is supported).
*
* @param orders The orders to fulfill. Note that both
* the offerer and the fulfiller must first
* approve this contract (or the
* corresponding conduit if indicated) to
* transfer any relevant tokens on their
* behalf and that contracts must implement
* `onERC1155Received` to receive ERC1155
* tokens as consideration.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableOrders(
Order[] calldata orders,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Attempt to fill a group of orders, fully or partially, with an
* arbitrary number of items for offer and consideration per order
* alongside criteria resolvers containing specific token
* identifiers and associated proofs. Any order that is not
* currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or their preferred conduit if
* indicated by the order) to transfer any
* relevant tokens on their behalf and that
* contracts must implement
* `onERC1155Received` to enable receipt of
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on this contract.
* @param recipient The intended recipient for all received
* items, with `address(0)` indicating that
* the caller should receive the items.
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function fulfillAvailableAdvancedOrders(
AdvancedOrder[] calldata advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
)
external
payable
returns (bool[] memory availableOrders, Execution[] memory executions);
/**
* @notice Match an arbitrary number of orders, each with an arbitrary
* number of items for offer and consideration along with as set of
* fulfillments allocating offer components to consideration
* components. Note that this function does not support
* criteria-based or partial filling of orders (though filling the
* remainder of a partially-filled order is supported).
*
* @param orders The orders to match. Note that both the offerer and
* fulfiller on each order must first approve this
* contract (or their conduit if indicated by the order)
* to transfer any relevant tokens on their behalf and
* each consideration recipient must implement
* `onERC1155Received` to enable ERC1155 token receipt.
* @param fulfillments An array of elements allocating offer components to
* consideration components. Note that each
* consideration component must be fully met for the
* match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchOrders(
Order[] calldata orders,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Match an arbitrary number of full or partial orders, each with an
* arbitrary number of items for offer and consideration, supplying
* criteria resolvers containing specific token identifiers and
* associated proofs as well as fulfillments allocating offer
* components to consideration components.
*
* @param orders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or a preferred conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function matchAdvancedOrders(
AdvancedOrder[] calldata orders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Cancel an arbitrary number of orders. Note that only the offerer
* or the zone of a given order may cancel it. Callers should ensure
* that the intended order was cancelled by calling `getOrderStatus`
* and confirming that `isCancelled` returns `true`.
*
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancel(OrderComponents[] calldata orders)
external
returns (bool cancelled);
/**
* @notice Validate an arbitrary number of orders, thereby registering their
* signatures as valid and allowing the fulfiller to skip signature
* verification on fulfillment. Note that validated orders may still
* be unfulfillable due to invalid item amounts or other factors;
* callers should determine whether validated orders are fulfillable
* by simulating the fulfillment call prior to execution. Also note
* that anyone can validate a signed order, but only the offerer can
* validate an order without supplying a signature.
*
* @param orders The orders to validate.
*
* @return validated A boolean indicating whether the supplied orders have
* been successfully validated.
*/
function validate(Order[] calldata orders)
external
returns (bool validated);
/**
* @notice Cancel all orders from a given offerer with a given zone in bulk
* by incrementing a counter. Note that only the offerer may
* increment the counter.
*
* @return newCounter The new counter.
*/
function incrementCounter() external returns (uint256 newCounter);
/**
* @notice Retrieve the order hash for a given order.
*
* @param order The components of the order.
*
* @return orderHash The order hash.
*/
function getOrderHash(OrderComponents calldata order)
external
view
returns (bytes32 orderHash);
/**
* @notice Retrieve the status of a given order by hash, including whether
* the order has been cancelled or validated and the fraction of the
* order that has been filled.
*
* @param orderHash The order hash in question.
*
* @return isValidated A boolean indicating whether the order in question
* has been validated (i.e. previously approved or
* partially filled).
* @return isCancelled A boolean indicating whether the order in question
* has been cancelled.
* @return totalFilled The total portion of the order that has been filled
* (i.e. the "numerator").
* @return totalSize The total size of the order that is either filled or
* unfilled (i.e. the "denominator").
*/
function getOrderStatus(bytes32 orderHash)
external
view
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
);
/**
* @notice Retrieve the current counter for a given offerer.
*
* @param offerer The offerer in question.
*
* @return counter The current counter.
*/
function getCounter(address offerer)
external
view
returns (uint256 counter);
/**
* @notice Retrieve configuration information for this contract.
*
* @return version The contract version.
* @return domainSeparator The domain separator for this contract.
* @return conduitController The conduit Controller set for this contract.
*/
function information()
external
view
returns (
string memory version,
bytes32 domainSeparator,
address conduitController
);
/**
* @notice Retrieve the name of this contract.
*
* @return contractName The name of this contract.
*/
function name() external view returns (string memory contractName);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
OrderType,
BasicOrderType,
ItemType,
Side
} from "./ConsiderationEnums.sol";
/**
* @dev An order contains eleven components: an offerer, a zone (or account that
* can cancel the order or restrict who can fulfill the order depending on
* the type), the order type (specifying partial fill support as well as
* restricted order status), the start and end time, a hash that will be
* provided to the zone when validating restricted orders, a salt, a key
* corresponding to a given conduit, a counter, and an arbitrary number of
* offer items that can be spent along with consideration items that must
* be received by their respective recipient.
*/
struct OrderComponents {
address offerer;
address zone;
OfferItem[] offer;
ConsiderationItem[] consideration;
OrderType orderType;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
uint256 salt;
bytes32 conduitKey;
uint256 counter;
}
/**
* @dev An offer item has five components: an item type (ETH or other native
* tokens, ERC20, ERC721, and ERC1155, as well as criteria-based ERC721 and
* ERC1155), a token address, a dual-purpose "identifierOrCriteria"
* component that will either represent a tokenId or a merkle root
* depending on the item type, and a start and end amount that support
* increasing or decreasing amounts over the duration of the respective
* order.
*/
struct OfferItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
}
/**
* @dev A consideration item has the same five components as an offer item and
* an additional sixth component designating the required recipient of the
* item.
*/
struct ConsiderationItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
address payable recipient;
}
/**
* @dev A spent item is translated from a utilized offer item and has four
* components: an item type (ETH or other native tokens, ERC20, ERC721, and
* ERC1155), a token address, a tokenId, and an amount.
*/
struct SpentItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
/**
* @dev A received item is translated from a utilized consideration item and has
* the same four components as a spent item, as well as an additional fifth
* component designating the required recipient of the item.
*/
struct ReceivedItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
address payable recipient;
}
/**
* @dev For basic orders involving ETH / native / ERC20 <=> ERC721 / ERC1155
* matching, a group of six functions may be called that only requires a
* subset of the usual order arguments. Note the use of a "basicOrderType"
* enum; this represents both the usual order type as well as the "route"
* of the basic order (a simple derivation function for the basic order
* type is `basicOrderType = orderType + (4 * basicOrderRoute)`.)
*/
struct BasicOrderParameters {
// calldata offset
address considerationToken; // 0x24
uint256 considerationIdentifier; // 0x44
uint256 considerationAmount; // 0x64
address payable offerer; // 0x84
address zone; // 0xa4
address offerToken; // 0xc4
uint256 offerIdentifier; // 0xe4
uint256 offerAmount; // 0x104
BasicOrderType basicOrderType; // 0x124
uint256 startTime; // 0x144
uint256 endTime; // 0x164
bytes32 zoneHash; // 0x184
uint256 salt; // 0x1a4
bytes32 offererConduitKey; // 0x1c4
bytes32 fulfillerConduitKey; // 0x1e4
uint256 totalOriginalAdditionalRecipients; // 0x204
AdditionalRecipient[] additionalRecipients; // 0x224
bytes signature; // 0x244
// Total length, excluding dynamic array data: 0x264 (580)
}
/**
* @dev Basic orders can supply any number of additional recipients, with the
* implied assumption that they are supplied from the offered ETH (or other
* native token) or ERC20 token for the order.
*/
struct AdditionalRecipient {
uint256 amount;
address payable recipient;
}
/**
* @dev The full set of order components, with the exception of the counter,
* must be supplied when fulfilling more sophisticated orders or groups of
* orders. The total number of original consideration items must also be
* supplied, as the caller may specify additional consideration items.
*/
struct OrderParameters {
address offerer; // 0x00
address zone; // 0x20
OfferItem[] offer; // 0x40
ConsiderationItem[] consideration; // 0x60
OrderType orderType; // 0x80
uint256 startTime; // 0xa0
uint256 endTime; // 0xc0
bytes32 zoneHash; // 0xe0
uint256 salt; // 0x100
bytes32 conduitKey; // 0x120
uint256 totalOriginalConsiderationItems; // 0x140
// offer.length // 0x160
}
/**
* @dev Orders require a signature in addition to the other order parameters.
*/
struct Order {
OrderParameters parameters;
bytes signature;
}
/**
* @dev Advanced orders include a numerator (i.e. a fraction to attempt to fill)
* and a denominator (the total size of the order) in addition to the
* signature and other order parameters. It also supports an optional field
* for supplying extra data; this data will be included in a staticcall to
* `isValidOrderIncludingExtraData` on the zone for the order if the order
* type is restricted and the offerer or zone are not the caller.
*/
struct AdvancedOrder {
OrderParameters parameters;
uint120 numerator;
uint120 denominator;
bytes signature;
bytes extraData;
}
/**
* @dev Orders can be validated (either explicitly via `validate`, or as a
* consequence of a full or partial fill), specifically cancelled (they can
* also be cancelled in bulk via incrementing a per-zone counter), and
* partially or fully filled (with the fraction filled represented by a
* numerator and denominator).
*/
struct OrderStatus {
bool isValidated;
bool isCancelled;
uint120 numerator;
uint120 denominator;
}
/**
* @dev A criteria resolver specifies an order, side (offer vs. consideration),
* and item index. It then provides a chosen identifier (i.e. tokenId)
* alongside a merkle proof demonstrating the identifier meets the required
* criteria.
*/
struct CriteriaResolver {
uint256 orderIndex;
Side side;
uint256 index;
uint256 identifier;
bytes32[] criteriaProof;
}
/**
* @dev A fulfillment is applied to a group of orders. It decrements a series of
* offer and consideration items, then generates a single execution
* element. A given fulfillment can be applied to as many offer and
* consideration items as desired, but must contain at least one offer and
* at least one consideration that match. The fulfillment must also remain
* consistent on all key parameters across all offer items (same offerer,
* token, type, tokenId, and conduit preference) as well as across all
* consideration items (token, type, tokenId, and recipient).
*/
struct Fulfillment {
FulfillmentComponent[] offerComponents;
FulfillmentComponent[] considerationComponents;
}
/**
* @dev Each fulfillment component contains one index referencing a specific
* order and another referencing a specific offer or consideration item.
*/
struct FulfillmentComponent {
uint256 orderIndex;
uint256 itemIndex;
}
/**
* @dev An execution is triggered once all consideration items have been zeroed
* out. It sends the item in question from the offerer to the item's
* recipient, optionally sourcing approvals from either this contract
* directly or from the offerer's chosen conduit if one is specified. An
* execution is not provided as an argument, but rather is derived via
* orders, criteria resolvers, and fulfillments (where the total number of
* executions will be less than or equal to the total number of indicated
* fulfillments) and returned as part of `matchOrders`.
*/
struct Execution {
ReceivedItem item;
address offerer;
bytes32 conduitKey;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
import { SeaportInterface } from "../../interfaces/SeaportInterface.sol";
// prettier-ignore
import {
AdvancedOrder,
CriteriaResolver,
Order,
OrderComponents,
Fulfillment,
Execution
} from "../../lib/ConsiderationStructs.sol";
/**
* @title PausableZone
* @author cupOJoseph, BCLeFevre, ryanio
* @notice PausableZone is a simple zone implementation that approves every
* order. It can be self-destructed by its controller to pause
* restricted orders that have it set as their zone.
*/
interface PausableZoneInterface {
/**
* @notice Cancel an arbitrary number of orders that have agreed to use the
* contract as their zone.
*
* @param seaport The Seaport address.
* @param orders The orders to cancel.
*
* @return cancelled A boolean indicating whether the supplied orders have
* been successfully cancelled.
*/
function cancelOrders(
SeaportInterface seaport,
OrderComponents[] calldata orders
) external returns (bool cancelled);
/**
* @notice Execute an arbitrary number of matched orders, each with
* an arbitrary number of items for offer and consideration
* along with a set of fulfillments allocating offer components
* to consideration components.
*
* @param seaport The Seaport address.
* @param orders The orders to match.
* @param fulfillments An array of elements allocating offer components
* to consideration components.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function executeMatchOrders(
SeaportInterface seaport,
Order[] calldata orders,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Execute an arbitrary number of matched advanced orders,
* each with an arbitrary number of items for offer and
* consideration along with a set of fulfillments allocating
* offer components to consideration components.
*
* @param seaport The Seaport address.
* @param orders The orders to match.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root.
* @param fulfillments An array of elements allocating offer components
* to consideration components.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function executeMatchAdvancedOrders(
SeaportInterface seaport,
AdvancedOrder[] calldata orders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments
) external payable returns (Execution[] memory executions);
/**
* @notice Pause this contract, safely stopping orders from using
* the contract as a zone. Restricted orders with this address as a
* zone will not be fulfillable unless the zone is redeployed to the
* same address.
*/
function pause() external;
/**
* @notice Assign the given address with the ability to operate the zone.
*
* @param operatorToAssign The address to assign as the operator.
*/
function assignOperator(address operatorToAssign) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
enum OrderType {
// 0: no partial fills, anyone can execute
FULL_OPEN,
// 1: partial fills supported, anyone can execute
PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
PARTIAL_RESTRICTED
}
// prettier-ignore
enum BasicOrderType {
// 0: no partial fills, anyone can execute
ETH_TO_ERC721_FULL_OPEN,
// 1: partial fills supported, anyone can execute
ETH_TO_ERC721_PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
ETH_TO_ERC721_FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
ETH_TO_ERC721_PARTIAL_RESTRICTED,
// 4: no partial fills, anyone can execute
ETH_TO_ERC1155_FULL_OPEN,
// 5: partial fills supported, anyone can execute
ETH_TO_ERC1155_PARTIAL_OPEN,
// 6: no partial fills, only offerer or zone can execute
ETH_TO_ERC1155_FULL_RESTRICTED,
// 7: partial fills supported, only offerer or zone can execute
ETH_TO_ERC1155_PARTIAL_RESTRICTED,
// 8: no partial fills, anyone can execute
ERC20_TO_ERC721_FULL_OPEN,
// 9: partial fills supported, anyone can execute
ERC20_TO_ERC721_PARTIAL_OPEN,
// 10: no partial fills, only offerer or zone can execute
ERC20_TO_ERC721_FULL_RESTRICTED,
// 11: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC721_PARTIAL_RESTRICTED,
// 12: no partial fills, anyone can execute
ERC20_TO_ERC1155_FULL_OPEN,
// 13: partial fills supported, anyone can execute
ERC20_TO_ERC1155_PARTIAL_OPEN,
// 14: no partial fills, only offerer or zone can execute
ERC20_TO_ERC1155_FULL_RESTRICTED,
// 15: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC1155_PARTIAL_RESTRICTED,
// 16: no partial fills, anyone can execute
ERC721_TO_ERC20_FULL_OPEN,
// 17: partial fills supported, anyone can execute
ERC721_TO_ERC20_PARTIAL_OPEN,
// 18: no partial fills, only offerer or zone can execute
ERC721_TO_ERC20_FULL_RESTRICTED,
// 19: partial fills supported, only offerer or zone can execute
ERC721_TO_ERC20_PARTIAL_RESTRICTED,
// 20: no partial fills, anyone can execute
ERC1155_TO_ERC20_FULL_OPEN,
// 21: partial fills supported, anyone can execute
ERC1155_TO_ERC20_PARTIAL_OPEN,
// 22: no partial fills, only offerer or zone can execute
ERC1155_TO_ERC20_FULL_RESTRICTED,
// 23: partial fills supported, only offerer or zone can execute
ERC1155_TO_ERC20_PARTIAL_RESTRICTED
}
// prettier-ignore
enum BasicOrderRouteType {
// 0: provide Ether (or other native token) to receive offered ERC721 item.
ETH_TO_ERC721,
// 1: provide Ether (or other native token) to receive offered ERC1155 item.
ETH_TO_ERC1155,
// 2: provide ERC20 item to receive offered ERC721 item.
ERC20_TO_ERC721,
// 3: provide ERC20 item to receive offered ERC1155 item.
ERC20_TO_ERC1155,
// 4: provide ERC721 item to receive offered ERC20 item.
ERC721_TO_ERC20,
// 5: provide ERC1155 item to receive offered ERC20 item.
ERC1155_TO_ERC20
}
// prettier-ignore
enum ItemType {
// 0: ETH on mainnet, MATIC on polygon, etc.
NATIVE,
// 1: ERC20 items (ERC777 and ERC20 analogues could also technically work)
ERC20,
// 2: ERC721 items
ERC721,
// 3: ERC1155 items
ERC1155,
// 4: ERC721 items where a number of tokenIds are supported
ERC721_WITH_CRITERIA,
// 5: ERC1155 items where a number of ids are supported
ERC1155_WITH_CRITERIA
}
// prettier-ignore
enum Side {
// 0: Items that can be spent
OFFER,
// 1: Items that must be received
CONSIDERATION
}
File 4 of 5: OperatorFilterRegistry
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
interface IOperatorFilterRegistry {
function isOperatorAllowed(address registrant, address operator) external returns (bool);
function register(address registrant) external;
function registerAndSubscribe(address registrant, address subscription) external;
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
function updateOperator(address registrant, address operator, bool filtered) external;
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
function subscribe(address registrant, address registrantToSubscribe) external;
function unsubscribe(address registrant, bool copyExistingEntries) external;
function subscriptionOf(address addr) external returns (address registrant);
function subscribers(address registrant) external returns (address[] memory);
function subscriberAt(address registrant, uint256 index) external returns (address);
function copyEntriesOf(address registrant, address registrantToCopy) external;
function isOperatorFiltered(address registrant, address operator) external returns (bool);
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
function filteredOperators(address addr) external returns (address[] memory);
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
function isRegistered(address addr) external returns (bool);
function codeHashOf(address addr) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {OperatorFilterRegistryErrorsAndEvents} from "./OperatorFilterRegistryErrorsAndEvents.sol";
/**
* @title OperatorFilterRegistry
* @notice Borrows heavily from the QQL BlacklistOperatorFilter contract:
* https://github.com/qql-art/contracts/blob/main/contracts/BlacklistOperatorFilter.sol
* @notice This contracts allows tokens or token owners to register specific addresses or codeHashes that may be
* * restricted according to the isOperatorAllowed function.
*/
contract OperatorFilterRegistry is IOperatorFilterRegistry, OperatorFilterRegistryErrorsAndEvents {
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.Bytes32Set;
/// @dev initialized accounts have a nonzero codehash (see https://eips.ethereum.org/EIPS/eip-1052)
/// Note that this will also be a smart contract's codehash when making calls from its constructor.
bytes32 constant EOA_CODEHASH = keccak256("");
mapping(address => EnumerableSet.AddressSet) private _filteredOperators;
mapping(address => EnumerableSet.Bytes32Set) private _filteredCodeHashes;
mapping(address => address) private _registrations;
mapping(address => EnumerableSet.AddressSet) private _subscribers;
/**
* @notice restricts method caller to the address or EIP-173 "owner()"
*/
modifier onlyAddressOrOwner(address addr) {
if (msg.sender != addr) {
try Ownable(addr).owner() returns (address owner) {
if (msg.sender != owner) {
revert OnlyAddressOrOwner();
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert NotOwnable();
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
_;
}
/**
* @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
* true if supplied registrant address is not registered.
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != address(0)) {
EnumerableSet.AddressSet storage filteredOperatorsRef;
EnumerableSet.Bytes32Set storage filteredCodeHashesRef;
filteredOperatorsRef = _filteredOperators[registration];
filteredCodeHashesRef = _filteredCodeHashes[registration];
if (filteredOperatorsRef.contains(operator)) {
revert AddressFiltered(operator);
}
if (operator.code.length > 0) {
bytes32 codeHash = operator.codehash;
if (filteredCodeHashesRef.contains(codeHash)) {
revert CodeHashFiltered(operator, codeHash);
}
}
}
return true;
}
//////////////////
// AUTH METHODS //
//////////////////
/**
* @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
*/
function register(address registrant) external onlyAddressOrOwner(registrant) {
if (_registrations[registrant] != address(0)) {
revert AlreadyRegistered();
}
_registrations[registrant] = registrant;
emit RegistrationUpdated(registrant, true);
}
/**
* @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
* Note that this does not remove any filtered addresses or codeHashes.
* Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
*/
function unregister(address registrant) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
_subscribers[registration].remove(registrant);
emit SubscriptionUpdated(registrant, registration, false);
}
_registrations[registrant] = address(0);
emit RegistrationUpdated(registrant, false);
}
/**
* @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
*/
function registerAndSubscribe(address registrant, address subscription) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration != address(0)) {
revert AlreadyRegistered();
}
if (registrant == subscription) {
revert CannotSubscribeToSelf();
}
address subscriptionRegistration = _registrations[subscription];
if (subscriptionRegistration == address(0)) {
revert NotRegistered(subscription);
}
if (subscriptionRegistration != subscription) {
revert CannotSubscribeToRegistrantWithSubscription(subscription);
}
_registrations[registrant] = subscription;
_subscribers[subscription].add(registrant);
emit RegistrationUpdated(registrant, true);
emit SubscriptionUpdated(registrant, subscription, true);
}
/**
* @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
* address without subscribing.
*/
function registerAndCopyEntries(address registrant, address registrantToCopy)
external
onlyAddressOrOwner(registrant)
{
if (registrantToCopy == registrant) {
revert CannotCopyFromSelf();
}
address registration = _registrations[registrant];
if (registration != address(0)) {
revert AlreadyRegistered();
}
address registrantRegistration = _registrations[registrantToCopy];
if (registrantRegistration == address(0)) {
revert NotRegistered(registrantToCopy);
}
_registrations[registrant] = registrant;
emit RegistrationUpdated(registrant, true);
_copyEntries(registrant, registrantToCopy);
}
/**
* @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
*/
function updateOperator(address registrant, address operator, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
if (!filtered) {
bool removed = filteredOperatorsRef.remove(operator);
if (!removed) {
revert AddressNotFiltered(operator);
}
} else {
bool added = filteredOperatorsRef.add(operator);
if (!added) {
revert AddressAlreadyFiltered(operator);
}
}
emit OperatorUpdated(registrant, operator, filtered);
}
/**
* @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
*/
function updateCodeHash(address registrant, bytes32 codeHash, bool filtered)
external
onlyAddressOrOwner(registrant)
{
if (codeHash == EOA_CODEHASH) {
revert CannotFilterEOAs();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
if (!filtered) {
bool removed = filteredCodeHashesRef.remove(codeHash);
if (!removed) {
revert CodeHashNotFiltered(codeHash);
}
} else {
bool added = filteredCodeHashesRef.add(codeHash);
if (!added) {
revert CodeHashAlreadyFiltered(codeHash);
}
}
emit CodeHashUpdated(registrant, codeHash, filtered);
}
/**
* @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
*/
function updateOperators(address registrant, address[] calldata operators, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
uint256 operatorsLength = operators.length;
unchecked {
if (!filtered) {
for (uint256 i = 0; i < operatorsLength; ++i) {
address operator = operators[i];
bool removed = filteredOperatorsRef.remove(operator);
if (!removed) {
revert AddressNotFiltered(operator);
}
}
} else {
for (uint256 i = 0; i < operatorsLength; ++i) {
address operator = operators[i];
bool added = filteredOperatorsRef.add(operator);
if (!added) {
revert AddressAlreadyFiltered(operator);
}
}
}
}
emit OperatorsUpdated(registrant, operators, filtered);
}
/**
* @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
*/
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
uint256 codeHashesLength = codeHashes.length;
unchecked {
if (!filtered) {
for (uint256 i = 0; i < codeHashesLength; ++i) {
bytes32 codeHash = codeHashes[i];
bool removed = filteredCodeHashesRef.remove(codeHash);
if (!removed) {
revert CodeHashNotFiltered(codeHash);
}
}
} else {
for (uint256 i = 0; i < codeHashesLength; ++i) {
bytes32 codeHash = codeHashes[i];
if (codeHash == EOA_CODEHASH) {
revert CannotFilterEOAs();
}
bool added = filteredCodeHashesRef.add(codeHash);
if (!added) {
revert CodeHashAlreadyFiltered(codeHash);
}
}
}
}
emit CodeHashesUpdated(registrant, codeHashes, filtered);
}
/**
* @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
* subscription if present.
* Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
* subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
* used.
*/
function subscribe(address registrant, address newSubscription) external onlyAddressOrOwner(registrant) {
if (registrant == newSubscription) {
revert CannotSubscribeToSelf();
}
if (newSubscription == address(0)) {
revert CannotSubscribeToZeroAddress();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration == newSubscription) {
revert AlreadySubscribed(newSubscription);
}
address newSubscriptionRegistration = _registrations[newSubscription];
if (newSubscriptionRegistration == address(0)) {
revert NotRegistered(newSubscription);
}
if (newSubscriptionRegistration != newSubscription) {
revert CannotSubscribeToRegistrantWithSubscription(newSubscription);
}
if (registration != registrant) {
_subscribers[registration].remove(registrant);
emit SubscriptionUpdated(registrant, registration, false);
}
_registrations[registrant] = newSubscription;
_subscribers[newSubscription].add(registrant);
emit SubscriptionUpdated(registrant, newSubscription, true);
}
/**
* @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
*/
function unsubscribe(address registrant, bool copyExistingEntries) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration == registrant) {
revert NotSubscribed();
}
_subscribers[registration].remove(registrant);
_registrations[registrant] = registrant;
emit SubscriptionUpdated(registrant, registration, false);
if (copyExistingEntries) {
_copyEntries(registrant, registration);
}
}
/**
* @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
*/
function copyEntriesOf(address registrant, address registrantToCopy) external onlyAddressOrOwner(registrant) {
if (registrant == registrantToCopy) {
revert CannotCopyFromSelf();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
address registrantRegistration = _registrations[registrantToCopy];
if (registrantRegistration == address(0)) {
revert NotRegistered(registrantToCopy);
}
_copyEntries(registrant, registrantToCopy);
}
/// @dev helper to copy entries from registrantToCopy to registrant and emit events
function _copyEntries(address registrant, address registrantToCopy) private {
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrantToCopy];
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrantToCopy];
uint256 filteredOperatorsLength = filteredOperatorsRef.length();
uint256 filteredCodeHashesLength = filteredCodeHashesRef.length();
unchecked {
for (uint256 i = 0; i < filteredOperatorsLength; ++i) {
address operator = filteredOperatorsRef.at(i);
bool added = _filteredOperators[registrant].add(operator);
if (added) {
emit OperatorUpdated(registrant, operator, true);
}
}
for (uint256 i = 0; i < filteredCodeHashesLength; ++i) {
bytes32 codehash = filteredCodeHashesRef.at(i);
bool added = _filteredCodeHashes[registrant].add(codehash);
if (added) {
emit CodeHashUpdated(registrant, codehash, true);
}
}
}
}
//////////////////
// VIEW METHODS //
//////////////////
/**
* @notice Get the subscription address of a given registrant, if any.
*/
function subscriptionOf(address registrant) external view returns (address subscription) {
subscription = _registrations[registrant];
if (subscription == address(0)) {
revert NotRegistered(registrant);
} else if (subscription == registrant) {
subscription = address(0);
}
}
/**
* @notice Get the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscribers(address registrant) external view returns (address[] memory) {
return _subscribers[registrant].values();
}
/**
* @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscriberAt(address registrant, uint256 index) external view returns (address) {
return _subscribers[registrant].at(index);
}
/**
* @notice Returns true if operator is filtered by a given address or its subscription.
*/
function isOperatorFiltered(address registrant, address operator) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].contains(operator);
}
return _filteredOperators[registrant].contains(operator);
}
/**
* @notice Returns true if a codeHash is filtered by a given address or its subscription.
*/
function isCodeHashFiltered(address registrant, bytes32 codeHash) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].contains(codeHash);
}
return _filteredCodeHashes[registrant].contains(codeHash);
}
/**
* @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
*/
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external view returns (bool) {
bytes32 codeHash = operatorWithCode.codehash;
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].contains(codeHash);
}
return _filteredCodeHashes[registrant].contains(codeHash);
}
/**
* @notice Returns true if an address has registered
*/
function isRegistered(address registrant) external view returns (bool) {
return _registrations[registrant] != address(0);
}
/**
* @notice Returns a list of filtered operators for a given address or its subscription.
*/
function filteredOperators(address registrant) external view returns (address[] memory) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].values();
}
return _filteredOperators[registrant].values();
}
/**
* @notice Returns the set of filtered codeHashes for a given address or its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashes(address registrant) external view returns (bytes32[] memory) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].values();
}
return _filteredCodeHashes[registrant].values();
}
/**
* @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredOperatorAt(address registrant, uint256 index) external view returns (address) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].at(index);
}
return _filteredOperators[registrant].at(index);
}
/**
* @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashAt(address registrant, uint256 index) external view returns (bytes32) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].at(index);
}
return _filteredCodeHashes[registrant].at(index);
}
/// @dev Convenience method to compute the code hash of an arbitrary contract
function codeHashOf(address a) external view returns (bytes32) {
return a.codehash;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract OperatorFilterRegistryErrorsAndEvents {
error CannotFilterEOAs();
error AddressAlreadyFiltered(address operator);
error AddressNotFiltered(address operator);
error CodeHashAlreadyFiltered(bytes32 codeHash);
error CodeHashNotFiltered(bytes32 codeHash);
error OnlyAddressOrOwner();
error NotRegistered(address registrant);
error AlreadyRegistered();
error AlreadySubscribed(address subscription);
error NotSubscribed();
error CannotUpdateWhileSubscribed(address subscription);
error CannotSubscribeToSelf();
error CannotSubscribeToZeroAddress();
error NotOwnable();
error AddressFiltered(address filtered);
error CodeHashFiltered(address account, bytes32 codeHash);
error CannotSubscribeToRegistrantWithSubscription(address registrant);
error CannotCopyFromSelf();
event RegistrationUpdated(address indexed registrant, bool indexed registered);
event OperatorUpdated(address indexed registrant, address indexed operator, bool indexed filtered);
event OperatorsUpdated(address indexed registrant, address[] operators, bool indexed filtered);
event CodeHashUpdated(address indexed registrant, bytes32 indexed codeHash, bool indexed filtered);
event CodeHashesUpdated(address indexed registrant, bytes32[] codeHashes, bool indexed filtered);
event SubscriptionUpdated(address indexed registrant, address indexed subscription, bool indexed subscribed);
}
File 5 of 5: PayableProxy
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import { PayableProxyInterface } from "../interfaces/PayableProxyInterface.sol";
interface IUpgradeBeacon {
/**
* @notice An external view function that returns the implementation.
*
* @return The address of the implementation.
*/
function implementation() external view returns (address);
}
/**
* @title PayableProxy
* @author OpenSea Protocol Team
* @notice PayableProxy is a beacon proxy which will immediately return if
* called with callvalue. Otherwise, it will delegatecall the beacon
* implementation.
*/
contract PayableProxy is PayableProxyInterface {
// Address of the beacon.
address private immutable _beacon;
constructor(address beacon) payable {
// Ensure the origin is an approved deployer.
require(
(tx.origin == address(0x939C8d89EBC11fA45e576215E2353673AD0bA18A) ||
tx.origin ==
address(0xe80a65eB7a3018DedA407e621Ef5fb5B416678CA) ||
tx.origin ==
address(0x86D26897267711ea4b173C8C124a0A73612001da) ||
tx.origin ==
address(0x3B52ad533687Ce908bA0485ac177C5fb42972962)),
"Deployment must originate from an approved deployer."
);
// Set the initial beacon.
_beacon = beacon;
}
function initialize(address ownerToSet) external {
// Ensure the origin is an approved deployer.
require(
(tx.origin == address(0x939C8d89EBC11fA45e576215E2353673AD0bA18A) ||
tx.origin ==
address(0xe80a65eB7a3018DedA407e621Ef5fb5B416678CA) ||
tx.origin ==
address(0x86D26897267711ea4b173C8C124a0A73612001da) ||
tx.origin ==
address(0x3B52ad533687Ce908bA0485ac177C5fb42972962)),
"Initialize must originate from an approved deployer."
);
// Get the implementation address from the provided beacon.
address implementation = IUpgradeBeacon(_beacon).implementation();
// Create the initializationCalldata from the provided parameters.
bytes memory initializationCalldata = abi.encodeWithSignature(
"initialize(address)",
ownerToSet
);
// Delegatecall into the implementation, supplying initialization
// calldata.
(bool ok, ) = implementation.delegatecall(initializationCalldata);
// Revert and include revert data if delegatecall to implementation
// reverts.
if (!ok) {
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
/**
* @dev Fallback function that delegates calls to the address returned by
* `_implementation()`. Will run if no other function in the contract
* matches the call data.
*/
fallback() external payable override {
_fallback();
}
/**
* @dev Internal fallback function that delegates calls to the address
* returned by `_implementation()`. Will run if no other function
* in the contract matches the call data.
*/
function _fallback() internal {
// Delegate if call value is zero.
if (msg.value == 0) {
_delegate(_implementation());
}
}
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will
* return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this
// inline assembly block because it will not return to
// Solidity code. We overwrite the Solidity scratch pad
// at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(
gas(),
implementation,
0,
calldatasize(),
0,
0
)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This function returns the address to which the fallback function
* should delegate.
*/
function _implementation() internal view returns (address) {
return IUpgradeBeacon(_beacon).implementation();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* @title PayableProxyInterface
* @author OpenSea Protocol Team
* @notice PayableProxyInterface contains all external function interfaces
* for the payable proxy.
*/
interface PayableProxyInterface {
/**
* @dev Fallback function that delegates calls to the address returned by
* `_implementation()`. Will run if no other function in the contract
* matches the call data.
*/
fallback() external payable;
}