Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7651: 部分代表的非同质化代币

部分代表的非同质化代币的规范。

Authors Acme (@0xacme), Calder (@caldereth)
Created 2024-03-05
Discussion Link https://ethereum-magicians.org/t/erc-7651-fractionally-represented-non-fungible-token/19176
Requires EIP-20, EIP-165, EIP-721

摘要

本提案介绍了一个用于部分代表的非同质化代币的标准,允许在一个合约中对 NFT 进行部分管理和所有权分配。这种方法使 NFT 能够与底层同质化表示无缝共存,从而在不分割 NFT 本身或不需要显式转换步骤的情况下,增强流动性和访问性。该标准包括部分和整体代币转移、批准和事件发布的机制。本规范借鉴了 ERC-721ERC-20 的设计,但与这两个标准都不完全兼容。

动机

NFT 的部分所有权历来依赖于外部协议,这些协议管理单个 NFT 的分割和重组为部分表示。分割特定 NFT 的方法导致总代币供应的流动性分散,因为两个 NFT 的部分表示不相等,因此必须单独交易。此外,这种方法需要锁定部分化的 NFT,阻止自由转移,直到它们被重组。

本标准提供了一个统一的部分所有权解决方案,旨在提高 NFT 的流动性和可访问性,而不影响可转移性或灵活性。

规范

本文档中的关键词“必须”,“禁止”,“必需”,“应”,“不应”,“建议”,“不建议”,“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

部分代表的非同质化代币接口

所有 ERC-7651 兼容合约必须实现 ERC-7651 和 ERC-165 接口。

兼容合约必须在部分表示中的代币批准或转移时,发布 fractional Approval 或 Transfer 事件。

兼容合约还必须在非同质化表示中的全部批准、批准和转移时,发布 non-fungible ApprovalForAll、Approval 或 Transfer 事件。

请注意,此接口借鉴了 ERC-721ERC-20 标准中类似定义的函数,但与这两个标准并不完全向后兼容。

interface IERC7651 is IERC165 {
  /// @dev 当给定 spender 的部分表示批准被更改或重申时,会发布此事件。
  event FractionalApproval(address indexed owner, address indexed spender, uint256 value);

  /// @dev 当通过任何机制更改部分表示代币的所有权时,会发布此事件。
  ///      当代币被创建和销毁时,会发布此事件,
  ///      即当 from 和 to 分别被分配到零地址时。
  event FractionalTransfer(address indexed from, address indexed to, uint256 amount);

  /// @dev 当为所有者启用或禁用操作员时,会发布此事件。
  ///      操作员可以管理所有者的所有 NFT。
  event ApprovalForAll(
    address indexed owner,
    address indexed operator,
    bool approved
  );

  /// @dev 当给定 NFT 的批准 spender 被更改或重申时,会发布此事件。
  ///      作为 spender 发布的零地址意味着没有地址被批准用于此代币。
  event NonFungibleApproval(
    address indexed owner,
    address indexed spender,
    uint256 indexed id
  );

  /// @dev 当任何 NFT 的所有权通过任何机制更改时,会发布此事件。
  ///      当 NFT 被创建和销毁时,会发布此事件,
  ///      即当 from 和 to 分别被分配到零地址时。
  event NonFungibleTransfer(address indexed from, address indexed to, uint256 indexed id);

  /// @notice 部分表示中的小数位数
  /// @dev 小数位用作确定余额或数量何时
  ///      包含整体或纯粹部分组件的手段
  /// @return 部分表示中使用的小数位数
  function decimals() external view returns (uint8 decimals);

  /// @notice 以部分表示的代币总供应量
  /// @dev 可以通过计算来恢复 NFT 的总供应量
  ///      `totalSupply() / 10 ** decimals()`
  /// @return 以部分表示的代币总供应量
  function totalSupply() external view returns (uint256 totalSupply);

