HomeEIPs
EIPsERC-7208
ERC-7208

On-Chain Data Container

Abstracting logic away from storage
DraftStandards Track: ERC
Created: 2023-06-09
Requires: EIP-165, EIP-721
Rachid Ajaja <rachid@allianceblock.io>, Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba)
DiscussionsOriginal linkEdit
1 min read
Anyone may contribute to propose contents.
Go propose
Video
Anyone may contribute to propose contents.
Go propose
Original

Abstract

On-chain Data Containers are smart contracts that inherit from ERC-721 to store on-chain data in structures called "Properties". Information stored in Properties can be accessed and modified by the implementation of smart contracts called "Property Managers". This ERC defines a series of interfaces for the separation of the storage from the interface implementing the functions that govern the data. We introduce the interface for "Restrictions", structures associated with Properties that apply limitations in the capabilities of Property Managers to access or modify the data stored within Properties.

Motivation

As the Ethereum ecosystem continues to grow, it is becoming increasingly important to enable more flexible and sophisticated on-chain data management solutions, as well as off-chain assets and their on-chain representation. We have seen too many times where the market hype has driven an explosion of new standard token proposals, most of them targeting a specific business use-case rather than increasing interoperability.

This ERC defines several interface for ODCs (On-Chain Data Containers) that together allow for the storage and retrieval of additional on-chain data, referred to as "Properties". This provides an interoperable solution for dynamic data management across various token standards, enabling them to hold mutable states and reflect changes over time.

ODCs aim to address the limitations of both previous and future ERCs by enabling on-chain data aggregation, providing an interface for standardized, interoperable, and composable data management solutions.

ODCs address some of the existing limitations with previous ERC token contracts by separating the logic from the storage.

This ERC defines a standard interface for interacting with the concept of "Restrictions" for data stored within an ODC ("Properties"). This enables greater flexibility, interoperability, and utility for multiple ERCs to work together.

This proposal is motivated by the need to extend the capabilities of on-chain stored data beyond the static nature of each ERC, enabling complex logic to be abstracted away from the stored variables. This is particularly relevant for use cases where the state of the NFT needs to change in response to certain events or conditions, as well as when the storage and the logic must be separated. For instance, NFTs representing Account Abstraction contracts, Smart Wallets, or the digital representation of Real World Assets, all of which require dynamic and secure storage.

NFTs conforming to standards such as ERC-721 have often faced limitations when representing complex digital assets. The Ethereum ecosystem hosts a rich diversity of token standards, each designed to cater to specific use cases. While such diversity spurs innovation, it also results in a highly fragmented landscape, especially for Non-Fungible Tokens (NFTs). Different projects might implement their own ways of managing mutable states, incurring further fragmentation and interoperability issues. While each standard serves its purpose, they often lack the flexibility needed to manage additional on-chain data associated with the utility of these tokens.

Real-world assets have multiple ways in which they can be represented as on-chain tokens by utilizing different standard interfaces. However, for those assets to be exchanged, traded or interacted with, the marketplace is required to implement each of those standards in order to be able to access and modify the on-chain data.

Therefore, there is a need for standardization to manage these mutable states for tokenization in a manner that abstracts the on-chain data handling from the logical accounting. Such standard would provide all ERCs, regardless of their specific use case, with the mechanisms for interacting with each other in a consistent and predictable way.

