Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5501: 租赁 & 委托 NFT - EIP-721 扩展

为 EIP-721 添加一个有条件的时间限制用户角色。此角色可以被委托或借用。

Authors Jan Smrža (@smrza), David Rábel (@rabeles11), Tomáš Janča <tomas.janca@jtbstorage.eu>, Jan Bureš (@JohnyX89), DOBBYLABS (@DOBBYLABS)
Created 2022-08-18
Discussion Link https://ethereum-magicians.org/t/eip-tbd-rental-delegation-nft-erc-721-extension/10441
Requires EIP-165, EIP-721, EIP-4400, EIP-4907

摘要

以下标准为 EIP-721 提出了一个额外的 user 角色。此角色授予使用 NFT 的权限,但没有转移或设置用户的能力。它具有到期时间和标志,指示代币是否被借用。 Owner 可以将 NFT 委托给热钱包使用或借出 NFT。如果代币被借用,即使所有者也不能更改用户,直到状态过期或双方同意终止。这样,可以同时保持两个角色都处于活动状态。

动机

收藏品、游戏资产、元宇宙、活动门票、音乐、视频、域名、真实物品表示是众多 NFT 用例中的几个。使用 EIP-721,只有所有者才能获得收益。然而,对于大多数实用程序来说,区分代币所有者及其用户是有益的。例如,可以租借音乐或电影。元宇宙土地可以委托使用。

设置用户的两个原因是:

  • 委托 - 将用户分配给您的热钱包,以安全地与应用程序交互。在这种情况下,所有者可以随时更改用户。
  • 租赁 - 此用例带有额外的要求。一旦建立的借贷期限结束,就需要终止贷款。这由用户的 expires 提供。还有必要保护借款人免受所有者重置其状态的影响。因此,必须实施 isBorrowed 检查以禁用在合约到期之前设置用户的选项。

拥有额外用户角色的最常见用例是:

  • 委托 - 出于安全原因。
  • 游戏 - 您想尝试一款游戏(或特定的游戏资产),但不确定您是否会喜欢它?先租用资产。
  • 公会 - 将 NFT 的所有者保留为多重签名钱包,并将用户设置为在您的公会成员之间共享私钥的热钱包。
  • 活动 - 区分 ownerOfuserOf。每个角色都有不同的访问权限。
  • 社交 - 区分不同房间的角色。例如,所有者具有读 + 写访问权限,而 userOf 仅具有读取访问权限。

本提案是 EIP-4400EIP-4907 的后续,并引入了租赁和借贷的额外升级,包括:

  • NFT 在租赁期间保留在所有者的钱包中
  • NFT 的上市和销售不终止租金
  • 在租赁期间声明所有者权益

现在,通过额外的 isBorrowed 检查构建标准允许创建租赁市场,这些市场可以设置 NFT 的用户,而无需必要的质押机制。按照当前的标准,如果在租赁期间没有质押代币,所有者可以通过重复设置用户来简单地终止贷款。这通过在代币被借用时禁用该功能来解决,这反过来又为所有者提供了额外的好处。他们可以将代币绑定到他们的钱包,这意味着他们仍然可以收到空投,根据代币所有权声明免费铸币,或以其他方式使用第三方服务为所有者提供的 NFT。他们还可以保留 NFT 以供出售。以前可以接收空投或免费铸币,但所有者完全依赖于租赁市场的实施及其自由裁量权。

去中心化应用程序现在可以区分 ownerOf 和 userOf,而两种状态可以共存。

规范

本文档中的关键词“必须 (MUST)”、“不得 (MUST NOT)”、“必需 (REQUIRED)”、“应 (SHALL)”、“不应 (SHALL NOT)”、“应该 (SHOULD)”、“不应该 (SHOULD NOT)”、“建议 (RECOMMENDED)”、“可以 (MAY)”和“可选 (OPTIONAL)”应按照 RFC 2119 中的描述进行解释。

每个合规合约都必须实现 IERC5501 接口。此扩展对于 EIP-721 合约是可选的。

/**
 * @title IERC5501: Rental & Delegation NFT - EIP-721 Extension
 * @notice the EIP-165 identifier for this interface is 0xf808ec37.
 */
interface IERC5501 /* is IERC721 */ {
    /**
     * @dev Emitted when the user of an NFT is modified.
     */
    event UpdateUser(uint256 indexed _tokenId, address indexed _user, uint64 _expires, bool _isBorrowed);

