Skip to content

Commit

Permalink
Merge pull request #966 from ProjectOpenSea/ryan/seaport-router
Browse files Browse the repository at this point in the history
Add SeaportRouter
  • Loading branch information
0age authored Feb 8, 2023
2 parents 4f2210b + c873f2a commit 59f6673
Show file tree
Hide file tree
Showing 5 changed files with 1,024 additions and 7 deletions.
218 changes: 218 additions & 0 deletions contracts/helpers/SeaportRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {
SeaportRouterInterface
} from "../interfaces/SeaportRouterInterface.sol";

import { SeaportInterface } from "../interfaces/SeaportInterface.sol";

import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol";

import {
Execution,
AdvancedOrder,
CriteriaResolver,
FulfillmentComponent
} from "../lib/ConsiderationStructs.sol";

/**
* @title SeaportRouter
* @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth)
* @notice A utility contract for fulfilling orders with multiple
* Seaport versions. DISCLAIMER: This contract only works when
* all consideration items across all listings are native tokens.
*/
contract SeaportRouter is SeaportRouterInterface, ReentrancyGuard {
/// @dev The allowed v1.1 contract usable through this router.
address private immutable _SEAPORT_V1_1;
/// @dev The allowed v1.2 contract usable through this router.
address private immutable _SEAPORT_V1_2;

/**
* @dev Deploy contract with the supported Seaport contracts.
*
* @param seaportV1point1 The address of the Seaport v1.1 contract.
* @param seaportV1point2 The address of the Seaport v1.2 contract.
*/
constructor(address seaportV1point1, address seaportV1point2) {
_SEAPORT_V1_1 = seaportV1point1;
_SEAPORT_V1_2 = seaportV1point2;
}

/**
* @dev Fallback function to receive excess ether, in case total amount of
* ether sent is more than the amount required to fulfill the order.
*/
receive() external payable override {
// Ensure we only receive ether from Seaport.
_assertSeaportAllowed(msg.sender);
}

/**
* @notice Fulfill available advanced orders through multiple Seaport
* versions.
* See {SeaportInterface-fulfillAvailableAdvancedOrders}
*
* @param params The parameters for fulfilling available advanced orders.
*/
function fulfillAvailableAdvancedOrders(
FulfillAvailableAdvancedOrdersParams calldata params
)
external
payable
override
returns (
bool[][] memory availableOrders,
Execution[][] memory executions
)
{
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard(true);

// Put the number of Seaport contracts on the stack.
uint256 seaportContractsLength = params.seaportContracts.length;

// Set the availableOrders and executions arrays to the correct length.
availableOrders = new bool[][](seaportContractsLength);
executions = new Execution[][](seaportContractsLength);

// Track the number of order fulfillments left.
uint256 fulfillmentsLeft = params.maximumFulfilled;

// To help avoid stack too deep errors, we format the calldata
// params in a struct and put it on the stack.
AdvancedOrder[] memory emptyAdvancedOrders;
CriteriaResolver[] memory emptyCriteriaResolvers;
FulfillmentComponent[][] memory emptyFulfillmentComponents;
CalldataParams memory calldataParams = CalldataParams({
advancedOrders: emptyAdvancedOrders,
criteriaResolvers: emptyCriteriaResolvers,
offerFulfillments: emptyFulfillmentComponents,
considerationFulfillments: emptyFulfillmentComponents,
fulfillerConduitKey: params.fulfillerConduitKey,
recipient: params.recipient,
maximumFulfilled: fulfillmentsLeft
});

// Iterate through the provided Seaport contracts.
for (uint256 i = 0; i < params.seaportContracts.length; ) {
// Ensure the provided Seaport contract is allowed.
_assertSeaportAllowed(params.seaportContracts[i]);

// Put the order params on the stack.
AdvancedOrderParams calldata orderParams = params
.advancedOrderParams[i];

// Assign the variables to the calldata params.
calldataParams.advancedOrders = orderParams.advancedOrders;
calldataParams.criteriaResolvers = orderParams.criteriaResolvers;
calldataParams.offerFulfillments = orderParams.offerFulfillments;
calldataParams.considerationFulfillments = orderParams
.considerationFulfillments;

// Execute the orders, collecting availableOrders and executions.
// This is wrapped in a try/catch in case a single order is
// executed that is no longer available, leading to a revert
// with `NoSpecifiedOrdersAvailable()`.
try
SeaportInterface(params.seaportContracts[i])
.fulfillAvailableAdvancedOrders{
value: orderParams.etherValue
}(
calldataParams.advancedOrders,
calldataParams.criteriaResolvers,
calldataParams.offerFulfillments,
calldataParams.considerationFulfillments,
calldataParams.fulfillerConduitKey,
calldataParams.recipient,
calldataParams.maximumFulfilled
)
returns (
bool[] memory newAvailableOrders,
Execution[] memory newExecutions
) {
availableOrders[i] = newAvailableOrders;
executions[i] = newExecutions;

// Subtract the number of orders fulfilled.
uint256 newAvailableOrdersLength = newAvailableOrders.length;
for (uint256 j = 0; j < newAvailableOrdersLength; ) {
if (newAvailableOrders[j]) {
unchecked {
--fulfillmentsLeft;
++j;
}
}
}

// Break if the maximum number of executions has been reached.
if (fulfillmentsLeft == 0) {
break;
}
} catch {}

// Update fulfillments left.
calldataParams.maximumFulfilled = fulfillmentsLeft;

unchecked {
++i;
}
}

// Return excess ether that may not have been used or was sent back.
if (address(this).balance > 0) {
_returnExcessEther();
}

// Clear the reentrancy guard.
_clearReentrancyGuard();
}

/**
* @notice Returns the Seaport contracts allowed to be used through this
* router.
*/
function getAllowedSeaportContracts()
external
view
override
returns (address[] memory seaportContracts)
{
seaportContracts = new address[](2);
seaportContracts[0] = _SEAPORT_V1_1;
seaportContracts[1] = _SEAPORT_V1_2;
}

/**
* @dev Reverts if the provided Seaport contract is not allowed.
*/
function _assertSeaportAllowed(address seaport) internal view {
if (
_cast(seaport == _SEAPORT_V1_1) | _cast(seaport == _SEAPORT_V1_2) ==
0
) {
revert SeaportNotAllowed(seaport);
}
}

/**
* @dev Function to return excess ether, in case total amount of
* ether sent is more than the amount required to fulfill the order.
*/
function _returnExcessEther() private {
// Send received funds back to msg.sender.
(bool success, bytes memory data) = payable(msg.sender).call{
value: address(this).balance
}("");

// Revert with an error if the ether transfer failed.
if (!success) {
revert EtherReturnTransferFailed(
msg.sender,
address(this).balance,
data
);
}
}
}
104 changes: 104 additions & 0 deletions contracts/interfaces/SeaportRouterInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {
AdvancedOrder,
CriteriaResolver,
FulfillmentComponent
} from "../lib/ConsiderationStructs.sol";