This EIP proposes a series of interfaces for storing and accessing data on-chain, codifying information as generic Properties associated with Restrictions specific to use cases. This enhancement is designed to work by extending existing token standards, providing a flexible, efficient, and coherent way to manage the data associated with:

  • Standard Neutrality: The standard aims to separate the data logic from the token standard. This neutral approach would allow ERCs to transition seamlessly between different token standards, promoting interactions with platforms or marketplaces designed for those standards. This could significantly improve interoperability among different standards, reducing fragmentation in the landscape.

  • Consistent Interface: A uniform interface abstracts the data storage from the use case, irrespective of the underlying token standard. This consistent interface simplifies interoperability, enabling platforms and marketplaces to interact with a uniform data interface, regardless of individual token standards. This common ground for all tokenization could reduce fragmentation in the ecosystem.

  • Simplified Upgrades: A standard interface for representing the utility of the on-chain data would simplify the process of integrating new token standards. This could help to reduce fragmentation caused by outdated standards, facilitating easier transition to new, more efficient, or feature-rich implementations.

  • Data Abstraction: A standardized interface would allow developers to separate the data storage code from the underlying token utility logic, reducing the need for off-chain services to implement multiple interfaces and promoting greater unity in the ecosystem.

  • Actionable data: Current practices often store token metadata off-chain, rendering it inaccessible for smart contracts without the use of oracles. Moreover, metadata is often used to store information that could otherwise be considered data relevant to the token's inherent identity. This ERC seeks to rectify this issue by introducing a standardized interface for reading and storing additional on-chain data related to ODC.

A case-by-case limited analysis is provided in the compatibility appendix.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Terms

ODC: A uniquely identifiable non-fungible token. An ODC MAY store information within Properties.

Property: A modifiable information unit stored within an ODC. Properties SHOULD be capable of undergoing modifications and MUST be able to act as an indexed data container.

Restriction: A configuration stored within the ODC, that SHOULD describe under which conditions a certain Property Manager is allowed to modify the information stored within a certain Property.

Property Manager: A type of Smart Contract that MUST implement a PM interface in order to manage data stored within the ODC.

Category: Property Managers MUST be grouped in Categories that SHOULD represent access to Properties. Each Property Manager MAY be part of one or more Categories. The assignment of categories SHOULD be managed by Governance.

ODC Interface

An ODC MUST extend the functionality of ERC-721 through the incorporation of Properties in its internal storage. The Properties MAY have Restrictions.

Properties

Properties are modifiable information units stored within an ODC. Properties SHOULD be capable of undergoing modifications and MUST be able to act as an indexed data container.

/** * @notice Gets a Property Data point of the ODC. * @dev This function allows anyone to get a property of the ODC. * @param ouid The unique ID of the ODC. * @param propertyKey The key of the property to be retrieved. * @param dataKey The key of the data inside of Property. * @return The value of the data point within the Property. */ function getPropertyData( uint256 ouid, bytes32 propertyKey, bytes32 dataKey ) external view returns (bytes32);
/** * @notice Sets a Property Data point within the ODC. * @dev This function allows the owner or an authorized operator to set a property of the ODC. * @param ouid The unique ID of the ODC. * @param propertyKey The key of the property to be set. * @param dataKey The Key of the property to be set. * @param dataValue The value of the data point to be set within the Property. */ function setPropertyData( uint256 ouid, bytes32 propertyKey, bytes32 dataKey, bytes32 dataValue ) external;
  • getProperty: This function MUST retrieve a specific dataValue of an ODC, identifiable through the tokenId, propertyKey, and dataKey parameters.

  • setProperty: This function MUST set or update a specific Property data point within an ODC. This operation is REQUIRED to be executed solely by the owner of the ODC or an approved Smart Contract. This function MUST set the dataValue within the dataKey of propertyKey for the tokenId.

