Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-3386: ERC-721 和 ERC-1155 到 ERC-20 包装器

Authors Calvin Koder (@ashrowz)
Created 2021-03-12
Discussion Link https://github.com/ethereum/EIPs/issues/3384
Requires EIP-165

简单总结

为创建通用 ERC-20 代币的合约提供一个标准接口,这些代币来源于一组独特的 ERC-721/ERC-1155 代币。

摘要

本标准概述了一个智能合约接口,用于用同质化代币包装可识别的代币。这允许通过锁定基础 ERC-721 非同质化代币和 ERC-1155 多代币到一个池子中来铸造衍生 ERC-20 代币。可以销毁衍生代币以从池子中赎回基础代币。这些衍生品与这些基础代币的唯一 ID 没有关联,并且应与基础代币具有成比例的兑换率。作为基础代币的代表,这些通用衍生代币可以根据 ERC-20 进行交易和以其他方式利用,因此每个基础代币的唯一标识符是无关紧要的。

ERC-721 和 ERC-1155 代币被认为是有效的基础代币,因为它们具有唯一的标识符并且按照类似的规则进行转移。这允许将 ERC-721 NFT 和 ERC-1155 多代币包装在单个通用接口下。

动机

ERC-20 代币标准是以太坊上最广泛和最具流动性的代币标准。另一方面,ERC-721 和 ERC-1155 代币只能通过其单独的 ID 以整数金额进行转移。衍生代币允许在利用 ERC-20 代币的合约中受益于基础资产的同时,还能够接触到基础资产。这允许基础代币被分割、交易并在 AMM 上进行通用池化、抵押,并用于任何其他 ERC-20 类型合约。此提案的几个实现已经存在,但没有通用标准。

给定基础代币和衍生代币之间的固定汇率,衍生代币的价值与池化代币的底价成正比。由于衍生代币在 AMM 中使用,因此存在衍生代币市场和基础 NFT 市场之间的套利机会。通过指定可以池化的一部分基础代币,可以最小化池中最低价值代币和最高价值代币之间的差异。这允许池化更大集合中更高价值的代币。此外,使用诸如荷兰式拍卖之类的方法进行价格计算(如 NFT20 所实现的)允许对基础代币的子类进行价格发现。这允许更高价值基础代币的提供者获得比价值底价代币更多的衍生代币。

规范

本文档中的关键词“必须”、“不得”、“必需”、“应”、“不应”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

每个 IWrapper 合规合约必须实现 IWrapperERC165 接口

pragma solidity ^0.8.0;

/**
    @title IWrapper 可识别代币包装器标准
    @dev {Wrapper} 指的是任何实现此接口的合约。
    @dev {Base} 指的是任何 ERC-721 或 ERC-1155 合约。它可以是 {Wrapper}。
    @dev {Pool} 指的是持有 {Base} 代币的合约。它可以是 {Wrapper}。
    @dev {Derivative} 指的是由 {Wrapper} 铸造/销毁的 ERC-20 合约。它可以是 {Wrapper}。
    @dev 所有使用 "single", "batch" 均指的是代币 ID 的数量。这包括通过 ID 标识的单个 ERC-721 代币和通过 ID 标识的多个 ERC-1155 代币。一个 ERC-1155 `TransferSingle` 事件可能会发出一个大于 `1` 的 `value`,但它仍然被认为是单个代币。
    @dev 所有名为 `_amount`、`_amounts` 的参数均指的是 ERC-1155 中的 `value` 参数。当将此接口与 ERC-721 一起使用时,`_amount` 必须为 1,并且 `_amounts` 必须是一个空列表或一个长度与 `_ids` 相同的列表,其元素均为 1。
*/
interface IWrapper /* is ERC165 */ {
    /**
     * @dev 必须在 {Pool} 收到单个 {Base} 代币时发生铸造时发出。
     * `_from` 参数必须是发送 {Base} 代币的帐户地址。
     * `_to` 参数必须是接收 {Derivative} 代币的帐户地址。
     * `_id` 参数必须是传输的 {Base} 代币的 ID。
     * `_amount` 参数必须是传输的 {Base} 代币的数量。
     * `_value` 参数必须是铸造的 {Derivative} 代币的数量。
     */
    event MintSingle (address indexed _from, address indexed _to, uint256 _id, uint256 _amount, uint256 _value);