import { Execution } from "../lib/ConsiderationStructs.sol";

/**
* @title SeaportRouterInterface
* @author Ryan Ghods (ralxz.eth), 0age (0age.eth), James Wenzel (emo.eth)
* @notice A utility contract for fulfilling orders with multiple
* Seaport versions. DISCLAIMER: This contract only works when
* all consideration items across all listings are native tokens.
*/
interface SeaportRouterInterface {
/**
* @dev Advanced order parameters for use through the
* FulfillAvailableAdvancedOrdersParams struct.
*/
struct AdvancedOrderParams {
AdvancedOrder[] advancedOrders;
CriteriaResolver[] criteriaResolvers;
FulfillmentComponent[][] offerFulfillments;
FulfillmentComponent[][] considerationFulfillments;
uint256 etherValue; /// The ether value to send with the set of orders.
}

/**
* @dev Parameters for using fulfillAvailableAdvancedOrders
* through SeaportRouter.
*/
struct FulfillAvailableAdvancedOrdersParams {
address[] seaportContracts;
AdvancedOrderParams[] advancedOrderParams;
bytes32 fulfillerConduitKey;
address recipient;
uint256 maximumFulfilled;
}

/**
* @dev Calldata params for calling FulfillAvailableAdvancedOrders.
*/
struct CalldataParams {
AdvancedOrder[] advancedOrders;
CriteriaResolver[] criteriaResolvers;
FulfillmentComponent[][] offerFulfillments;
FulfillmentComponent[][] considerationFulfillments;
bytes32 fulfillerConduitKey;
address recipient;
uint256 maximumFulfilled;
}

/**
* @dev Revert with an error if a provided Seaport contract is not allowed
* to be used in the router.
*/
error SeaportNotAllowed(address seaport);

/**
* @dev Revert with an error if an ether transfer back to the fulfiller
* fails.
*/
error EtherReturnTransferFailed(
address recipient,
uint256 amount,
bytes returnData
);

/**
* @dev Fallback function to receive excess ether, in case total amount of
* ether sent is more than the amount required to fulfill the order.
*/
receive() external payable;

/**
* @notice Fulfill available advanced orders through multiple Seaport
* versions.
* See {SeaportInterface-fulfillAvailableAdvancedOrders}
*
* @param params The parameters for fulfilling available advanced orders.
*/
function fulfillAvailableAdvancedOrders(
FulfillAvailableAdvancedOrdersParams calldata params
)
external
payable
returns (
bool[][] memory availableOrders,
Execution[][] memory executions
);

/**
* @notice Returns the Seaport contracts allowed to be used through this
* router.
*/
function getAllowedSeaportContracts()
external
view
returns (address[] memory);
}
10 changes: 10 additions & 0 deletions contracts/test/Reenterer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,14 @@ contract Reenterer {
isPrepared = false;
}
}

function execute(address to, uint256 value, bytes memory data) external {
(bool success, ) = payable(to).call{ value: value }(data);
if (!success) {
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
}
Loading

0 comments on commit 59f6673

Please sign in to comment.