// SPDX-License-Identifier: Apache-2.0
/*

  Copyright 2021 ZeroEx Intl.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

*/

pragma solidity ^0.6;
pragma experimental ABIEncoderV2;

import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "../../fixins/FixinERC721Spender.sol";
import "../../migrations/LibMigrate.sol";
import "../../storage/LibERC721OrdersStorage.sol";
import "../interfaces/IFeature.sol";
import "../interfaces/IERC721OrdersFeature.sol";
import "../libs/LibNFTOrder.sol";
import "../libs/LibSignature.sol";
import "./NFTOrders.sol";


/// @dev Feature for interacting with ERC721 orders.
contract ERC721OrdersFeature is
    IFeature,
    IERC721OrdersFeature,
    FixinERC721Spender,
    NFTOrders
{
    using LibSafeMathV06 for uint256;
    using LibNFTOrder for LibNFTOrder.ERC721Order;
    using LibNFTOrder for LibNFTOrder.NFTOrder;

    /// @dev Name of this feature.
    string public constant override FEATURE_NAME = "ERC721Orders";
    /// @dev Version of this feature.
    uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);

    /// @dev The magic return value indicating the success of a `onERC721Received`.
    bytes4 private constant ERC721_RECEIVED_MAGIC_BYTES = this.onERC721Received.selector;


    constructor(address zeroExAddress, IEtherTokenV06 weth)
        public
        NFTOrders(zeroExAddress, weth)
    {}

    /// @dev Initialize and register this feature.
    ///      Should be delegatecalled by `Migrate.migrate()`.
    /// @return success `LibMigrate.SUCCESS` on success.
    function migrate()
        external
        returns (bytes4 success)
    {
        _registerFeatureFunction(this.sellERC721.selector);
        _registerFeatureFunction(this.buyERC721.selector);
        _registerFeatureFunction(this.cancelERC721Order.selector);
        _registerFeatureFunction(this.batchBuyERC721s.selector);
        _registerFeatureFunction(this.matchERC721Orders.selector);
        _registerFeatureFunction(this.batchMatchERC721Orders.selector);
        _registerFeatureFunction(this.onERC721Received.selector);
        _registerFeatureFunction(this.preSignERC721Order.selector);
        _registerFeatureFunction(this.validateERC721OrderSignature.selector);
        _registerFeatureFunction(this.validateERC721OrderProperties.selector);
        _registerFeatureFunction(this.getERC721OrderStatus.selector);
        _registerFeatureFunction(this.getERC721OrderHash.selector);
        _registerFeatureFunction(this.getERC721OrderStatusBitVector.selector);
        return LibMigrate.MIGRATE_SUCCESS;
    }

    /// @dev Sells an ERC721 asset to fill the given order.
    /// @param buyOrder The ERC721 buy order.
    /// @param signature The order signature from the maker.
    /// @param erc721TokenId The ID of the ERC721 asset being
    ///        sold. If the given order specifies properties,
    ///        the asset must satisfy those properties. Otherwise,
    ///        it must equal the tokenId in the order.
    /// @param unwrapNativeToken If this parameter is true and the
    ///        ERC20 token of the order is e.g. WETH, unwraps the
    ///        token before transferring it to the taker.
    /// @param callbackData If this parameter is non-zero, invokes
    ///        `zeroExERC721OrderCallback` on `msg.sender` after
    ///        the ERC20 tokens have been transferred to `msg.sender`
    ///        but before transferring the ERC721 asset to the buyer.
    function sellERC721(
        LibNFTOrder.ERC721Order memory buyOrder,
        LibSignature.Signature memory signature,
        uint256 erc721TokenId,
        bool unwrapNativeToken,
        bytes memory callbackData
    )
        public
        override
    {
        _sellERC721(
            buyOrder,
            signature,
            erc721TokenId,
            unwrapNativeToken,
            msg.sender, // taker
            msg.sender, // owner
            callbackData
        );
    }

    /// @dev Buys an ERC721 asset by filling the given order.
    /// @param sellOrder The ERC721 sell order.
    /// @param signature The order signature.
    /// @param callbackData If this parameter is non-zero, invokes
    ///        `zeroExERC721OrderCallback` on `msg.sender` after
    ///        the ERC721 asset has been transferred to `msg.sender`
    ///        but before transferring the ERC20 tokens to the seller.
    ///        Native tokens acquired during the callback can be used
    ///        to fill the order.
    function buyERC721(
        LibNFTOrder.ERC721Order memory sellOrder,
        LibSignature.Signature memory signature,
        bytes memory callbackData
    )
        public
        override
        payable
    {
        uint256 ethBalanceBefore = address(this).balance
            .safeSub(msg.value);
        _buyERC721(
            sellOrder,
            signature,
            msg.value,
            callbackData
        );
        uint256 ethBalanceAfter = address(this).balance;
        // Cannot use pre-existing ETH balance
        if (ethBalanceAfter < ethBalanceBefore) {
            LibNFTOrdersRichErrors.OverspentEthError(
                msg.value + (ethBalanceBefore - ethBalanceAfter),
                msg.value
            ).rrevert();
        }
        // Refund
        _transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
    }

    /// @dev Cancel a single ERC721 order by its nonce. The caller
    ///      should be the maker of the order. Silently succeeds if
    ///      an order with the same nonce has already been filled or
    ///      cancelled.
    /// @param orderNonce The order nonce.
    function cancelERC721Order(uint256 orderNonce)
        public
        override
    {
        // Mark order as cancelled
        _setOrderStatusBit(msg.sender, orderNonce);
        emit ERC721OrderCancelled(msg.sender, orderNonce);
    }

    /// @dev Cancel multiple ERC721 orders by their nonces. The caller
    ///      should be the maker of the orders. Silently succeeds if
    ///      an order with the same nonce has already been filled or
    ///      cancelled.
    /// @param orderNonces The order nonces.
    function batchCancelERC721Orders(uint256[] calldata orderNonces)
        external
        override
    {
        for (uint256 i = 0; i < orderNonces.length; i++) {
            cancelERC721Order(orderNonces[i]);
        }
    }

    /// @dev Buys multiple ERC721 assets by filling the
    ///      given orders.
    /// @param sellOrders The ERC721 sell orders.
    /// @param signatures The order signatures.
    /// @param revertIfIncomplete If true, reverts if this
    ///        function fails to fill any individual order.
    /// @param callbackData The data (if any) to pass to the taker
    ///        callback for each order. Refer to the `callbackData`
    ///        parameter to for `buyERC721`.
    /// @return successes An array of booleans corresponding to whether
    ///         each order in `orders` was successfully filled.
    function batchBuyERC721s(
        LibNFTOrder.ERC721Order[] memory sellOrders,
        LibSignature.Signature[] memory signatures,
        bytes[] memory callbackData,
        bool revertIfIncomplete
    )
        public
        override
        payable
        returns (bool[] memory successes)
    {
        require(
            sellOrders.length == signatures.length &&
            sellOrders.length == callbackData.length,
            "ERC721OrdersFeature::batchBuyERC721s/ARRAY_LENGTH_MISMATCH"
        );
        successes = new bool[](sellOrders.length);

        uint256 ethBalanceBefore = address(this).balance
            .safeSub(msg.value);
        if (revertIfIncomplete) {
            for (uint256 i = 0; i < sellOrders.length; i++) {
                // Will revert if _buyERC721 reverts.
                _buyERC721(
                    sellOrders[i],
                    signatures[i],
                    address(this).balance.safeSub(ethBalanceBefore),
                    callbackData[i]
                );
                successes[i] = true;
            }
        } else {
            for (uint256 i = 0; i < sellOrders.length; i++) {
                // Delegatecall `_buyERC721` to swallow reverts while
                // preserving execution context.
                // Note that `_buyERC721` is a public function but should _not_
                // be registered in the Exchange Proxy.
                (successes[i], ) = _implementation.delegatecall(
                    abi.encodeWithSelector(
                        this._buyERC721.selector,
                        sellOrders[i],
                        signatures[i],
                        address(this).balance.safeSub(ethBalanceBefore), // Remaining ETH available
                        callbackData[i]
                    )
                );
            }
        }

        // Cannot use pre-existing ETH balance
        uint256 ethBalanceAfter = address(this).balance;
        if (ethBalanceAfter < ethBalanceBefore) {
            LibNFTOrdersRichErrors.OverspentEthError(
                msg.value + (ethBalanceBefore - ethBalanceAfter),
                msg.value
            ).rrevert();
        }

        // Refund
        _transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
    }

    /// @dev Matches a pair of complementary orders that have
    ///      a non-negative spread. Each order is filled at
    ///      their respective price, and the matcher receives
    ///      a profit denominated in the ERC20 token.
    /// @param sellOrder Order selling an ERC721 asset.
    /// @param buyOrder Order buying an ERC721 asset.
    /// @param sellOrderSignature Signature for the sell order.
    /// @param buyOrderSignature Signature for the buy order.
    /// @return profit The amount of profit earned by the caller
    ///         of this function (denominated in the ERC20 token
    ///         of the matched orders).
    function matchERC721Orders(
        LibNFTOrder.ERC721Order memory sellOrder,
        LibNFTOrder.ERC721Order memory buyOrder,
        LibSignature.Signature memory sellOrderSignature,
        LibSignature.Signature memory buyOrderSignature
    )
        public
        override
        returns (uint256 profit)
    {
        // The ERC721 tokens must match
        if (sellOrder.erc721Token != buyOrder.erc721Token) {
            LibNFTOrdersRichErrors.ERC721TokenMismatchError(
                address(sellOrder.erc721Token),
                address(buyOrder.erc721Token)
            ).rrevert();
        }

        LibNFTOrder.NFTOrder memory sellNFTOrder = sellOrder.asNFTOrder();
        LibNFTOrder.NFTOrder memory buyNFTOrder = buyOrder.asNFTOrder();

        {
            LibNFTOrder.OrderInfo memory sellOrderInfo = _getOrderInfo(sellNFTOrder);
            LibNFTOrder.OrderInfo memory buyOrderInfo = _getOrderInfo(buyNFTOrder);

            _validateSellOrder(
                sellNFTOrder,
                sellOrderSignature,
                sellOrderInfo,
                buyOrder.maker
            );
            _validateBuyOrder(
                buyNFTOrder,
                buyOrderSignature,
                buyOrderInfo,
                sellOrder.maker,
                sellOrder.erc721TokenId
            );

            // Mark both orders as filled.
            _updateOrderState(sellNFTOrder, sellOrderInfo.orderHash, 1);
            _updateOrderState(buyNFTOrder, buyOrderInfo.orderHash, 1);
        }

        // The buyer must be willing to pay at least the amount that the
        // seller is asking.
        if (buyOrder.erc20TokenAmount < sellOrder.erc20TokenAmount) {
            LibNFTOrdersRichErrors.NegativeSpreadError(
                sellOrder.erc20TokenAmount,
                buyOrder.erc20TokenAmount
            ).rrevert();
        }

        // The difference in ERC20 token amounts is the spread.
        uint256 spread = buyOrder.erc20TokenAmount - sellOrder.erc20TokenAmount;

        // Transfer the ERC721 asset from seller to buyer.
        _transferERC721AssetFrom(
            sellOrder.erc721Token,
            sellOrder.maker,
            buyOrder.maker,
            sellOrder.erc721TokenId
        );

        // Handle the ERC20 side of the order:
        if (
            address(sellOrder.erc20Token) == NATIVE_TOKEN_ADDRESS &&
            buyOrder.erc20Token == WETH
        ) {
            // The sell order specifies ETH, while the buy order specifies WETH.
            // The orders are still compatible with one another, but we'll have
            // to unwrap the WETH on behalf of the buyer.

            // Step 1: Transfer WETH from the buyer to the EP.
            //         Note that we transfer `buyOrder.erc20TokenAmount`, which
            //         is the amount the buyer signaled they are willing to pay
            //         for the ERC721 asset, which may be more than the seller's
            //         ask.
            _transferERC20TokensFrom(
                WETH,
                buyOrder.maker,
                address(this),
                buyOrder.erc20TokenAmount
            );
            // Step 2: Unwrap the WETH into ETH. We unwrap the entire
            //         `buyOrder.erc20TokenAmount`.
            //         The ETH will be used for three purposes:
            //         - To pay the seller
            //         - To pay fees for the sell order
            //         - Any remaining ETH will be sent to
            //           `msg.sender` as profit.
            WETH.withdraw(buyOrder.erc20TokenAmount);

            // Step 3: Pay the seller (in ETH).
            _transferEth(payable(sellOrder.maker), sellOrder.erc20TokenAmount);

            // Step 4: Pay fees for the buy order. Note that these are paid
            //         in _WETH_ by the _buyer_. By signing the buy order, the
            //         buyer signals that they are willing to spend a total
            //         of `erc20TokenAmount` _plus_ fees, all denominated in
            //         the `erc20Token`, which in this case is WETH.
            _payFees(
                buyNFTOrder,
                buyOrder.maker, // payer
                1,              // fillAmount
                1,              // orderAmount
                false           // useNativeToken
            );

            // Step 5: Pay fees for the sell order. The `erc20Token` of the
            //         sell order is ETH, so the fees are paid out in ETH.
            //         There should be `spread` wei of ETH remaining in the
            //         EP at this point, which we will use ETH to pay the
            //         sell order fees.
            uint256 sellOrderFees = _payFees(
                sellNFTOrder,
                address(this), // payer
                1,             // fillAmount
                1,             // orderAmount
                true           // useNativeToken
            );

            // Step 6: The spread must be enough to cover the sell order fees.
            //         If not, either `_payFees` will have reverted, or we
            //         have spent ETH that was in the EP before this
            //         `matchERC721Orders` call, which we disallow.
            if (spread < sellOrderFees) {
                LibNFTOrdersRichErrors.SellOrderFeesExceedSpreadError(
                    sellOrderFees,
                    spread
                ).rrevert();
            }
            // Step 7: The spread less the sell order fees is the amount of ETH
            //         remaining in the EP that can be sent to `msg.sender` as
            //         the profit from matching these two orders.
            profit = spread - sellOrderFees;
            if (profit > 0) {
                _transferEth(msg.sender, profit);
            }
        } else {
            // ERC20 tokens must match
            if (sellOrder.erc20Token != buyOrder.erc20Token) {
                LibNFTOrdersRichErrors.ERC20TokenMismatchError(
                    address(sellOrder.erc20Token),
                    address(buyOrder.erc20Token)
                ).rrevert();
            }

            // Step 1: Transfer the ERC20 token from the buyer to the seller.
            //         Note that we transfer `sellOrder.erc20TokenAmount`, which
            //         is at most `buyOrder.erc20TokenAmount`.
            _transferERC20TokensFrom(
                buyOrder.erc20Token,
                buyOrder.maker,
                sellOrder.maker,
                sellOrder.erc20TokenAmount
            );

            // Step 2: Pay fees for the buy order. Note that these are paid
            //         by the buyer. By signing the buy order, the buyer signals
            //         that they are willing to spend a total of
            //         `buyOrder.erc20TokenAmount` _plus_ `buyOrder.fees`.
            _payFees(
                buyNFTOrder,
                buyOrder.maker, // payer
                1,              // fillAmount
                1,              // orderAmount
                false           // useNativeToken
            );

            // Step 3: Pay fees for the sell order. These are paid by the buyer
            //         as well. After paying these fees, we may have taken more
            //         from the buyer than they agreed to in the buy order. If
            //         so, we revert in the following step.
            uint256 sellOrderFees = _payFees(
                sellNFTOrder,
                buyOrder.maker, // payer
                1,              // fillAmount
                1,              // orderAmount
                false           // useNativeToken
            );

            // Step 4: The spread must be enough to cover the sell order fees.
            //         If not, `_payFees` will have taken more tokens from the
            //         buyer than they had agreed to in the buy order, in which
            //         case we revert here.
            if (spread < sellOrderFees) {
                LibNFTOrdersRichErrors.SellOrderFeesExceedSpreadError(
                    sellOrderFees,
                    spread
                ).rrevert();
            }

            // Step 5: We calculate the profit as:
            //         profit = buyOrder.erc20TokenAmount - sellOrder.erc20TokenAmount - sellOrderFees
            //                = spread - sellOrderFees
            //         I.e. the buyer would've been willing to pay up to `profit`
            //         more to buy the asset, so instead that amount is sent to
            //         `msg.sender` as the profit from matching these two orders.
            profit = spread - sellOrderFees;
            if (profit > 0) {
                _transferERC20TokensFrom(
                    buyOrder.erc20Token,
                    buyOrder.maker,
                    msg.sender,
                    profit
                );
            }
        }

        emit ERC721OrderFilled(
            sellOrder.direction,
            sellOrder.maker,
            buyOrder.maker, // taker
            sellOrder.nonce,
            sellOrder.erc20Token,
            sellOrder.erc20TokenAmount,
            sellOrder.erc721Token,
            sellOrder.erc721TokenId,
            msg.sender
        );

        emit ERC721OrderFilled(
            buyOrder.direction,
            buyOrder.maker,
            sellOrder.maker, // taker
            buyOrder.nonce,
            buyOrder.erc20Token,
            buyOrder.erc20TokenAmount,
            buyOrder.erc721Token,
            sellOrder.erc721TokenId,
            msg.sender
        );
    }

    /// @dev Matches pairs of complementary orders that have
    ///      non-negative spreads. Each order is filled at
    ///      their respective price, and the matcher receives
    ///      a profit denominated in the ERC20 token.
    /// @param sellOrders Orders selling ERC721 assets.
    /// @param buyOrders Orders buying ERC721 assets.
    /// @param sellOrderSignatures Signatures for the sell orders.
    /// @param buyOrderSignatures Signatures for the buy orders.
    /// @return profits The amount of profit earned by the caller
    ///         of this function for each pair of matched orders
    ///         (denominated in the ERC20 token of the order pair).
    /// @return successes An array of booleans corresponding to
    ///         whether each pair of orders was successfully matched.
    function batchMatchERC721Orders(
        LibNFTOrder.ERC721Order[] memory sellOrders,
        LibNFTOrder.ERC721Order[] memory buyOrders,
        LibSignature.Signature[] memory sellOrderSignatures,
        LibSignature.Signature[] memory buyOrderSignatures
    )
        public
        override
        returns (uint256[] memory profits, bool[] memory successes)
    {
        require(
            sellOrders.length == buyOrders.length &&
            sellOrderSignatures.length == buyOrderSignatures.length &&
            sellOrders.length == sellOrderSignatures.length,
            "ERC721OrdersFeature::batchMatchERC721Orders/ARRAY_LENGTH_MISMATCH"
        );
        profits = new uint256[](sellOrders.length);
        successes = new bool[](sellOrders.length);

        for (uint256 i = 0; i < sellOrders.length; i++) {
            bytes memory returnData;
            // Delegatecall `matchERC721Orders` to catch reverts while
            // preserving execution context.
            (successes[i], returnData) = _implementation.delegatecall(
                abi.encodeWithSelector(
                    this.matchERC721Orders.selector,
                    sellOrders[i],
                    buyOrders[i],
                    sellOrderSignatures[i],
                    buyOrderSignatures[i]
                )
            );
            if (successes[i]) {
                // If the matching succeeded, record the profit.
                (uint256 profit) = abi.decode(returnData, (uint256));
                profits[i] = profit;
            }
        }
    }

    /// @dev Callback for the ERC721 `safeTransferFrom` function.
    ///      This callback can be used to sell an ERC721 asset if
    ///      a valid ERC721 order, signature and `unwrapNativeToken`
    ///      are encoded in `data`. This allows takers to sell their
    ///      ERC721 asset without first calling `setApprovalForAll`.
    /// @param operator The address which called `safeTransferFrom`.
    /// @param tokenId The ID of the asset being transferred.
    /// @param data Additional data with no specified format. If a
    ///        valid ERC721 order, signature and `unwrapNativeToken`
    ///        are encoded in `data`, this function will try to fill
    ///        the order using the received asset.
    /// @return success The selector of this function (0x150b7a02),
    ///         indicating that the callback succeeded.
    function onERC721Received(
        address operator,
        address /* from */,
        uint256 tokenId,
        bytes calldata data
    )
        external
        override
        returns (bytes4 success)
    {
        // Decode the order, signature, and `unwrapNativeToken` from
        // `data`. If `data` does not encode such parameters, this
        // will throw.
        (
            LibNFTOrder.ERC721Order memory buyOrder,
            LibSignature.Signature memory signature,
            bool unwrapNativeToken
        ) = abi.decode(
            data,
            (LibNFTOrder.ERC721Order, LibSignature.Signature, bool)
        );

        // `onERC721Received` is called by the ERC721 token contract.
        // Check that it matches the ERC721 token in the order.
        if (msg.sender != address(buyOrder.erc721Token)) {
            LibNFTOrdersRichErrors.ERC721TokenMismatchError(
                msg.sender,
                address(buyOrder.erc721Token)
            ).rrevert();
        }

        _sellERC721(
            buyOrder,
            signature,
            tokenId,
            unwrapNativeToken,
            operator,       // taker
            address(this),  // owner (we hold the NFT currently)
            new bytes(0)    // No taker callback
        );

        return ERC721_RECEIVED_MAGIC_BYTES;
    }

    /// @dev Approves an ERC721 order on-chain. After pre-signing
    ///      the order, the `PRESIGNED` signature type will become
    ///      valid for that order and signer.
    /// @param order An ERC721 order.
    function preSignERC721Order(LibNFTOrder.ERC721Order memory order)
        public
        override
    {
        require(
            order.maker == msg.sender,
            "ERC721OrdersFeature::preSignERC721Order/ONLY_MAKER"
        );
        bytes32 orderHash = getERC721OrderHash(order);
        LibERC721OrdersStorage.getStorage().preSigned[orderHash] = true;

        emit ERC721OrderPreSigned(
            order.direction,
            order.maker,
            order.taker,
            order.expiry,
            order.nonce,
            order.erc20Token,
            order.erc20TokenAmount,
            order.fees,
            order.erc721Token,
            order.erc721TokenId,
            order.erc721TokenProperties
        );
    }

    // Core settlement logic for selling an ERC721 asset.
    // Used by `sellERC721` and `onERC721Received`.
    function _sellERC721(
        LibNFTOrder.ERC721Order memory buyOrder,
        LibSignature.Signature memory signature,
        uint256 erc721TokenId,
        bool unwrapNativeToken,
        address taker,
        address currentNftOwner,
        bytes memory takerCallbackData
    )
        private
    {
        _sellNFT(
            buyOrder.asNFTOrder(),
            signature,
            SellParams(
                1, // sell amount
                erc721TokenId,
                unwrapNativeToken,
                taker,
                currentNftOwner,
                takerCallbackData
            )
        );

        emit ERC721OrderFilled(
            buyOrder.direction,
            buyOrder.maker,
            taker,
            buyOrder.nonce,
            buyOrder.erc20Token,
            buyOrder.erc20TokenAmount,
            buyOrder.erc721Token,
            erc721TokenId,
            address(0)
        );
    }

    // Core settlement logic for buying an ERC721 asset.
    // Used by `buyERC721` and `batchBuyERC721s`.
    function _buyERC721(
        LibNFTOrder.ERC721Order memory sellOrder,
        LibSignature.Signature memory signature,
        uint256 ethAvailable,
        bytes memory takerCallbackData
    )
        public
        payable
    {
        _buyNFT(
            sellOrder.asNFTOrder(),
            signature,
            BuyParams(
                1, // buy amount
                ethAvailable,
                takerCallbackData
            )
        );

        emit ERC721OrderFilled(
            sellOrder.direction,
            sellOrder.maker,
            msg.sender,
            sellOrder.nonce,
            sellOrder.erc20Token,
            sellOrder.erc20TokenAmount,
            sellOrder.erc721Token,
            sellOrder.erc721TokenId,
            address(0)
        );
    }


    /// @dev Checks whether the given signature is valid for the
    ///      the given ERC721 order. Reverts if not.
    /// @param order The ERC721 order.
    /// @param signature The signature to validate.
    function validateERC721OrderSignature(
        LibNFTOrder.ERC721Order memory order,
        LibSignature.Signature memory signature
    )
        public
        override
        view
    {
        bytes32 orderHash = getERC721OrderHash(order);
        _validateOrderSignature(orderHash, signature, order.maker);
    }

    /// @dev Validates that the given signature is valid for the
    ///      given maker and order hash. Reverts if the signature
    ///      is not valid.
    /// @param orderHash The hash of the order that was signed.
    /// @param signature The signature to check.
    /// @param maker The maker of the order.
    function _validateOrderSignature(
        bytes32 orderHash,
        LibSignature.Signature memory signature,
        address maker
    )
        internal
        override
        view
    {
        if (signature.signatureType == LibSignature.SignatureType.PRESIGNED) {
            // Check if order hash has been pre-signed by the maker.
            bool isPreSigned = LibERC721OrdersStorage.getStorage().preSigned[orderHash];
            if (!isPreSigned) {
                LibNFTOrdersRichErrors.InvalidSignerError(maker, address(0)).rrevert();
            }
        } else {
            address signer = LibSignature.getSignerOfHash(orderHash, signature);
            if (signer != maker) {
                LibNFTOrdersRichErrors.InvalidSignerError(maker, signer).rrevert();
            }
        }
    }

    /// @dev Transfers an NFT asset.
    /// @param token The address of the NFT contract.
    /// @param from The address currently holding the asset.
    /// @param to The address to transfer the asset to.
    /// @param tokenId The ID of the asset to transfer.
    /// @param amount The amount of the asset to transfer. Always
    ///        1 for ERC721 assets.
    function _transferNFTAssetFrom(
        address token,
        address from,
        address to,
        uint256 tokenId,
        uint256 amount
    )
        internal
        override
    {
        assert(amount == 1);
        _transferERC721AssetFrom(IERC721Token(token), from, to, tokenId);
    }

    /// @dev Updates storage to indicate that the given order
    ///      has been filled by the given amount.
    /// @param order The order that has been filled.
    /// @param fillAmount The amount (denominated in the NFT asset)
    ///        that the order has been filled by.
    function _updateOrderState(
        LibNFTOrder.NFTOrder memory order,
        bytes32 /* orderHash */,
        uint128 fillAmount
    )
        internal
        override
    {
        assert(fillAmount == 1);
        _setOrderStatusBit(order.maker, order.nonce);
    }

    function _setOrderStatusBit(address maker, uint256 nonce)
        private
    {
        // The bitvector is indexed by the lower 8 bits of the nonce.
        uint256 flag = 1 << (nonce & 255);
        // Update order status bit vector to indicate that the given order
        // has been cancelled/filled by setting the designated bit to 1.
        LibERC721OrdersStorage.getStorage().orderStatusByMaker
            [maker][uint248(nonce >> 8)] |= flag;
    }

    /// @dev If the given order is buying an ERC721 asset, checks
    ///      whether or not the given token ID satisfies the required
    ///      properties specified in the order. If the order does not
    ///      specify any properties, this function instead checks
    ///      whether the given token ID matches the ID in the order.
    ///      Reverts if any checks fail, or if the order is selling
    ///      an ERC721 asset.
    /// @param order The ERC721 order.
    /// @param erc721TokenId The ID of the ERC721 asset.
    function validateERC721OrderProperties(
        LibNFTOrder.ERC721Order memory order,
        uint256 erc721TokenId
    )
        public
        override
        view
    {
        _validateOrderProperties(
            order.asNFTOrder(),
            erc721TokenId
        );
    }

    /// @dev Get the current status of an ERC721 order.
    /// @param order The ERC721 order.
    /// @return status The status of the order.
    function getERC721OrderStatus(LibNFTOrder.ERC721Order memory order)
        public
        override
        view
        returns (LibNFTOrder.OrderStatus status)
    {
        // Only buy orders with `erc721TokenId` == 0 can be property
        // orders.
        if (order.erc721TokenProperties.length > 0 &&
                (order.direction != LibNFTOrder.TradeDirection.BUY_NFT ||
                 order.erc721TokenId != 0))
        {
            return LibNFTOrder.OrderStatus.INVALID;
        }

        // Buy orders cannot use ETH as the ERC20 token, since ETH cannot be
        // transferred from the buyer by a contract.
        if (order.direction == LibNFTOrder.TradeDirection.BUY_NFT &&
            address(order.erc20Token) == NATIVE_TOKEN_ADDRESS)
        {
            return LibNFTOrder.OrderStatus.INVALID;
        }

        // Check for expiry.
        if (order.expiry <= block.timestamp) {
            return LibNFTOrder.OrderStatus.EXPIRED;
        }

        // Check `orderStatusByMaker` state variable to see if the order
        // has been cancelled or previously filled.
        LibERC721OrdersStorage.Storage storage stor =
            LibERC721OrdersStorage.getStorage();
        // `orderStatusByMaker` is indexed by maker and nonce.
        uint256 orderStatusBitVector =
            stor.orderStatusByMaker[order.maker][uint248(order.nonce >> 8)];
        // The bitvector is indexed by the lower 8 bits of the nonce.
        uint256 flag = 1 << (order.nonce & 255);
        // If the designated bit is set, the order has been cancelled or
        // previously filled, so it is now unfillable.
        if (orderStatusBitVector & flag != 0) {
            return LibNFTOrder.OrderStatus.UNFILLABLE;
        }

        // Otherwise, the order is fillable.
        return LibNFTOrder.OrderStatus.FILLABLE;
    }

    /// @dev Get the order info for an NFT order.
    /// @param order The NFT order.
    /// @return orderInfo Info about the order.
    function _getOrderInfo(LibNFTOrder.NFTOrder memory order)
        internal
        override
        view
        returns (LibNFTOrder.OrderInfo memory orderInfo)
    {
        LibNFTOrder.ERC721Order memory erc721Order = order.asERC721Order();
        orderInfo.orderHash = getERC721OrderHash(erc721Order);
        orderInfo.status = getERC721OrderStatus(erc721Order);
        orderInfo.orderAmount = 1;
        orderInfo.remainingAmount = orderInfo.status == LibNFTOrder.OrderStatus.FILLABLE ? 1 : 0;
    }

    /// @dev Get the EIP-712 hash of an ERC721 order.
    /// @param order The ERC721 order.
    /// @return orderHash The order hash.
    function getERC721OrderHash(LibNFTOrder.ERC721Order memory order)
        public
        override
        view
        returns (bytes32 orderHash)
    {
        return _getEIP712Hash(LibNFTOrder.getERC721OrderStructHash(order));
    }

    /// @dev Get the order status bit vector for the given
    ///      maker address and nonce range.
    /// @param maker The maker of the order.
    /// @param nonceRange Order status bit vectors are indexed
    ///        by maker address and the upper 248 bits of the
    ///        order nonce. We define `nonceRange` to be these
    ///        248 bits.
    /// @return bitVector The order status bit vector for the
    ///         given maker and nonce range.
    function getERC721OrderStatusBitVector(address maker, uint248 nonceRange)
        external
        override
        view
        returns (uint256 bitVector)
    {
        LibERC721OrdersStorage.Storage storage stor =
            LibERC721OrdersStorage.getStorage();
        return stor.orderStatusByMaker[maker][nonceRange];
    }
}