  /// @notice 给定地址以部分表示的余额
  /// @dev 可以通过计算来恢复 NFT 的总供应量
  ///      `totalSupply() / 10 ** decimals()`
  /// @param owner_ 拥有代币的地址
  /// @return 给定地址以部分表示的余额
  function balanceOf(address owner_) external view returns (uint256 balance);

  /// @notice 查询地址是否为另一个地址的授权操作员
  /// @param owner_ 拥有 NFT 的地址
  /// @param operator_ 被检查是否已被批准代表所有者行事的地址
  /// @return 如果 `operator_` 是 `owner_` 的批准操作员,则为 True,否则为 false
  function isApprovedForAll(
    address owner_,
    address operator_
  ) external view returns (bool isApproved);

  /// @notice 查询地址可以为另一个地址花费的允许金额
  /// @param owner_ 拥有部分表示代币的地址
  /// @param spender_ 被检查是否已被批准代表所有者花费的地址
  /// @return 批准 `spender_` 代表 `owner_` 花费的代币数量
  function allowance(
    address owner_,
    address spender_
  ) external view returns (uint256 allowance);

  /// @notice 查询特定 NFT 的所有者。
  /// @dev 由零地址拥有的代币被认为是无效的,应在所有权查询时恢复。
  /// @param id_ NFT 的唯一标识符。
  /// @return 代币所有者的地址。
  function ownerOf(uint256 id_) external view returns (address owner);

  /// @notice 设置批准地址以花费部分金额,
  ///         或花费特定 NFT。
  /// @dev 有效 id 和部分值之间不得有重叠。
  /// @dev 除非 `msg.sender` 是当前 NFT 所有者,或者如果是 id,则是当前所有者的授权
  ///      操作员,否则会抛出异常。
  /// @dev 如果 id 不是有效的 NFT,则抛出异常
  /// @param spender_ 给定代币或价值的 spender。
  /// @param amountOrId_ 要批准的部分值或 id。
  /// @return 批准操作是否成功。
  function approve(
    address spender_,
    uint256 amountOrId_
  ) external returns (bool success);

  /// @notice 设置批准第三方来管理调用者的所有
  ///         非同质化资产
  /// @param operator_ 要添加到调用者授权操作员集的地址
  /// @param approved_ 如果操作员已批准,则为 True,如果未批准,则为 false
  function setApprovalForAll(address operator_, bool approved_) external;

  /// @notice 将部分代币或 NFT 从一个地址转移到另一个地址
  /// @dev 有效 id 和部分值之间不得有重叠
  /// @dev 如果调用者不是 `from_` 或未被批准,则该操作应恢复
  ///      花费 `from_` 拥有的代币或 NFT
  /// @dev 如果值小于 `from_` 的余额或
  ///      如果 NFT 不属于 `from_`,则该操作应恢复
  /// @dev 如果 id 不是有效的 NFT,则抛出异常
  /// @param from_ 从中转移部分代币或 NFT 的地址
  /// @param to_ 将部分代币或 NFT 转移到的地址
  /// @param amountOrId_ 要转移的部分值或不同的 NFT id
  /// @return 如果操作成功,则为 True
  function transferFrom(
    address from_,
    address to_,
    uint256 amountOrId_
  ) external returns (bool success);

  /// @notice 将部分代币从一个地址转移到另一个地址
  /// @dev 如果 amount 小于 `from_` 的余额,则该操作应恢复
  /// @param to_ 将部分代币转移到的地址
  /// @param amount_ 要转移的部分值
  /// @return 如果操作成功,则为 True
  function transfer(address to_, uint256 amount_) external returns (bool success);

