Alert Source Discuss
Standards Track: ERC

ERC-7409: 公共非同质化代币表情符号库

使用 Unicode 表情符号对任何非同质化代币做出反应。

Authors Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer)
Created 2023-07-26
Requires EIP-165

摘要

❗️ ERC-7409 取代 ERC-6381 ❗️

公共非同质化代币表情符号库标准通过允许对 NFT 使用表情符号,为 ERC-721ERC-1155 提供了增强的交互式实用程序。

本提案引入了使用 Unicode 标准化表情符号对 NFT 做出反应的功能,该表情符号位于公共的非门控存储库智能合约中,该合约可在所有网络上的相同地址访问。

动机

随着 NFT 成为 Ethereum 生态系统中一种广泛的代币形式,并被用于各种用例,现在是时候标准化它们的附加实用程序了。 任何人与 NFT 交互的能力为拥有 NFT 引入了交互方面,并解锁了基于反馈的 NFT 机制。

此 ERC 在以下领域为基于 ERC-721 的代币引入了新实用程序:

本提案修复了 ERC-6381 接口规范中的兼容性问题,其中表情符号使用 bytes4 值表示。 变体标志和表情符号肤色的引入使得 bytes4 命名空间不足以表示所有可能的表情符号,因此新标准使用 string 代替。 除了此修复之外,此提案在功能上等同于 ERC-6381

互动性

对 NFT 使用表情符号的能力为拥有 NFT 引入了互动性方面。 这可以反映对表情符号发送者(对 NFT 发送表情符号的人)的钦佩,也可以是代币所有者执行的特定操作的结果。 在代币上积累表情符号可以增加其独特性和/或价值。

基于反馈的演变

标准化的链上 NFT 反应允许基于反馈的演变。

当前的解决方案要么是专有的,要么是链下的,因此容易受到操纵和不信任。 能够跟踪链上的交互可以实现对给定代币的信任和客观评估。 将代币设计为在满足某些表情符号阈值时进行演变,可以激励与代币集合的交互。

估值

当前的 NFT 市场在很大程度上依赖于代币之前已售出的价值、上市代币的最低价格以及市场提供的稀缺性数据。 没有特定代币的钦佩或可取性方面的实时指示。 允许用户对代币使用表情符号增加了潜在买家和卖家根据代币收集到的印象来衡量代币价值的可能性。

规范

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

/// @title 用于非同质化代币的 ERC-7409 表情符号扩展
/// @dev 请参阅 https://eips.ethereum.org/EIPS/eip-7409
/// @dev 注意:此接口的 ERC-165 标识符是 0x1b3327ab。

pragma solidity ^0.8.16;

interface IERC7409 /*is IERC165*/ {
    /**
     * @notice 用于通知侦听器具有指定 ID 的代币已被使用表情符号或该反应已被撤销。
     * @dev 仅当表情符号的状态发生更改时才必须发出此事件。
     * @param emoter 使用表情符号或撤销对代币的反应的帐户地址
     * @param collection 包含正在使用表情符号或撤销反应的代币的集合智能合约地址
     * @param tokenId 代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param on 布尔值,表示是否对代币使用了表情符号 (`true`) 或反应是否已撤销 (`false`)
     */
    event Emoted(
        address indexed emoter,
        address indexed collection,
        uint256 indexed tokenId,
        string emoji,
        bool on
    );

    /**
     * @notice 用于获取代币上特定表情符号的表情符号数量。
     * @param collection 包含要检查表情符号数量的代币的集合地址
     * @param tokenId 要检查表情符号数量的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @return 代币上带有表情符号的表情符号数量
     */
    function emoteCountOf(
        address collection,
        uint256 tokenId,
        string memory emoji
    ) external view returns (uint256);

    /**
     * @notice 用于获取一组代币上特定表情符号的表情符号数量。
     * @param collections 包含要检查表情符号数量的代币的集合地址数组
     * @param tokenIds 要检查表情符号数量的代币 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @return 代币上带有表情符号的表情符号数量数组
     */
    function bulkEmoteCountOf(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory emojis
    ) external view returns (uint256[] memory);

