Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SeaportRouter #966

Merged
merged 13 commits into from
Feb 8, 2023
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