Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5298: ENS Trust,代表 ENS 名称持有 NFT

一个智能合约的接口,该智能合约充当“信托”,通过 ENS 名称持有 token。

Authors Zainan Victor Zhou (@xinbenlv)
Created 2022-07-12
Discussion Link https://ethereum-magicians.org/t/erc-eip-5198-ens-as-token-holder/10374
Requires EIP-137, EIP-721, EIP-1155

摘要

此 EIP 标准化了一个智能合约的接口,该合约代表 ENS 域名持有 EIP-721EIP-1155 token。

动机

目前,如果有人想接收 token,他们必须设置一个钱包地址。此 EIP 将 NFT 所有权与钱包地址分离。

规范

  1. 兼容合约必须实现 ERC721TokenReceiver,如 EIP-721 中定义。
  2. 兼容合约实现以下接口:
interface IERC_ENS_TRUST is ERC721Receiver, ERC1155Receiver {
    function claimTo(address to, bytes32 ensNode, address operator, uint256 tokenId) payable external;
}
  1. claimTo 必须检查 msg.sender 是否是由 bytes32 ensNode 标识的 ENS 节点的所有者(和/或以实现特定的方式获得域名批准)。兼容合约然后必须调用 EIP-721EIP-1155safeTransferFrom 函数。

  2. 允许任何 ensNode

原理

  1. 选择 ENS 是因为它是一个完善的、具有作用域的所有权命名空间。 但这仍然与其他具有作用域的所有权命名空间兼容。

  2. 我们没有暴露 ensRoot 的 getter 或 setter,因为它超出了此 EIP 的范围。

向后兼容性

未发现向后兼容性问题。

测试用例

import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";

describe("FirstENSBankAndTrust", function () {

    describe("Receive and Claim Token", function () {

        it("Should ACCEPT/REJECT claimTo based on if ENS owner is msg.sender", async function () {
            ...
            // Steps of testing:
            // 测试步骤:
            // mint to charlie
            // 铸造给 charlie
            // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth
            // charlie 发送到 ENSTrust 并记录在 bob.xinbenlvethsf.eth 下
            // bob try to claimTo alice, first time it should be rejected
            // bob 尝试 claimTo alice,第一次应该被拒绝
            // bob then set the ENS record
            // bob 然后设置 ENS 记录
            // bob claim to alice, second time it should be accepted
            // bob claim to alice,第二次应该被接受

            // mint to charlie
            // 铸造给 charlie
            await erc721ForTesting.mint(charlie.address, fakeTokenId);

            // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth
            // charlie 发送到 ENSTrust 并记录在 bob.xinbenlvethsf.eth 下
            await erc721ForTesting.connect(charlie)["safeTransferFrom(address,address,uint256,bytes)"](
                charlie.address, firstENSBankAndTrust.address,
                fakeTokenId,
                fakeReceiverENSNamehash
            );

            // bob try to claimTo alice, first time it should be rejected
            // bob 尝试 claimTo alice,第一次应该被拒绝
            await expect(firstENSBankAndTrust.connect(bob).claimTo(
                alice.address,
                fakeReceiverENSNamehash,
                firstENSBankAndTrust.address,
                fakeTokenId
                ))
                .to.be.rejectedWith("ENSTokenHolder: node not owned by sender");

            // bob then set the ENS record
            // bob 然后设置 ENS 记录
            await ensForTesting.setOwner(
                fakeReceiverENSNamehash, bob.address
            );

            // bob claim to alice, second time it should be accepted
            // bob claim to alice,第二次应该被接受
            await expect(firstENSBankAndTrust.connect(bob).claimTo(
                alice.address,
                fakeReceiverENSNamehash,
                erc721ForTesting.address,
                fakeTokenId
            ));
        });
    });
});

参考实现

pragma solidity ^0.8.9;

contract FirstENSBankAndTrust is IERC721Receiver, Ownable {
    function getENS() public view returns (ENS) {
        return ENS(ensAddress);
    }

    function setENS(address newENSAddress) public onlyOwner {
        ensAddress = newENSAddress;
    }

    // @dev This function is called by the owner of the token to approve the transfer of the token
    // @dev 此函数由 token 的所有者调用,以批准 token 的转移
    // @param data MUST BE the ENS node of the intended token receiver this ENSHoldingServiceForNFT is holding on behalf of.
    // @param data 必须是此 ENSHoldingServiceForNFT 代表的预期 token 接收者的 ENS 节点。
    function onERC721Received(
        address operator,
        address /*from*/,
        uint256 tokenId,
        bytes calldata data
    ) external override returns (bytes4) {
        require(data.length == 32, "ENSTokenHolder: last data field must be ENS node.");
        // --- START WARNING ---
        // --- 开始警告 ---
        // DO NOT USE THIS IN PROD
        // 不要在生产环境中使用
        // this is just a demo purpose of using extraData for node information
        // 这只是使用 extraData 获取节点信息的演示目的
        // In prod, you should use a struct to store the data. struct should clearly identify the data is for ENS
        // 在生产环境中,您应该使用一个结构体来存储数据。结构体应该清楚地标识数据用于 ENS
        // rather than anything else.
        // 而不是其他任何东西。
        bytes32 ensNode = bytes32(data[0:32]);
        // --- END OF WARNING ---
        // --- 结束警告 ---

        addToHolding(ensNode, operator, tokenId); // conduct the book keeping
        // 进行簿记
        return ERC721_RECEIVER_MAGICWORD;
    }

    function claimTo(address to, bytes32 ensNode, address tokenContract uint256 tokenId) public {
        require(getENS().owner(ensNode) == msg.sender, "ENSTokenHolder: node not owned by sender");
        removeFromHolding(ensNode, tokenContract, tokenId);
        IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId);
    }
}

安全注意事项

需要讨论。

版权

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

Citation

Please cite this document as:

Zainan Victor Zhou (@xinbenlv), "ERC-5298: ENS Trust,代表 ENS 名称持有 NFT [DRAFT]," Ethereum Improvement Proposals, no. 5298, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5298.