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

Support ERC1155: MultiToken Standard #24

Closed
dekz opened this issue Nov 12, 2018 · 2 comments
Closed

Support ERC1155: MultiToken Standard #24

dekz opened this issue Nov 12, 2018 · 2 comments
Labels
protocol-transition-complete Protocol has transitioned to the new state vote-accepted Vote for this change has been accepted

Comments

@dekz
Copy link
Member

dekz commented Nov 12, 2018

Summary

The current AssetProxy contracts allow an order to buy or sell ERC20 and ERC721 assets. This proposes a new AssetProxy to enable the exchange of assets managed by an ERC1155 contract, which provides a standard interface for managing a collection of fungible and non-fungible assets.

Motivation

Existing token standards (ERC20, ERC721) require a separate smart contract to manage each asset. ERC1155 defines an interface for a contract that manages a collection of assets. Applications range from game assets to deposit and staking contracts.

Specification

Requirements

  • The ERC1155Proxy should be able to transfer any number of fungible/non-fungible ERC1155 assets
  • The ERC1155Proxy should only be callable by approved senders (the exchange contract and multi asset proxy)
  • The ERC1155Proxy should support partial fills: the amount of each asset transferred should be proportional to the amount that the order is filled (see Fill Scenarios section below).

Asset Data Encoding

The ERC1155Proxy should expect data to be encoded using the following function signature:

// 0xa7cb5fb7
ERC1155Assets(address,uint256[],uint256[],bytes)

Arguments:

  • address - The address of the ERC1155 contract
  • uint256[] - A collection of asset ids to transfer
  • uint256[] - The value of each each asset to transfer
  • bytes - Callback data forwarded to the receiver, if the receiver is a contract

Fill Scenarios

Fills operate in a similar way to the MultiAssetProxy. Values encoded in the asset data are multiplied by the amount to compute the total quantity of an asset to transfer.

# Scenario 1: Maker wants to sell 2000 units of AssetA and 4000 units of AssetB.
#             Both assets are fungible and partial fills are allowed.
order.makerAssetAmount = 2000
order.makerAssetData.ids = [AssetA, AssetB]
order.makerAssetData.values = [1, 2]

# Scenario 2: Maker wants to sell 2000 units of AssetA and 4000 units of AssetB.
#             Both assets are fungible but partial fills are *not* allowed.
order.makerAssetAmount = 1
order.makerAssetData.ids = [AssetA, AssetB]
order.makerAssetData.values = [2000, 4000]

# Scenario 3: Maker wants to sell 2000 units of AssetA and a non-fungible AssetC.
#             Because AssetC is non-fungible partial fills are *not* allowed.
order.makerAssetAmount = 1
order.makerAssetData.ids = [AssetA, AssetC]
order.makerAssetData.values = [2000, 1]

Note that partial fills of non-fungible assets should be prohibited by the maker by setting the amount to 1. The proxy cannot distinguish between fungible and non-fungible assets, so it cannot force partial fills to fail in this scenario. If the amount is not set to 1 and an asset is non-fungible then the behavior depends on the implementation of the ERC1155 contract.

Architecture

The ERC1155Proxy inherits MixinAuthorizable. Like the ERC20Proxy and ERC721Proxy, this contract must be registered with the Exchange and MultiAssetProxy, which in turn must be authorized to call ERC1155Proxy.transferFrom.

When an order's asset data specifies the ERC1155Proxy, the chain of calls looks like:

  1. Call to Exchange contract
  2. Exchange calls ERC1155Proxy
  3. ERC1155Proxy calls batchTransferFrom on the destination ERC1155 contract

OR

  1. Call to Exchange contract
  2. Exchange calls MultiAssetProxy
  3. MultiAssetProxy decodes the 1155 asset data and calls ERC1155Proxy
  4. ERC1155Proxy calls batchTransferFrom on the destination ERC1155 contract

Reference Implementation

Below is the reference implementation for the ERC1155Proxy contract. The optimized version is under development and can be found in the 0x codebase here.

pragma solidity 0.5.5;
pragma experimental ABIEncoderV2;

import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/SafeMath.sol";
import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
import "./MixinAuthorizable.sol";