/** * @title Onchain Data Container (ODC) Interface for Properties Data * Provides functions to manipulate data stored in ODC token */ interface IODCProperties { /** * @notice Returns data stored in a property * @param odcid ODC token id * @param prop Property key * @param dataType Defines which data is returned (implementation-specific) * @param hint Data-type dependent, can be zero-length, can be used to specify part of the data to get (for example, if `dataType` specifies a Map, `hint` can be a key in this Map) * @return Data previously stored. SHOULD be empty array if nothing was stored. */ function propertyDataOf( uint256 odcid, bytes32 prop, bytes32 dataType, bytes calldata hint ) external view returns (bytes memory); /** * @notice Returns data stored in a property * @dev Access to this function SHOULD be allowed only to PropertyManagers approved for this property, see IODCPermissions * @param odcid ODC token id * @param prop Property key * @param dataType Defines which data is returned (implementation-specific) * @param data Data to store (how the data is stored is implementation-specifiec and can be different for different dataType) */ function setPropertyData( uint256 odcid, bytes32 prop, bytes32 dataType, bytes calldata data ) external; }
/** * @title Onchain Data Container (ODC) Interface for Restrictions * Provides functions to restrict certain actions with ODC token */ interface IODCRestrictions { /** * @notice Retrievs list of restrictions associated with specified ODC token * @param odcid ODC token id * @return rids List of restriction ids */ function restrictionsOf(uint256 odcid) external view returns(uint256[] memory rids); /** * @notice Retrieves detalis of restriction * @param rid Id of restriction to read * @return odcid ODC token id * @return prop property the restriction is associated with * @return restrictionType Type of restriction * @return data Data of restriction (may be different from the `initData` used to add restriction) */ function restriction(uint256 rid) external view returns(uint256 odcid, bytes32 prop, bytes32 restrictionType, bytes memory data); /** * @notice Adds a restriction associated with `odcid` and a property * @dev Access to this function SHOULD be allowed only to PropertyManagers approved for this property, see IODCPermissions * @param odcid ODC token id * @param prop Property key * @param restrictionType Defines type of Restriction. The implementation should manage list of supported Restriction Types * @param initData Data to initialize the Restriction * @return rid ID of added restriction (unique for all `odcid` in this contract, will never change untill removed and can't be reused) */ function addRestriction( uint256 odcid, bytes32 prop, bytes32 restrictionType, bytes calldata initData ) external returns(uint256 rid); /** * @notice Removes restriction from the ODC * @param odcid Id of Data Container * @param prop Property key * @param rid ID of restriction to remove * @return If a restriction was deleted - returns true, if it does not exist - returns false, if it exists but can't be deleted - throws an exception */ function removeRestriction( uint256 odcid, bytes32 prop, uint256 rid ) external returns(bool); }
interface IODCMetadata { function metadataOf(uint256 odcid) external view returns(string calldata); } interface IODCTypes { /** * @notice Enum of operations which PropertyManager can initiate on ODC */ enum PMOperation { MODIFY_PROPERTY_DATA, ADD_RESTRICTION, REMOVE_RESTRICTION } /** * */ enum Mutatation { SPLIT, MERGE, ATTACH, DETACH } }
/** * @title Onchain Data Container (ODC) Interface for the registry of properties and categories */ interface IODCRegistry is IODCTypes { /** * @notice Returns address responsible for category management: add/remove properties, approve PropertyManagers etc */ function maintenerOf(bytes32 category) external view returns(address); /** * @notice Returns category of specified property */ function categoryOf(bytes32 property) external view returns(bytes32 category); /** * @notice Returns all properties of specified category */ function propertiesOf(bytes32 categoy) external view returns(bytes32[] memory properties); /** * @notice Returns metadata provider for specific property */ function metadataProviderOf(bytes32 property) external view returns(IMetadataProvider); /** * @notice Returns if mutation is allowed for the category */ function isAllowed(Mutatation mutation, bytes32 category) external view returns(bool); }
/** * @title Onchain Data Container (ODC) Interface for mutations between several ODCs */ interface IODCMutations is IODCTypes { function merge( uint256 from, uint256 to, bytes32[] calldata categories ) external; function split(uint256 from, bytes32[] calldata categories) external returns (uint256); } interface IODCPermissions is IODCTypes { /** * @notice Verifies if PropertyManager is allowed to execute an operation on the ODC * @param pm Address of PropertyManager * @param op Operation to execute * @param odcid Id of Data Container * @param prop Property key * @return if PropertyManager is allowed to execute the operation */ function isPropertyManagerAllowed(address pm, PMOperation op, uint256 odcid, bytes32 prop) external returns(bool); }
interface IMetadataProvider { function metadataOf() view; }
interface IODCErrors is IODCTypes { /** * @param odcid Id of Data Container * @param rid Id of restriction wich triggered the error * @param restrictionReason Restriction-specific explanaition of the reason to revert the call */ error Restricted(uint256 odcid, uint rid, string restrictionReason); error OperationNotAllowedForPropertyManager(address pm, PMOperation op, uint256 odcid, bytes32 prop); error MutationNotAllowed(Mutatation mutation, bytes32 category); }
/** * @notice Queries whether a given ODC token has a specific property * @param ouid ODC unique ID * @param prop property ID (property to inquire) * @return bool true if the token has the property, false if not */ function hasProperty(uint256 ouid, bytes32 prop) external view returns (bool)
/** * @notice Adds a given property to an existing ODC token * @param ouid ODC unique ID * @param prop property ID (property to add) * @param restrictions An array of restrictions to be associated with the property * @return An array with the respective restriction indexes */ function addProperty( uint256 ouid, bytes32 prop, IMetaRestrictions.Restriction[] calldata restrictions ) external returns (uint256[] memory)
/** * @notice Removes an existing property from an existing ODC * @param ouid ODC unique ID * @param prop property ID (property to remove) */ function removeProperty(uint256 ouid, bytes32 prop) external
/** * @notice Retrieves all the properties of a given category * @param category category ID to consult * @return An array with all the respective property IDs */ function propertiesOfCategory(bytes32 category) external view returns (bytes32[] memory)
/** * @notice Retrieves all enabled properties of a given ODC * @param ouid ODC unique ID * @return An array with all the enabled property IDs */ function propertiesOf(uint256 ouid) external view returns (bytes32[] memory)
/** * @notice Retrieves a given user's ODC unique ID that has a specific property attached * @param account user's account address * @param prop property ID * @return uint256 the ODC token ID, or 0 if such a user's token doesn't exist */ function getToken(address account, bytes32 prop) external view returns (uint256)
/** * @notice Retrieves all the ODC unique IDs owned by a given user that have a specific property attached * @param account user's account address * @param prop property ID to inquire * @return An array with all the ODC token IDs that have the property attached */ function tokensWithProperty(address account, bytes32 prop) external view returns (uint256[] memory)
/** * @notice Checks if a specific property exists in a given ODC token * @param ouid ODC unique ID * @param prop property ID * @return bool true if the property is attached to the given token, false if not */ function exists(uint256 ouid, bytes32 prop) external view returns (bool)
/** * @notice Merges the given categories' related properties from one ODC token to another * @param from origin ODC unique ID * @param to target ODC unique ID * @param categories An array with all the category IDs to merge */ function merge( uint256 from, uint256 to, bytes32[] calldata categories) external
/** * @notice Splits an ODC token from its specific categories and mints a new one with the related properties attached * @param ouid origin ODC unique ID * @param categories category IDs to split * @return newOuid the resulting (newly minted) ODC token ID */ function split(uint256 ouid, bytes32[] calldata categories) external returns (uint256 newOuid)
/** * @notice Adds a new restriction to a given ODC property * @param ouid ODC unique ID * @param prop property ID * @param restr the restriction to add * @return uint256 The restriction's id */ function addRestriction( uint256 ouid, bytes32 prop, Restriction calldata restr ) external returns (uint256) {
/** * @notice Removes a restriction identified by its index from a given ODC's property * @param ouid ODC unique ID * @param prop property ID * @param ridx restriction index */ function removeRestriction( uint256 ouid, bytes32 prop, uint256 ridx ) external
/** * @notice Retrieves all restrictions attached to a given ODC's property * @param ouid ODC unique ID * @param prop property ID * @return An array with all the requested restrictions (of type Restriction) */ function restrictionsOf(uint256 ouid, bytes32 prop) external view returns (Restriction[] memory)

Restrictions

Restrictions serve as a protective measure, ensuring that changes to Properties adhere to predefined rules, thereby maintaining the integrity and intended use of the information stored within the ODC. The Restrictions structure provides a layer of governance over the mutable Properties. Property Managers can check Restrictions applied to Properties before modifying the data stored within them. This further abstract the logic away from the storage, ensuring that mutability can be achieved and preserving the overall stability and reliability of the ODC.

/** * @dev ridxCounter Utilized to give continuous indices (ridxs) to restrictions * @dev restrictions Mapping of restrictions' ridxs to the respective restriction data * @dev byProperty Mapping of properties' unique identifiers to the respective set of ridxs (restrictions' indices) * @dev byType Mapping of restrictions' unique types to the respective set of ridxs (restrictions' indices) */ struct TokenRestrictions { uint256 ridxCounter; mapping(uint256 => IMetaRestrictions.Restriction) restrictions; mapping(bytes32 => EnumerableSet.UintSet) byProperty; mapping(bytes32 => EnumerableSet.UintSet) byType; }
/** * @dev Adds a restriction to a given ODC's property * @param l storage layout * @param ouid ODC unique ID * @param property identifier for the property * @param r the restriction to add * @return uint256 The index of the newly added restriction */ function addRestriction( Layout storage l, uint256 ouid, bytes32 property, IMetaRestrictions.Restriction memory r ) internal returns (uint256)
/** * @dev Removes a restriction identified by its index from a given ODC's property * @param l storage layout * @param ouid ODC unique ID * @param property identifier for the property * @param ridx restriction index */ function removeRestriction( Layout storage l, uint256 ouid, bytes32 property, uint256 ridx ) internal

PropertyManagers

Both Properties and Restrictions within the ODC SHALL be stored on-chain and made accessible though Property Managers. The interface defining this interaction is as follows:

Categories and Registry

Although the owner of ODCs can decide to implement an allow list for Property Managers that are enabled for interacting with the Properties and Restrictions stored within it, there are security considerations to be had regarding which Property Managers are allowed to interact with the ODCs internal storage.

The Registry is a smart contract for managing the internal governance by managing roles and permissions for Property Managers. This component is the single reference point for organizing Property Managers in Categories. As such, this system increases security by defining who has access to what, mitigating the possibility of unauthorized transactions or modifications.

The Registry keeps track of all the Categories as well as the Properties and Restrictions that the Property Managers have access to within those Categories.

Registry Management Functions

/** * @notice Retrieves the category info of a given property * @param property property ID * @return category The category ID of the property * @return splitAllowed true if splitting is allowed, false if not */ function getCategoryInfoForProperty(bytes32 property) external view returns (bytes32 category, bool splitAllowed);
/** * @notice Retrieves the info of a given category * @param category category ID * @return properties Array of property IDs included within the category * @return splitAllowed true if splitting is allowed, false if not */ function getCategoryInfo(bytes32 category) external view returns (bytes32[] memory properties, bool splitAllowed);
/** * @notice Consults if a given address is a manager for a given category * @param category category ID * @param manager the address to inquire * @return bool true if the address is manager for the category, false if not */ function isCategoryManager(bytes32 category, address manager) external view returns (bool);
/** * @notice Consults if a given address is a registered manager for a given property * @param property property ID * @param manager the address to inquire * @return bool true if the address is manager for the property, false if not */ function isPropertyManager(bytes32 property, address manager) external view returns (bool);
/** * @notice Consults if a given address has been granted ODC minter role * @param manager the address to inquire * @return bool true if the address has been granted minter role, false if not */ function isMinter(address manager) external view returns (bool);

Metadata structure

Non-fungible tokens (NFTs) have rapidly gained prominence in the Ethereum ecosystem, serving as a foundation for various digital assets, ranging from art pieces to real estate tokens, to Identity-based systems. These tokens require metadata: information describing the properties of the token, which provides context and enriches the token's functionality within its ecosystem. More often than not, developers manually generate NFT metadata for their respective projects, often leading to inconsistent structures and formats across different projects. This inconsistency hampers interoperability between NFT platforms and applications, slightly impeding the growth and development of the Ethereum NFT ecosystem. Moreover, many protocols and standards rely on Metadata to store actual information that is not actionable by Smart Contracts. This generates a segregated model where NFTs as data-containers are not a self-contained unit, but a digital entity that lives fragmented between on-chain and off-chain storage. The current EIP introduces a Metadata library that is designed to standardize the generation and handling of ODC metadata, promoting consistency, interoperability, and upgradeability. The Metadata library includes base properties for ERC-721 tokens and allows for the addition of extra Properties. These are flexible and extendable, covering string, date, integer, and decimal Properties. This broad range of property types caters to the diverse metadata needs across different use cases. The Metadata library includes functions to generate metadata, add extra Properties to the metadata, merge two sets of Properties, and encode the metadata in a format compatible with popular NFT platforms like OpenSea. The library promotes reusability and reduces the amount of boilerplate code developers need to write. It is backwards compatible so that previous metadata models can also be implemented by generating a constant metadata link that always points to the same URI, as regular NFTs.

ODC Metadata Functions

/** * @notice Generates metadata for a given property of a ODC token * @param prop property ID of ODC token * @param ouid ODC unique ID * @return The generated metadata */ function getMetadata(bytes32 prop, uint256 ouid) external view returns (Metadata.ExtraProperties memory);

Rationale

The decision to encode Properties as bytes32 data containers in the ODC Interface is primarily driven by flexibility and future-proofing. Encoding as bytes32 allows for a wide range of data types to be stored, including but not limited to strings, integers, addresses, and more complex data structures, providing the developer with flexibility to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the ODC standard can support future data types or structures without requiring significant changes to the standard itself.

Having a 2-layer data structure of propertyKey => dataKey => dataValue allows different applications to have their own address space. Implementations can manage access to this space using different propertyKey for different applications.

A case-by-case example on potential Properties encodings was performed and summarized is provided in the appendix.

The inclusion of Properties within an ODC provides the capability to associate a richer set of on-chain accessible information within the storage. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts.

Properties in an ODC offer flexibility in storing mutable on-chain data that can be modified as per the requirements of the token's use case. This allows the ODC to hold mutable states and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through a standardized interface.

By leveraging Properties within the ODC, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). In particular, ODCs can be leveraged to represent Account Abstraction contracts, abstracting the data-storage from the logic that consumes it, enabling for a single data-point to have multiple representations depending on the implementation.