    /**
     * @notice Set the user info of an NFT.
     * @dev User address cannot be zero address.
     * Only approved operator or NFT owner can set the user.
     * If NFT is borrowed, the user info cannot be changed until user status expires.
     * @param _tokenId uint256 ID of the token to set user info for
     * @param _user address of the new user
     * @param _expires Unix timestamp when user info expires
     * @param _isBorrowed flag whether or not the NFT is borrowed
     */
    function setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) external;

    /**
     * @notice Get the user address of an NFT.
     * @dev Reverts if user is not set.
     * @param _tokenId uint256 ID of the token to get the user address for
     * @return address user address for this NFT
     */
    function userOf(uint256 _tokenId) external view returns (address);

    /**
     * @notice Get the user expires of an NFT.
     * @param _tokenId uint256 ID of the token to get the user expires for
     * @return uint64 user expires for this NFT
     */
    function userExpires(uint256 _tokenId) external view returns (uint64);

    /**
     * @notice Get the user isBorrowed of an NFT.
     * @param _tokenId uint256 ID of the token to get the user isBorrowed for
     * @return bool user isBorrowed for this NFT
     */
    function userIsBorrowed(uint256 _tokenId) external view returns (bool);
}

每个实现 IERC5501 接口的合约都可以自由定义 user 的权限。但是,用户不得被视为 owner。他们不得执行转账和批准。此外,如果 userIsBorrowed 返回 trueuserExpires 大于或等于 block.timestamp,则必须阻止执行 setUser

user 更改时,必须发出 UpdateUser 事件。 除非 msg.senderowner 或批准的操作员,否则 setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) 函数应 revert。如果代币被借用且状态尚未过期,则必须 revert。它可以是 publicexternal。 如果 user 未设置或已过期,则 userOf(uint256 _tokenId) 函数应 revert。 userExpires(uint256 _tokenId) 函数返回用户状态过期的unix时间戳。 userIsBorrowed(uint256 _tokenId) 函数返回 NFT 是否被借用。 当使用 0xf808ec37 调用时,supportsInterface 函数必须返回 true。 在每次 transfer 时,如果代币未被借用,则必须重置 user。如果代币被借用,则 user 必须保持不变。

余额扩展是可选的。 这提供了查询 user 拥有的代币数量的选项。

/**
 * @title IERC5501Balance
 * Extension for ERC5501 which adds userBalanceOf to query how many tokens address is userOf.
 * @notice the EIP-165 identifier for this interface is 0x0cb22289.
 */
interface IERC5501Balance /* is IERC5501 */{
    /**
     * @notice Count of all NFTs assigned to a user.
     * @dev Reverts if user is zero address.
     * @param _user an address for which to query the balance
     * @return uint256 the number of NFTs the user has
     */
    function userBalanceOf(address _user) external view returns (uint256);
}

对于零地址,userBalanceOf(address _user) 函数应 revert

可枚举扩展是可选的。 这允许迭代用户余额。

/**
 * @title IERC5501Enumerable
 * This extension for ERC5501 adds the option to iterate over user tokens.
 * @notice the EIP-165 identifier for this interface is 0x1d350ef8.
 */
interface IERC5501Enumerable /* is IERC5501Balance, IERC5501 */ {
    /**
     * @notice Enumerate NFTs assigned to a user.
     * @dev Reverts if user is zero address or _index >= userBalanceOf(_owner).
     * @param _user an address to iterate over its tokens
     * @return uint256 the token ID for given index assigned to _user
     */
    function tokenOfUserByIndex(address _user, uint256 _index) external view returns (uint256);
}

对于零地址,tokenOfUserByIndex(address _user, uint256 _index) 函数应 revert,如果索引大于或等于 user 余额,则应 throw

可终止扩展是可选的。 如果双方同意,这允许提前终止租金。

/**
 * @title IERC5501Terminable
 * This extension for ERC5501 adds the option to terminate borrowing if both parties agree.
 * @notice the EIP-165 identifier for this interface is 0x6a26417e.
 */
interface IERC5501Terminable /* is IERC5501 */ {
    /**
     * @dev Emitted when one party from borrowing contract approves termination of agreement.
     * @param _isLender true for lender, false for borrower
     */
    event AgreeToTerminateBorrow(uint256 indexed _tokenId, address indexed _party, bool _isLender);

    /**
     * @dev Emitted when agreements to terminate borrow are reset.
     */
    event ResetTerminationAgreements(uint256 indexed _tokenId);

