Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-4799: 非同质化代币所有权指定标准

一种用于指定 NFT 所有权的标准接口

Authors David Buckman (@davidbuckman), Isaac Buckman (@isaacbuckman)
Created 2022-02-13
Discussion Link https://ethereum-magicians.org/t/erc-4799-non-fungible-token-wrapping-standard/8396
Requires EIP-165

摘要

以下定义了一个标准接口,用于在 NFT 由智能合约托管时,将 NFT 的所有权指定给某人。该标准允许构建一个 NFT 的有向无环图,其中给定链中每个 NFT 的指定所有者是该链的终端地址。这使得可以向预先存在的 NFT 引入额外的功能,而无需放弃原始 NFT 的真实性。实际上,这意味着所有 NFT 都是可组合的,可以出租、用作抵押品、进行碎片化等等。

动机

许多 NFT 旨在为其持有者提供一些效用——这种效用可以有多种形式。这可以是居住公寓的权利、参加活动的门票、代币的空投,或者是无数其他潜在的应用之一。然而,就其当前形式而言,NFT 受限于这样一个事实,即与 NFT 关联的唯一可验证钱包是所有者,因此想要分配效用的客户端被迫将其分配给 NFT 的已列出所有者。这意味着任何复杂的所有权协议都必须编码到原始 NFT 合约中——没有一种机制可以让所有者将其原始 NFT 的真实性链接到任何外部合约。

这个标准的目的是允许用户和开发者能够在已经铸造的 NFT 上定义任意复杂的所有权协议。这样,就可以部署具有创新所有权结构的新合约,但它们仍然可以利用已建立的 NFT 合约所提供的真实性——在过去,包装合约意味着全新的 NFT,没有已建立的真实性。

在此标准之前,将 NFT 包装在另一个合约中是在 NFT 合约部署后添加功能的唯一方法,但这意味着失去了持有原始 NFT 的效用。任何查询该 NFT 所有者的应用程序都将确定包装智能合约是所有者。使用此标准,应用程序将拥有与包装合约交互的标准化方法,以便即使在 NFT 被包装后,它们也可以继续将其效用导向用户。

规范

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

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

interface IERC4799NFT is IERC165 {
    /// @dev 当任何 NFT 的所有权通过任何机制发生变化时,会发出此事件。
    ///  此事件在创建 NFT 时(`from` == 0)和销毁 NFT 时
    ///  (`to` == 0)发出。例外:在合约创建期间,可以创建和分配任意数量的 NFT
    ///  而无需发出转移事件。在
    ///  任何转移时,该 NFT 的批准地址(如果有)将重置为无。
    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    /// @notice 查找 NFT 的所有者
    /// @dev 分配给零地址的 NFT 被认为是无效的,并且有关它们的查询
    ///  会抛出异常
    /// @param tokenId NFT 的标识符
    /// @return NFT 所有者的地址
    function ownerOf(uint256 tokenId) external view returns (address);
}
/// @title ERC-4799 非同质化代币所有权指定标准
/// @dev 参见 https://eips.ethereum.org/EIPS/eip-4799
/// 注意:此接口的 ERC-165 标识符为 [TODO]。

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./IERC4799NFT.sol";

interface IERC4799 is IERC165 {
    /// @dev 当源代币将其所有权指定给目标代币的所有者时发出
    event OwnershipDesignation(
        IERC4799NFT indexed sourceContract,
        uint256 sourceTokenId,
        IERC4799NFT indexed targetContract,
        uint256 targetTokenId
    );

    /// @notice 查找指定的 NFT
    /// @param sourceContract 源 NFT 的合约地址
    /// @param sourceTokenId 源 NFT 的 tokenId
    /// @return (targetContract, targetTokenId) 父 NFT 的合约地址和 tokenId
    function designatedTokenOf(IERC4799NFT sourceContract, uint256 sourceTokenId)
        external
        view
        returns (IERC4799NFT, uint256);
}

根据源合约,指定 ERC-4799 合约对原始 NFT 的所有权授予了指定 NFT 所有权的真实性。客户端必须通过查询源合约来验证这一点。

尊重此规范的客户端不得将任何效用分配给 ERC-4799 合约的地址。相反,他们必须将其分配给 ERC-4799 合约指向的指定代币的所有者。

理由