contract ERC1155Proxy is
    MixinAuthorizable,
    SafeMath
{
    using LibBytes for bytes;

    // Id of this proxy.
    bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC1155Assets(address,uint256[],uint256[],bytes)"));

    /// @dev Transfers assets. Either succeeds or throws.
    /// @param assetData Byte array encoded for the respective asset proxy.
    /// @param from Address to transfer asset from.
    /// @param to Address to transfer asset to.
    /// @param amount Amount of asset to transfer.
    function transferFrom(
        bytes calldata assetData,
        address from,
        address to,
        uint256 amount
    )
        external
        onlyAuthorized
    {
        // decode asset data
        bytes memory assetDataWithoutProxyId = assetData.slice(4, assetData.length);
        (
            address erc1155Contract,
            uint256[] memory ids,
            uint256[] memory values,
            bytes memory data
        ) = abi.decode(
            assetDataWithoutProxyId,
            (address,uint256[],uint256[],bytes)
        );

        // scale values
        for(uint i = 0; i != values.length; ++i) {
            values[i] = safeMul(values[i], amount);
        }

        // execute transfer
        IERC1155(erc1155Contract).safeBatchTransferFrom(
            from,
            to,
            ids,
            values,
            data
        );
    }

    /// @dev Gets the proxy id associated with the proxy address.
    /// @return Proxy id.
    function getProxyId()
        external
        pure
        returns (bytes4)
    {
        return PROXY_ID;
    }
}

Issues

  • The EIP155 proposal has not yet been ratified and is subject to change. I recommend we hold off on third-party audits until the EIP has been finalized.
  • The receiver can implement a custom callback, which means that stateful assets can be tampered with during a trade. This is a known issue when exchanging some ERC721 tokens and more generally with contract-fillable liquidity.
  • It would be more ideal to prevent partial fills of non-fungible assets within the proxy, rather than relying on the Maker to set the makerAssetAmount or takerAssetAmount to 1. That said, this is the same trust model used by existing proxies, like the MultiAssetProxy which has the same issue.
@redsquirrel
Copy link

ERC115 has support for single transfer and batch transfer.

ERC1155

@PhABC
Copy link
Contributor

PhABC commented Nov 19, 2018

I made this a few months ago to test 0x with 1155, might find it useful, although not optimized at all :

Click to see code
/*
  Copyright 2018 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.
*/

/** 
* TO DO : 
*    + Add support for batchTransferFrom
*    + Add support for safeBatchTransferFrom
*    + Convert to assembly code
*/

pragma solidity 0.4.24;

import "../../utils/LibBytes/LibBytes.sol";
import "./MixinAuthorizable.sol";
import "multi-token-standard/contracts/token/IERC1155.sol";

contract ERC1155Proxy is MixinAuthorizable
{   
  using LibBytes for bytes;

  // assetData index constants
  uint256 constant internal TOKEN_ADDRESS_INDEX = 16; // To use with readAddress()
  uint256 constant internal TOKEN_ID_INDEX      = 36; // To use with readUint256()
  // uint8 constant internal TOKEN_AMOUNT_INDEX  = 0x68; // To use with readUint256()

  // Id of this proxy.
  bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC1155Token(address,uint256)"));

  /// @dev Transfers assets. Either succeeds or throws.
  /// @param assetData Byte array encoded for the respective asset proxy.
  /// @param from Address to transfer asset from.
  /// @param to Address to transfer asset to.
  /// @param amount Amount of asset to transfer.
  function transferFrom(
    bytes assetData,
    address from,
    address to,
    uint256 amount
  )
    external
  {

      // Asset data itself is encoded as follows:
      //
      // | Area     | Offset | Length  | Contents                            |
      // |----------|--------|---------|-------------------------------------|
      // | Header   | 0      | 4       | Proxy ID                            |
      // | Params   |        | 2 * 32  | Function parameters:                |
      // |          | 4      | 12 + 20 |   1. Token address                  |
      // |          | 36     | 32      |   2. Token Id                       |
      // |          |        |         |                                     |

      // Token address
      address token = assetData.readAddress(TOKEN_ADDRESS_INDEX); 

      // Token Id
      uint256 tokenId = assetData.readUint256(TOKEN_ID_INDEX);

      // Transfer token in ERC155 contract
      IERC1155(token).safeTransferFrom(from, to, tokenId, amount, '');
  }    

  /// @dev Gets the proxy id associated with the proxy address.
  /// @return Proxy id.
  function getProxyId()
    external
    pure
    returns (bytes4)
  {
    return PROXY_ID;
  }

}

@hysz hysz mentioned this issue Mar 20, 2019
@dekz dekz added vote-started Voting for this ZEIP is on-going vote-accepted Vote for this change has been accepted and removed vote-started Voting for this ZEIP is on-going labels Jul 22, 2019
@willwarren89 willwarren89 added the protocol-transition-complete Protocol has transitioned to the new state label Sep 7, 2019
@dekz dekz closed this as completed Sep 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
protocol-transition-complete Protocol has transitioned to the new state vote-accepted Vote for this change has been accepted
Projects
None yet
Development

No branches or pull requests

4 participants