    /**
     * @notice 用于获取有关指定地址是否在特定代币上使用了特定表情符号的信息。
     * @param emoter 我们要检查对代币的反应的帐户地址
     * @param collection 包含要检查表情符号反应的代币的集合智能合约地址
     * @param tokenId 要检查表情符号反应的代币的 ID
     * @param emoji 要检查反应的 ASCII 表情符号代码
     * @return 一个布尔值,指示 `emoter` 是否在代币上使用了 `emoji` (`true`) 或没有 (`false`)
     */
    function hasEmoterUsedEmote(
        address emoter,
        address collection,
        uint256 tokenId,
        string memory emoji
    ) external view returns (bool);

    /**
     * @notice 用于获取有关指定地址是否在特定代币上使用了特定表情符号的信息。
     * @param emoters 我们要检查对代币的反应的帐户地址数组
     * @param collections 包含要检查表情符号反应的代币的集合智能合约地址数组
     * @param tokenIds 要检查表情符号反应的代币 ID 数组
     * @param emojis 要检查反应的 ASCII 表情符号代码数组
     * @return 一个布尔值数组,指示 `emoter`s 是否在代币上使用了 `emoji`s (`true`) 或没有 (`false`)
     */
    function haveEmotersUsedEmotes(
        address[] memory emoters,
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory emojis
    ) external view returns (bool[] memory);

    /**
     * @notice 用于获取要由 `emoter` 签名的消息,以便其他人可以提交反应。
     * @param collection 包含正在使用表情符号的代币的集合智能合约地址
     * @param tokenId 正在使用表情符号的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     * @param deadline 用于提交签名的截止时间的 UNIX 时间戳
     * @return 要由 `emoter` 签名的消息,以便其他人可以提交反应
     */
    function prepareMessageToPresignEmote(
        address collection,
        uint256 tokenId,
        string memory emoji,
        bool state,
        uint256 deadline
    ) external view returns (bytes32);

    /**
     * @notice 用于获取要由 `emoter` 签名的多个消息,以便其他人可以提交反应。
     * @param collections 包含正在使用表情符号的代币的集合智能合约地址数组
     * @param tokenIds 正在使用表情符号的代币 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     * @param deadlines 用于提交签名的截止时间的 UNIX 时间戳数组
     * @return 要由 `emoter` 签名的消息数组,以便其他人可以提交反应
     */
    function bulkPrepareMessagesToPresignEmote(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory emojis,
        bool[] memory states,
        uint256[] memory deadlines
    ) external view returns (bytes32[] memory);

    /**
     * @notice 用于在代币上使用表情符号或撤消表情符号。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @param collection 包含正在使用表情符号的代币的集合地址
     * @param tokenId 正在使用表情符号的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     */
    function emote(
        address collection,
        uint256 tokenId,
        string memory emoji,
        bool state
    ) external;

    /**
     * @notice 用于在多个代币上使用表情符号或撤消表情符号。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @param collections 包含正在使用表情符号的代币的集合地址数组
     * @param tokenIds 正在使用表情符号的代币 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     */
    function bulkEmote(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory emojis,
        bool[] memory states
    ) external;

