Alert Source Discuss
Standards Track: ERC

ERC-7208: 链上数据容器

通过将逻辑与存储分离来实现互操作性

Authors Rachid Ajaja (@abrajaja), Matthijs de Vries (@sudomati), Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba), Daniel Berbesi (@berbex), Apostolos Mavropoulos (@ApostolosMavro), Barbara Marcano (@Barbara-Marcano), Daniel Ortega (@xdaniortega)
Created 2023-06-09
Requires EIP-165

摘要

本 ERC 定义了一系列接口,用于抽象链上数据的存储,通过在独立的智能合约上实现管理这些数据的逻辑功能。“链上数据容器” (ODC) 指的是数据存储与数据管理的分离和索引。我们建议链上数据可以被抽象并存储在名为“数据对象” (DO) 的智能合约中,这些合约响应名为“数据点” (DP) 的外部数据索引机制。这些数据可以被实现(一个或多个)独立智能合约访问和修改,这些智能合约被标识为“数据管理器” (DM)。我们引入了两种访问管理机制:首先,通过“数据索引” (DI) 的实现,“数据管理器” (DM) 可以被限制访问“数据对象” (DO);其次,“数据点注册表” (DPR) 的实现管理“数据点” (DP) 的发行。最后,我们引入了“数据索引” (DI) 实现之间的数据可移植性(水平数据移动性)的概念,从而能够对逻辑进行大规模更新,而不会影响底层数据存储。

动机

随着以太坊生态系统的发展,对链上功能的需求也在增长。市场鼓励通过更复杂的系统来更广泛地采用,并且不断需要提高效率。我们已经看到,有时新的标准代币提案的爆炸式增长完全是由市场炒作驱动的。虽然最终每个标准都有其用途,但它们中的大多数需要更大的灵活性来管理与其他标准的互操作性。需要一种标准适配器机制,通过驱动在不同 ERC 下发行的资产之间的交互来增强互操作性。

如果没有这种机制,大多数项目都为互操作性实施了定制解决方案。这是一种低效的方法,会导致生态系统的碎片化。我们认识到没有“一刀切”的解决方案来解决标准化和互操作性挑战。大多数资产 - 同质化、非同质化、数字孪生、真实世界资产、DePin 等 - 都有多种机制可以通过使用不同的标准接口将它们表示为链上代币,并且标准的多样性刺激了创新。

然而,为了使这些资产能够被交换、交易或以其他方式交互,协议必须实现与相关接口的兼容性,以访问和修改链上数据。当考虑到前面提到的互操作性定制解决方案时,这尤其具有挑战性。此外,智能合约的不可变性使得已经部署的协议难以适应新的代币化标准,这对于未来的实施至关重要。必须共同努力,以实现根据不同标准代币化的资产之间的交互。当前的 ERC 提供了开发这种链上适配器的工具。

我们的目标是从逻辑实现中抽象出链上数据处理,独立于 ERC 接口公开底层数据。我们提出了一系列接口,用于在名为“数据对象” (DO) 的合约中存储和访问链上数据,将底层资产分组为通用的“数据点” (DP),这些数据点可以与多个可互操作甚至并发的“数据管理器” (DM) 合约相关联。该提案旨在通过与先前和未来的代币标准共存来工作,从而提供一种灵活、高效且连贯的机制来管理资产互操作性。

  • 数据抽象:我们提出了一个标准化接口,使开发人员能够将数据存储代码与底层代币实用程序逻辑分离,从而减少了支持和实现多个继承的(通常是冲突的)接口以实现资产兼容性的需求。数据(以及因此的资产)可以独立于管理这些数据的逻辑进行存储。

  • 标准中立性:一种中立的方法必须使任何代币化资产的底层数据能够在不同的代币标准之间无缝过渡。这将显着提高其他标准之间的互操作性,从而减少格局的碎片化。我们的提案旨在将表示底层资产的数据存储与用于表示代币的标准接口分离。

  • 一致的接口:无论底层代币的标准或暴露此类数据的接口如何,原始函数的统一接口都会从用例中抽象出数据存储。数据和元数据可以存储在链上,并通过相同的原语公开。

  • 数据可移植性:我们提供了一种在此标准的实现之间水平移动数据的机制,从而激励了可互操作的解决方案和标准适配器的实现。

规范

术语