  /// @notice 将 NFT 的所有权从一个地址转移到另一个地址
  /// @dev 除非 `msg.sender` 是当前所有者,否则会抛出异常,
  ///      操作员,或此 NFT 的批准地址
  /// @dev 如果 `from_` 不是当前所有者,则抛出异常
  /// @dev 如果 `to_` 是零地址,则抛出异常
  /// @dev 如果 `tokenId_` 不是有效的 NFT,则抛出异常
  /// @dev 当转移完成时,此函数会检查 `to_` 是否为
  ///      智能合约(代码大小 > 0)。如果是,它会调用 `onERC721Received`
  ///      在 `to_` 上,如果返回值不是
  ///      `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`,则抛出异常。
  /// @param from_ 从中转移 NFT 的地址
  /// @param to_ 将 NFT 转移到的地址
  /// @param tokenId_ 要转移的 NFT
  /// @param data_ 没有指定格式的附加数据,在对 `to_` 的调用中发送
  function safeTransferFrom(
    address from_,
    address to_,
    uint256 id_,
    bytes calldata data_
  ) external;

  /// @notice 将 NFT 的所有权从一个地址转移到另一个地址
  /// @dev 这与上述函数 safeTransferFrom 接口相同
  ///      虽然必须将空字节作为数据传递给 `to_`
  /// @param from_ 从中转移 NFT 的地址
  /// @param to_ 将 NFT 转移到的地址
  /// @param tokenId_ 要转移的 NFT
  function safeTransferFrom(address from_, address to_, uint256 id_) external;
}

interface IERC165 {
    /// @notice 查询合约是否实现了接口
    /// @param interfaceID_ 接口标识符,如 ERC-165 中所指定
    /// @dev 接口标识在 ERC-165 中指定。此函数
    ///      使用少于 30,000 gas。
    /// @return 如果合约实现了 `interfaceID` 并且
    ///         `interfaceID` 不是 0xffffffff,则返回 `true`,否则返回 `false`
    function supportsInterface(bytes4 interfaceID_) external view returns (bool);
}

部分代表的非同质化代币元数据接口

这是一个建议的接口,其定义与 ERC-721 元数据接口相同。与其直接使用此接口,不如在此处使用不同的元数据接口,以避免围绕 ERC-721 继承的混淆。鉴于此处的函数定义是相同的,因此重要的是要注意,此规范的元数据接口与 ERC-721 的元数据接口之间的 ERC-165 interfaceId 将是相同的。

/// @title ERC-7651 部分非同质化代币标准,可选元数据扩展
interface IERC7651Metadata {
  /// @notice 给定代币集合的描述性长名称
  function name() external view returns (string memory name);

  /// @notice 给定代币集合的缩写短名称
  function symbol() external view returns (string memory symbol);

  /// @notice 给定资产的不同的统一资源标识符 (URI)。
  /// @dev 如果 `tokenId_` 不是有效的 NFT,则抛出异常。URI 在 RFC 中定义
  ///      3986. URI 可以指向符合 "ERC721
  ///      元数据 JSON 模式" 的 JSON 文件。
  /// @param id_ 要获取代币 URI 的 NFT
  /// @return 代币的 URI 作为字符串
  function tokenURI(uint256 id_) external view returns (string memory uri);
}

部分代表的非同质化代币银行接口

这是一个建议的接口,旨在供实现 NFT ID 重用的 ERC-7651 的实现使用。

interface IERC7651NFTBanking {
  /// @notice 获取已铸造但当前未拥有的 NFT 数量。
  /// @dev 这应该是未拥有的 NFT 数量,受总数限制
  ///      部分供应。
  /// @return 当前未拥有的 NFT 数量。
  function getBankedNFTsLength() external view returns (uint256 bankedNFTsLength);

  /// @notice 获取已铸造但当前未拥有的 NFT 的分页列表。
  /// @param start_ 银行中的起始索引。
  /// @param count_ 从起始索引返回的代币数量,包括起始索引。
  /// @return 从 `start_` 开始的 banked NFT 数组,最大长度为 `count_`。
  function getBankedNFTs(
    uint256 start_,
    uint256 count_
  ) external view returns (uint256[] memory bankedNFTs);

  /// @notice 查询当前流通中的 NFT 供应量。
  /// @dev 给定供应量可能仍然是 banked 或未铸造的,此函数应始终是
  ///      由 `totalSupply() / 10 ** decimals()` 包含性地限制的上限。
  /// @return 已铸造的 NFT 的当前供应量
  function totalNonFungibleSupply() external view returns (unit256);
}