Backwards Compatibility

This ERC is intended to augment the functionality of existing token standards without introducing breaking changes. As such, it does not present any backwards compatibility issues. Already deployed ERCs can be wrapped as Properties, with the application of on-chain data relevant to each use-case.

It offers an extension that allows for the storage and retrieval of Properties within an ODC while maintaining compatibility with existing ERCs related to tokenization.

Reference Implementation

The DataStorage Library (ODC storage example)

This library implementation allows the creation of On-chain Data Containers that can store various data types, handle versions of data, and efficiently manage stored data. The DataStorageLib provides a system for handling data that is both efficient and flexible. The struct DataStorageInternal includes mappings that enable the storage of different data types. These are:

  • keyValueData for bytes32 key-value pairs,
  • keyBytesData for storing bytes,
  • keySetData for sets of bytes32 values,
  • keyMapData for mappings of bytes32 => bytes32 values.

Dynamic Data Versions: The library handles versioning of data. The DataStorage struct contains a 'current' version and a mapping that links versions to specific indexes in the storage. This allows the smart contract to maintain a historical record of state changes, as well as revert to previous versions if necessary.

Clearing and Relocation: Several functions, such as clear(), wipe(), and moveData() are dedicated to clearing and relocating stored data. This functionality allows efficient management of stored data.

