ERC-7208 Onchain data container (On-Chain Data Containers)
Final · Other · created 2023-06-09
A standard that separates the storage of asset data from the logic that handles that data into separate contracts. The same data can be exposed through the interfaces of multiple token standards, which makes compatibility between standards easier.
Why it is needed
Ethereum has many token standards, and even for the same asset, each standard stores and handles data differently. To exchange or interact between assets issued under different standards, each protocol has to support the relevant interfaces one by one, and until now each project has built its own ad hoc solution, leaving the ecosystem fragmented. In addition, a smart contract cannot be changed once deployed, so it is hard for protocols that are already deployed to adapt to newly introduced token standards. If data storage is separated from logic, the logic can be changed while the stored data stays as is, or the data can be exposed through several standards at once, which gives a common framework for building adapters that bridge standards.
What to implement
function read(bytes32 dp, bytes4 operation, bytes calldata data) external view returns(bytes memory);: Reads the data that a data point refers to in a data object, using the specified read operation.function write(bytes32 dp, bytes4 operation, bytes calldata data) external returns(bytes memory);: Stores data in a data object using the specified write operation.function setDIImplementation(bytes32 dp, address newImpl) external;: Delegates management authority over the data point to a new data index implementation.function isAdmin(bytes32 dp, address account) external view returns (bool);: Checks whether an address holds the Admin role for the data point.function allocate(address owner) external payable returns (DataPoint);: Issues a new data point and assigns it to the specified owner. The owner must be granted the Admin role upon assignment.function transferOwnership(bytes32 dp, address newOwner) external;: Transfers ownership of the data point to a new owner.function grantAdminRole(bytes32 dp, address account) external returns (bool);: Grants admin authority, which can give or take away other roles for the data point. Returns true if the role was newly granted.function revokeAdminRole(bytes32 dp, address account) external returns (bool);: Revokes admin authority. If an owner revoked their own Admin role, it can be added back.function isApprovedDataManager(bytes32 dp, address dm) external view returns(bool);: Checks whether a specific data manager is approved to write to the data point.function allowDataManager(bytes32 dp, address dm, bool approved) external;: Approves or revokes a data manager's write permission for the data point. It must be callable only by the party that manages the data point.function read(address dobj, bytes32 dp, bytes4 operation, bytes calldata data) external view returns(bytes memory);: Reads data from the specified data object through the data index.function write(address dobj, bytes32 dp, bytes4 operation, bytes calldata data) external returns(bytes memory);: Writes data to the specified data object through the data index. It must be callable only by an approved data manager.
In depth
Five components
This standard defines five components with different roles. A data point (DP) is a unique identifier that points to an onchain data structure. A data object (DO) is a contract responsible for the low-level storage of information indexed by a data point. A data manager (DM) is one or more contracts that implement high-level logic and the end-user interface. A data point registry (DPR) manages the issuance of data points and defines the space of mutually compatible data points. A data index (DI) is the gateway that manages a data manager's access to data objects. Storage (DO) and logic (DM) are separated, and issuance (DPR) and access control (DI) connect the two.
Internal structure of a data point
A data point must be a bytes32 storage unit, and it must be used to index data, not to hold the asset data itself. The recommended structure is a 4-byte prefix at the front (2 bytes of type prefix, 1 byte of specification version, 1 reserved byte), a 32-bit identifier for internal use within the registry, a 4-byte chain ID (required), and finally 20 bytes holding the address of the registry that issued it. The prefix is used by data objects to quickly identify compatibility problems, and the registry address suffix is used to quickly filter out erroneous transactions that try to use a data point from a different registry. The bytes32 encoding was chosen to accommodate a variety of encodings and to let future data types be referenced without major changes.
The IDataObject interface
A data object must be used to store asset data and must use data points to index its internal structure. It must implement the IDataObject interface, and read(dp, operation, data) and write(dp, operation, data) are generic primitive functions that take an operation identifier (bytes4) and operation-specific data (bytes) and return the result as bytes. setDIImplementation(dp, newImpl) is the function that delegates the management functions of that data point to a new IDataIndex implementation, and it pairs with the requirement that a data object implement the logic for moving management to a different data index implementation. A data object responds to requests according to the gateway mechanism provided by a single data index.
Rules for data managers
A data manager is an independent contract that implements business logic. Reads may use IDataIndex.read() or IDataObject.read() (SHOULD), but writes must go through IDataIndex.write() (MUST). A single data manager may write to multiple data points, and a single data point may be shared by multiple data managers (MAY). It should also implement the logic that asks the data point registry to allocate data points (SHOULD). Multiple data managers sharing the same storage, each exposing a different standard interface, is the adapter structure this standard envisions.
Division of roles between the registry and the index
The data point registry manages the creation, transfer and access control of data points. It issues them with allocate(), moves ownership with transferOwnership(), and manages admin roles with grantAdminRole() and revokeAdminRole(). This is useful when a new data manager is deployed while the application is running. The data index is the gateway through which a data manager accesses data objects. It checks write approval with isApprovedDataManager(), manages approval with allowDataManager(), and read() and write() take the target data object address along with the request and forward it. The data index must also manage per-user internal IDs, and how these IDs are managed determines the space that is compatible across implementations.
Horizontal data portability
The data manager can move the entire storage management of a data object from one data index implementation to another. Because the data index handles user ID management, only the internal access control mechanism can be upgraded while the place where the value is stored (the data object) stays untouched. The original text calls this Horizontal Data Mobility/Portability. It also presents that a data object can hold mutable onchain data, so unlike the static storage of other standard interfaces, state that changes over time can be kept in a delegated storage.
Backward compatibility and reference implementation
This standard supplements the features of existing token standards and does not introduce breaking changes. Tokens of other ERCs that are already deployed can also be wrapped, indexed as data points, managed as data objects, and then exposed through any data manager implementation. However, every integration needs a compatibility analysis for its use case. The reference implementation is an educational example: a fungible token interface and a non-fungible token interface share storage through one Fungible Fractions data object, and both interfaces emit events no matter which side transfer() is called from. This example has not been audited, so it must not be used in production. The status of this proposal is finalized as Final.
Things to watch out for
- Three-Layer Access Control: Access control is divided into three layers. In layer 1, the data point registry allocates data points and manages ownership (administrator and write permissions). In layer 2, the data index manages the data manager's approval for data points, and checks with the registry who can grant and revoke this access. In layer 3, the data object manages the trust relationship between the data point and the data index implementation, and lets only a trusted data index execute write operations.
- Choosing the User ID Carefully: A data index can provide user management logic that is independent of any specific implementation, such as address-based IDs, but choosing the wrong identifier can get in the way of migrating a data manager to a different data index, so it must be chosen carefully.