    /**
     * @dev 必须在 {Wrapper} 收到多个 {Base} 代币时发生铸造时发出。
     * `_from` 参数必须是发送 {Base} 代币的帐户地址。
     * `_to` 参数必须是接收 {Derivative} 代币的帐户地址。
     * `_ids` 参数必须是传输的 {Base} 代币的 ID 列表。
     * `_amounts` 参数必须是传输的 {Base} 代币的数量列表。
     * `_value` 参数必须是铸造的 {Derivative} 代币的数量。
     */
    event MintBatch (address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts, uint256 _value);

    /**
     * @dev 必须在 {Wrapper} 发送单个 {Base} 代币时发生销毁时发出。
     * `_from` 参数必须是发送 {Derivative} 代币的帐户地址。
     * `_to` 参数必须是接收 {Base} 代币的帐户地址。
     * `_id` 参数必须是传输的 {Base} 代币的 ID。
     * `_amount` 参数必须是传输的 {Base} 代币的数量。
     * `_value` 参数必须是销毁的 {Derivative} 代币的数量。
     */
    event BurnSingle (address indexed _from, address indexed _to, uint256 _id, uint256 _amount, uint256 _value);

    /**
     * @dev 必须在 {Wrapper} 发送多个 {Base} 代币时发生销毁时发出。
     * `_from` 参数必须是发送 {Derivative} 代币的帐户地址。
     * `_to` 参数必须是接收 {Base} 代币的帐户地址。
     * `_ids` 参数必须是传输的 {Base} 代币的 ID 列表。
     * `_amounts` 参数必须是传输的 {Base} 代币的数量列表。
     * `_value` 参数必须是销毁的 {Derivative} 代币的数量。
     */
    event BurnBatch (address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts, uint256 _value);

    /**
     * @notice 将 ID 为 `_id` 的 {Base} 代币从 `msg.sender` 转移到 {Pool},并将 {Derivative} 代币铸造到 `_to`。
     * @param _to       目标地址。
     * @param _id       {Base} 代币的 ID。
     * @param _amount   {Base} 代币的数量。
     *
     * 发出一个 {MintSingle} 事件。
     */
    function mint(
        address _to,
        uint256 _id,
        uint256 _amount
    ) external;

    /**
     * @notice 将 ID 为 `_ids[i]` 的 {Base} 代币的 `_amounts[i]` 数量从 `msg.sender` 转移到 {Pool},并将 {Derivative} 代币铸造到 `_to`。
     * @param _to       目标地址。
     * @param _ids      {Base} 代币的 ID 列表。
     * @param _amounts  {Base} 代币的数量列表。
     *
     * 发出一个 {MintBatch} 事件。
     */
    function batchMint(
        address _to,
        uint256[] calldata _ids,
        uint256[] calldata _amounts
    ) external;

    /**
     * @notice 从 `_from` 销毁 {Derivative} 代币,并将一些 {Base} 代币的 `_amounts` 数量从 {Pool} 转移到 `_to`。不保证提取什么代币。
     * @param _from     来源地址。
     * @param _to       目标地址。
     * @param _amount   {Base} 代币的数量。
     *
     * 发出一个 {BurnSingle} 或 {BurnBatch} 事件。
     */
    function burn(
        address _from,
        address _to,
        uint256 _amount
    ) external;