Addition and Removal of Data: The library includes functions to set and get values of different data types. Functions such as setValue() and getBytes32Value() facilitate this functionality. The addition or removal of data is reflected in the respective set (e.g., kvKeys, kbKeys, ksKeys, kmKeys) to ensure that the library correctly keeps track of all existing keys.

Efficient Data Retrieval: There are several getter functions to facilitate data retrieval from these storage structures. These include getting all keys (getAllKeys()), checking if a set contains a value (getSetContainsValue()), and getting all entries from a mapping (getMapAllEntries()).

Data Deletion: The library provides efficient ways to delete data, like deleteAllFromEnumerableSet() and deleteAllFromEnumerableMap().

Examples of Properties and Restrictions

On-chain Metadata: This could include the name, description, image URL, and other metadata associated with the ODC. For example, in the case of an art NFT, the setProperty function could be used to set the artist's name, the creation date, the medium, and other relevant information. Afterwards, the implementation could include a tokenUri() function that procedurally exposes the Property Data, directly rendered from within the ODC.

Locking Restrictions: They are utilized when an asset needs to be prevented from transfer() events or activity that would potentially change its internal storage. For example, when staking an asset or when locking for Fractionalization.

Ownership History: The setProperty function could be used to record the ownership history of the ODC. For example, each time the ODC is transferred, a new entry could be added to the ownership history property.

