Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-4987: 持有 token 接口

用于查询持有 token 的所有权和余额的接口

Authors Devin Conley (@devinaconley)
Created 2021-09-21
Discussion Link https://ethereum-magicians.org/t/eip-4987-held-token-standard-nfts-defi/7117
Requires EIP-20, EIP-165, EIP-721, EIP-1155

摘要

所提出的标准定义了一个轻量级的接口,用于公开持有 token 的功能性所有权和余额。持有 token 是指由合约拥有的 token。此标准可以由持有 EIP-20EIP-721EIP-1155 token 的智能合约实现,旨在供依赖于所有权和余额验证的链上和链下系统使用。

动机

随着加密货币的不同领域(DeFi、NFT 等)融合以及可组合性提高,token 的实际所有者(可能是合约)和功能性所有者(可能是用户)之间通常会存在差异。目前,这导致了需要 token 存款的机制与依赖于这些 token 进行所有权或余额验证的系统之间的冲突。

本提案旨在通过为 token 持有者公开所有权和余额信息的标准接口来解决该冲突。这将允许用户参与这些 DeFi 机制,而无需放弃现有的 token 效用。总的来说,这将大大提高跨系统的互操作性,从而使用户和协议开发者都受益。

此 ERC 标准的示例实现者包括

  • staking 或 farming 合约
  • 借贷池
  • 定时锁定或 vesting vaults
  • 分数化 NFT 合约
  • 智能合约钱包

此 ERC 标准的示例使用者包括

  • 治理系统
  • 游戏
  • PFP验证
  • 艺术画廊或展示
  • 基于 token 的会员计划

规范

实现 ERC20 持有 token 标准的智能合约必须实现 IERC20Holder 接口中的所有函数。

实现 ERC20 持有 token 标准的智能合约还必须实现 ERC165,并在传递接口 ID 0x74c89d54 时返回 true。

/**
 * @notice the ERC20 holder standard provides a common interface to query
 * token balance information
 * @notice ERC20 持有者标准提供了一个通用的接口来查询 token 余额信息
 */
interface IERC20Holder is IERC165 {
  /**
   * @notice emitted when the token is transferred to the contract
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenAmount held token amount
   * @notice 当 token 转移到合约时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenAmount 持有 token 数量
   */
  event Hold(
    address indexed owner,
    address indexed tokenAddress,
    uint256 tokenAmount
  );

  /**
   * @notice emitted when the token is released back to the user
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenAmount held token amount
   * @notice 当 token 释放回用户时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenAmount 持有 token 数量
   */
  event Release(
    address indexed owner,
    address indexed tokenAddress,
    uint256 tokenAmount
  );

  /**
   * @notice get the held balance of the token owner
   * @dev should throw for invalid queries and return zero for no balance
   * @param tokenAddress held token address
   * @param owner functional token owner
   * @return held token balance
   * @notice 获取 token 所有者的持有余额
   * @dev 对于无效查询应抛出异常,对于没有余额的情况应返回零
   * @param tokenAddress 持有 token 地址
   * @param owner 功能性 token 所有者
   * @return 持有 token 余额
   */
  function heldBalanceOf(address tokenAddress, address owner)
    external
    view
    returns (uint256);
}

实现 ERC721 持有 token 标准的智能合约必须实现 IERC721Holder 接口中的所有函数。

实现 ERC721 持有 token 标准的智能合约还必须实现 ERC165,并在传递接口 ID 0x16b900ff 时返回 true。

/**
 * @notice the ERC721 holder standard provides a common interface to query
 * token ownership and balance information
 * @notice ERC721 持有者标准提供了一个通用的接口来查询 token 所有权和余额信息
 */
interface IERC721Holder is IERC165 {
  /**
   * @notice emitted when the token is transferred to the contract
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenId held token ID
   * @notice 当 token 转移到合约时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenId 持有 token ID
   */
  event Hold(
    address indexed owner,
    address indexed tokenAddress,
    uint256 indexed tokenId
  );

  /**
   * @notice emitted when the token is released back to the user
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenId held token ID
   * @notice 当 token 释放回用户时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenId 持有 token ID
   */
  event Release(
    address indexed owner,
    address indexed tokenAddress,
    uint256 indexed tokenId
  );

  /**
   * @notice get the functional owner of a held token
   * @dev should throw for invalid queries and return zero for a token ID that is not held
   * @param tokenAddress held token address
   * @param tokenId held token ID
   * @return functional token owner
   * @notice 获取持有 token 的功能性所有者
   * @dev 对于无效查询应抛出异常,对于未持有的 token ID 应返回零
   * @param tokenAddress 持有 token 地址
   * @param tokenId 持有 token ID
   * @return 功能性 token 所有者
   */
  function heldOwnerOf(address tokenAddress, uint256 tokenId)
    external
    view
    returns (address);