    /**
     * @notice 从 `_from` 销毁 {Derivative} 代币,并将一些 {Base} 代币的 `_amounts` 数量从 {Pool} 转移到 `_to`。不保证提取什么代币。
     * @param _from     来源地址。
     * @param _to       目标地址。
     * @param _amounts  {Base} 代币的数量列表。
     *
     * 发出一个 {BurnSingle} 或 {BurnBatch} 事件。
     */
    function batchBurn(
        address _from,
        address _to,
        uint256[] calldata _amounts
    ) external;

    /**
     * @notice 从 `_from` 销毁 {Derivative} 代币,并将 ID 为 `_ids[i]` 的 {Base} 代币的 `_amounts[i]` 数量从 {Pool} 转移到 `_to`。
     * @param _from     来源地址。
     * @param _to       目标地址。
     * @param _id       {Base} 代币的 ID。
     * @param _amount   {Base} 代币的数量。
     *
     * 发出一个 {BurnSingle} 或 {BurnBatch} 事件。
     */
    function idBurn(
        address _from,
        address _to,
        uint256 _id,
        uint256 _amount
    ) external;

    /**
     * @notice 从 `_from` 销毁 {Derivative} 代币,并将 ID 为 `_ids[i]` 的 {Base} 代币的 `_amounts[i]` 数量从 {Pool} 转移到 `_to`。
     * @param _from     来源地址。
     * @param _to       目标地址。
     * @param _ids      {Base} 代币的 ID 列表。
     * @param _amounts   {Base} 代币的数量列表。
     *
     * 发出一个 {BurnSingle} 或 {BurnBatch} 事件。
     */
    function batchIdBurn(
        address _from,
        address _to,
        uint256[] calldata _ids,
        uint256[] calldata _amounts
    ) external;
}

理由

命名

被池化的 ERC-721/ERC-1155 代币被称为 {Base} 代币。替代名称包括:

  • 基础的。
  • NFT。但是,ERC-1155 代币可能被认为是“半同质化”的。

被铸造/销毁的 ERC-20 代币被称为 {Derivative} 代币。替代名称包括:

  • 包装的。
  • 通用的。

函数名 mintburn 借鉴了 ERC-20 的铸造和销毁扩展。替代的名称包括:

选择函数名称 *idBurn 是为了减少对正在销毁的内容的混淆。也就是说,销毁 {Derivative} 代币是为了赎回 ID。

根据 NFTX,wrapper/pool 本身可以称为“指数基金”,或者根据 NFT20,可以称为“DEX”。但是,{NFT20Pair} 合约允许直接的 NFT-NFT 交换,这超出了本标准的范围。

铸造

铸造需要将 {Base} 代币转移到 {Pool} 中,以换取 {Derivative} 代币。以这种方式存入的 {Base} 代币不得再次转移,除非通过销毁函数。这确保了 {Derivative} 代币的价值代表了 {Base} 代币的价值。

除了将 {Base} 代币转移到 {Pool} 中之外,代币还可以被锁定为抵押品,以换取 {Derivative} 贷款,如 NFTX 简报中所提出的,类似于 Maker 金库。这仍然遵循移除 {Base} 代币的可转让性以换取 {Derivative} 代币的一般铸造模式。

销毁

销毁需要将 {Base} 代币从 {Pool} 中转移出来,以换取销毁 {Derivative} 代币。销毁函数通过赎回的 {Base} 代币的数量和质量来区分。

  • 对于不指定 id 的销毁:burnbatchBurn
  • 对于指定 id(s) 的销毁:idBurnbatchIdBurn

通过允许针对特定 ID,可以从池中选择更高价值的 {Base} 代币。NFTX 建议对这种有针对性的提款收取额外费用,以抵消耗尽价值高于底价的 {Base} 代币的 {Pool} 的意愿。

定价

价格不应是必须固定的。因此,Mint/Burn 事件必须包括铸造/销毁的 ERC-20 _value