为了最大化包装合约的未来兼容性,我们首先定义了一个规范的 NFT 接口。我们创建了 IERC4799NFT,一个几乎所有流行的 NFT 合约都隐式实现的接口,包括所有已部署的 ERC-721 兼容合约。此接口表示 NFT 的本质:从代币标识符到单一所有者的地址的映射,由函数 ownerOf 表示。

我们提案的核心是 IERC4799 接口,它是标准 NFT 所有权指定合约 (ODC) 的接口。ERC4799 要求实现一个 designatedTokenOf 函数,该函数将源 NFT 映射到恰好一个目标 NFT。通过此函数,ODC 表达了其对指定所有权的信念。只有当 ODC 被列为原始 NFT 的所有者时,此指定所有权才是真实的,从而保持了每个 NFT 恰好有一个指定所有者的不变性。

向后兼容性

IERC4799NFT 接口与 IERC721 向后兼容,因为 IERC721 隐式扩展了 IERC4799NFT。这意味着 ERC-4799 标准(包装实现 ERC4799NFT 的 NFT)与 ERC-721 完全向后兼容。

参考实现

// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0 <0.9.0;

import "./IERC4799.sol";
import "./IERC4799NFT.sol";
import "./ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

contract ERC721Composable is IERC4799, IERC721Receiver {
    mapping(IERC4799NFT => mapping(uint256 => IERC4799NFT)) private _targetContracts;
    mapping(IERC4799NFT => mapping(uint256 => uint256)) private _targetTokenIds;

    function designatedTokenOf(IERC4799NFT sourceContract, uint256 sourceTokenId)
        external
        view
        override
        returns (IERC4799NFT, uint256)
    {
        return (
            IERC4799NFT(_targetContracts[sourceContract][sourceTokenId]),
            _targetTokenIds[sourceContract][sourceTokenId]
        );
    }

    function designateToken(
        IERC4799NFT sourceContract,
        uint256 sourceTokenId,
        IERC4799NFT targetContract,
        uint256 targetTokenId
    ) external {
        require(
            ERC721(address(sourceContract)).ownerOf(sourceTokenId) == msg.sender ||
            ERC721(address(sourceContract)).getApproved(sourceTokenId) == msg.sender, 
            "ERC721Composable: 只有所有者或批准的地址可以设置指定的所有权");
        _targetContracts[sourceContract][sourceTokenId] = targetContract;
        _targetTokenIds[sourceContract][sourceTokenId] = targetTokenId;
        emit OwnershipDesignation(
            sourceContract, 
            sourceTokenId,  
            targetContract,
            targetTokenId
        );
    }

    function onERC721Received(
        address,
        address from,
        uint256 sourceTokenId,
        bytes calldata
    ) external override returns (bytes4) {
        ERC721(msg.sender).approve(from, sourceTokenId);
        return IERC721Receiver.onERC721Received.selector;
    }

        function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override
        returns (bool)
    {
        return
            (interfaceId == type(IERC4799).interfaceId ||
            interfaceId == type(IERC721Receiver).interfaceId);
    }
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0 <0.9.0;

import "./IERC4799.sol";
import "./IERC4799NFT.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

contract DesignatedOwner {
    function designatedOwnerOf(
        IERC4799NFT tokenContract,
        uint256 tokenId,
        uint256 maxDepth
    ) public view returns (address owner) {
        owner = tokenContract.ownerOf(tokenId);
        if (ERC165Checker.supportsInterface(owner, type(IERC4799).interfaceId)) {
            require(maxDepth > 0, "designatedOwnerOf: 超出深度限制");
            (tokenContract, tokenId) = IERC4799(owner).designatedTokenOf(
                tokenContract,
                tokenId
            );
            return designatedOwnerOf(tokenContract, tokenId, maxDepth - 1);
        }
    }
}

安全注意事项

长期/循环所有权链

主要的安全问题是恶意行为者创建过长或循环的所有权链,导致尝试查询给定代币的指定所有者的应用程序耗尽 gas 并且无法运行。为了解决这个问题,预计客户端始终会考虑 maxDepth 参数进行查询,在一定数量的链遍历后停止计算。

版权

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

Citation

Please cite this document as:

David Buckman (@davidbuckman), Isaac Buckman (@isaacbuckman), "ERC-4799: 非同质化代币所有权指定标准 [DRAFT]," Ethereum Improvement Proposals, no. 4799, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4799.