  /**
   * @notice get the held balance of the token owner
   * @dev should throw for invalid queries and return zero for no balance
   * @param tokenAddress held token address
   * @param owner functional token owner
   * @return held token balance
   * @notice 获取 token 所有者的持有余额
   * @dev 对于无效查询应抛出异常,对于没有余额的情况应返回零
   * @param tokenAddress 持有 token 地址
   * @param owner 功能性 token 所有者
   * @return 持有 token 余额
   */
  function heldBalanceOf(address tokenAddress, address owner)
    external
    view
    returns (uint256);
}

实现 ERC1155 持有 token 标准的智能合约必须实现 IERC1155Holder 接口中的所有函数。

实现 ERC1155 持有 token 标准的智能合约还必须实现 ERC165,并在传递接口 ID 0xced24c37 时返回 true。

/**
 * @notice the ERC1155 holder standard provides a common interface to query
 * token balance information
 * @notice ERC1155 持有者标准提供了一个通用的接口来查询 token 余额信息
 */
interface IERC1155Holder is IERC165 {
  /**
   * @notice emitted when the token is transferred to the contract
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenId held token ID
   * @param tokenAmount held token amount
   * @notice 当 token 转移到合约时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenId 持有 token ID
   * @param tokenAmount 持有 token 数量
   */
  event Hold(
    address indexed owner,
    address indexed tokenAddress,
    uint256 indexed tokenId,
    uint256 tokenAmount
  );

  /**
   * @notice emitted when the token is released back to the user
   * @param owner functional token owner
   * @param tokenAddress held token address
   * @param tokenId held token ID
   * @param tokenAmount held token amount
   * @notice 当 token 释放回用户时发出
   * @param owner 功能性 token 所有者
   * @param tokenAddress 持有 token 地址
   * @param tokenId 持有 token ID
   * @param tokenAmount 持有 token 数量
   */
  event Release(
    address indexed owner,
    address indexed tokenAddress,
    uint256 indexed tokenId,
    uint256 tokenAmount
  );

  /**
   * @notice get the held balance of the token owner
   * @dev should throw for invalid queries and return zero for no balance
   * @param tokenAddress held token address
   * @param owner functional token owner
   * @param tokenId held token ID
   * @return held token balance
   * @notice 获取 token 所有者的持有余额
   * @dev 对于无效查询应抛出异常,对于没有余额的情况应返回零
   * @param tokenAddress 持有 token 地址
   * @param owner 功能性 token 所有者
   * @param tokenId 持有 token ID
   * @return 持有 token 余额
   */
  function heldBalanceOf(
    address tokenAddress,
    address owner,
    uint256 tokenId
  ) external view returns (uint256);
}

理由

此接口旨在极其轻量级,并与任何现有的 token 合约兼容。任何 token 持有者合约可能已经存储了所有相关信息,因此该标准纯粹是添加一个通用接口来公开该数据。

包含 token 地址参数是为了支持可以同时持有多个 token 合约的合约。虽然某些合约可能只持有单个 token 地址,但这对于这两种情况都更通用。

为每种 token 类型(EIP-20、EIP-721、EIP-1155)提出了单独的接口,因为支持持有这些不同 token 的任何合约逻辑很可能是独立的。在单个合约确实持有多种 token 类型的情况下,它可以简单地实现每个适当的持有 token 接口。

向后兼容性

重要的是,所提出的规范与所有现有的 EIP-20、EIP-721 和 EIP-1155 token 合约完全兼容。

需要更新 Token 持有者合约以实现此轻量级接口。

需要更新此标准的消费者,以在任何相关的所有权逻辑中遵守此接口。

参考实现

一个完整的示例实现,包括接口,一个 vault token 持有者和一个消费者,可以在 assets/eip-4987/ 中找到。

值得注意的是,IERC721Holder 接口的消费者可以使用以下逻辑对任何特定 token ID 的所有者进行链式查找。

  /**
   * @notice get the functional owner of a token
   * @param tokenId token id of interest
   * @notice 获取 token 的功能性所有者
   * @param tokenId 感兴趣的 token id
   */
  function getOwner(uint256 tokenId) external view returns (address) {
    // get raw owner
    // 获取原始所有者
    address owner = token.ownerOf(tokenId);

    // if owner is not contract, return
    // 如果所有者不是合约,则返回
    if (!owner.isContract()) {
      return owner;
    }

    // check for token holder interface support
    // 检查 token 持有者接口支持
    try IERC165(owner).supportsInterface(0x16b900ff) returns (bool ret) {
      if (!ret) return owner;
    } catch {
      return owner;
    }

    // check for held owner
    // 检查持有所有者
    try IERC721Holder(owner).heldOwnerOf(address(token), tokenId) returns (address user) {
      if (user != address(0)) return user;
    } catch {}

    return owner;
  }

安全注意事项

此标准的消费者在使用来自未知合约的所有权信息时应谨慎。不良行为者可以实现该接口,但报告无效或恶意信息,目的是操纵治理系统、游戏、会员计划等。

消费者还应验证持有者合约的整体 token 余额和所有权,以作为完整性检查。

版权

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

Citation

Please cite this document as:

Devin Conley (@devinaconley), "ERC-4987: 持有 token 接口 [DRAFT]," Ethereum Improvement Proposals, no. 4987, September 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4987.