数据点:对存储在一个或多个数据对象中并由一个或多个数据管理器管理的一个链上数据结构的唯一可识别的引用。数据点数据点注册表发行。

数据对象:一个实现通过数据点索引的信息的低级存储管理的智能合约。

数据管理器:一个或多个智能合约,用于实现管理数据对象的高级逻辑和最终用户界面。

数据点注册表:一个或多个智能合约,用于管理数据点的发行。此外,数据点注册表定义了一个兼容或可互操作的数据点空间。

数据索引:一个或多个智能合约,用于管理数据管理器数据对象的访问。

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

数据点结构

  • 数据点 MUST 是 bytes32 存储单元。
  • 数据点 SHOULD NOT 用于存储资产数据。
  • 数据点 SHOULD 用于索引数据。
  • 数据点 SHOULD 使用 4 字节前缀来存储与其他数据点兼容的相关信息。
  • 数据点 MUST 使用 4 字节存储 Chain ID
  • 数据点 SHOULD 使用最后 20 字节来标识哪个注册表分配了它们。
  • 推荐的数据点内部结构如下:
/**
 * 推荐的参考实现中的内部数据点结构:
 * 0xPPPPVVRRIIIIIIIIHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 * - 前缀 (bytes4)
 * -- PPPP - 类型前缀(即 0x4450 - 字母 "DP" 的 ASCII 表示)
 * -- VV - DataPoint 规范的版本(即参考实现的 0x00)
 * -- RR - 保留
 * - 注册表本地标识符
 * -- IIIIIIII - DataPoint 的 32 位特定于实现的 ID
 * - Chain ID (bytes4)
 * -- HHHHHHHH - 32 位链标识符
 * - 注册表地址 (bytes20)
 * -- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - 分配 DataPoint 的注册表的地址
**/

数据点是一种抽象和索引信息的低级结构。数据点充当指向数据对象中的数据结构中存储的信息的指针。 数据点数据点注册表分配。数据点 SHOULD 在其内部结构中存储哪个数据点注册表初始化它。每个数据点 SHOULD 具有由数据点注册表在实例化时提供的唯一标识符。

数据对象接口

  • 数据对象 SHOULD 用于存储资产数据。
  • 数据对象 SHOULD 使用数据点来索引存储数据的结构。
  • 数据对象 SHOULD 实现与处理数据结构直接相关的逻辑。
  • 数据对象 SHOULD 实现将其数据点的管理权转移到不同的数据索引实现的逻辑。
  • 数据对象 MUST 使用 IDataObject 接口:
interface IDataObject {
    /**
     * @notice 读取存储的数据
     * @param dp DataPoint 的标识符
     * @param operation 要在数据上运行的读取操作
     * @param data 特定于操作的数据
     * @return 特定于操作的数据
     */
    function read(bytes32 dp, bytes4 operation, bytes calldata data) external view returns(bytes memory);

    /**
     * @notice 存储数据
     * @param dp DataPoint 的标识符
     * @param operation 要在数据上执行的写入操作
     * @param data 特定于操作的数据
     * @return 特定于操作的数据(可以为空)
     */
    function write(bytes32 dp, bytes4 operation, bytes calldata data) external returns(bytes memory);

    /**
     * @notice 设置 DataIndex 实现
     * @param dp DataPoint 的标识符
     * @param newImpl 新 DataIndex 实现的地址
     */
    function setDIImplementation(bytes32 dp, address newImpl) external;
}

数据对象受托存储和管理数据。数据对象 SHOULD 实现用于管理链上数据存储的逻辑。数据对象内部数据结构 SHOULD 使用数据点来索引信息。

数据对象 CAN 接收来自数据管理器read()write() 或任何其他自定义请求,该请求访问由数据点索引的存储结构。

因此,数据对象响应于由单个数据索引给出的门控机制。setDIImplementation() 函数 SHOULD 启用将管理功能委托给 IDataIndex 实现。

数据管理器合约

  • 数据管理器 SHOULD 使用 IDataIndex.read()IDataObject.read()数据对象读取数据
  • 数据管理器 MUST 使用 IDataIndex.write() 将数据写入数据对象
  • 数据管理器 MAY 与其他数据管理器共享数据点
  • 数据管理器 MAY 使用多个数据点
  • 数据管理器 SHOULD 实现用于从数据点注册表请求数据点的逻辑。