部分代表的非同质化代币转移豁免接口

这是一个建议的接口,旨在供希望允许用户选择退出 NFT 转移的 ERC-7651 的实现使用。

interface IERC7651NFTTransferExemptable {
  /// @notice 返回地址是否免于 NFT 转移。
  /// @param account_ 要检查的地址。
  /// @return 地址是否免于 NFT 转移。
  isNFTTransferExempt(address account_) external view returns (bool);

  /// @notice 允许地址将自己设置为免于 NFT 转移。
  /// @param isExempt_ 标志,true 表示免除,false 表示不免除。
  setSelfNFTTransferExempt(bool isExempt_) external;
}

基本原理

此标准统一了部分所有权的表示形式与非同质化代币模型,在启用 ERC-20 转移功能的同时,与 ERC-721 原则紧密结合。这种双重兼容性旨在减轻现有协议的集成复杂性。我们的目标是隐式支持尽可能高的 ERC-20 和 ERC-721 标准的向后兼容性,以减少或消除现有协议的集成提升。此部分 NFT 标准的核心原理集中在两个主要策略上:首先,设计与 ERC-721 或 ERC-20 标准清晰对齐的接口,以避免歧义;其次,详细说明明显分离重叠功能逻辑的实现方法。

ID 和金额隔离

确保代币 ID 和部分金额之间的清晰区分是此设计的核心。此非重叠设计原则意味着不得将任何输入含糊地解释为 ID 和金额。我们不会深入研究实现指南,但实现可以通过各种方式实现此目的,例如验证 ID 输入的所有权或为代币 ID 保留特定范围。

此方法确保“重叠”接口中的逻辑也同样隔离,从而最大限度地降低意外结果的可能性。

事件

ERC-20ERC-721 标准之间事件签名的重叠对我们的部分 NFT 标准中的向后兼容性提出了挑战。已经探索了各种方法,包括与单个标准的事件对齐或引入具有不同参数索引的唯一事件来解决冲突。

我们认为,在朝着标准化方向发展时,尽管引入了索引软件的复杂性,但确保事件得到正确的描述和隔离是理想的解决方案。因此,我们坚持传统的转移和批准事件定义,但通过 FractionalNonFungible 前缀来区分这些事件。

转移

在一个标准的 ERC-7651 转移中,可以通过指定部分金额或特定的 NFT ID 来转移价值。

NFT ID 转移:通过 NFT ID 转移非常简单。指定的 NFT 及其所有相关的部分价值(相当于 10 ** decimals())从发送者转移到接收者。

部分金额转移:转移部分金额引入了管理 NFT 分配的复杂性。主要有三种情况:

  1. 整体代币余额没有变化:如果转移不改变任何一方的整体余额,则 NFT 分配保持不变。
  2. 发送者的整体代币余额减少:如果发送者的整体余额减少到最接近的整数以下,则必须从其持有的代币中按比例移除 NFT 数量。
  3. 接收者的整体代币余额增加:相反,如果接收者的整体余额增加到最接近的整数以上,则必须按比例增加其 NFT 持有量。

虽然 ERC-7651 为部分 NFT 提供了一个广泛的框架,但它没有规定处理这些情况的具体方法。常见的做法包括单调地铸造或燃烧代币以反映变化,或者在转移部分金额期间使用堆栈或队列跟踪 NFT 所有权。

NFT 转移豁免

转移部分金额意味着可以在单笔交易中移动大量 NFT,这可能会导致 gas 使用成本高昂。我们建议一个可选的 NFT 转移选择加入机制,EOA 和合约都可以使用该机制来减少转移大量代币时不需要 NFT 表示的 gas 负担。