Royalties: The setProperty function could be used to set a royalties property for the ODC. This could specify a percentage of future sales that should be paid to the original creator.

Zero-Knowledge Proofs: The setProperty function could be used to store Identity information related to the ODCs owner and signed by KYC provider. This is combined with Transfer Restrictions to achieve identity-based Recovery of assets.

Oracle Subscription: An oracle system can stream data periodically into the Property (i.e. asset price, weather condition, metrics, etc)

Storage Abstraction: Multiple ERCs can make use of the same ODC for storing variables. For example, in a DeFi protocol, a Property with a stored value of 100 could signify either USDT or USDC, provided that the logic is connected to both USDT and USDC protocols.

Wrapping of Assets (Example Property Manager)

The Wrapper addresses challenges and requirements that have emerged in the Ethereum ecosystem, specifically regarding the handling and manipulation of assets from different standards. The Wrapper component provides Backwards Compatibility by:

Standardization: With the proliferation of various token standards on Ethereum, there is a need for a universal interface that can handle these token types uniformly. The Wrapper provides standardization, enabling a consistent approach to dealing with various token types, regardless of their implementation. By allowing different token types to be bundled together into a single entity (ODC), this approach increases the composability and interoperability between various protocols and platforms. A more efficient and flexible asset management is achieved, allowing users to bundle multiple assets into a single ODC and reducing the complexity of handling individual assets. Furthermore, the capability to 'unwrap' these bundled assets as needed, provides users with granular control over their digital assets. The transferring or interaction with multiple individual tokens often leads to a high accumulation of gas fees. However, by wrapping these assets into a single entity, users can perform operations in a more cost-effective manner.