数据管理器是独立的智能合约,用于实现业务逻辑或“高级”数据管理。它们可以从数据对象地址 read(),并通过数据索引实现 write(),该实现管理数据点的委托存储。

数据点注册表接口

  • 数据点注册表 SHOULD 定义函数来管理数据点的创建、转移和访问控制
  • 数据点注册表 SHOULD 管理数据管理器数据点访问管理
  • 数据点注册表 MUST 使用 IDataPointRegistry 接口:
interface IDataPointRegistry {

  /**
     * @notice 验证地址是否具有 DataPoint 的 Admin 角色
     * @param dp DataPoint
     * @param account 要验证的帐户
     */
    function isAdmin(bytes32 dp, address account) external view returns (bool);

    /**
     * @notice 将 DataPoint 分配给所有者
     * @param owner 新 DataPoint 的所有者
     * @dev 所有者应在分配期间被授予 Admin 角色
     */
    function allocate(address owner) external payable returns (DataPoint);

    /**
     * @notice 将 DataPoint 转移给所有者
     * @param dp 要转移的数据点
     * @param owner 新 DataPoint 的所有者
     */
    function transferOwnership(bytes32 dp, address newOwner) external;

    /**
     * @notice 授予在 Data Index 实现中授予/撤销 DataPoint 上的其他角色的权限
     * 如果在应用程序的生命周期中部署了 DataManager ,这将非常有用。
     * @param dp DataPoint
     * @param account 新管理员
     * @return 如果授予了角色(否则帐户已经拥有该角色)
     */
    function grantAdminRole(bytes32 dp, address account) external returns (bool);

    /**
     * @notice 撤销在 Data Index 实现中授予/撤销 DataPoint 上的其他角色的权限
     * @param dp DataPoint
     * @param account 旧管理员
     * @dev 如果所有者从自己撤销 Admin 角色,他可以再次添加它
     * @return 如果撤销了角色(否则帐户没有该角色)
     */
    function revokeAdminRole(bytes32 dp, address account) external returns (bool);
}

数据点注册表是受托进行数据点访问控制的智能合约。数据管理器可以向数据点注册表请求分配数据点

数据索引接口

  • DataIndex SHOULD 管理数据管理器数据对象的访问。
  • DataIndex SHOULD 管理每个用户的内部 ID。
  • DataIndex MUST 使用 IDataIndex 接口:
interface IDataIndex {
    /**
     * @notice 验证是否允许 DataManager 在特定的 DataObject 上写入特定的 DataPoint
     * @param dp DataPoint 的标识符
     * @param dm DataManager 的地址
     * @return 如果允许写入访问
     */
    function isApprovedDataManager(bytes32 dp, address dm) external view returns(bool);

    /**
     * @notice 定义是否允许 DataManager 写入特定的 DataPoint
     * @param dp DataPoint 的标识符
     * @param dm DataManager 的地址
     * @param approved 是否应批准 DataManager 用于 DataPoint
     * @dev 该函数应仅限于 DataPoint 维护者
     */
    function allowDataManager(bytes32 dp, address dm, bool approved) external;

    /**
     * @notice 读取存储的数据
     * @param dobj 数据对象的标识符
     * @param dp 数据点的标识符
     * @param operation 要在数据上执行的读取操作
     * @param data 特定于操作的数据
     * @return 特定于操作的数据
     */
    function read(address dobj, bytes32 dp, bytes4 operation, bytes calldata data) external view returns(bytes memory);

    /**
     * @notice 存储数据
     * @param dobj 数据对象的标识符
     * @param dp 数据点的标识符
     * @param operation 要在数据上执行的读取操作
     * @param data 特定于操作的数据
     * @return 特定于操作的数据(可以为空)
     * @dev 该函数应仅限于允许的 DM
     */
    function write(address dobj, bytes32 dp, bytes4 operation, bytes calldata data) external returns(bytes memory);
}

数据索引是受托进行访问控制的智能合约。它是数据管理器访问数据对象的门控机制。如果数据管理器打算访问数据点(通过 read()write() 或任何其他方法),则 SHOULD 使用数据索引来验证对数据的访问。

ID 管理机制决定了实现之间的兼容空间。

理由