    /**
     * @notice 用于代表他人使用表情符号或撤消表情符号。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @dev 如果 `deadline` 已过,则必须恢复。
     * @dev 如果恢复的地址是零地址,则必须恢复。
     * @param emoter 预先签署表情符号的地址
     * @param collection 包含正在使用表情符号的代币的集合智能合约地址
     * @param tokenId 正在使用表情符号的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     * @param deadline 用于提交签名的截止时间的 UNIX 时间戳
     * @param v 通过 `prepareMessageToPresignEmote` 获得的 ECDSA 签名的 `v` 值
     * @param r 通过 `prepareMessageToPresignEmote` 获得的 ECDSA 签名的 `r` 值
     * @param s 通过 `prepareMessageToPresignEmote` 获得的 ECDSA 签名的 `s` 值
     */
    function presignedEmote(
        address emoter,
        address collection,
        uint256 tokenId,
        string memory emoji,
        bool state,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于批量代表他人使用表情符号或撤消表情符号。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @dev 如果 `deadline` 已过,则必须恢复。
     * @dev 如果恢复的地址是零地址,则必须恢复。
     * @param emoters 预先签署表情符号的帐户地址数组
     * @param collections 包含正在使用表情符号的代币的集合智能合约地址数组
     * @param tokenIds 正在使用表情符号的代币 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否使用表情符号 (`true`) 或撤消 (`false`) 表情符号
     * @param deadlines 用于提交签名的截止时间的 UNIX 时间戳
     * @param v 通过 `prepareMessageToPresignEmote` 获得的消息的 ECDSA 签名的 `v` 值数组
     * @param r 通过 `prepareMessageToPresignEmote` 获得的消息的 ECDSA 签名的 `r` 值数组
     * @param s 通过 `prepareMessageToPresignEmote` 获得的消息的 ECDSA 签名的 `s` 值数组
     */
    function bulkPresignedEmote(
        address[] memory emoters,
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory emojis,
        bool[] memory states,
        uint256[] memory deadlines,
        uint8[] memory v,
        bytes32[] memory r,
        bytes32[] memory s
    ) external;
}

预签名表情符号的消息格式

为了让其他人可以提交反应,要由 emoter 签名的消息格式如下:

keccak256(
        abi.encode(
            DOMAIN_SEPARATOR,
            collection,
            tokenId,
            emoji,
            state,
            deadline
        )
    );

生成要签名的消息时传递的值是:

  • DOMAIN_SEPARATOR - 可表情符号库智能合约的域分隔符
  • collection - 包含正在使用表情符号的代币的集合地址
  • tokenId - 正在使用表情符号的代币的 ID
  • emoji - 表情符号的 Unicode 标识符
  • state - 布尔值,表示是否使用表情符号 (true) 或撤消 (false) 表情符号
  • deadline - 用于提交签名的截止时间的 UNIX 时间戳

DOMAIN_SEPARATOR 的生成方式如下:

keccak256(
        abi.encode(
            "ERC-7409: Public Non-Fungible Token Emote Repository",
            "1",
            block.chainid,
            address(this)
        )
    );

由于链 ID 不同,部署可表情符号库智能合约的每个链都将具有不同的 DOMAIN_SEPARATOR 值。

可表情符号库的预定地址

可表情符号库智能合约的地址旨在类似于它所服务的功能。 它以 0x3110735 开头,这是 EMOTES 的抽象表示。 地址是:

0x3110735F0b8e71455bAe1356a33e428843bCb9A1

理由

在设计提案时,我们考虑了以下问题:

  1. 提案是否支持自定义表情符号,还是仅支持 Unicode 指定的表情符号?
    该提案仅接受 Unicode 标识符,这是一个 string 值。 这意味着,虽然我们鼓励实施者使用标准化表情符号添加反应,但 Unicode 标准未涵盖的值可用于自定义表情符号。 唯一的缺点是显示反应的界面必须知道要渲染哪种图像,并且此类添加可能仅限于创建它们的界面或市场。
  2. 提案应该使用表情符号来传递 NFT 的印象,还是使用其他方法?
    可以使用用户提供的字符串或数值来完成印象,但我们决定使用表情符号,因为它们是传递印象和情感的公认手段。
  3. 提案应该建立一个可表情符号扩展还是一个公益存储库?
    最初,我们着手创建一个可表情符号扩展,以用于任何符合 ERC-721 的代币。 但是,我们意识到,如果它是一个可表情符号代币的公益存储库,该提案将更有用。 这样,可以做出反应的代币不仅是新代币,而且是提案之前就存在的旧代币。
    根据这一决定,我们决定为存储库智能合约计算一个确定性地址。 这样,任何 NFT 集合都可以使用该存储库,而无需在给定链上搜索地址。
  4. 我们应该只包括单操作操作、只包括多操作操作,还是两者都包括?
    我们考虑过仅包括单操作操作,用户只能对单个代币使用单个表情符号做出反应,但我们决定同时包括单操作和多操作操作。 这样,用户可以选择他们想在单个代币上使用表情符号或撤消表情符号,还是同时在多个代币上使用表情符号。
    做出此决定是为了提案的长期可行性。 根据网络的 gas 成本和集合中的代币数量,用户可以选择最具成本效益的表情符号方式。
  5. 我们是否应该添加代表他人使用表情符号的能力?
    虽然在起草该提案时我们并不打算将其作为提案的一部分添加,但我们意识到它对它来说将是一个有用的功能。 这样,用户可以代表他人使用表情符号,例如,如果他们自己无法做到,或者如果表情符号是通过链下活动获得的。
  6. 我们如何确保代表他人使用表情符号是合法的?
    我们可以向提案添加委托;当用户将他们使用表情符号的权利委托给其他人时,委托可以代表他们使用表情符号。 但是,这会给提案增加很多复杂性和额外的逻辑。
    使用 ECDSA 签名,我们可以确保用户已同意代表他们使用表情符号。 这样,用户可以使用表情符号的参数签署消息,并且签名可以由其他人提交。
  7. 在对代币做出反应时,我们是否应该添加链 ID 作为参数?
    在讨论该提案的过程中,有人提出建议,我们可以在对代币做出反应时添加链 ID 作为参数。 这将允许用户在另一个链上对一个链的代币使用表情符号。
    我们反对这样做,因为我们认为额外的参数很少被使用,并且会增加反应交易的额外成本。 如果集合智能合约想要利用链上表情符号对它们包含的代币,它们需要将反应记录在同一链上。 集成此提案的市场和钱包也将依赖于与同一链中的反应,因为如果支持链 ID 参数,这意味着他们需要在部署存储库的所有链上查询存储库智能合约,以便获取给定代币的反应。
    此外,如果集合创建者希望用户在不同的链上记录他们的反应,他们仍然可以指示用户这样做。 存储库不验证正在做出反应的代币的存在,这在理论上意味着您可以对不存在的代币或尚未存在的代币做出反应。 在另一个链上的同一地址存在不同集合的可能性非常低,因此用户可以使用另一个链上集合的地址做出反应,并且他们不太可能无意中对另一个集合的代币做出反应。
  8. 我们应该使用 bytes4 还是 strings 来表示表情符号?
    最初,该提案使用了 bytes4。 这是因为假设所有表情符号都使用 UTF-4 编码,但事实并非如此。
    表情符号通常最多使用 8 个字节,但带有肤色的个性化表情符号使用更多。 这就是我们决定使用 strings 来表示表情符号的原因。 这使得存储库与将来可能添加到 Unicode 标准中的任何表情符号向前兼容。
    这就是此提案如何解决 ERC-6381 的向前兼容性问题。

向后兼容性

表情符号存储库标准与 ERC-721 完全兼容,并且与可用于 ERC-721 实现的强大工具以及现有的 ERC-721 基础设施兼容。

测试用例

测试包含在 emotableRepository.ts 中。

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

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

参考实现

请参阅 EmotableRepository.sol

安全注意事项

该提案没有设想处理用户的任何形式的资产,因此与表情符号存储库交互时,资产不应处于风险之中。

使用 ECDSA 签名代表他人使用表情符号的能力引入了重放攻击的风险,而要签名的消息格式可以防止这种情况。 消息中使用的 DOMAIN_SEPARATOR 对于部署它的链的存储库智能合约是唯一的。 这意味着签名在任何其他链上都是无效的,如果尝试进行重放攻击,部署在这些链上的表情符号存储库应恢复操作。

另一个需要考虑的问题是预签名消息重用的能力。 由于该消息包括签名有效性截止日期,因此该消息可以在截止日期到达之前的任何时间重复使用。 该提案仅允许使用给定的表情符号对特定代币进行单次反应,因此无法滥用预签名消息来增加代币上的反应计数。 但是,如果使用存储库的服务依赖于在某些操作后撤销反应的能力,则可以使用有效的预签名消息重新对代币做出反应。 我们建议将存储库与预签名消息结合使用的服务使用在合理短的时间后使预签名消息无效的截止日期。

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

版权

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

Citation

Please cite this document as:

Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer), "ERC-7409: 公共非同质化代币表情符号库," Ethereum Improvement Proposals, no. 7409, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7409.