/** * @notice This struct is used to receive all parameter for wrap function * @param tokens The token addresses of the assets. * @param amounts The amount of each asset to wrap (if applicable). * @param ids The id of each asset to wrap (if applicable). * @param types The type of each asset to wrap. * @param unlockTimestamps The unlocking timestamps of wrapped assets. * @param existingOuidToUse If different than 0, it represents the ODC to wrap assets on. If 0, new mNFT is minted. */ struct WrapData { address[] tokens; uint256[] amounts; uint256[] ids; uint256[] types; uint256[] unlockTimestamps; uint256 existingOuidToUse; }
/** * @notice This function is called by the users to wrap assets inside an ODC * @param data The data used when wrapping. */ function wrap(WrapData memory data) external returns (uint256, uint256[] memory);
/** * @notice Rewrap * @dev This function is called by the users to be able to extend fungible assets' amounts. * @param data The data used when rewrapping. */ function rewrap(RewrapData memory data) external returns (uint256, uint256[] memory);
/** * @notice Unwrap * @dev This function is called by the users to unwrap assets from a ODC. * @dev This function is called by the users to unwrap assets from a ODC. * @param ouid The ODC id associated. * @param restrictionIds The restriction ids of the properties. * @param tokens The token addresses of the assets. * @param types The type of each asset to unwrap. * @param ids The ids of each asset to unwrap if applicable */ function unwrap(uint256 ouid, uint256[] calldata restrictionIds, address[] calldata tokens, uint256[] calldata types, uint256[] calldata ids) external;
/** * @notice RegisterNewType * @dev This function is called by the owner to register a new type of asset. * @param propertyManager The property manager address that is handling this specific type. * @param isFungible true if the asset is fungible and false otherwise. */ function registerNewType(IExternalTokenPropertyManager propertyManager, bool isFungible) external