数据点编码为 bytes32 数据指针的决定主要是由灵活性和面向未来考虑的。使用 bytes32 允许进行广泛的数据编码。这为开发人员提供了许多选项来适应不同的用例。此外,随着以太坊及其标准的不断发展,编码为 bytes32 可确保使用当前 ERC 构建的标准适配器可以引用未来的数据类型或结构,而无需对适配器本身进行重大更改。数据点编码应具有前缀,以便数据对象可以在访问数据存储时有效地识别兼容性问题。此外,前缀应用于查找数据点注册表并验证对数据点的管理员访问权限。还需要使用后缀来标识数据点注册表,以便数据对象可以快速丢弃旨在从不匹配的数据点注册表中使用数据点的格式错误事务。

数据管理器实现决定了它们将使用哪些数据点。它们的分配通过数据点注册表进行管理,并且对数据点write() 访问通过数据索引实现进行管理。

数据对象是独立的智能合约,它们实现相同的 read/write 接口,用于与数据管理器通信。此决定主要是由系统的可伸缩性驱动的。为此分层结构提供一个简单的接口使不同的应用程序能够拥有其用于存储数据的地址,而与资产的接口无关。每个实现都可以管理对其数据点存储空间的访问。这使得可以使用多个 ERC 以及其他智能合约来实现各种复杂的、动态的和交互式的用例,包括嵌入用于资产管理的跨链和 rollup 逻辑。

数据对象在存储可变的链上数据方面提供了灵活性,这些数据可以根据每个特定用例的要求进行修改。这使数据管理器可以在委托的存储中保持可变状态并反映随时间的变化,从而为通过大多数其他标准化接口的存储的静态性质提供动态层。

数据管理器可以决定将数据对象的完整存储管理从一个数据索引实现迁移到另一个数据索引实现。由于数据索引实现了用户 ID 管理,因此该机制使数据管理器能够升级其内部访问控制机制,而不会影响价值的底层存储,因为它位于其他位置(在数据对象中)。我们将此机制称为“水平数据移动性/可移植性”。

向后兼容性

此 ERC 旨在增强现有代币标准的功能,而不会引入中断性更改。因此,它不存在任何向后兼容性问题。根据其他 ERC 已经部署的代币可以包装并索引为数据点,并由数据对象管理,然后通过数据管理器的任何实现公开。所有互操作性集成都需要进行兼容性分析,具体取决于用例。但是,此 ERC 中定义的接口定义了一个框架,用于通过存储抽象将一个标准调整为另一个标准。

参考实现

我们提供了一个教育示例,展示了一个标准适配器,其中包含两种类型的代币(同质化和半同质化),它们共享数据存储。最终用户可以通过两种类型的接口之一与此实现进行交互:半同质化接口(由单个数据管理器表示)和同质化接口(由多个数据管理器表示)。通过使用单个同质化分数 数据对象来实现存储与逻辑的分离。工厂用于部署与半同质化集合共享存储的同质化代币接口。请注意,如果任一接口(同质化或半同质化)调用 transfer(),则两个接口都会发出事件。

此示例未经审核,不应在生产环境中使用。

参见 contracts

安全注意事项

访问控制分为三个层:

  • 第 1 层数据点注册表数据管理器分配并管理数据点的所有权(管理/写入权限)。
  • 第 2 层数据索引智能合约通过管理数据管理器数据点的批准来实现访问控制。它使用数据点注册表来验证谁可以授予/撤销此访问。
  • 第 3 层数据对象管理 数据点数据索引 实现之间的信任关系,并允许受信任的 数据索引 执行 write 操作。

数据对象的常见任务是存储与用户相关的数据,而数据管理器实现用于管理此类数据的逻辑。数据对象数据管理器通常都需要管理用户 ID。数据索引可以提供独立于任何特定实现的用户管理逻辑(即基于 address 的 ID),但在选择这些标识符时必须小心。如果选择不当,它们可能会阻碍数据管理器在不同的数据索引之间迁移的能力。

没有其他安全注意事项专门从此 ERC 派生。

版权

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

Citation

Please cite this document as:

Rachid Ajaja (@abrajaja), Matthijs de Vries (@sudomati), Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba), Daniel Berbesi (@berbex), Apostolos Mavropoulos (@ApostolosMavro), Barbara Marcano (@Barbara-Marcano), Daniel Ortega (@xdaniortega), "ERC-7208: 链上数据容器," Ethereum Improvement Proposals, no. 7208, June 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7208.