现有的定价实现如下(以 base:derivative 衡量):

  • 相等:每个 {Base} 花费 1 个 {Derivative}
    • NFTX
    • Wrapped Kitties
  • 按比例
    • NFT20 设置每个 {Derivative} 代币 100 个 {Base} 代币的固定比率。
  • 可变的
    • NFT20 还允许在铸造时进行荷兰式拍卖。
    • NFTX 建议在定位 {Base} 代币的 ID 时支付额外费用。

由于定价实现的多样性,Mint* 和 Burn* 事件必须包括铸造/销毁的 {Derivative} 代币的数量。

继承

ERC-20

{Wrapper} 可以继承自 {ERC20},以便直接调用 super.mintsuper.burn。 如果 {Wrapper} 不继承自 {ERC20},则 {Derivative} 合约必须受到限制,使得 {Wrapper} 拥有 mintburn 和以其他方式更改代币供应的唯一权力。

ERC721Receiver、ERC1155Receiver

如果未继承自 {ERC721Receiver} 和/或 {ERC1155Receiver},则池必须受到限制,使得基础代币只能通过 Wrapper 的 mintburn 进行转移。

对于给定的(地址,id)对,每个 ERC-721 代币只有一个。但是,给定(地址,id)的 ERC-1155 代币的数量可能大于 1。因此,每个标准中“Single”和“Batch”含义各不相同。在这两个标准中,“single”指的是单个 ID,“batch”指的是多个 ID。在 ERC-1155 中,根据 value 字段,单个 ID 事件/函数可能涉及多个代币。

在构建一组通用的事件和函数时,我们必须意识到这些实现上的差异。当前实现将 ERC-721 代币视为一种特殊情况,其中,在引用每个 {Base} 代币的数量时:

  • 所有名为 _amount 的参数,必须为 1
  • 所有名为 _amounts 的参数必须是一个空列表或一个与 _ids 长度相同的 1 列表。

这保持了代币与 ERC-1155 的一致枚举。替代实现包括:

  • 具有专门函数的通用接口。EX:mintFromERC721
  • 每个类型的单独接口。EX:ERC721WrapperERC1155Wrapper

ERC721, ERC1155

{Wrapper} 可以继承自 {ERC721} 和/或 {ERC1155} 以直接调用 super.mint。这是可选的,因为本标准中不需要铸造 {Base} 代币。“Initial NFT Offering”可以使用它在合约中创建一组 {Base} 代币,并直接分配 {Derivative} 代币。

如果 {Wrapper} 不继承自 {ERC721} 或 {ERC1155},则它必须包含对 {IERC721} 和 {IERC1155} 的调用才能转移 {Base} 代币。

批准

所有底层转移方法都未与 {Wrapper} 绑定,而是调用 ERC-20/721/1155 转移方法。本标准的实现必须:

  • 要么为销毁实现 {Derivative} 转移批准,并为铸造实现 {Base} 转移批准。
  • 要么在尝试执行之前,通过 {IERC721} / {IERC1155} 检查 {Wrapper} 外部的批准。

向后兼容性

大多数现有实现都继承自 ERC-20,使用函数 mintburn。 事件:

  • 铸造
    • WK: DepositKittyAndMintToken
    • NFTX: Mint
  • 销毁
    • WK: BurnTokenAndWithdrawKity
    • NFTX: Redeem

参考实现

ERC-3386 参考实现

安全考虑

建议包装器合约继承自可销毁的 ERC-20 代币。如果不是,则 {Derivative} 代币的供应必须由 Wrapper 控制。同样,价格实现必须确保 {Base} 代币的供应反映在 {Derivative} 代币中。

使用函数 idBurnidBurns,用户可以定位通用批次中最有价值的 NFT。如果不同 ID 的代币价值存在显着差异,则合约应考虑创建专门的池(NFTX)或定价(NFT20)来解决此问题。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Calvin Koder (@ashrowz), "ERC-3386: ERC-721 和 ERC-1155 到 ERC-20 包装器 [DRAFT]," Ethereum Improvement Proposals, no. 3386, March 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3386.