Alert Source Discuss
⚠️ Review Standards Track: ERC

ERC-7590: NFT 的 ERC-20 持有者扩展

允许 NFT 接收和转移 ERC-20 代币的扩展。

Authors Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer)
Created 2024-01-05
Requires EIP-20, EIP-165, EIP-721

摘要

本提案建议对 ERC-721 进行扩展,以方便 ERC-20 代币的交换。通过增强 ERC-721,它允许独特的代币管理和交易捆绑在单个 NFT 中的 ERC-20 同质化代币。这是通过包含将 ERC-20 代币拉入 NFT 合约到特定 NFT,并由此 NFT 的所有者将其转移出去的方法来实现的。包含转出 nonce 以防止抢跑问题。

动机

在区块链技术和去中心化生态系统不断发展的背景下,不同代币标准之间的互操作性已成为一个至关重要的问题。通过增强 ERC-721 的功能,该提案使非同质化代币 (NFT) 能够参与复杂的交易,从而促进在单个协议中交换同质化代币、独特资产和多类别资产。

此 ERC 在以下领域引入了新的实用程序:

  • 扩展用例
  • 促进复合交易
  • 市场流动性和价值创造

扩展用例

使 ERC-721 代币能够处理各种代币类型,为各种创新用例打开了大门。从游戏和数字收藏品到去中心化金融 (DeFi) 和供应链管理,此扩展通过允许 NFT 参与复杂的、多代币交易,增强了 NFT 的潜力。

促进复合交易

通过此扩展,涉及同质化和非同质化资产的复合交易变得更加容易。此功能对于需要复杂交易的应用程序尤其有价值,例如游戏生态系统,其中游戏内资产可能包括同质化和独特代币的组合。

市场流动性和价值创造

通过允许 ERC-721 代币持有和交易不同类型的代币,它可以提高所有类型代币市场的流动性。

规范


interface IERC7590 /*is IERC165, IERC721*/  {
    /**
     * @notice Used to notify listeners that the token received ERC-20 tokens.
     * @param erc20Contract The address of the ERC-20 smart contract
     * @param toTokenId The ID of the token receiving the ERC-20 tokens
     * @param from The address of the account from which the tokens are being transferred
     * @param amount The number of ERC-20 tokens received
     */
    event ReceivedERC20(
        address indexed erc20Contract,
        uint256 indexed toTokenId,
        address indexed from,
        uint256 amount
    );

    /**
     * @notice Used to notify the listeners that the ERC-20 tokens have been transferred.
     * @param erc20Contract The address of the ERC-20 smart contract
     * @param fromTokenId The ID of the token from which the ERC-20 tokens have been transferred
     * @param to The address receiving the ERC-20 tokens
     * @param amount The number of ERC-20 tokens transferred
     */
    event TransferredERC20(
        address indexed erc20Contract,
        uint256 indexed fromTokenId,
        address indexed to,
        uint256 amount
    );

    /**
     * @notice Used to retrieve the given token's specific ERC-20 balance
     * @param erc20Contract The address of the ERC-20 smart contract
     * @param tokenId The ID of the token being checked for ERC-20 balance
     * @return The amount of the specified ERC-20 tokens owned by a given token
     */
    function balanceOfERC20(
        address erc20Contract,
        uint256 tokenId
    ) external view returns (uint256);

    /**
     * @notice Transfer ERC-20 tokens from a specific token.
     * @dev The balance MUST be transferred from this smart contract.
     * @dev MUST increase the transfer-out-nonce for the tokenId
     * @dev MUST revert if the `msg.sender` is not the owner of the NFT or approved to manage it.
     * @param erc20Contract The address of the ERC-20 smart contract
     * @param tokenId The ID of the token to transfer the ERC-20 tokens from
     * @param amount The number of ERC-20 tokens to transfer
     * @param data Additional data with no specified format, to allow for custom logic
     */
    function transferHeldERC20FromToken(
        address erc20Contract,
        uint256 tokenId,
        address to,
        uint256 amount,
        bytes memory data
    ) external;