    /**
     * @dev Emitted when borrow of token ID is terminated.
     */
    event TerminateBorrow(uint256 indexed _tokenId, address indexed _lender, address indexed _borrower, address _caller);

    /**
     * @notice Agree to terminate a borrowing.
     * @dev Lender must be ownerOf token ID. Borrower must be userOf token ID.
     * If lender and borrower are the same, set termination agreement for both at once.
     * @param _tokenId uint256 ID of the token to set termination info for
     */
    function setBorrowTermination(uint256 _tokenId) external;

    /**
     * @notice Get if it is possible to terminate a borrow agreement.
     * @param _tokenId uint256 ID of the token to get termination info for
     * @return bool, bool first indicates lender agrees, second indicates borrower agrees
     */
    function getBorrowTermination(uint256 _tokenId) external view returns (bool, bool);

    /**
     * @notice Terminate a borrow if both parties agreed.
     * @dev Both parties must have agreed, otherwise revert.
     * @param _tokenId uint256 ID of the token to terminate borrow of
     */
    function terminateBorrow(uint256 _tokenId) external;
}

当贷方或借方同意终止租金时,必须发出 AgreeToTerminateBorrow 事件。 当代币被借用并转移或调用 setUserterminateBorrow 函数时,必须发出 ResetTerminationAgreements 事件。 当租金终止时,必须发出 TerminateBorrow 事件。 setBorrowTermination(uint256 _tokenId)。它必须为调用该函数的任何一方设置协议。如果贷款人和借款人是同一个地址,则必须同时为双方分配协议。 getBorrowTermination(uint256 _tokenId) 返回双方的协议是 true 还是 false。 可以由任何人调用 terminateBorrow(uint256 _tokenId) 函数。如果终止的所有协议都不是 true,则必须 revert。此函数应将 isBorrowed 标志从 true 更改为 false。 在每次 transfer 时,如果代币被借用,则必须重置任何一方的终止协议。

理由

影响该标准的主要因素是:

  • EIP-4400EIP-4907
  • 允许在所有者保留所有权的同时进行借贷而无需必要的抵押或过度抵押
  • 保持委托选项可用
  • 在实现所需功能的同时,将接口中的函数数量保持在最低限度
  • 模块化其他扩展,让开发人员选择他们项目所需的功能

名称

选择附加角色的名称是为了适应目的并保持与 EIP-4907 的兼容性。

所有权保留

许多藏品为其所有者提供各种代币的空投或免费铸币。如果所有者通过将其抵押到合约中来借出代币,这基本上会被打破(除非该合约正在实施一种至少声明空投代币的方式)。应用程序还可以在其生态系统中为所有者和用户角色提供不同的访问权限和好处。

余额和可枚举扩展

由于基于用户状态过期时余额较少并且没有立即进行链上交易来评估这一事实的实施复杂性,因此已选择将这些作为可选扩展。在 userBalanceOftokenOfUserByIndex 函数中,必须有一种方法来确定用户状态是否已过期。

可终止扩展

如果所有者错误地将借用状态的用户设置为较大的值,那么他们实际上将被阻止再次设置用户。如果双方同意终止用户状态,则此扩展可以解决此问题。

安全

一旦应用程序采用用户角色,就可以将所有权委托给热钱包并与其交互,而无需担心连接到恶意网站。

向后兼容性

此标准通过添加扩展函数集与当前的 EIP-721 兼容。引入的新函数与 EIP-721 中现有的函数类似,这保证了开发人员和应用程序可以轻松采用。考虑到用户角色及其到期时间,此标准也与 EIP-4907 相似,这意味着如果使用任一标准,应用程序将能够确定用户。

测试用例

测试用例可以在参考实现中找到:

参考实现

参考实现可在此处获得:

安全注意事项

实施此标准的开发人员和应用程序必须考虑他们授予用户和所有者的所有权限。由于所有者和用户都是同时处于活动状态的角色,因此必须避免重复消费问题。必须以不会导致任何 Gas 费问题的方式实施余额扩展。市场应让用户知道列出待售的代币是否被借用。

版权

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

Citation

Please cite this document as:

Jan Smrža (@smrza), David Rábel (@rabeles11), Tomáš Janča <tomas.janca@jtbstorage.eu>, Jan Bureš (@JohnyX89), DOBBYLABS (@DOBBYLABS), "ERC-5501: 租赁 & 委托 NFT - EIP-721 扩展 [DRAFT]," Ethereum Improvement Proposals, no. 5501, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5501.