Fractionalization (Example Property Manager)

Fractionalizer is a Property Manager that enables the creation of fraction tokens for a specific ODC. As an ODC can be the repository of information for multiple types of assets, the Fractionalization process may require of a special contract ruling the governance of said assets. For example, if the ODC represents a piece of art, and the fractions represent a percentage of the ownership over it, a governor Property Manager contract would be required to implement the logic detailing under which conditions the full ownership of the ODC is transferable.

/** * @notice createNewErc20Fractions * @param name The name of the erc20 token. * @param symbol The symbol of the erc20 token. * @param amountToMint Amount of erc20 fractions to mint to the msg.sender. * @param ouid The id of ODC to lock. * @param governor The governor of the fractions, if it's empty, governor is created. * @param data The governance data to use if the governor is empty. */ function createNewErc20Fractions( string calldata name, string calldata symbol, uint256 amountToMint, uint256 ouid, address governor, GovernanceDeployer.GovernanceData calldata data ) external returns (address)
/** * @notice createNewErc721Fractions * @param name The name of the erc721 token. * @param symbol The symbol of the erc721 token. * @param baseUri The baseUri of the erc721 token. * @param idsToMint ids of erc721 fractions to mint to the msg.sender. * @param ouid The id of ODC to lock. * @param governor The governor of the fractions, if it's empty, msg.sender is used. */ function createNewErc721Fractions( string calldata name, string calldata symbol, string calldata baseUri, uint256[] calldata idsToMint, uint256 ouid, address governor ) external returns (address)

Security Considerations

  1. The management of Properties should be handled securely, with appropriate access control mechanisms in place to prevent unauthorized modifications.
  2. Storing enriched metadata on-chain could potentially lead to higher gas costs. This should be considered during the design and implementation of ODCs.
  3. Increased on-chain data storage could also lead to potential privacy concerns. It's important to ensure that no sensitive or personally identifiable information is stored within ODC metadata.
  4. Ensuring decentralized control over the selection of Property Managers is critical to maintain the decentralization ethos of Ethereum.
  5. Developers must also be cautious of potential interoperability and compatibility issues with systems that have not yet adapted to this new standard.

The presence of mutable Properties can be used to implement security measures. In the context of preventing unauthorized access and modifications, an ODC-based system could implement the following strategies, adapted to each use-case:

Role-Based Access Control (RBAC): Only accounts assigned to specific roles at a Property level can perform certain actions on a Property. For instance, only an 'owner' might be able to call setProperty functions.

Time Locks: Time locks can be used to delay certain actions, giving the community or a governance mechanism time to react if something malicious is happening. For instance, changes to Properties could be delayed depending on the use-case.

Multi-Signature (Multisig) Properties: Multisig Properties could be implemented in a way that require more than one account to approve an action performed on the Property. This could be used as an additional layer of security for critical functions. For instance, changing certain properties might require approval from multiple trusted signers.

Copyright and related rights waived via CC0.

Further reading
Anyone may contribute to propose contents.
Go propose
Adopted by projects
Anyone may contribute to propose contents.
Go propose

Not miss a beat of EIPs' update?

Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.

View all
Serve Ethereum Builders, Scale the Community.
Resources
GitHub
Supported by