当执行函数调用以选择加入或退出 NFT 转移时,地址持有的 NFT 将被定向重新平衡,以确保它们与新的豁免状态保持同步。换句话说,当退出 NFT 转移时,地址的 NFT 将被 banked,并且其 NFT 余额设置为 0。当选择加入 NFT 转移时,将从银行中提取足够的 NFT 并转移到该地址,以匹配其部分代币余额。

NFT 银行

如“转移”部分所述,当地址在部分条款中新获得完整代币时,它们因此欠一个 NFT。同样,当地址在部分条款中低于完整代币时,必须从其余额中移除一个 NFT,以与他们的部分余额保持同步。

NFT banking 机制提供了一个空间,可以在其中跟踪相对于供应的未拥有但可用的 NFT。我们在此处保持不表达意见,但希望提供一些符合规范的示例。

协调银行的一种方法是单调地刻录和铸造 NFT ID,因为它们分别从流通中提取并添加回流通中。该策略的铸造部分可能会产生大量的 gas 成本,这些成本通常不会被删除已刻录代币 ID 的存储空间所带来的少量 gas 退款所弥补。此外,此方法为希望拥有持久、有限 ID 空间的集合引入了不灵活性。

ERC-7651 的另一种实现包括存储和重用 ID 而不是重复刻录和铸造它们的机制。这节省了大量的 gas 成本,并且还具有提供可预测且可外部读取的代币 ID 流的额外好处,该代币 ID 流可以保存在队列、堆栈或其他数据结构中以供以后重用。用于此 banking 机制的特定数据结构无关紧要,并且由任何遵守该标准的实现自行决定。

ERC-165 接口

我们在规范中包含 ERC-165 接口,既是为了遵守 ERC-721 设计理念,也是作为在合约级别公开接口的一种手段。我们认为这是一个有价值的、被接受的标准,因此集成应用程序可以识别底层规范。

请注意,ERC-7651 合约不应通过 supportsInterface 声称支持 ERC-721ERC-20 标准,因为尽管做出了强大的向后兼容性努力,但这些合约无法完全遵守现有规范。

元数据

ERC-721 一致,我们已决定通过单独的接口隔离复制的元数据功能。此接口包括传统的命名和代币 URI 逻辑,但也引入了围绕代币 banking 可见性的模式,如上文“NFT 银行”和“转移逻辑”部分中所述。

向后兼容性

部分非同质化代币标准旨在与现有的 ERC-721ERC-20 标准几乎向后兼容,但并不声称完全符合这两个标准,因此已通过不同的接口提出。

事件

ERC-721ERC-20 规范中的事件在批准和转移方面共享冲突的签名,这意味着无法实现两者的粘合混合。

这是有意破坏向后兼容性的少数几个领域之一,从而产生了一系列带有 FractionalNonFungible 前缀的新事件。我们认为,采取果断行动转向非冲突、描述性的解决方案是理想的选择,但这将需要索引软件的外部提升。

balanceOf

ERC-20ERC-721 标准中定义的 balanceOf 函数在实践中有所不同,分别表示部分或整体代币所有权。鉴于部分非同质化代币应遵守底层部分表示,因此此函数应以该表示形式返回余额。但是,这确实意味着部分 NFT 合约无法完全遵守 ERC-721 提供的 balanceOf 规范。

成功返回值

transferapprove 函数都返回一个布尔值,指示成功或失败。这对于 ERC-721 规范来说是非标准的,但对于 ERC-20 来说是标准的。部分非同质化代币遵守返回的布尔值,以满足 ERC-20 标准的最低期望,同时承认这偏离了理想的向后兼容性状态。

安全考虑

接口误解

本节是占位符,用于进一步讨论围绕将 ERC-7651 误认为 ERC-20 或 ERC-721 的问题。即,需要彻底考虑围绕接口误解的潜在安全隐患的讨论。

需要讨论。

版权

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

Citation

Please cite this document as:

Acme (@0xacme), Calder (@caldereth), "ERC-7651: 部分代表的非同质化代币 [DRAFT]," Ethereum Improvement Proposals, no. 7651, March 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7651.