    /**
     * @notice Transfer ERC-20 tokens to a specific token.
     * @dev The ERC-20 smart contract must have approval for this contract to transfer the ERC-20 tokens.
     * @dev The balance MUST be transferred from the `msg.sender`.
     * @param erc20Contract The address of the ERC-20 smart contract
     * @param tokenId The ID of the token to transfer ERC-20 tokens to
     * @param amount The number of ERC-20 tokens to transfer
     * @param data Additional data with no specified format, to allow for custom logic
     */
    function transferERC20ToToken(
        address erc20Contract,
        uint256 tokenId,
        uint256 amount,
        bytes memory data
    ) external;

    /**
     * @notice Nonce increased every time an ERC20 token is transferred out of a token
     * @param tokenId The ID of the token to check the nonce for
     * @return The nonce of the token
     */
    function erc20TransferOutNonce(
        uint256 tokenId
    ) external view returns (uint256);
}

理由

拉取机制

我们建议使用拉取机制,其中合约将代币转移到自身,而不是通过“安全转移”接收它,原因有以下两个:

  1. 通过 Hook 进行可定制性。通过以这种方式启动流程,智能合约开发者可以灵活地在转移代币之前和之后执行特定操作。

  2. 缺乏带回调的转移:ERC-20 代币缺乏带有回调方法的标准化转移,例如 ERC-721 上的“safeTransfer”,这意味着没有可靠的方法来通知接收者转移成功,也不知道目标代币是什么。

这具有在实际将代币转移到 NFT 之前需要批准要转移的代币的缺点。

细粒度与通用

我们考虑了两种提出提案的方式:

  1. 细粒度方法,其中每种持有代币类型都有一个独立的接口。
  2. 通用代币持有者,也可以持有和转移 ERC-721ERC-1155

在 gas 方面,细粒度版本的实现稍微便宜一些,如果你只使用一两种类型,那么它的合约大小会更小。通用版本更小,并且具有用于发送或接收的单一方法,但它也通过始终要求转移方法上的 Id 和金额来增加一些复杂性。Id 对于 ERC-20 不是必需的,而金额对于 ERC-721 不是必需的。

我们还考虑到,由于 ERC-721ERC-1155 上都存在安全转移方法,以及常用的 IERC721ReceiverIERC1155Receiver 接口,因此没有太多需要声明额外的接口来管理此类代币。但是,ERC-20 的情况并非如此,它不包含带有回调以通知接收者转移的方法。

由于上述原因,我们决定采用细粒度方法。

向后兼容性

未发现向后兼容性问题。

测试用例

测试包含在 erc7590.ts 中。

要在终端中运行它们,可以使用以下命令:

cd ../assets/eip-erc7590
npm install
npx hardhat test

参考实现

请参阅 ERC7590Mock.sol

安全考虑事项

ERC-721 一样的安全注意事项适用:隐藏逻辑可能存在于任何函数中,包括 burn、add resource、accept resource 等。

建议在处理未经审计的合约时要小心。

当实现将代币转移到 NFT 中时,它们必须使用消息发送者作为 from 参数。否则,由于当前合约需要批准,它可能会将外部代币拉入不同的 NFT 中。

当将 ERC-20 代币转移到 NFT 中或从 NFT 中转移出来时,可能会出现转移的金额与请求的金额不同的情况。如果 ERC-20 合约在转移时收取费用,则可能会发生这种情况。如果你没有正确管理它,这可能会导致你的 Token Holder 合约出现 bug。有两种方法可以做到这一点,这两种方法都是有效的:

  1. 使用 IERC20 接口在转移前后检查合约的余额,如果余额不是预期的余额,则恢复,因此不支持在转移时收取费用的代币。
  2. 使用 IERC20 接口在转移前后检查合约的余额,并使用差额来计算实际转移的代币数量。

为了防止卖家抢先出售持有 ERC-20 代币的 NFT,以便在执行销售之前转移出此类代币,市场必须注意 erc20TransferOutNonce,如果自上市以来已更改,则恢复。

直接转移到 NFT 合约的 ERC-20 代币将丢失。

版权

CC0 下放弃版权和相关权利。

Citation

Please cite this document as:

Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer), "ERC-7590: NFT 的 ERC-20 持有者扩展 [DRAFT]," Ethereum Improvement Proposals, no. 7590, January 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7590.