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

NFT Orders #93

Open
dekz opened this issue Feb 9, 2022 · 1 comment
Open

NFT Orders #93

dekz opened this issue Feb 9, 2022 · 1 comment

Comments

@dekz
Copy link
Member

dekz commented Feb 9, 2022

Summary

This ZEIP introduces a new set of features to enable NFT orders on the 0x V4 protocol. NFT orders allows for trades of ERC-721 or ERC-1155 in exchange for ERC-20. We have optimized the implementation to reduce costs by up to 54% against other comparable protocols.

Type:

CORE

Motivation

Similar to previous versions of 0x protocol we want to support a thriving NFT ecosystem and offer traders the best experience.

Specification

Basics

Users can sign an off-chain order to indicate that they are interested in:

  • selling a particular NFT for some amount of ERC20 token (or ETH), or
  • buying a particular NFT for some amount of ERC20 token.

There are two Solidity order structs:

  • ERC721Order for buying/selling ERC721 assets
  • ERC1155Order for buying/selling ERC1155 assets.

ERC1155Order has one additional field compared to ERC721Order, used to specify the amount of the ERC1155 being bought or sold. This would be used for fungible ERC1155 assets.

The TradeDirection enum is used to indicate whether an order is a bid or an ask:

enum TradeDirection {
    SELL_NFT,
    BUY_NFT
}

Order structs look like the following:

struct ERC721Order {
    TradeDirection direction;
    address maker;
    address taker;
    uint256 expiry;
    uint256 nonce;
    IERC20TokenV06 erc20Token;
    uint256 erc20TokenAmount;
    Fee[] fees;
    IERC721Token erc721Token;
    uint256 erc721TokenId;
    Property[] erc721TokenProperties;
}

struct ERC1155Order {
    TradeDirection direction;
    address maker;
    address taker;
    uint256 expiry;
    uint256 nonce;
    IERC20TokenV06 erc20Token;
    uint256 erc20TokenAmount;
    Fee[] fees;
    IERC1155Token erc1155Token;
    uint256 erc1155TokenId;
    Property[] erc1155TokenProperties;
    uint128 erc1155TokenAmount;
}

Upon discovering a listing, a trader, with the help of a dApp, will fill the ERC721 Order on-chain.

/// @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 _calldata_ sellOrder,
    LibSignature.Signature _calldata_ signature,
    bytes _calldata_ callbackData
)
    external
    payable;

Collection/floor/property-based orders

In lieu of specifying a particular token ID, buy orders can specify a list of properties that an NFT asset must satisfy to be used to fill the order.

These properties are encoded using the following struct:

struct Property {
    IPropertyValidator propertyValidator;
    bytes propertyData;
}

where propertyValidator implements the following function:

/// @dev Checks that the given ERC721/ERC1155 asset satisfies the properties encoded in `propertyData`.
///      Should revert if the asset does not satisfy the specified properties.
/// @param tokenAddress The ERC721/ERC1155 token contract address.
/// @param tokenId The ERC721/ERC1155 tokenId of the asset to check.
/// @param propertyData Encoded properties or auxiliary data needed to perform the check.
function validateProperty(
    address tokenAddress,
    uint256 tokenId,
    bytes calldata propertyData
)
    external
    view;

Fees

An order can specify fees to be paid out during settlement, denominated in the ERC20 token of the order. Both ERC721Order and ERC1155Order have a Fee[] fees field, where Fee is the following struct:

struct Fee {
    address recipient;
    uint256 amount;
    bytes feeData;
}

If the feeData length is greater than 0 then a fee callback will be called. The receiver must implement the IFeeRecipient interface.

interface IFeeRecipient {

    /// @dev A callback function invoked in the ERC721Feature for each ERC721 
    ///      order fee that get paid. Integrators can make use of this callback
    ///      to implement arbitrary fee-handling logic, e.g. splitting the fee 
    ///      between multiple parties. 
    /// @param tokenAddress The address of the token in which the received fee is 
    ///        denominated. `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` indicates 
    ///        that the fee was paid in the native token (e.g. ETH).
    /// @param amount The amount of the given token received.
    /// @param feeData Arbitrary data encoded in the `Fee` used by this callback.
    /// @return success The selector of this function (0x0190805e), 
    ///         indicating that the callback succeeded.
    function receiveFeeCallback(
        address tokenAddress,
        uint256 amount,
        bytes calldata feeData
    )
        external
        returns (bytes4 success);
}

ETH/WETH handling

A sell order must use WETH instead of ETH, since we require the ERC20 transferFrom functionality to transfer WETH from the maker to the taker. Even so, the taker can choose to receive ETH when filling a WETH sell order by setting the unwrapNativeToken parameter to true in sellERC721 or sellERC1155.

A buy order can specify either ETH or WETH, i.e. the buyer can indicate whether they would like to receive ETH or WETH. A WETH sell order can be filled by a taker using ETH: the buyERC721 and buyERC1155 functions are payable and the msg.value can be used to fill a WETH sell order.

Pre-signing orders

Since smart contracts cannot sign orders in the traditional sense, they can instead “sign” orders by calling the preSignERC721Order or preSignERC1155Order functions.

If an order has been pre-signed, it can by providing a “null” signature with the PRESIGNED signature type (see LibSignature.sol (https://github.com/0xProject/protocol/blob/refactor/nft-orders/contracts/zero-ex/contracts/src/features/libs/LibSignature.sol#L42-L61)):

LibSignature.Signature({
   signatureType: LibSignature.SignatureType.PRESIGNED,
   v: uint8(0),
   r: bytes32(0),
   s: bytes32(0)
});

Implementation

https://github.com/0xProject/protocol/compare/audit/nft-orders

Designated team

0x Labs

Audits

This change was audited by ABDK. Final report to be published here.

Bug Bounty

0x has an outstanding bug bounty for our contracts. For more information see the Bug Bounty page.

@davidedaji
Copy link

Sorry but what is SELL_NFT, BUY_NFT
image
I really can't make this work for buy-orders

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants
@dekz @davidedaji and others