From e6001ba408de30b673575fd63fce43c584a4c807 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Mon, 4 Dec 2023 10:48:00 +0100 Subject: [PATCH 01/34] EIP-7208 (import from PR 7210) --- ERCS/erc-7208.md | 596 +++++++++++++++++++++++++++ assets/erc-7208/CoreAndPMs.jpg | Bin 0 -> 132599 bytes assets/erc-7208/erc-7208-compat.md | 31 ++ assets/erc-7208/erc-7208-encoding.md | 6 + 4 files changed, 633 insertions(+) create mode 100644 ERCS/erc-7208.md create mode 100644 assets/erc-7208/CoreAndPMs.jpg create mode 100644 assets/erc-7208/erc-7208-compat.md create mode 100644 assets/erc-7208/erc-7208-encoding.md diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md new file mode 100644 index 00000000000..78915211ea7 --- /dev/null +++ b/ERCS/erc-7208.md @@ -0,0 +1,596 @@ +--- +eip: 7208 +title: On-Chain Data Container +description: Abstracting logic away from storage for RWA Tokenization +author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) +discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 +status: Draft +type: Standards Track +category: ERC +created: 2023-06-09 +requires: 721 +--- + +## Abstract + +- 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. + +## 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 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](./eip-721.md) 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 in 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 appendix. + + +## Specification + +### 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. + +### The DataStorage Library (example - not part of the ERC) +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()`. + + +### ODC Interface + +An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the incorporation of **Properties** in its internal storage. The **Properties** MAY have **Restrictions**. + +```solidity +/** + * @notice Queries whether a given ODC token has a specific property + * @param mtid ODC token ID + * @param prop property ID (property to inquire) + * @return bool true if the token has the property, false if not + */ + function hasProperty(uint256 mtid, bytes32 prop) external view returns (bool) +``` + +```solidity +/** + * @notice Adds a given property to an existing ODC token + * @param mtid ODC token 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 mtid, + bytes32 prop, + IMetaRestrictions.Restriction[] calldata restrictions + ) external returns (uint256[] memory) +``` + +```solidity +/** + * @notice Removes an existing property from an existing ODC + * @param mtid ODC token ID + * @param prop property ID (property to remove) + */ + function removeProperty(uint256 mtid, bytes32 prop) external +``` + +```solidity +/** + * @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 getCategoryProperties(bytes32 category) external view returns (bytes32[] memory) +``` + +```solidity +/** + * @notice Retrieves all enabled properties of a given ODC + * @param mtid ODC token ID + * @return An array with all the enabled property IDs + */ + function getAllProperties(uint256 mtid) external view returns (bytes32[] memory) +``` + +```solidity +/** + * @notice Retrieves a given user's ODC token 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) +``` + +```solidity +/** + * @notice Retrieves all the ODC token 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 getAllTokensWithProperty(address account, bytes32 prop) external view returns (uint256[] memory) +``` + +```solidity +/** + * @notice Checks if a specific property exists in a given ODC token + * @param mtid ODC token ID + * @param prop property ID + * @return bool true if the property is attached to the given token, false if not + */ + function exists(uint256 mtid, bytes32 prop) external view returns (bool) +``` + +```solidity +/** + * @notice Merges the given categories' related properties from one ODC token to another + * @param from origin ODC token ID + * @param to target ODC token ID + * @param categories An array with all the category IDs to merge + */ + function merge( + uint256 from, uint256 to, bytes32[] calldata categories) external +``` + +```solidity +/** + * @notice Splits an ODC token from its specific categories and mints a new one with the related properties attached + * @param mtid origin ODC token ID + * @param categories category IDs to split + * @return newMtid the resulting (newly minted) ODC token ID + */ + function split(uint256 mtid, bytes32[] calldata categories) external returns (uint256 newMtid) +``` + +```solidity +/** + * @notice Adds a new restriction to a given ODC property + * @param mtid ODC token ID + * @param prop property ID + * @param restr the restriction to add + * @return uint256 The restriction's id + */ + function addRestriction( + uint256 mtid, + bytes32 prop, + Restriction calldata restr + ) external returns (uint256) { +``` + +```solidity +/** + * @notice Removes a restriction identified by its index from a given ODC's property + * @param mtid ODC token ID + * @param prop property ID + * @param ridx restriction index + */ + function removeRestriction( + uint256 mtid, + bytes32 prop, + uint256 ridx + ) external +``` + +```solidity +/** + * @notice Retrieves all restrictions attached to a given ODC's property + * @param mtid ODC token ID + * @param prop property ID + * @return An array with all the requested restrictions (of type Restriction) + */ + function getRestrictions(uint256 mtid, bytes32 prop) external view returns (Restriction[] memory) +``` + +### 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. + +```solidity +/** + * @notice Gets a Property Data point of the ODC. + * @dev This function allows anyone to get a property of the ODC. + * @param tokenId The 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 tokenId, + bytes32 propertyKey, + bytes32 dataKey + ) external view returns (bytes32); +``` + +```solidity +/** + * @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 tokenId The 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 tokenId, + 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`. + + +### 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. + +```solidity +/** + * @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; + } +``` + +```solidity +/** + * @dev Adds a restriction to a given ODC's property + * @param l storage layout + * @param mtid ODC token ID + * @param property identifier for the property + * @param r the restriction to add + * @return uint256 The index of the newly added restriction + */ + function add( + Layout storage l, + uint256 mtid, + bytes32 property, + IMetaRestrictions.Restriction memory r + ) internal returns (uint256) +``` + +```solidity +/** + * @dev Removes a restriction identified by its index from a given ODC's property + * @param l storage layout + * @param mtid ODC token ID + * @param property identifier for the property + * @param ridx restriction index + */ + function remove( + Layout storage l, + uint256 mtid, + 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** + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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](./eip-721.md) 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** + +```solidity +/** + * @notice Generates metadata for a given property of a ODC token + * @param prop property ID of ODC token + * @param mtid ODC token ID + * @return The generated metadata + */ + function generateMetadata(bytes32 prop, uint256 mtid) external view returns (Metadata.ExtraProperties memory); +``` + +**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. + + +```solidity +/** + * @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 existingMtidToUse 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 existingMtidToUse; + } +``` + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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 mtid 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 mtid, uint256[] calldata restrictionIds, address[] calldata tokens, uint256[] calldata types, uint256[] calldata ids) external; +``` + +```solidity +/** + * @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. + + +```solidity +/** + * @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 mtid 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 mtid, + address governor, + GovernanceDeployer.GovernanceData calldata data + ) external returns (address) +``` + +```solidity +/** + * @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 mtid 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 mtid, + address governor + ) external returns (address) +``` + +## 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 + +See Nexera Protocol (nexeraprotocol dot com) + +## 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. + + +This ERC requires further discussions + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). + diff --git a/assets/erc-7208/CoreAndPMs.jpg b/assets/erc-7208/CoreAndPMs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d9e2264e50b6e74ca704cd4f1ac7d4a8d6fe0c51 GIT binary patch literal 132599 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ⓛ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

P2eHpy-c!aJR*dwH`3(!6MPA&DNocP zRh6Lb517-HIG&jB)PMou{E<%82|IuPn9nQ?i;Cpw9YtACL6UNu<&DH9{?9O?+3*-Y zk5MEv7Q9N&lkzC1+{O0gn|;)Z*kvaIsre0-SNnfDV`D9@f$!+moaf&G76e-uZyWwj zq~+{>rwA@Z@ayZcP9`8j*fJ5$OLvVNl!7kjCwbf(E@TNeM)zyv4L&N=!9($8YL>8j zy)7a#L6lA^h#2-)TgAPUGlxW&k&X~jarO_TtSAY28be2XQeZu{B1VXQ?k&(fOrh$N zJl2pkZovw@NEGMOJ%i~)D^t?!SD%QgXXY_OYB`8z4e02`rO}`O@GE0Pm=1YJf~2ln zaVlwqeNZDA=Q}vRIE!juld9-8XMGrnxgldVVN!~Lz_n8G@D#k6xF1RdNs-~Tlt@AJ zJ-yi9VZqVJgRM}M;8ZLIc-g8FJ2Sx)hJXO2FK~tkSJ&M&S+fzZdJ~h1GD>Vd%Xo|< zl|~|y3vp;V=A8$P&lPXKl`H_OT$EJcK5lE8mq}(+EC=$eM2U1;NJQD6W09&_i^kh6 z+gIBQ%xnv~;E1(=}N~34#vXDC7Qgdal* z5R`He&Dzob_S)Q|K<+q*phAl4XZ?LBqTh$AoYQtq(yY9G)*A;gB_zS|`;57C?4So_ z3!4OTX$L5l?`iewkYgR@uSdc{^dXNL1S;<{K(%oS;)XM)`W zV%;h zELD_3F0QMb>$d;lS^P3(;OWe**@$l(9e%Dxzsy_vw)dc43`nd+7>Is$bt0?urTChO zDa|Y|{kh_Wq>)2G+kqi4HyU-9$O}!)h#*YaqVH76yWa9svE))ifKHyv@*Vc+*2CKr z4yG5xF+#Nuu!m?DdqTJ3-u_<*3J-OAh-b?OMNhmC!T)01UlH3rd!&M?8_VoUvMphu zj25NMfq4&Ah5_h&Od@ERRc(ygvJ}mrUqsIgVoPd$L6&*^I*vu0uK3@i-;XC22pl?Q z1UuFxnN?$RESCM<9X*KUDJVJT~$C5HI%8nv_bnvXYg4IY#x0t>T*dJ{W^e4c~f*3}u%*0^t>?x?uTVVN7 zQKx&PXg}|T?~f9@3kLh1;RG!{H-oYg>fD~w-Y3ELT;&IZNui;AkyD4QIuIofDn}Vk zt#;FfyRLWjW&XEa64<&1GAiJUYObEY+UY#|6>KQ*>cgR7?tAi?_kyBwDtxqJ?x6jH z`2PW2|KXbvii=MmkG+1D=+BZ1|5X$0rVBl9u#LP)Db?(_yQf$euI-UFmOW8LG9>H8 zt>XdYaAMt^(S4= z&pHlc4CEx?uOqock|l|5p<~}5$*fw>%Gu}BK3pk^QC+D-=Q~barFmniDoBYD!1W`+ z#NX}Yo8EO=G^FxKeIh^d;m;)3z|UCTXcU@Qi-&gBMiQcLsK>Po*ZMD2$5%7&aY&eX z;qS0Eqx zv2=Gvfl|B3**gZKQk-k!dyhr7eJmAIBX!c=y2^SUb?QEjL7ap~%{-S+D~=3hpS&fe z*9^5;L%g?x+f4Gai;#r|i|jk!NXN>ywrM3xQxNZROSf6@;-6xX0+R_kn$O^uCAH=# z&>W6`Xg1smS7RFNxCd;e!1Iutun_$$=r|NRIC-{1DhTT2Nh!+?%SjNRFl*EpU+2f= z2IDh^RK+&6NRYPIeq_JcQ*YGao6cS1tVfISbR#;H+rnW%px)-`k~t+nSu*5ycqU+U zH>2&1aU@B&{JkHbrsx{oYE7%EM~bEd3*F^9XW@SKYWqqOW-r!vU3*0Xx>l5Wxxd_5 zr*(NsM){C_b}i5HehE9ceiiD_KPBcI6&hxCu^ENvOETy)fb&FUEzAWH{+PKvzcy|c z*xfaSavvqhDMgDmj_P5MpV0smTF1YE&+Cldv(CPjd{}R_F z`C`Yt(ft1sp?L`*NsndvjueJTEsTRdWz#R1T2Kb zU-a2+S1yvTXg;1|S2G{OU|@#an_sc^zid33fAk6ZsMB|oQkxM!Wh3Qju80lOqsEv$$* zT)|2}SE!gwBT^vUL}^43FN0AWn>_qpt81m?-sZ&OKI=M1#8nJ&-&+s5oBVwQ?9(Ks zu~vKURGWy zFdI04N8wh31GL}fSOY?=SrLl!+L#>kI^DZJX z*co$1coRXTt=u&e9male6YkR(9=fnV+WMQ(TQeCNj6-J+YH7_R-^91%njP-wAjZU7 znT?V1jIa=upr{aA4!OXeq(FabCP{w1K4`Csh<=rnUFCsn#c33qS$Q^l%4adBM2jUk z#aI?dn9HGg=%zkBYT1yqwOg7?)US-e9IO_<%6yA~rjhxi=G$4ki)-zj7zZg&dLWj> zAaW`=hz{5ZO`9FB@!osmic_P1D=i7Asr4q;!g`$pLP&DW^MMY%90OO<9dR;YIdr4%WGeG!s<$~IG05U_?KDkb-fc4d`X6+^H4kjF8 z&TB_t9Zj=k*L@VtP`O&7*v6HpX|C9sc=SM79YAtGLdXUO4}yz1&&*jDEaHRjFrvmP zDzWC)F-ZZEA6MbEOXPBsYTzAoX>yxreqJ*Ejw3StkO~^~>$`=!%Ne@kc}GFeo-&&z zV4=6dn|7i4QPDTI(i*BUpGfE^?NJ z@UMq4uEf3r!k0cHW#@i++ve{-djF(}#-Hb|vxWJY)X7h_0=@bHzN==v`vb*SO*2-H zO9-c5Dw|cn$>q-S8RRmIai@3W1J=j!W7kA2<*9YFDCJKnDM_Rm=)i+TFNUFBl0X1t zL2EopjegsN!n_1=7{8#8tfth*0?|n%@{7P1W9^km73dN`IKX7w_Ht1!Bhyi0K32dL zN-G2F=!RDAKK`?0$#4uj<|ZAbSehLyJ9420L{WUh`&Hj8Jrj|^pyk>)J!5S(5d$oU zx0K;To5b?62kG=#+4Mb9J#J1;4y)SEP;FS&1_fGsD|!A{1jQ&g9PS@E_!z|)4WS2~f{!F5zHnUQ%!Ut2FW@MZ<`z9O;qQ4>uNUeEn*4g7+d?F z>pqxlE^#Y=pYUY7;M^#x6^!xppIwV7$+$?$?3YwSX%-_8)orSvH=dvJ+u(M$ z2b;+p$7?pJmYO4yQjW`M(nPFDRL4v7s2IuMr-duBb2k&Osx%uRDIFl%Vxn_hab`_= zUF&Sl)hcz7h`@KGuFD4%tK`<-0Viqp_c2L)d2)GrD&$}x2g9n@kVA|_UbEI0+#x(S zMOEcEZgRewI=#cO5utRd>&$x&qP7loHGE8_5p$TEds%_fU-mDo5pZOI-`agmNcPJwMo>%|#QkttD`DD%!i z5PRgbN0fbft;}thoN}JD=`4ar^o*aGK8TF&fJwBD2kqQ4bfgH*aoSxo6~*m1zE z2(;}OxfNJt#!C&}ioUI7i`Lv?**1}Q4968j5Oz*jzaZBid~UOS6Q`m|6=TgZTt2j% zr#PP*UQ8XJATcB^n#{|bYI_D{SsL6j;$82?^K=f>dvF72| z>pzjtn!gSxZv~v?HQTDHL8|mOK*b8I509;l=#<(zqRl;S(wPo=1*LvaiB>)&VdYyJ zWKzRv*#iLZUJ^ckpKbUF_9%6(ZNTemS=!TEPj-ZB->qHa!AtK)>|Sbe1&GOxJDF{E zeA83=_eg6sI-g057K;(0sSofmM2E;-J%~?__X_Q*}V_xJH z&OZZGD!Z=9bPazo9DcH@BdE(h#%siaNPd{4PaxY8lUb(nf&4lz#__Gi4W=1M%fTpa zOzk{-BoA^5$e7GK)IpRqy1tVKdn#GQIXPsYY^q%q9_`ZVNo3Yyz>L6>8+36rn*weO zl}tj4d-vQ8+}J5|yFBd=(v%f|wZct25q!6LLg6-yO9 zdLR8ae(?(7t-;pZ;^n;HU;)=08!Dy6gydA%<^63iUQ9*AZBvMP6UZ+Lz;RAMB~mB< z7TBvTD<4{SJb}EY+?)Un!Z(b{!_pyuW&3&l2mF7_W;7jUghcd;$AB z*H)tMUUm5FxULgMRQ%v3%JHaccxnyU#1Rabbi+r8q%87eo||fNvn)ZalBp$qxpVe7ou0~tW=r9$aqTpCZ#t%y+cbV3f)4Tf7 z=$dqFzmS)3WDwT^+f}E7yf~9X3UBBUA6+upvZx$Qt}nO0RX`mw=1X`k9dfLSImbYk zexz&u+Hvts^h;~T=cp6i5_nmjxD~7+PqAxuG8OdJnq9G^GKs|2juc8shLd)4L=^-s z!O|MD=UZ|<1-2!lQ|V8XYuDIo=Rtf3wdjj1d0lj#C(gZRXSX#1&SArhFyKgEEY4{P zm7>I8lyvvH5)w*Dm&Kyz=v9IEDA{jd-tIG#8>&sFD4vlVmmxS2v!3lNEH|~hUW?V4 zmy!}I8dDHOss3WG`kUBwDJ)*w{k62k6$dqO${BmRD0Ih+HnZ0w-1waKk))(D-Kt~* zg0w99QUgVbJO}(I8=86NZ$F;=F=yj5=|4_K_zuu0G2=+b=l9QwU$>|?7QIP5HElSc zaM>}w?08R1!i*6A%`TT!VZhpst1w?qZf!*P2d}h-nWr#Ma8)6D?*vV^XOOni9PFcc&t`CQJXt?a7ZZj|8;=_d^**` zz0F_Kxd@uz|3^djQQ@yC)8I0jx00~S@$0!1NVyhDN{6V%MlG6$DSVP5{f2L}{r~MB z-arBCrb8nNItk8E-Xoi^&Pw}J>fL`hOfL+1g;j{3h`O@vAKZAcD(xwWEL(YKko-Zz31KI6%t_52_cyU17WpPv+W=zS)$>lQkX$u*7GC}!A( zf5-41b08W%PIWe?uIH9VWH8?NM~+?Z^(0Sj8WBi$qrR>u1>i48uC(5%?a5quQg_HT z4nr?H@G7>=PDx@yS&gQGv9qeH=hZE~;-nGR25Cs+Xn=fkiej5^lciP)*3A#CNjmT; z)(j{^5LvyK5^1hY*$6c_$ukF0(E1(VR5GP34^m7Gox$9AT!KE(3g-w-m9ire8>&LD zRMH5t9Y<$z@?JgJ08KS-<$0^m>5i{8YRAvPt}NAC!1|1Upk(;q$f2p&dCcLDWuW^dnB6eI|y!apkj(KXQ(qG5?Aq@OXlQjnfnANWoE>Zvg_i!-4t;2cSWW1 z{%O(cQH|ROqdwYw2RKZPh^55gj65Xe(d}Xh7sNxluIQ6R8jJp4YVj{9{I7```rrMc z9{zgte8s70y_5Z8{_ha=KLM^1k!z(MDpO=0efo%%E)RwMfj88`$tTz8J2G7qS)B6y z?IO160jiPKjcJmR6_dcoILTd5Z1KgER&bv=?_*Qx+42-$d%$ZoNOcbQ=CIjRu~wE6 z(?^)LOBkt+VhNM@C6+uH$Syi8$qBSV2>SwoSxKtgCa7JYSCQ0&_u9rc1#7;0k@Qx~ zci3%{p%qr;$m4GExDNaDJ$Arm7ZS85ukJi6jG3M1@URMOQ!GjoK-XyPRz{NK2uxb( zh&ORq3dBLCJd5u@))3anKYuSC|EG-F?*N5glitR4JbY&R;hJQ==x42s zzu>Cze{d5#K_ws)qy8IyFc8bd=BH8qlo=O2O1#^QVygfXO%!6t37E5YVVRBB89u1L{RSuHQicRg zL4;=T0Vl&!8dU7;_s^R{%P4_;`4OCb#dIkC;XVJ!r{m;ohP?CWPNZJ{5h zdR#=4>^o?*@>r+zT^^LjioV5f{mfTN?mCv+IC{FRC0`NX~h@J_#M-7&z_h*nV`ak-mzSzD#FY08vb|KzFddKHwusI)yz==^ox7g9tF zM_rZV@ZIuaq|5v_)VOj1;cCm??W?%dMDI=`mv6Usb#bKw-?O0Vv^k2`Y2m?s8E!E* zpszvWoTU8pb}S!ISD~|}jNgKbKAUBbEGn#TUq8U25sIGp=r>pMW=lVo^kwu|UD3scSFQgb2L zi?GhCa{CXnXHBQqP#fl`KU3{gb(hhvUbM6n3zj$#nq}_aSh1$g52GpKZ0Im-di$;cliKdmD)?GSIfAvHL*A7$=%STSz`^FrwL1D)Svf} zh!l!je&!nHJhn)tkn zUar}4#6@XXUA$wGk3Lb~Tag|9Q-;T#J8v)Ya-J*CO^kf_8qfSEyy%^3@@@?GE5@%x z6rcRQ1H>P`{PiLi?sH@$i&CGB(xp~sKT}v&9qkaHqao_fXY^J(5_)z>$JJ ze0d_H<_L-xk#@pi!{K6mO9lKs0#v+~4Q!ek41(bzLFw55wHlFoDF-6y-6sMboXZiMST3N|s;=1KODDRa%~@wJHCex#3yk~M6^Qq5 z2PJ;Rcy`EPcsQxy*5)MQdx>c-Z8^(Y@%g9s@>`j=@PZT>yFzAB;kPNYgfyj=8+QzD z&d^-yyU5w>=+yq!6*ngyA}2{W&c;xw_*-YOPy%}@!8(APSk5qLWV~3}i7peK*NH`x zNR-^1AR#vd3rbZi!JK?Ov49~aCpj=fSs?JfV(~XoQ##*V6~dXf06>%@K|qzrZ$Hdt zjijpGfCYI+7I6K?J%6YnB>o;-HKzb=4sHHFM$mr4=`qL-z*eXZ!uHEZ*uA7v{u6=J zLqj&-8=IfyKhbf#`|!>E%dg{Qmsy#kUlvbQ=ab7kl3|vF<-s2YYyTZhnU`UUKYge& zPEm&W?tz;$aXyPsT1)Zd_7Np4I=6<4*X_5vmQA}NJn_fGxPRBOKre>g~QbF=9?9@xrSBIal^ET6{K!U zjp$TUTB#M%?Kj~MuBB!Nt7==Au+j|pR|TTG$a5jtUH)&I2bW=nuaQeJ8s(yiJBb{s zy<3214>E;>Nr1>;D$I1n-2TL&moJIOC=@rL|dH{f!KC z`(&vRp19F)6S$2B>Ez1zcD+u5`yoNYaR0`*dT+i7b~T3bUgc_gBPe%j%?m)7Xl{!& zfyKIVPe9;)hN>S!7lKv4E_Y~4Dxo#GNppq9kRs-It(y=ycA~hmK%Fd8P)r;xPxX*z zFeLu-C|-0EHTqOT2b)gdXkMk2oG{?_)y2fe72j`Y) zz@}-Mx5WY-rPJ0h1mj^-;$Yq023E;0?n@|E0ho1#JV;5WZJ)?MZn%xw)U@OtN(PxIIRAb)$Be2u1B8vUnC-5^2KgV5{PI+vQ zwV=!bOZH5F@+G!$0j%Cw(qGbFB$Q#T_N?TAm5Kp6B04Bla%Z0(9*(ycS z@ixsw18>a*O+lJnGMlH=x#1El>(O9uQxxvo;d4l%paU-fj+zi5)IRiP0uwC&J z^r_eEhVQsH1jFzDPhE-(n@h&2Owogm@%-jna~@Xuy3(`0lbv<@T~KhG}4uyU1LOi zy8iDEj`u374;0gE`YE$>zxt*WT9cc1REdev+S7n;@; z*DU~{RMIu;i~UZWM|et+mhgkk2HR3lxjB-inIHHQ7|);8qr#vUy<}t5{Nulf!E@6~bMz$W&(0>3qIldyCM;Q$h7Ieufe; zqsu5XFt&^@cwr5t^)7j(5u)-0m&(5;`5?{<%2E=d0{nu%vp!V^m=gh6AQ7<4NuWXX ziv{0i2acrAF1c>ixQ8r5R8b`g%KUDq_7n9lEZ+h1T-xHqATfi&ha#QZ=4GT~l=fY8 zt|L-r|A|H?z1neKK{a1N{&ngq=eof0-7k1Xdl?lc4$a^CA70){G|?jCCU>M~FR8!h zYAw%Nm0O({PeI1Y3Dx`@RS_ztmSm(_kcC=re49qpp}#L)f-UKjtM+*EnT}_qeRBAX zC2OsXBoHZiQMtAbnMLC8tmJx?_3ja)L??+Bnn~)dOkK_}Ci&7gv%2sOITabM1gF-< zD-dh^2~r>kjr6Id>@lu^MDOFFz|`a6Y~6~wg#q{|eC)d5?yN(lKGnz}2rQ{Je3{_B zj+I*km-3#>wlwQ!AJpH1jAQZ{x8!Y-G!$^4j{tjuyjLv}Pae?IQ0b3Xfk!Q_8Ed2- z@b14EJQn=rv3>9auqLZ=CkM5giadu!O-9J~EnpL4PyllTAU^E$BRU~`x9i?gTIF=Q zFE{E|)3V$H-cY3EQ5NDZN;Z%;Sd77xK4!Uz{x&TdJ{dAwqm`OG+uk&or0p`*DoFG) zDkMB(D4ld`YB8?bw>QJRZ?*JP;iZe<20fgf^%6=`28risGUe!6BGHBDC=uPulG$&x zkDF*4Jx8^r%x1@G$VCO7G}+I-KZ*epbxkOfSn@%P)P(ghN@85j{Q1iVo?i|eU;Z*a zee}f5V(pY~U-#SCqqFgax}9bhwbi3V%{y~4*|wh~z5}e(uE@^m3JxwFU8H{pFi9R^ z@9;qjzLvgixt-dB2z{SXHxsCPjv^knh*|HjU_ZOXx0-iN_f+ksz@5#J6`EhA8e+3;^V9R=SdG%vim;2i|gS6#|@CGj!ON*-j;()>l!PtcFB@0*eY=egQ{( zFVf~-3IJZ_LHm`3$`Rp1iVz(RIzjs*Ywj+HmS<5Fle%f6gOrXYn^O{thS$t$Lk}e7 z1}|QA#RZ#|>4pP)_V!_h23;0C$QD>!s>gYsmb(y7F%iwo8rOzqIKdqq2o!$cNwK4X z6&rN;*+0ZZKH8$ypu-4)sg*QDHIw8f95_^*c;f&O73nC;;s3Xv``P{rf9tv@4I7JW z9)HIUKbtn2WYt*jKZYU=DsESFxGzpZi>(<$3T8~pqDNcPTnjiQjmsD*9gc~7R~hhl zR()Iv_8F#Fx5-pjL=OjpU-r0i*4eGp0Fjg^b@IxUE=D|vhTI~3OSR#Vp|hPfQkhuFpm2j@*#o{5OW6jx*S58!us${R6G?W1Z$qZuBcdp|6ncN1d9aw zq5f#JM%pu1Wiyo+0(}H~h^;25hrI2AbLHZo61Dh>K)0DUc>A?qPOn)J2$`(iqj1Ji z$H!fj<`O%>S!%lVe!SAKy}PN(;72D!tepYM%)?BR0#dzcKST`uy((BwBDJC2_}a56 zvmhpK}`;oken%FJ)Y>Z?Doy_jK-d!BW{R z^4w?PEW@8si0PNmpgYc=1m;G`V|pCq28@!HrIl$9#guIc^E%Aw=?ej0_8K*!4S*+F zc(ER?MxO@Umwfp)1&GwRGTHGQ(yL$|TXi6N{>5cxJKd zdWF`IlIcq$e~H#r$5WpKBw35`-OY)HikY=Z`;P+fh`XuB%> zl-axwGC^BCc%EF8V%XT8K+8C{oi>{W5>|v~V8>Y7HN}h<)n#Xorg`yZZW**F-+&cJ z11mGp#!RJR-S8mx|=-5k2QqeH>J&9$i#~X$Hyy;yr-#j`gR` z2Qu}x*(D}9)n0-0?cWy9yG@vByJoblOVb46PcUtBxp zR>5%MGwaP`>?B57z8Gn_UznbSggZcQh9OTgB!!c)jCbd>;(4bg51gbJP)FK^%X&3^y+ zP0#S}p898BT~K@90Y@e;uCe{U6?XoMFQrVMO4#h39Ht@Fc!=c?fg-cv+nA>4yduOa z)>ZOC``Z&wr3~NWm-Li_7RXeYqC7_6Q^nCDHZhmM@hvkOW6S&~$!YzhqwKJd+Jpeo zK@;1Id>ulZkmbF~DEq*6MDkb%J&YLhLq_Yf``J*PfWj=5G+=45#px2Al!(RnGsPzR z*wA6oL3Rm6pC@tGURRPg1PR94Vm)kc9KB=RTrAhIBhS93(WMfZeK zr4Vla4|{I`700$E40kt;H`Z9=*0^i11eeC$NpOcW7Dx#0?ivz;JHa7@1eajJgF6HW z8iAnsJNMpqpZss;n^`k&{(si`R@JFpr>p8z^^twHoxKyO3)AK-NmHpC^tD_*RZSR_ zIzjkKc#W3?JO&t#itUV*Cyli2THueBmysCk0_r${p$2Qh#Bvk{i4*4aBl^wUB>7hN z8#7F zc#1T`$=OoKqrPG2YnI4yHf9Rv-X6&(r}_B$=HK5~DnSl~3WmvS$n;-g1qNk=Da(mQ zYmMp4c;l3hl~65KigJdh1r)sXN)t=#zIvK`Y|<#OMGol$mW}XQ=sl;kTo_w^W3Jn# zYiUGgwfabgO(X)--YvH{7LDi4wwa>>=35M8>Qjjps=eoc-HqK&TSc9L;OtcWt4Xf> zbWV?|INMSRVzM3KiVr~2DDY>Y?m@+rA#sl3SlyB(jioGO2O{tU2RNciac1*3k;jkK5M#-4X{rWQ4|dCm|A+NI|6yfR zrHcOaVKq9(L3w)5+|C#&PD{J`V}H;4o4<>`w@~hw)u$YU33gH=6T|p_i&6d;)x~8g z7fe#St;U40KK{c3eZbitnv9dV-=kK2GY%~Tf7AL25FzGilNdHh4AD^kikl)6C52R& zm9smd-edoTu@EZ1W3167u}Pg~xAn1<^adY%ue9oCt)4~2F3Ua7nK}R0-mJRNFh-a$jEc#rM?)0(Q=LK_l2*~B4%i*EL$V)53i_ba08#DliLHP74b^h@Ubxsz#DUf10}B11l*j#gQ=Wk_%I$&zD0|h7K!4qI^vH~7jb2Y^+;yNb6a0BUr?awdpp}b`b$I; zhEMw=#5y8eSu1lK!bt|>Vrn;|CuSG#g$|bJrQ3u=LlRgB_gN}4H}dBg8U2K}>d!$| zC)7mVmLon}%~jXNp-nmJEdi%RDT7T;C{>Rd`8n+j&E$XKf>~Pbv&d;!jzIC;!(4ZAmk7) zR|`ngTIw{^_&rQ#O7VFP+pxI)I>vQlFz!8Db$_9NN=w?BfAXA*gB``f-1vxKQ(b-* zN*(u?;;ZDEWQx;8-o42(Y;a*YxJCQN-owknvn+>Of*q4WR7vzzR;k08t*i3P2^61O zppYzejT1Ph(;@%Xl$J7V`|vGU-saKK6SuXEG--rroO)=VSu#@QwW(E4DP+FPND1>^ zE|w&PqzZzH;%2NbQMt)$8Tim^Q{s)?i6^0N2v^s$Z`zO6_}ey(t9qXqDQ{b^uYWHb zUsXHpK7N#@LP<@p8V%xwj=#2D0LPc(pX{qB)CykY%I3wr$z6npF~*V-E5moB=vHa~ z`0GUSh8hrK+NnMYu?tZ&NqgE!kw6vLhSeAgQv`D41;|z3W3Ov4N5Ckbady{YhJ`yC zbYO(`m{oK}ikOCDd;8t{v?yG;^w4&h4jt=<;t#2y78#jb4j7EfAFBL>jT;wVHWY|j z1tsy1ZJ04BhY9NL=?HKu@2M%irdX;j{1|C5N*?kaz9QK-Jj)+4_M%^o5Zf~cvot_+ zi)p^7sd?1jZcb&IhjW<7brz4rKwwpTAlnh)=rMJ~SIle&-awQUpJP z%A0IdD|nKSvM0~yER)%K_YZ`AbJSMRSBx{g!}+>6I2sazVi|4}7fzzlzn*8kK< z|B1&t@n7cpyRn~%nCS(Q{{*1rosqnn;58VC$QSc_s8IUj>dlL1t;N9(W#zN5PIQR% zvl_Fz4Ond7EhhoW037MY>SeuKW<{`Z>N?S*T>oIZ-VhUNL9aexeLYA9k!71WJ#@(h z>=ffrl+$W$U9%(8wedvb+AOyI{rYk?Uw*nDVfHki$ex6%?2;8b1sTIf^_VIZ|HZjW zO0Isq+Ly5z2|7$>k`0Ahqa5AL7_WNw%aqU_)(V9P@IrhN31#$%j3K;JQD!l!D)bH#B{B3hFE?_n)?SSzH7JX*a!f>nSvHy=E*6z1RgSe$u(Ql;i1a~T$ z*Ez-p!=8~cDN!~u-RqNCa(l`Xv(lNQnf^rlCgzto@?UrE(Xi%XP_Vzd7oZnPu z>ekCN?k`Rddv?jFnY$LfK=alV;2?R^)zDh+-A*MD%WC@ngHt14xs70L z?N*%penGy9v7O6`aFSDKb1aeF=MsIW;VtlQ=kPzds>O)MqAcYghuF+9&AEC)IbRXuA`)(m@2l-@?q@cyi1xi94k3fxr#ZO+L6e~zQbLCqrRtKL_b5H-lrf7(bB;57 z%wS60CVObXu_{%}R9!g5p3||UUTxb|Xcm$C|IMu&Gb&nN4B?@>r9|7lr)V!TOP;Jd zf>RU(?2+HQ!s02UyToNC8w5F6+Pk;*vCerm*vT4j^Fgp4s*g;QECtRi3dCX>a%m9V zKsDdPDnEr4aigdf89|PUj8G+&qA!mxSr7;H;d;A;DIz%Qo(6F&TYaOBFQHqAB?|9vt?29r31!`;SOcZiECDFn1XB3@m433?VYU_G z3$Zkt_eGxa6b;g-O5j5Y;`H9up^JOXJ{^oeW?{iUX`A!E8+wiI)$uJzefi*aV757l z?wK2m`Ai}v*Cp^K)0ga}|9LEn)L}c7Ty*$DjPVUi)2>F*EQ1qQd=}FG`{RFCg=jsP z^$omz`LCPkaS zg+CpSQn*dL4%ok6u9srYR8)@RHKxrzNOnE*`fQPhSUV3}@kbOw=T0``cugF=mKqoW zU?wCa+ymZ5c_uT$_F*`(k%+kBQ_9`$I1sM-tEq{p1_93v9CHmF?T)WlDQU%QBrgE~ z8UWo_Td2CtNKO&^pamhnVplUUUv{W%Ay}zM+SO67<9&N6G4E}R$n-8|{7#u``8Wa9 zsMezHOWHd*`OT%$k`IODTs4AxSDYdio&!$XXYiWDft!yjsGPje`oHS^?|aN(2vL3} z+HOzEy3HKwj6GD?uavA+0MaEXW{@rhfP%aLfj#=MSBjnkHoAlM#^C*0Br;V}fhBhs#<8C#MMNT3CCg3h8;&)!TUG zH!v3<(;9y+0I+~S0I?L{hli-ltzFI;(NDk|gH=pn*AvhG3;vH)pyY!JW_K`+Pmdze znpGfJN+8PhpsC6aE&k9Ai>644O=sxdmyLUjBFe6HuuxACu+rH! z;5ALGb|`5`00tN0*~OspMG(6p05}JrJNXtv#fjgqyHIsc-ktO@>oMECK_1h+TU_4D zKD83g*hfToQ6QNWx=a8sqoEuL3eJ??J4x=)=#TNU0O3n0BmSh;Jartkb`>%{)x{ zVX(z@YQ4ZuA1U`$5nWvotGNS*U)(oEM@uv-8s)HPdRw5wk;`{0UWq&~sesRDszni9 zN|xd5(Tk~TFhn_llxMnS!@1I-W{_}Hk^+D;eQJ))<7R7vzGX?PylK*{oG)z%mCoV} z{=tV)qho3iA+ZOFreMbEB!!ok#^|Mp*Gj#24X%h%Nk?t5*vVMeE#+LqN8lfU9z97@?AJ0sz>X;uwKh zqEV0j^g5w23ksA5cq(d5XC@&UQYLSTN! zNBA3;ENlgXl}vd_zY+n*W>kYe-f8vi*^usJBVkTq2WPa1*MlZ%*b;KlV(y$9^13}? zzxm-g7WZZ2ZGyv_AZ`QYJkH!)co)Gn`o)VvaM7{g0QH0S>4T!zslw{6Oxd%h@AGHS zwOctVUd-UI8H;LjWgIe@CdGW(3j*}eB{RMllV*rv&zDbsz|P~fB|r$Q^fRuruulf3 zP*}xT5VBhpSuO^H^_Y9G?~g%(PpG-Sl5Ldkoz2t_vZi#wB$n1*d{tQwdBaKw=UW7_ySE6X>kL__5PyrhaO?8r49!oVtb8r_>Tndp+uI+L^F z)+?${>=!L-m?zAaDh&*3J*T8jc6TzHXSpbaCcSZ~L}aoyHJO5vO<=LnNDc7b%;-|8 zk?g4|^>7A$FSyU3nk!VJUmM9?Wy73JFyY~U3B89gJ#?I|p&_t#quj-EG>TvaNZ3X~CvMsek~V4*_#s$_VDA9K zk8FV_s9;X~d1oDu->&{3a~`xx#)icWjbK%;W8Qs;CHC1@=7VWB{*x6#&)JjE7cF7b z-_d(L$}G&*R8sq!$bM~UiuJAQUAgXeLw?Wye#}m;Z#&}~sEwI6v5K1@qd>^|VUw11 z*i!ufV&3St(|`KsFUWU?y8(NaZQ*oIxv!}5M^Y4fV%i9CY7c}k^xIq9u#uss2i~{C z-uE=R2AiU8(WeIkzY(ne1lTqncr(y_v-t^t+tNq_=kwKsYm}vqx_|)+LovOnK^G{b zROcN_p=6L1A|Tj3 zK{ScK)5=L$r>++tJpoR61+!AVwFar%tp}-Vtb8c!k6=#7LT`O^0>R^BXi&}xvOL?~ zl#$`GJzyV*D)7S<(Y!U|q$}=yb-NY=&O6K6?l~M_?@rnbX@!(t5X#Wue)sr}J;b7W zJ30ShDGefp(N!K{AUCHy@ILLt^2D7^+0D_CA4&`Jw!wYO2v0HPw zn^>&qNv7L67P%jU_g-DZ)1zA_Pp9)nA6S73bZS^UfOGla9L2B#hI`1mNV6GaY?cfqV9M6Y*khp}0S zOV2>Q>ss|d`oxA&4c;@p-ba%pa95*GW-y{~CV~QPgOB8J>nw-^TziMO zf*r0{*$Xb{Y>x{HIsyPlb@a5DAU0ZgKLJ|!bR$zFqFY%SKrwewqR1~*T0sk=cOZJn z(H{6DRjD>)`O|hEC_}JPAy~p8Wt7@c{XhU((2oa|nUyzL$JVe9u%Cd6jz+XYeE+|0!26t@2K(AhrV1wF@ zB0LDw*$~wZpPCa4J59QR`nswkXiY+<3U%%(g|l`#4^ttH(Os5__Bri#6SYn6$Uljh zZ)2J^7@eRkXm*SGPTJt5R95}0;I6{I;3h*6f7;zWx6{HP*R8bBxPoTbV@H6#g6Tz& zK3XmQD@(`2n{`?#2DUz`6XCO>3JUcAf}^|*y5O31|GXQ8Q(_f@jk4sQfXdHwS>sk= z4f)!~AilCEh)r+#M&)(3egz`{PQtD7hVx5Hks^9zzONL)*Rmt8f}kFc`@5sG zmgSX1IPH|LY*MbD%^0JP$=aM_i;13^q!v>mC~x(f`D-mcVP$WyvWq#Gr^1YmwZ~sR zHVU%rB?R4s>7BbuaNmY*Bmuw-9cT@!=;CTG^u+(Qf`_#$Gg`E_qYB!b@EQDb;%E`?}^0uhX?*zQoNnv zu(!CZGILa~{pH6!i?cwDrnBxgm0??!qJ)I=YhZ;bymqF2^*LWKFE3 z4@l7)Y#Oju=vVrjU=RWF1{0X&#A|J-Zr{bTvL@%oK-eHTguzWeJlB}S@B%m1kiW5h z(X3qu*E%hUe6?&N#?O&j0HWW^YhEQ;XC%ND_+ ziDy$zb`gwAs=-mP@!dY36gD2Aml->PqT`Bmn(UPgMem)aWku=mkunEG zurWt`GnHlV(}A8^)0#e&5(E;fsb>uy_5$Z6{z z&&$%og~w~*VMI`S2ghNERD@X=9CSk*7L?EtXnzoUW@zw5VNzEhE&0jnQpTjQ)P=nG z+iRNpdzIJs@-L|#qK4#yAu{0~Y(1#9Go1`X{AZJ{SKNn8esF?_eZQuB^o*_@kj$qsge3zp9ljeP3HZU^;`OXCJ`vBa|OB9Jz_GYm(n>txW)W^4DZ82v%Zof z8$x|6BvteP7_d-NaZC2*w|7n}{5xp|?-s@AO-2yGn3m$8gw6L(%*YQw&t>;tq%cRr z{fQ|gvC0rls@fA@C<>E$aJf9{p28wXp{$3A2;rU*i}eVRoxN|P3XY`D8%t){3t)X1 z0Ey2j$GYEK?mTqk>PM4x^#8H1PZV4G1sfb5WR=hnIawB!CG9ONzi)21g4v`@A9I(~ z&8@&1w3rM&j#X^sq1S&~M3!7;w%W`uOM2fkZ{-O;*E7y<&J7`GNbjUv8g)3oGA-5T z()K|zX=#TXx#ZoTgEB~@>8WR>iMy@ept&ukA!I|45tCg(I$=EuhxK8-S_R4Xt1Bt{ z@7R~CmouAE*KPmMqUJqM1J>VlT>c0sUY)-tnCv>ZeeV4I-R+0@8`*%Co&EC@Z@-dP zCzp}CuJ2wgaia$9RmA5A&TNVwAqGvhJJrUID-G^D&1B~M2Qy*yC-ij3y3`03X84Pi z&+C_&f%Yd-s+hGB`5qO7#AyliY^0UE!cN1ZCiPzps@iA%n=;1PS&G}nX?CmtmBim_ z<5}fu*UV8)N6$W8e)DAqvT5ngz!BA}i`&8rbsyE)o2S2ZB=o*%t#`W4_?Vo@e?@(N zj|l(UbINi|g{vd_X$j*>reD4Neu%%FGW_8$_f2Hg@QCb9#E+XE@1FpvdQL~4`x1^X zXCM{`ITiO#XxcE3bMt)PNC=Bn1Swz}eL8n0y4zXRCxa#Mvg|Z&vlfE{XZ;luTT9}_ zSe0==CmvH+iaZS+-h~BhEz4;=f+aqD^y};3d~6t5F9}{{-zYBPJKy}|h85501TP_c z>yTcLox#~YW^S2r$dkr~-a;jOQ>lPgCLM~#KkP78?^k4D3T6A|J*~K*Is;Lx^T^nG zn!bj4{N+VJz*k2dvu?P1vb%6WwcF>QCREiR*LtW$ZTVE zQ?oHzRd=YxDSfY*@`tFW>*ctlNoK4Exi8nVR4QKH3VUsryje&Zc8A-f^vd^!_XL4n zh8OU`7-KnV@D>8TYs7s9dm2;3w)%8JJK0!rCaTjn-&$28Lk!0VksH#0nO@59qr=i} z32HP5EHsLy$O0WJD%rNRImTxMWjAI~NBd!XQHOA-^Ndi*;j-n=K6?`lQ+t)ENy-#; zuJ~ZPyHO84fjEW*Em%)dk9LT?pZ(KlIb&X`h-W+r`bhJ>!JUCaX=M^(04ihyiU)g% zeyblz5GP7EcDaNr?vJ?!-5m{A3|c6@}^@E4-_ zfKchcCW{a*(?c;moEVrn2`ez7AXR}ME*OmkI|Fq>(TR?&-HZlqLq6eXM`YNtdHe+M zIe*DH%B=na*T}0$_cwml`1kijdR&^67&CW58rvi$XET+am`v^- ztD2b8>^Th{EjCh!za5vXUBFw`UsVozGDbG%?-KsVz{yMHA z&Nr!t_iH(eJcdyM7^Hr8#OZUgT&|21GySv+4UK7(EmJV&;=H|2e0&FX4B$J7KlMbn zzBF5yiM#f%FLYKde9P_V=zRFUZ+~O!q{#zC7val!Mc2&{NOmIjITa7kV zD=XsbBbAqayS}f+!eZf3MeGN0&8(|5X|~!)nM6T>)C0UX<9jYqVyH)C&k-+y^8xADd`xxOYCtj{=8$goQd~2FB4HN788iw+J=Bv zQN?y_p2i4sU`vLSFsd?IfGZQhdOsZ_NqCggDBq-f>Hm!#teb_cxM|vwYw7hc)K3Gkl@~*Hb zN1z`e_hU`Lk@!f)z8N25)Y5*N@NcHZYe9nBu~cYrfpk&alhs#|`{p-!z1NK>H&Wn? zPl)&2yU}c1q~WzPV{x5vX_$gr#!xxG#T>dyQUtGu z4AgA%%j3Xi2~Nh;?3b!Y&6`?hH9G_6rnlZBh;!9qNtI80X@e!Q8oXUYc|8!mLK2JE zsEj3SEG-~xp_~KVMhlXW`O5z&G0E;}6V`;P1v&3Q5;ZaHEN;*hpeR~Psp@p7OZvi^ z4#!4+D&mg4mh#5v@tQhZrQVE+;b@;oN3lrGC6*V2d8NqQ$r_2{WtgOL_KYZ9)8hP| zv`*Pg?ojg@b$}CIMJ@*{jGKMNQ6)n+IzDzy|Lfm&r(c}On$PFctMHojcT&hzt;fU9 zdIHO)u&1wzpg;=gA3p)VS8wMty*E42aQL?6`Dg6czwi6xHqwAjq>HoOYjC7{^9ps+d2ux;@fI>je*8%I!tq&6lp&?#u zMrdDQvqujET0Ab&-(-$dFTJyM+y2Vw!<%jT`svW}@Q%U1d2tRnf|ZI-`Ja6ZRi=(n zmCF^^u&CMm7%c5~LJcJf)8K;P!}N+b?tSy1AQzSG1L6*#^`|wqSsS9W9aJDBb#(X>g-j#?3ms#PIrNuMn;eKDN8EYNaVeX;czZ|&nyoc06&bM%UP|Q zFKsyU#9#9#fP{Qkf!Nk}U=3m7=R-kS?6AZ)+FJ%Vn++|q8rjP>B@XISaQQ+XX>#Wg zq$ss3jYp_#$lXClQS4bGs2Be?VdX!o$X7oBVI7@*S5m>Ao?JS1rbYn>NP3Ie6Q{+R zyTYy?X>*!CC#suz50q55ZB7|1yYE%HwSJrVXE)^gxgVqa(0~4QQ`!9w+1-yokN&O{ z#A9*P!VlN~P+`B`rk%Oum~o~zOkq*{UP`I@;vKoy%q^YP4PHy<@yFjL2LHfo=3BJi zDfI6brRmkwa+A8$HjVAx#@9{EZ!zY8QJp%D(CA_P_4>JUj zLO1*YpKsOqF+&byhw3$E zN5c=g;*emJWXFlQHKi%mcb0q8s3qQNMfksRs%#`6F0WF17O0h1eerip$zNW-zic*u zCY}H;^WZ1_H-+@Sv;hC5i=}&1NR*v|BfJLRQK1%2J@a2G?VoEB{`kmB#}pfgX!=N4 z+>>)bILBzU9TI)8`W?R0;@Zyt8-({Jw~~#AhO*Ot^qRR)s_$cdY~0n0_7c~&D8c!0 z+$(&qsWqxQ?V_(NYZBYvb!32Q*C`xNh_jYkYwrWr4X)g#egdLW@k;G(YW%r!Ow!K2 zG}0gNWh0YGdOI5NqA>Ri6%WOl+uxT~*4<7)vg>~W>`L3(78qLiq-|IxrRG&APK`~s z5W=E+cW@=Y?)4t=U@-1Nl9Jz4EFJ!O54U{whq?%+vOMt;EC?%qLj4uZru5O- zFn%5^H28QeU}XBoT6RbZG2H>$yz0aUuGwKmvusA(mzZsP&=&*|*2lU=gS)+643_XQ zrFT2)F=;F>#vCEZRcr>{^x!iR5hY?u*DJ0Cumf*W$DTl1r~b*H z@6K~mk`-?k-N!TksVrC>hmBAPphb~)vx*w~YbxtRfQtqZ08aCBRvgX2G}h{0JowD< zyQ&J~#6gbW1%xP2t=3ue&4-fxZgYX6ewp(MwoT*e@P?>WL+u$qO+ZmbnKsm_uFS`N z)?o%@1?-*a7V_w~>t#fIQMAI+(xgk5BYxd~KukpxIU%M=Ik;{dtw*ig2^t|G{<^N! z|Hbc10~-^tk6%_0pU&_GP7`qZVJ#p72Q^QzotY^x_lpG&CCd>Rq$s(7${4d73N(`K z9p2Ntq)baH7y6iS7WDw_8^$}h9#K1+)r*AH%MFPriyMa}AP?3>kNVxdV?u*vZq z7eOl$%7BQsNu%f~rnwvAq8SWvBe?ub%jFI4;ixY4Xpg#VP4T6Oz7-X*2DM^y2A^~9 z&D(k>d8})b{1ykdXdVw08+`dJ$?$BCI3P>$Zetm`5D+pbyAizNi4!3-A0Be>B8 z%(XZ3C3H{;(iDFF#mf)1D*93J7Fbt28E^3eb43|iTy!}dS&_GE5L;T2> zdHIGkq79r+25ai(H0e6`66ncpUK-eX^ss|MqoESswZ>;bN=_)fD2J-pa7Y-v@>)El z##CP}4h{K}YuZ~onq*xs{SzEaD1z_f0s+zV_455&f>%DDPv~OqcBK`itofM`lAwM0 zpxU(^vSXXz`|OxLF0*G#kM=j-Q$GrJyYRP~)_?yzMXTzbWm-ZvRxhO^%1;cp;9fyZ z#tSVQ_*E0h2}O;Z zYeO13A%X^S+=m6JiY0nu(sA^XPR`h~mJk5BY7zgZ7$IViY7oF_+?zQR#_apN)37U8 ziA@7DINuR@Vp>=??#L|9-^+g=oUAox*7eYp`B_H#tS4DUkz2=pWliLp+1d71at63I zfDL!B&6`(YNjB-dSN=x$mTw3=j%BS%rmxb1z!|ThPC0HQ$r=QDw<$()L*xzZKBng< zvkL>L=4wqE4<E0o@4fzI`Y~BD6SnIcg;RnUx(hfXWj@Fi6 z4VlOa%IX$OOyv)1yNP6mW1*^0M??668y|m$87}LZ^JUogW#IssKl}2>aL>WH+`AK6 z(pEX-Od#!u_e_H^^NNs8yUH@J2D__v4N-4D6dTai&|4qQ5}0A( zY!jQ&to-_a6DW|5YMozv0q{o2eS|Jzo1j2 zquPV_Nq&f#fVL_u_ObH$TIudQbn+=^x7R?dCu=;US1`9fg#oRH9AW-!ugS)TQI;-b z4HL)Cr-iCb>5*zyigfA#pIf)6UbF7lzPO@NCR5=O%v`S-b7_ME%huFXp*BY>Mvi() zMX@Gtr8`gmQ0>vU5->(tFwDMB5Ean3T4&DrwMg8BZA-s2QOrBAsJkN9f<#>?UDjwG z4w;Qv)wz`!xWS2Vk0L7x6ymJPmas|cSR$8*o>pR&=li0?W97p>@h>tAQ}>rZ_TF1a zQok1V$7n%ugDKlj|19aHF0_??B^nw%xY2v=hT`MgUIqBK+-2v24!RWcle5*{eVF}L z_Ok}A_QpEBH{(fmb^U7iPYpn-a?L&~U)vrYi+EXDL~%uBC?TyRowl90Vm zyJ-H>FdY?AdOrb=85ub8HXKYB@dmtKfxw+CSXjyZxpOTzM%B`Q1&T&t0jj49FOEt~ zBF0EemjUZ?1LC5C0beY0O4**nFRCbiJiyAAPm~qBx;OOZg@m21GgyE=ecN{&@rZdW z+mEckM-Xkig&jixt!EYLZ}|Nq?S~1|U&epP$c<45_o!vMz!Lq*;6h_Znf}wXyt}($KZVTCqSU3L+K*@?WNR6_3^~5?Zbe?ubh700<-@MceP23 zR3A>Ppvp=>mF4A=ZMs<4KD4q-^DlejU%p<|6K}$cJT;lc0hhpjxBBwtCa=?3K9h5= z$Q}cZ?BcXY%xT8DsQj0MvNE+V=4xL%fOGtR-*}(9=cxx0+k@Z941kmf32>xI_4OLrf@b`{rKVN2V+N?cxbBjYDd-NRD` zWcPDw>%Yx}XO{fyFB-}Mw6`+!Tp;`d3xwVs=I#eKq#X}B8g7aHZdN_v!NA3jvW;5` z2Ndkkzp}GLMZaz3^@c;EuwEm*w9%|`f~JWY5fsnSwt({=-Xt2fnjBG8uI4@5Pc(cN zxc2<|=HW*(KPiQWA5rRBO65U*I~UD1rFm2N_L)Xil=aAe6PVV3r~J~N{S8W>``a&h z@*vfGs5atOx=9A|5&Dk33}G~W0?NS@crPg^{2H4w#FWg$CfS`Jw;s$U`cf|Ha3F%T zb}1U=@kG7Xh9Qw(NdYcpiz^ZG5wtzxE{Vn>P*|Tt!B3jcH=NtXF{lzSI;_~qWZ_PF zx4N&9fLz%e*J!9g5z(7#7=e*8>+{&eYCnP$J1#~|J(OEW0AytlkIxywP?zpM)!$gm z*VvE9=WpO3%#wtI{G8ZW_&2?Uw34YKB;hs!aa!(0E$N>gTaP{ma}m#OdU4uD>K-ce7)NqW$y3B$at&T(-4MSA|hg ztdL+;Vir@G3uqupXx5WQY{!TQz?s$10C|Z+){sJpFac6fh$5cDNDg6?F)PNVL81`~ zIzx&&_)Nk15BVd>;#t;N%sOPHKP94?&LF_a~Rziv?4oyL?r_PaO>Qtgs!s zln*E)l9e8qU;Qw9<2}&u&EN*b^{(IAS6YnmFltpy)bma~Esk^k zisV#Ojas(J5ja(p*P#)eZkEQ;F|nPe($VU)iO>z!yFYD(U0TnMJ1EX@)cRUW{+UYp zaN-0hP42=ZO8Z4&qH6RpnDDV zEd>gAWk{Bi&5~szC}z?816#g_BV?0M0$=DXY=G%NU0hjc1MQ}n=tinD=5?*e?m)KEdzn$@X_>hTE;Z9SRbEo?M*b4&6x%!wQ_%aLl1-t2S_WR74Zt~ksJUZ-2iAQXOWPo*ArUW5ZS<}MUaTvcII#%LN ze?fiL*aPK;UO|&^94gd|c6B!|6Q%V1gV4usurglW1g8YNychK-WsCSk;`fKzwKBCw1AxTi0=`=gSX+( z2SQZ{lKMDW+L-5V++R;NhCUl7PY~e}!3-4*G@st^ISgvp#N6deA&&MwkQ%{pboUW) zIjR}J=Ne_;aW*~U6oL-$Jq2tS7l1dlMWKDd?~?{)EM&lHJs`FrSjs`de0JKKjfwKU zXkn_LJ*2JR1~%9Nc1EP8-Bb*?otdj~8Q^DS2Ta^cC=z_3Oym9H%T9o(FgUnhq1U+bL!R*=KNIN!Mr7rU ztH>vj*H&0OTPbrBts|;3Tq))7(A)39rMV)Y_a2|>_R55?;Qm!asK(k|LMeWThRb>OGdcr#h<&- z`3YxXS!X3^39EV&Wxfs*+sB==O&<#BrW%Ur+`YyG zvto2kKPFjE)lDd5qh?QTU`q>nxkdoh0N^hStuA=NMthdeBd7zsNE58)f-`}~AiW?3 z1QmlAe4-a58Zr%F_z2A>Pb7yUN%q~bbhS1VnfUI*(Tq)(Inbz=L?KwBFGzzs9SP~s zBETWiRaI1AW>Y?<<<2;KDa>!4ei9|G2F3G!8QRPqW$x1)e*B9z;}@xlqIm{L)WU?S zI?Jc4vD%0Dl#U0P9981rS7quB3Ofd2_ig|60LH|?6h~&a&iu!#%sChyLygUYa7O1~ ze?$9xzOCmN;}oshXo&d(D5-V!uMbQ@Boq(@FR{ODNXHXmQ20_BC(9pV;ZO|1LZkuP zOq9GB1**N|BgE14>&MpJ&y&aFvN4czo7!!7EKQf_K(Il9eUAR~8rb`u+J>jKRTB^# zI|c*}E`i?;qBMz|TW}w>MCBZ9Z3!V717&-#Je>X$@TtFv5zY{5FR5}p8KrMYe?5pHiLVv$p%bPZ+pGM7U;3LR+MD9bNI&m?!0jl{s5NaJ z^Ee50+qVJVO-)BRYHXnBW7>+$%1qR3GE7k^N<@_^dPFcTWM4T$&W0nMAQ(s_Xoq|r zq_e2juk=`rNgjOja;(Uzsn(2G=sA5nT)P0wn;U*m>FgJq6e5w$tvA@skSftDJ*tRZ z^d|e&ijBOHj$nc~IHYL2DNmVdq?>_hYnZ|#3^08*C0`;bXs@;}SVPAzD<+6$dpivw z*;rE4LgMLkuiN%7NE8Te<4|+u>QXx_s;9Z|Fg`!U{l;V+DrJjRlx()3&LxUX>apVw zm7T6t7O;NU{xQ#2#KJb5Gcw(T$4 z^IuYzj*7d1dmqhBJ!zrxN7R+rM7Pc+v2jkPSbk0*PGs(Gdxb|aHDWRrv^mUfQDyUj)W{)2 z^=iEYS)!EWbWP1IxS>tD$6+x--rp`PEY+moT1P*pKrT|O3heKz0_32`X3(bd3? z;|_D4U35M~a|O9_agmeU#m&%6X49;NIc|b(lsrl7O3eDM6!4%n4yKii6mUjJjUsv| zjBE=ADNce+F~=%keDTt2$kH_IxFJGJotMCftg@bXs({!R>>bCdC91V0&&b>CbC-$v zdu}#~dvK!1@q7VTw_jS`D{_9*03>UT>9C^~feJ2WlkaGy%6>vESE^T{yMT{Glq88V zj+4ZlL9lcq4O^X>f!s$0nT-f?3(+8V6TTW|IHr&`@*Ee{3;tvdQ8+_8bbht#``GPH zhl2aTUquL+yV9#0@z0-cdi;0-jxcF>FY@dk{DEnD5h9k= z_C1)@5jb-ee?1gBlAt2G1cvUs8@nZNmh_e3d@rC9gHlY$&W*QX!zZ)1p;d&iRmbFX zY3!J3zIx4+g$s)mR*=$YHohoQ$klwN(A3q|ELsBM8B!Ec=(c0Z<~_;pF4!0qPJ?ed zz-;{%c*?l0}Dl2+=-@tHoA>C*=ySA|Mn z?8wJl8A#@6vNPK-iCCoD`}_Ig^|R7vIv+%xPG69gzh~z8>bIZAKz`LOYP)})sZWKe zqm@oibVd~X`TTb)nzpwWXSZcnTI@E^u_)y+tN}Em#mB<&Evdgi&YTU(5j1f(UE7v{ z$cnnP>(=bQ985S{(24;KU{TIg4_Tfc8p;C#o%kc8N&onyzXC&XI5ziAi=Hiie`LMf zgMUDI$7^T%OrMk~NE#SZw9PtNHQg{$Al8=fqHT<%S!lDrXT&w|ixX zid90ENEx*_9QbxyQEm0z0)EbBu*PdDeHu$;>S0=qBr>I9s=N)2K!?fIL&r z2OOM6owp2}EwiaDl(5)tV)%YZU7N7_;E%1m!4jnox+YqWn~E`? z{S8>=Z8qLKZ#E=)11UNq?tK(PE!O}-EwJOr(KDuxhUGuTkVNNR)jThT|KOb1j&M*QjDA$QMNEbH&PqLG|)lEf5WU=u)n z-P5053D-7FUVB=^@heB7s(JK)TmF2N^e4a){=Fh-iD8TNu#0NyV3KP?^>{E+_^nq7zO?<{ z7LZQ!$IDN@BUh|V}jdn0Bs4h0u>&kiXROKL&JHt4*(8{ZjuZnD%=0_4Gx%3RzWUkB#A3aSv zl-#yX?yh~N7DLf+%o2Ss>4pBD8rgcJMHabrGIkA47}u((*83(*nK@j4#h{b^=I=UB zmY8(1zz736vnVxz!gsF#+NZKr=npSaF~CQj_yGlXItEdU$5%fAaZ;N{G%^@vv;#>u z5x>^WAgXT0RNq{fkfG{^9>rcQc8mhn7-smq2K94~Q1PHsGNpOBKtbr>`dOCJf1Eow z?q;iF;$0T(wFB)}!JrXol#V)0Ux`SPj=e}vRv8O_NYVQyPs8i1x=5MH2SZhh)_0s5^QATJ|z&4K_Mh zpp8$%LVE}!&LBe>gNgKJT>XnOT&O8;jw1XhAgp>y)GWw}$$*`gsMdWSd>pdYy^~yN z`@!4e-a+aY*6tu@$#^1K4@froL0SIM22&{>Qop&?OCZi9m=w=F6&-H`r|Zj+)0x4& zpwX{ah39F~@!t_Hu>{tW}G z$r?UqkW{9-f2S?K`r!fxl!hyZ6mSLTtLyE>v3%Xtz|zD0EMe1rkd`UQ9QRtusW|T6 zFyOVXeSRu`-L-sk117PqBBgkPvuGmkGdrFwQqLaeg17NMc%!)1A$}oa6Bx7c=0_Xj z-`mcpRT?;rgpsk3AOa^jU~<^o;`19{E3;9rk$>Y*AAi?+o6yzhbIoDWzh)vCNLBZH zhm!YC)F?9jaDSX1ztBds{(S2i@9pw@I7Tk}lm(J0tM`;A>9LoiE{0{J8Vgnj*jBb+%MhQB%C%konVWf`A zLHA~siqe6@2XkU)^^50AQ5fkamem)Je*8tu=v+(#D<;}_tgIe&lgCO#Z7>4oNie#2 zb*kVXlt~y=YEcI1&l5iP^1)Av|0&-KJf5? zYIv`0s&unZDP448y_oU4Z0Zb?rX2SlrgyZ2vT4R~r$kE0YC42BZ&p3vqW6*#UuKBv zLW_F#zz|D{V1KYAwA_d}`uux-IExq$W!b>Y-P}msmR+%IPu+nmVF(N942zr>Q=0&o z8lqoa1iO~9hzf7}%{Jsc+nPR}9426m{yHT^E z$@0ya;ZX{fPj}O*9mrQivO@g=zIrjXXYMWEyb5gDn7`x>MDYWC3}w$&7rmd07=U!z z4wXUC-?Bq~P&JDy4^$3LN-Mc)AWpbFpC%wo5)1remR_-J>Tm8cGXI|;%FO%1n-A$N z*iFy2M<`Qc{G)#O_M7%k0Ku1o{l9GN$gH(zu3e~pBR=2%GYkB0i}xVCdvaD@p0%R- z{vo-_w4u2moh7g_*c49>aLhutU^P&&L8XHdJL7_Ci||pRTP09@A&aA((qLaZdS?}y z%@UIDK|`9l+L9Hp6lgg9O32oyrwkipX=F&IE@)@a3yhyE4Q#f)B`(S005WpyLYLDKV>Bz{*3UN<-=llDo6{# zTtwJvz)~($I5FQzI0&K~auIX=wJ_OOSFl)>>^Ci{D26j_y@6t*Gs zioUaFq7GZG;S44P+GW`z{Vr^T#oGG4lY+<5DpqQkzF3Vs-dOs>v+(<`(QSaGzG+N1 zHJ4n}m50JsK2NRU$aDvKjh;~Y&t;Er0)0jg4)2x^Z0P3j9GTLm_uD#B_Y1QWu*E;? zZ9sD-w00kY2%CFO+@8I)wnZA)_NL@S%veHQ_;_&>^o{V{Uz4KY3Vt=l6PR{-pN2E) zAv}T~tHogwI4-ia`Mj1V;q@n){?M;9_!-m7=#8kH`oj@h9%ai-G4s%76s!AOPQ2*1 zku-DlYWAgkXJ&UK8_dOn8`9aMx|pn+Ayv%Ly%+HN0pV+VS9@3W80sHpORfv=LC#Bc zNZ6aG^szQf2@0p(Z=qBxNHmdbi67e*C~Efbh&p8C6CPJVzpD5y&3#mZ3wiVi!BoMmjP*UlXyf3Om4(dYC5?*c zvnkCK=)9+;9{vs4>*$g#2R+)tNfs(KFN_6T8ah8;7BWTnAsvh!BRRDWU)AW<26b5i zn|m2#jd|^Soxh~A5RuQxhM2}L299=67z+*x%H<{2Be^(-N;=o@tej+GG?_ z$MKL5V%jLZA1Q0#1?{{^HJ6U0tqZB+mvr$f`VQWcP_k*Pby$tSs5)f=M^sm0T}#Lc-N`?WFD^EHH9I|H! zWhOLP00RBoor;ATGOSrOhj!YNh1dT!UgR##H|rsTM+|(pMBUhhT(wSp+Q}Vy__zY` zod!y{{7)SAm~hK*#pP4BQXQ#RxrBQ>c!N8tJE~V5o&X8q5qK{Tz5~dd94zu#O7xME zP3AqsAMzKw(vOkQ?mauJ>wfb}d7-ZAO#hcZ%C*f43cFbzVxlS2MDT2R*H|za4B<3q zSp{D~4sst5yJUcZyEKGGhr$<=$kM?%@W*QhtDI} zL(U1*r9q1#;5_V^a5t4_fjm8CspC-*I9$`{*pi<=fdN1m^J!W6!EZ*|2~PbV?xYh? z!LTsbo|_bz8T`AK)lwVV6r!5`9+Y`__pGD!vYAUcfmN=2?ix68 z3as_HE4x+LGzEq3=yO?eC9@oP6s8LF>nz#wx|>Oi8I#I1@i>0Xyi8zh`JQ*mkz7RT zb$!A|Rx@$K^_|!j9h_p(rFa+mCx^UN_&6i${fu3q@(gYC005nNfI zL8c0?6?YC8NbQD11*fS+9AXLh@oY1>EI)>Y6i<=K)sKynsxQa-qIsQunpl-%q*H z?||+t7kFf%jGg6HF^!_-)=`$+Ocjx}bOTQR+HtTTw=CWYo9(A^_RwmK!y|A5<)gR9 zphWE-ewF_HJ0YXnUVaH*@!MV@yP@?vS{sasI;LuGaHGWYQ=oKmNkFXSSaV7MYFmu8XQI^WWm_2xeefv3A24m3Jm@?r{d}Zf!b&J> zH2NZrXR^!&(!(`}-L1tOK^`E)v z9WkbT(7H%6biCmta@;i~%P9eCHl0m?LgXdzkuq)Ko#>HBejLitlsw$v-hxOlz!Ea1 z!9u6TpT0&&*zsm`5IqcLBL11!%My<^^!OeMVB~ftyv&NI<2<)$vQOmWkfO6JW-VEy zNby}e^tEogZbGRJe&zyv{ALWQPT7O+jQN>wezb@D?SX*a!h>FjxdIbm0a+Aq_G*_= z8YPkV<6GOM@&}YeqI7^;8{%|7;4I-aN|4e2@>2lI=l{Ejyo0=akzy)!Sa--pO?$CL z4fd;~Ugy2tzc>%@%m^WTJ6>X$Qzg=3Hlx2Aq-Roiqvx9XP47Q`3$>j6e5dYpyqDyw zpwVlRzn;7`ZXa=21C|*RGk%Am_@9$^{HMZ@|0|Juv(~RLV&?~Sv|j&i({!-V@YG^4 z(vsUDp|3IR+UY;!jv$N|Q0LMJx2y+0ykN&|}|k zM|F}#W6fuC`$pN^uJN+}Yzw7mr+m@p`_pio*kSUL^XFgvtkp_(-5=}r`DxM&h)}0M zCb#t?L?vU6JQO-9Gqk|i4)sFlbJ)6pgvvIQ{wsTU(BV}2W@6{%bf3vc=bqXk!>>5n zb)uMjAaX3Fmxr{+wvz50%;iG<9oV65&&3FH$`H{yWri{nU1@%etKeFjX9qkO!Lt4M z9IHs9M_e$e&xCu0IHV9NL>~_NydfgA4PQf{uMOc0f`pdw@r)SD0?=O*vW5>F+sTux zz-F%PqorCWE4cP7eI*`ube7c6QcDW`1myFNN$AItD)bD+(?846StUmu5h5gdi97pT zz!;G#8q8uUJn7IY&n6ixQZm8xs(`MIPYFK?LC;Qe(o9TKUV%7c_m16OG^(2M{c>e< ztlK!H^O5x*gog4*dCUBD(V{=y9Rp$g5QbL}-|qE|>gsjLo2472z|VW!7c$yr&6hDZ zAEphC0`vZjNc+Q0@+I6S>jp`&A)P&P{8#lw0r`=TSj0++l_nN8b<<7WGu>B`mlGgf2(rZ_xfswtCL9l5V zbd?(<0J9EXOufmt->Ux#h~)Sz7@hvrXUu;z0lNpFPO0kcogoIy49BN3;Th>hJ1b$W zMV3=^sRn*PN1+{!_#io=YRju-598G|tnP_a7W9jZg!=OPTH1PBO5hbkpuH0_iZD{4 zo`_?^!695KQibFOt5gAE8pd; zrBY*K)2=d!2auE2E2GN1KFidFjYqd1#7cpNAFPG(s70A*ZnxOMHu4!+vu@8SK1{X( zI@6T2?G+7xOW<_=dgj8ga(i0(*EzVY`I6xE>Z_aAznwfei(`Ir61Le+A8C%K<&VSd z)GtV7YJO)ovh&*RU`mtv2gGq~ox_z;Wb|x;wD%(x-GDmJK*_r8o##RUIZTf+3tM#E zw`a|FU)dt6DKHN@bUu(Z*b8+$5zQyhrNA7+!{V~WU*}bRurB-NJ|?_Bn#WH-glJlo zBdOP&Rqd-3Zyfr5u9}T6 z+D-bhQB$O=`!&zKhmXW-7z6X3M*7!2UXTD6rn|#?YIUn>r61m70nPf(<6)f$B|g53 zZ#-g!yydjZ*t6oUI4OY0XE>E)W7nIw^MrHYdAPsnU5Y^*9=)-|mhd1L4L@d4q*syO zmc+$!aGT_~6d!x6SJ#9g4vPw>1EsA+3L6@z1)wD2gXlkDv^S$>1V6v4vEuDpoZo<6 zE-`7_oS@D7znUFISzF|tFzntde)tqv*A5<`x54g25lmIE^sQj&%-p*t zUQ)m#>RJgmGrQg-yFSe-xc|$|^Y*$w4eegG7b^Qg`Rjt}#F*BV6EYS5r>=4*lGZ$Yqy$^8y-8xZ*0E7?rY%z;Nt zt%4wACMs*Fb4%{w&Otr~nLmx97 zEhHS?f!3F;K$|KxHcprZ2HRhunXk_Dh`AUQz<}viFaV*CUk+La95=5Svxs}b6DD5- z*kpC$D_e@n$!3x)p>p@M>AzBDn9aj`6TO@G5ayR=j(?6H zZVN1O9+{*vV5)Q1=P~$#!k+o)cO~8ajYhg1#k}SgTsa?g)PyEDCwYP94+l;KfUL)I73v{5)AfN6b?=Eiu5HQWbtV6`Up+p-i) zwJu>K$6*#0n$5GRq=eoG0;T()d?gDbZ}TW5ff#^!!$k1Gj9Vpxc^3z6FKHttCHq!% zNkzWzp(+7vAD>70VQWn!mSM0X#IsZ_x@av$GEY7bKI3M$^?j=*!x9@*MhpT7uvh!L zqDE-~YXs;m!WR7KJW|WaK|^KX5wbC9v$9Z(`#*70#jX#7SlQPAIev(pUtGM>9Q`(S z`sTMjyWc%|NB=m_SxXv?8KLW8>E!pxcjo<<*1eUB6>m4cFXC_CU%WC|xf-kd67X67 zqV2T&?-4wDJSu^LUpBCcaBPqJgJGd^z&+qocBnKs{x4>oEt_G6~){`KBJpwa8y z>ism4{h{vhr~6;Cf0-}ZnpeE9eiNPu8aD|5LxbPWpJ{)F$!mSS--C|ZF*&b(`|J&EQUZHnX=(WbRj(p0L1qEQG<4Md3p(T;BV z)WnZNMvFWwQll`#dzyLY1o`I?>=?~UA1Fp`9E?H5ZXe^13w8UD5JBKG>j*%qmEt_<+bcw z6vL_!^&h~TB~lBo2g@bLAVtZ!|4}l(WPx$6*E@3DZNu=PNA4-D4Iv32n5yzy(Dv+Q z`>B)vrymV%Zr4Mf9-@ReGk=lm9KA?4*}fX7JPp{^|K4`W?QiCr+;+cp=+m7Q8%awHX(LnClMjn}<8h|C$+(Rqv z7Gh&6$cbQmojanodK6CDA6>rKlcfN%JVOJHbsQs^iLr~`gR>s*EbidZL*_%^VgMw^ z7%aNZtRJM-T;xgz%*JnMEbryS<}E^DpmCpv>kW1=NIIG%`RL_3P0(5k4xS*nGQQDy zJkNK_0eP_4V#_fBP=^*hPCU_G88$;ka#o}rwrj>EQ<=I=?LHgWV(Z<&6qe3jwLv;O zeU45}dM^mE9p(){u78CYxht!v8rj2wmmXSg9e}0sP$12!QPqf3u4UIjFZGo(nspQ3gaaS1AB!y?E-22J##}@1 zk*AE3_%TnQu=bX*c+@d&05}+9PGup**sZ~zhjz^Be5PiS#m_-PMFE^4;I)pc3bZ#Z zN1?Lq(Oey0-=uf7@iI5cFqdLhs0lSv4I_7KpcsB(f22MUv2~5xKtm31EBs7$umli8;GvTH-G_ zR^mtU=#RbK?9%&<(S~yTf>RwCw9`%};fO4wSlQBRMahRb?HQFELq-O2w75C`33A#! zTwp-a?f&E;x3*&Ep)zzq32hRklKxoNq14Gniv8+X0C>;*{Z0D#dz4AqSs#~P~4jULggt6Cni-V zZBJEto|P-Mk{1|Q`C=MqdJc=fep*A4Qkh5A^@90m^+~2f+q_U;4F}*VY0kQ3#lQ(f zE4{S;<^AJu_gKPRP8gfQ_t77-=HS>>8!*8bW!J|io8Q$D>BKI#H;t{x$+?#vGdeF% zKy1A5lGjBcr7?HdL~EM{JUWuIEcRz^<(X!xkg4 zlsM>RW6c0~{q)H|t)_uUxipNZRn9R5{AwC%`aoy2iq7{-LuLK+B=p_Em@zi^^mxJ= zIuQE1hdJ4McO$t%+EK7g6eX}d9|l0URI75wjqqcQU*~_}no;2g^ORI$)!Yx;0s}%T zXkB?-EYo9f_<9M@b-!we)em(EtdT>y;CzP3z~Uc74l>J(5e_o{#SHe6reF~AA6ltawe328 zrr}(#>JLe%=JQhtmb7k@GFdJgO%sA#y|ED2@wO%Z!jXL zo7g#k;h=0T+|}X;>(mfsV$t*S7y)Qx)|{OVf5%3WK1C{=1}5i?Hv+&;(kK~(E1xM7 zdcp(_D>+THnM8weEZ<&~Yv^@a)9|lial8`f4Lj$%7mhAHgzjzAr*0}oFE`p3{^)@f zLFyCGS|s~65t#(;fP65AvpYZl@Jv7P`+qgsSemzV(#5|{Ds&5LhjZK^X#zTEI3%x> zJTITSbEoU7_n(IeQK)n3>F;6B9lqUXgz2+xLG0EU@#N zpAwSWHpzD|u-+RqMhs)8d=*W7xImn{Cch_J7i+H1YfjRp>t5J|ux8%YLnQLc97JJK zl@$B@1T4xo9%b(`Y%l|q?!KH#TP#X);R*{&enKVEJ%W+|oq)^?;^Vyi2_T=1nOyxW zOY)p!<5h_93Wpa=!UNBwQ;ge(oz)FhE8G3gx-v)r2y~!p8nm2ikpZToh@KxQQB=#0*AS<>=-cSg8G@Dw_ zIe9#hIOT=jh)tvnkCGyTr-)1Cks(ELVB{nY{?rG_cnoO(C(BN*C-UMEdoiqE3^2yWMt-0cY(C;BfK%zpzU z{VPnf{aH|9C2_*pOs>UlxqSDO{s)WQeY={oZ*_A&sD8bSe5vY;d?{THf`Lp|z^a3Q zpfiNG1Zqp@hNA$b2jlDa4og)VQ7u`P0NK(P_uSVh1Gs{Px?D{|$-2((P-+*XDX`Yj zD!z~vkmH~UU+1*oc6S-P7|XJT{{;Blr>hImURhm!z`Scl3W&WwRY~P%xZc^wR!ueh-O`Oco_~z)%dMWcF>4Z*>(|SFrJztDikl>-q_>UkA|( zz=|!bkog^4N_Zb^5|`J~&Kc8+Xdy{Ce7Ha81Kxk0c^C1fp1Xq5D@!0C z{T;RRV`GDjcD|6_Tv52yq04~sG1t#mMQkT|)S1^vB>3Rl2gemQCk+6={h@@EI?bke zV~-?xnw|+tSgE>q9)~I?-~AEBov`U%l$`Z4a&CbWbbdQBohTej=|uB#8Rt;1C_S4$ z6~%0hAJpF0u*i~WZy74teMo?QT#y!R{*16~BOu+h3TI0xLyqxi*#2d7e<-k&-3yel z;M3?j-XR|XIp~7D$=KHB&lDa_Xca|E$*{G4$>XU=b>}D|X>y4l!K^z)7J1byOT|+O zgcO(`#3l?R(_qZcaelVbg)pb!PGg+tzKWn-&eI>K?mc0kjp4vfAI8Y=bq)G4HUk=& z3$b@bPdvxfF!oIwU=XZ;RqzjKm9XIZmC_7+trM?X_qh?|KLAYE4(u4p@}?-`4;Y4P ziISm?*Hwi*qdFsHjbhmkB1rqd83*h0uj%oL$D0Cph?LxqdqFXwtjP!stgc`#==`8~ zo~N9QQQf(|wm-U#d{=XjnZMlx42XDxUnmS#4o@y;^89?v8Z!2Y3P$Rgtgw4qIeL#R#}9h9zC#Dz+s`g zo=oZanCBR8Bv_N)^$nBuks9T;MPGqn=$o71jfjywHBog`&9m&aVkM|P)Gy()5n8|n-4&a{Z^2q8$_hK%n-g2K zjHeCNC?#nfGJ+39Nhd#^`*fDLea8T+L|b4D}}JaEg~ zO1+sc9T>UC_lmaw2r{H4edx%^h0PrH!B-;v%ldRvHh11m93@j*aA_s)x$fJy!FMIn z3UGx!u-pij$I>*WqxZX77_CE^-JgjLn(e~AV?H6c*orI7Y zdfLD=#7q{WooCy?qDVo%byOS*Q@q@%JEtOsGO9+)GhktngxBR|N@Mr1iN#iB^~vnK z>NBtpG1+3x+Jc2*@w;C2zlojmb#j($Xk;+{(_@7#V{sw$G#1|&PWP6(u5g?NQR=CJ zX5=SO19*2U^Uyt`lk2m0kgNKSe8@rEmge9nVx7X!Mcbm#Gg@V3 z@KMu;!kof^b)m^_HnN5jC{t8y3E~?wF3FsNwkJ@EmeNjp4kc4Z9>Sp6Lr0EGRGoyn zfQ|!6$d}Py`(Vf$E$rh8C4ac$WjjZK^z%LMdv#a|nO4erKqNaJ#Xdm!I#LbNAJ;mR>6j)MBNX0C^UqH0n<4%2n zW_d?Snd)1Q5N{~BcRhwfZRy_71MZGWQ`RNz#~-|Kr4RDRONKrJV|3^zrABg~banNG z+5}WEA0^r$-7tLFqjp*lLf^Z%;CsGvsV7s$-T0JDBYF zJ_6C?O)c;2*lF8CFqS-=w;!HA=RUXrJ@ytA_7LTj%(;$HqzSvIE~LTIqX9@auLPJh znYmpz-=$q;JbP8cxNMGdjq_W-7LVmA(`wE0f=ALy9j95$*$Zv1WuhC<Y6{Vome0RoA(diKk$-j|W>`jyXBY8nQ)ErW_VHs*vMMUXWn~ zdyZd-89dPQ6}yluoLml=@}N)nQH)@y=(OiB-s~$qkVnyQh1k|04-4A@!YwfOl#xt0D_DgfHA0u+Gi6>sYE=b%_{=Ocl8-}GQ z7px`SA@w@EMC;>nDhF}#F;UdzN8fK|wNC528z?j-(cBY^Qt}jse7WGJXl3UK;&IUy z_d|4eq%xQWr8zR}CjfAZDmjWZZI^kkw6iz~=6+43e8&x5!B4_QgC%#9HttvQUbFF2 z0W=Av$-YbOtEJA-=L0=@XyuIW*6iN9lYQ?ezTPcwBwLmZvLCyaf8cwo_c_(1>~#1~ z1g-y=pmg2ddf-b(k~{9@n_(W&e)NaylMhf#ITN$ds!-P>52sk0dpYNWq=$!3q-v_I zy`{oYa-0B5Y@Q)mdI#GbltwJ9pMX*QH_9){W_G^l8Na``S8kC1UeEFDd0FL?_-}gU z6Dws6FC=FE<0S51TO+LALWb|*v0_B+DTHLK^$*Ie0uF5@DLsVK(khwQ@8JsEepqq+ zD5Xb}&coJif_5s9Oir>XTPPZd6NS=XPUIDu2kcFIU5)P%wz!bfg{wIye>Enp(f@{X z(B)}=!}#$;+Ra*Ln`QB1i733Q{nJM$?kaBwegb+F5l(g}P391pdYR=nt&Xy2T%u9y zPX^W*L5w5F7oTf!B-IlGf}eGN_iOJ~o_PN*d>#1cG@gxS@VuJftGN|b59Uq(6Ch(o zHqJKJ1p4E=n>v%PB;V`(H3zwOh6w`Zt(Fzp%vl0@2lj6+e+X z*YxD){$63WCqf>s#2Cy7_CRw=XFpWHr6?rO=@lU|J4ob-}^oJ_j%s*eIqw5R1rX-2%5UkC{B=wUk?{}eOw5jh9l9y>x zU>`s;SXH`P^Fvvbja??RMpz~^F&LnihP@2Ay5Lb%AW6e)Fixx`xjgC+=|2IoU)Trr z9SC4DBCuP;w@oP74>>WIAAXNv9x_86UoF;DG9;We#F{V89Qtp2jOWJQaRuV?40P9aObt zsX}}?3jz73xX?|@W;ze_Edr4XQDNbo~$?l~vO zEWOQtWJUZ}YfmgdBCWf{JZc~-k$&-Uy5NGdX~e*9=6%sw)A2Oa3mGoPq44)D+JNFo zN2g1^y3_odw3@6`rpCKmy7}6TDg~0EQtrEM>dq;lh0}zSN_`&|+0h-mu{b6*r;3zK zqD_RMs5v!!%8mo>peOE$H>FeRjah;$wivRg_4qVj;HxgPpm(1-IEijh|AD?f_T<1_ zZf>H&>rkyis$mp7`iC60ls83_T}XSq_~dIh)cebz5=D^{D zNsVUE+&tFfB!~`X%rP0FH(lUI!OHgUlKnLoeC4F6n?jvqdt9TOTaz)Wfx&NNhHe#< z5r`#tt%}B;x6}L4Z)xl)NeH5?S5<{{@t;+0Q7UBcbz6+ z*2<$+p!5}pvT@XbU)5==WOf3vq|+Yv6W~zw<`FB$%lnV$->Z3k6BwTE|L0!0fL+Sj zrRSG{2QQx~yklC19}PUWmZ-fqPka#GBgVAZ6u$yF-24Ytc%hPugV`US3k8u+L(g?? zzr7&NJV|M9aKSU1h9^u4gN(%02D_Mz7B6USb+0&sUcARq|DkF&{VY;K;a+b1c)z_> zy}$y^A>m&G%=39=G*A)dc_nQxDMyY1{yv#iJ%7}81oC}S`}-$hMc0LIx(KdjcEsI= zjIV)zH%LB3(Ua;qQ24FB;{oT6{7YJ#n?BCUE?t9U&hVU9DbX&uy?yZ|)7vH~no18h z0{u$3gxp-t)>x}etY=1PxymM&M)0&=y zdx0oRHQLO6X#npBh5A}a>*UfA($?1IuWI7f1%;$eg_;Ykp*5u&I?jcnP7N0y^g3H@ z_3wTvhOk%YvpgN^SZYD$xY5q>j#^XhO&8LBAwx!8MVP`^H=tx>Ew*4V=Pf#Wq1nnC zxv4;%k4@b7bYc{{VZQqcQucP0cv8l0(r>ksS_D(UIy*y;X;t$%pW5)j3Mc+$5O_aw zE`C@!yIyEE^2K=650#jn(6c``rVAXS9}~BuoP^CfQ7`kABJJBhF~cd*;~)LbMjQgd6{>)a-V3^@v^G*c{mkJ8Cz#*PwW=G97zTj721^LGtfU( zW|Of+YzhAa$p5*Sy6ayWdHeQ*s@YLEjU8Du6k+J@<{E@}ogt$9zYDsbyD17`UDd#P zs#w?;V%Y*6fZ4Vv=P+vuSZ8MWcI?+)-__#ZF z3%aT`m|*VXvHWL-Kt~N0ZEMC)ugMy=X?R3j< zGg&#e92JsFiS-M_jN$*>EAFarV&3%jeXFe^*sg8hleJqdm!}o8&CH6zy-fj>b!KAt z9NzCs_|GlMxrnnbOz|f)zgoekQZVgb5@YR~eWsaP;`1XgtWfg(C%0M2yxc4PJTY+< zLl;VAgC>gw)tX7aEwHzG;iPNQ$*Yvb_=z%<;FL61u1y-3p6p1J1T<@M62Xwjru*!* z%hTvVZHpYwN? ze!zH_B<^Kj_lCtW-k_Fs;rWzqNrRWgBO6{i%9gB#3K*NWIzh_%-ApOWdD@vmXEKU! zDp{f0JY|A@z95g{Y3BY1cexI=;@5ef6q4~K`o9~kp0DFF-F3-y`a|;7-*;xSlx+9T zL`WF#k3TI=efM2HcVp|4IO*gf2=_0gTf={ukh&xOXn6UWN$dFJC!lxM_?bA=F$7OM zE+9_Cm0Uh8enx|ie9B&oC@Ej=f;V#pR45qq@%A3)))8tU!rCiB`k^l$NZ#QnP#@WKSFZBvask~8SP|57zK^k1+*6vHemm?y)s?7E6b z>^&GPq$PirXe+&>?_^-0Q?hm(Z-xvM`Cr2)EWH(>OfBWK7~aYicZY?vqa0lN7DS2! zby6suV38TkWn4OY@cigX6`*$XaK`ggPkv2aaUPA5XwXAFE1+x1w=iE@M}gLNh)`c^ zd(6Ekq=L7;AgTZX%=6(w>Wlo0jyB*HPVi@?@D+m*kzL{2g6| z@W$zkJRKy_G<>6`N)J52v_UkZ70$}$ZNt6KrR=$GaJ;K||M{g@fgL`SdbrzWt@i7@ zk8|sTCwK1?rAGeUfcozfEStg*K|wkY5jh&OT);mg{%dl?iSpy68SleTjp+tQ#n-Pr zoQpx)Ea#W(QA_>);j>>F&MoO*I-v)zVtA=+*<}>0*#NlnY9SBw&D0AQR_EQ@in_j7>QExNr&{d_|5`w)Yd-Au zIE;YdCAa&VcpI0Tnw(%OZ{uhMqo07jwa}+S9a)0h)awnV6vL=y5atcm7xJ}zem4el zZ%{>RtwWl$E&p0eEjMmwzoFz zjG~r!_G9B9=P#@H{YknqHiDMV&Q4fA6pI&>@-`)3MEjDnG@cTOodBCPOk7SVXDB(U zgmqsUHn;ZTrE4_Szk@1`Pj9a&rQ*U5Vz?Jeu`}@XgGY=dN^E#XEaUD8W{RixiG4^i zJKbp)yT;%QAA+5Eo7b;}PDK)E(C~`>b1%;L^r?_gr$fgjU3Px+>MxIlJQa~*11pbD zZ%AbS-4r6YiBLLsM_onQ0(^Z+b|*A375#md0<-=Gg)8_@X8 z8gI#(W>PsXFJE+UqVIEF5Z6U4@ zF!$bc5!#H&`G+UtE17XTrbd~es$c^Nk<;42JmuZLeRRKN<-I|X-l8po*>u?bJ-O6x zz~M`{%?(8GwtzNBhp@dA`wtNedt8hAv!BHb>9@?il0(MyaenpQdsZ9t#Iss#sN*v? zMpgy!uU_#j(_7_#o#dbL!ftt2GQEd;6UW^xvt!?;cL&AzXeMPhp0jhHo%iJxLrf|8 zcvy~(P7!inc;T+6RiTzcedI@C)nfJB5o3BXj{4C?2C`D&S7cejIwePKr%F3Cx3)J* zk2!U$kd3r=3F9Kc-7FS}>VY|HXTb;mGJx|n2jsr`U&`XW%v>s7J5}Q`^tGS6oBj=< zXRE-!_I~f@?pz?zZhP;#k-U0HQfkIFxa8SNU*N4;!SoXKyb?*NiNh=+-XK!!3C)&| zRr1=u@~(c+_!vDqawBE(6JRJ@Nx({^ZioasJJx_Qv=J#qf%@{}$vkmvW5{V$$60O8 zyeZE|s7cAiG_p}TIBCM6g^14Cx(E^#I$EbZbbt&57oELcnoLkKGN?ouRy|r~6OC(-Eij7Go0;It?n?{0`f@R+)wIt|?+IhX1Cw zSih7bD%3y%On*qDjO-~IoRy+{L--SroA*Q4%ysDa{!c)bu{I>Q)UI=6c8@$mq9rAj zA#c!mskY>i&hCSI`MfEVZ>i+6OlBS7>t!6F8)xkBpuIKg>uG_@)ZLbn56-zRHBghA zOU3xt$i*tA{5&-lIV`R1d@qxF&Jm|&)WJji3HS!}XQIYqLj8iH{q^ieF)G^70EkMQ zZn}eo+g-}Bbrwyg1%=4dU5gBbcqfQDmweiz|EIUFfQvfY9v>QpE`_1HqzCEl7`h}S zWk5a!MyKncs-F>_7z2E=N`P}ch=bU?P-oDZtr>QC>ak7ZhE}d5eac%PXT0p`Za9OnCMC~u30*+x3t*K1Ywve7 zFn22QS*6W`V)eYj7vfrJ<@rKc(0k0uJF)y&WKXs8Z27u&^e{&C%*KuTa)XP~B>ISg zhtqSFOeS7UfTD9ks0wLWYS4#;UVtK1Xv&Kv@d@;Ip1}s5)7+($F7Motuj4z=iD}c7 zN4ZB%Z0Yn9O2I~YUi}Qd(#nR<73Z%Qy7?HGv|;gu<63#Fy-W`j+TgS?WmdeCiu5< zlwY)KDU_Ud>U)aUC`%%qlym<(lqEtKgzTR774XIp{<2aQAAo>_fQ*6$L`Fva{TOh6OMtq~J9fDmg)PWizr z!FxUwH9JUM+e|tKB|8^-D6h+B!k~G*G=84RDk<=ByvT^lo){bzyGtJd7_KpUk&3@J z$<<}%&qN{ngy9L@yoM|h98PO1tKz|Xk6D}=y}Hm%{I4dqDx0q=v*SKmQR$cx2W;Y3tH|r zDz6VRyvrNfZZ&#c>dhB0u+s9Ug1T zRk*%S$gIk@%N>0|Z$7@nbB#lvvd_UspJwwTubJx1#5a?ixli&Id|K4OTJ50-xqA{c z*%c!?oSr-HY`ohpaK)jzd+rcD1SNebua4 zQHXt^P%$ojCaI?>6Q?J9c|A*T8UZFW{d;&hrc}EMb7kPYyD+qx*VXl0OITav)4_tIKlMk1 zX@N+^)V1x~2T91P2opsvvNp2BnsGK!UwwdK>*Yw;|^t|*KKqr zm40i}tXZDUF0fhf$eHWHWX@wj1EYMtDT7^Yc+Y*}H>5CO)Q65UDJ zXs>%mzlAcjl%PcrX3Y{12*mhuEI$+q5%3gFc=|18oi=mw!pgHxXsT5>TJ$ARU*6%< zAfN&d@lg*ev8c$Kn>pDuxsJ)sz41dK>qm&F{TN7&<#r;`K}^{2-XghdKXP`mdJ;zR zK0|0zv%;}e-)KQ8Vy%}g$!s zJx_Jf9L4(jC6>83%7d&Oto}F#|l4+ zN`EUI&U(f>Y;GalkPw?ZZM@pC0R@$Z*dA?z*!;?VZ^ATW7@I#G41%rJT=14Lh(Lxf zUUmzlNY8F8G@o&S?_9_P{O~?Di z6VzLzAZDs3hv0I=@G@FGDY`cXB0@D^0cGRU6_}z^;0(b}#5m$X!07r~{=}N9>O~s= z!K?ykae7i5=_beq0L>x-^>NzV@J${^GMteQGy5WG8*Pe+zIS#KKx@cUcOUj;yDl|V z*by31Kgl@$957C|g}Tt-CBIR&w{Si2rs-{CUESj|-P8S>oyCQv1;e?f%M!20zHW{V zpZ0%W%b+6s`7MTyqhWHy;P7~+<%?IIgKl<@Y-$nr~18Qs=KQ!Cn0CcZs=+ptf z21>bbTL`i(q>v@2t_z#~JSz`u&Mk?I_Zz^y+U`B|Cxn1j=M{xsjyY0vLGB*TYyaOO z1V(0N^xO1N&J_J(4%*#*1fhs8fk|Gj#l};n7g`^R4D2pTYKkVl6szAl1w{A)u}N%* z%4~}aC>J0onu$%gHF)CUwv9MK*j~iotqLn0He=go;VZ^M#5g%d-s>vTc78-t;rZab z?wa8u19JpzkaM{Z@!c0v+2q!N2#QOJCsz)Q^t_P8>_* zS`Yt#{0Lpf(Q>5f5}AAL)v~E<@JEe2MDJ((C(>VebbXKf4@nl%o9FNo9N?Qb=ec}-9Er45G=f@t^{9+nG8@nLqp(y->b7tw*- zmtIg0q294&^{_vL4p^c>K4SMgLrL!I&pl)N44vE|Hm~K7(sQs1O{pTF-#CgN`P~(eEnLi~A zn3pJYO_e`nkZ`;ef}jz~m}JKXB0>&X$ftcHA~(xi*o}u-%rZ($G-dD=z|`QY`RT)# z#t(Ee|8g3Ab2rXo z%U3-qxb)GpFfDR6o%^8(x54^k45NeEZ-Qy-sZZZQ`MhSyr*PtT(bbo*9+vUL*1VNB zO>|Ay&>FWJ9bN@+yE4U;2|%uyXj{L2_C#r5#Riq_eZ533gdIt-97W1u z33vEN5^;}pRa>p^cR`CBjpv%K=<(50W9hfz3THGwx9)t1FVn=h@bO91BZ{jGJ@rpR zqoTcVG1GOd0Pz)UEOtI>f}RMwS#7JJY;#hdI#`=-)3Htj22IghBL=85Mo<~&i%Yom zh-2q+&NJ~V2pvKha*wou|8%kfhj=PyWW?6#=%zz1i1v;n-{YE>?JWzw>+m$hUtJ7D zzNZRB?};Zl%AwS@=!@{wh+^elmlG-c?@1XX9XP?Uzt(u`1PLk$;ropFHg4yr^K7C_ z0KDR@%}6+S_xRdhZWD2NvK~pEfK4LD$uo~k1)`1nR%tXX{PuzwP4{*?OdB@~IYJmv z?7sPfXa9o9p65bdV`hPFlk&Fq4soy1JwgXNOy(N{n$R$HBS_rYkO5MQ4D zSb+&i<~+9daGCouKf{n$Z7=3lt=jO=-ZXO0@KIo7sj zwGD{RRx&A^GEmFBd9zW(Fw9Ou6a+NsQPjkSuN8AC0%58G=EYc#(>B0UxBSHDF;GUu zb4Uv0J_E!Po=I#nE8>R(@mNgS(=<&tTw# zQ|d){CHUv)=8}#QiK3pH1vIk4f5al;9@|EJ{?S5R8%eln^Wh$a?+9z!{m0pxKlV`x z>52s{1}Z~WVNmS3GG{c&iu=IZNhZTA-e`yvUWlM9{^=P*(zkvDACb*vwWKjAN4HHJ zT_v&{WoxJlpRX`vMbK;Rn)~5Ns#BeK7HD)xxGl>{YQ#bX%J$E-^vvyN<+`C2NyBVE zy62hN1@l5>;opaRaWhC9mK+M-CGa84E^`~=(+;~1lH2=F031P@>m!!+*G8@RE^-O) zmRx1&&;W7rMZjxuF5L$kz=5l_dl^Y=yvHckKKK6v7GaV@qPgVGo1f>K$;@1tmTf+aW^K1PPyJB-i`|M&o0csm z@oZCpcaP4`GxEpVnR?mydliv7Ty$-~pe%PFH7fN=DYa;lO|ea@I`v(4ZG)Yf06*<9 z(?Ks0lv}ZdHCpZI5&~wjb9ovzn2nDI-0tO}eBz+is-95e3`jRc5orN>dJ`R;v#cLz z^)DABw*4MCeG~C$r|BNxNehCj`kQ46~pLyX$ znd9$DiH6F*LO-RU?EDKP=2g!|!%)^iw63miIt$G$JnMwY%AC%SZe~s4>qbWX4t<(U z+0#gzlH&dvVXbMZdmU8F$ge(NhM-=+YKU#C-VD7)UCOjKH3EQVwQDsY5pp|1%Qyrk z;Ks2?qCtLg&tk%-a061tircbWELn#Q3D&(3qdWuS zU64Df$~P{>^+CMBh5jpG_-1`j>n7xk4oCVjCWPj?wKtpU%b?e3 zv#p35eq8)!uEkh>_-1C|_2(na@OYxVQJbO-AaKQic1fs(HStXl!isH~|nhj88pcF!fy2-eCTMWm3m{WqRr9!DlI5fy_& z3Qrv**)V^gY|L7B`!$>EOnl?RzJyKIdiyhqmr)h zt$r}RpF>6gu2l20GW9vcVwfO8P&nu<@Rseib|smNr57;AXb;!xRJ$u{1?5@t>y;qd zIw{2S4p3HkH8NV0l@(Ld3i2|Rea>W=D|ZCnYZEs035#tkZ~P1Ym56Go>?>1Fm%uc) z&f&`YQeL6(%ZDbVP6f+(YP#CDQ4P5$PfG%;;)fkt{< z4mZ<>BEQ2-ZPem;BuzT}lG@?|AccV&HMbDUo;K5c?O|cjP@t!%pxEn)rtc$WXRi?; znRUCpt}T_#o3y=FW=CXl`^~f)o3cAG)SKU|3o5pRpdY z^QYSwA#`lnCEkX#9!d_$xp-9~`sWM6S2(ilj%@9!O;2 zl5KBFE1!m<{9ow!wGjT@VZt{pE!Q(5E@U#o#e*QrtZ#^dEu<-JVE96m z_>RalF^|dC@^U-aogis4QF(CH%kkK4Ha}cmu-vUX&Va8iA9hkCFKCUHJ>nq}K8|$3 zLdL{T;uVSB{VW>1mF;R7CoKgTHT1#l( zJR5YXtK>)U>JmGq8#cmLlRLYgsfv@B{4l~oE!R+a0u=6|Vt$Bhl7%(u%CbtWFx|#S z>=h(297(xVA@&)9eKd=iSNe&&q8ZdxuZJj(C(;s9QcK#)>5RHESxWeU zwM|UwpB`Gr_MRTM#fw@Iy@?ZS-%LfCp@EF|K=H8_BkbaBBmo>Jl|9(DgMx3L6u^kL zfgq5XI(K%}fM<{3P$_kQsnV$z;%gN}KJhk?qv=Gcj(%=#a5;I091}2tOKie2Bb_>6 z-%-EOoA|}>ut^V$mkd6~Ug+r4J9{A+Ug3cjlRWvMQV`+<^9`{}BXgA2#W%U(qxB>D zOpn1-gRU%YqfjPb%&xBDtTGJ#gJpG0Bkx{*W*!^5yT9si>1)@kWpiZ8$N@=EB9rO` zGB0yW4akl-uH9_{+^YHbrfuv(78n?_;!JRgP1H6sI@ zgau&as5=-i2Tl zdqj7U56sN-S&<%$xau0G#+kJ*4$nY;_CYZ|-Z8YKBs5Q%IgL!&K1Q@_9Cd3C;}wR= zBaNEOW{M!YAe%rLJ=4K}o!T>NhP&@6ycgjoe%{?WX4xBm#*oq05QfWyx$-4oqt)AT zqFQ9etehb?Lduf6vBE_%OXvxEJ~9pFJC6bdu*+jF(VDAqCjznud_sx>VkHB(ZcFX; zo=%UQzoCM>}F6^*4J<965g&Jk-=*7UwEmBJ(KW?_Ojb zJgf_U?QOsM9}rUHYpUtgZrr{C;QvGleA_mGh>VPahzh^6`uo0#AKNAf2#JVMZ{3!l z;Q{mA$8$oX=Hlj6r@c!TEh#0f@#D`&q@T7;K;65OA2W2 zyCi&co8}iF>&n1lCDG<8ye~X&rrw?vBn=EE{)xi%vIHN6)Li`^mbBgwY=Npq*H75O z9jXUo&ES7R42S}&-ezA(z>ibW+v*72-DdB$DSQpFe#aVoX_@l~g;G{Bw|+)r#}W>iNu^? z@g<17lEaqKCFwP%i3kP& z*DBvgF}O?7Ud}$mV&XyPv%;7WgXp-b`PF;K3FOwY?v?@f$Q!$}(~nWeLN~jt^3}cN z@2q`5KD^UeTTN8Xprox7B9uj&2_dY9SJ!XqirM6y^7%{0RW2wU34sLyRu!owQ5-L| zf|8tpWM;%bHzKkw4ckCeRm|Jaw)oy|Vk|aPrFuo?U}8*;Anj!1R}SyUs-%h|3rnqZ zq!dEy{#c%>h+#u_sXgX{nwjYtzgRC_Z+iRNeTS#f@-d=8zVq{!)IaC$RFYCAJS>?@ zS7r)t6hmmHSW|9|_lA}jrC%jDj&$u_9jQh_?davUk5@^!y&|Kfiq+{F`y&PpUI!Ke zO7ktuBB4Z>BCYGos#Nu*=*tT0??pb6;i}1fX`qK1-hl(|;2 ze8|El$`VZUvaIM;njJW7GP1e#Q4RsY>mdtXW?B`i3mg@5aJ0(SbIHz%_7C<2;}G^6j4jC^sd8XEI{7^B%|E)=5%Zkipbf#l?>6HF=2Ed zIodDcSNo@hM@%1UC^d)ayS4Vg)y45)%(U9%>hUDRf}N!7WKUirE;sjM(o^ussKnXj zmFZ#>5EwC_M?iCGhbG}Q>m8G^qM)fL&Dq`oXthA|11BfZ>+j2?U{T&8LBD@k-^zuk z<{#aPaThs&mL~AD_8yY#KpP!=*V=K@Y|f9y@eX*?Z9iO)%IC%%Gu|z)MT&jH(j$ez zG@;*7{3=Ap0;jl}LMK9~gv3pkYp2xguaX7NQ~M@saPoPCl6rS6Xt82bfZ*(1{GWP; z4Wr5<4P{DH-mQhL0e21VLiUC7NRe5rwkoDGfPS>iYFYe}mrVd@c z!;2rZl$?J?spSOJ&AY_fC}mEGzXDubb^}DhcG+$=HfNB3472wq<1a5>8<3V^O$OUZ z^JT$?2W_mYdH?SS{-mTvZ(BGRqGe*zm6F{&o+N~P<0_AI93+Jl6v zmh6M$1kz7_2k4=5Rw zqR?=(a#%)QZuPlZ3Gt+nszwyZ{Rx4NLq&e>X|DE%yZ3B_QsFy!Ch1$=d$PGCC*%_B zjHM2Bh7X)@NVV^1Calhj{HdmYNlQRG5{xe6dzM2U7w=Lf8+#|l@;UuI+IP1U{wRQp zPxv1HX$braS&xp6tu$zoo3qu}2kk?7)#D$N(b-@ z2l=a-RhY;1ZX5SpoMV zp^eDx3M5YPAAUI`9bvUJfFePtp}E*#|NLn1C@M^1Oo8akdMqznS+R5?nOC5{P{r_` z-J=S@t~^O%)(!_%H*9TI`#2WfvcWNYqVPczjXS9jT@%FeN3|a##jKwVB$U!eTAomkV{FbB^lPRXybA$$R*hA#rrQd91#Dh?E8jIp zX@{1S`G5bx$zrZ)L4s%SOX~DHI(~}SenI`|4AK)&7%sS)ENoJ?EeS{Ek%2nU$by&F zsJ{1@Xu2SYn1@v*3{pQ=$WV8(YHGA}0zIbJ{};#l!J6~(`Fz(EDvh(*ubkt+SHR6% zM0oW)bzeF%2v}vF3x8Q0t7L@@GrJYPK?Hj=Y-6CBnxhfaYCslakzbz=qHeT(na2#j zeEB+uyTD%I%OSi9zkn}7|FQS>mrb~T2NClz^_Tpqv;KK|eUQ=Pp>{@n36=XdAkbEu zYFr9$+{$ki(;Ks?pqJEkCkh?@lk?kGZ-;Z#DRPeZ-0gjA-}@=f!R%Yf;hkz)F z*D8&H#b8e32WR`|M%-r$P{eDER-_dUqw1P{-N};-t?f_Fdtsd*2-DwyTKqYhA=nrA z&TS}4JCB1{$THWBu~;Zh2jx6YhmMXys|2TLB1C(qqu9-CLa1^lP2WwmRH#7r?=x_2 zm?lzH@yB6>I}mNB9+LOv+9y%heifG#S|=JjIIskn#x$078b#>1V1@KvU+CE3q#KL& z6jT*Fxv8k3wbJ-bJw4J*A+qBk#Iu0lGd}!cMh)u>puI@S6FuDCK?$%^pOJ()1 zwASh{ZN7!Po;GjcJ7(XEgUjo{U98^16apEYlf_seM@0($+!raM@{n!1{F9imCkoqd z=v{x0p)TGLu92J!kA!=$nvlaQ_B|5yTO1ZB56|`+=J%U_7+H3C3oO`JD}IFr?zeL! zsNM}U3n0ZZ=}_$*s{-#v3e(Z}7g$1PNU^Ym=~R1}2a;pFb*ilep5f>zth>JW_g+}c zNtN+~FWutLTEf}lgOQK}E5W4r5ooK}V#r(+`&^`W{lZA{p-N6mTTwzjY*gdC(-Uv{ z)ILY}Vm!TNOMB4RW)#cOq>o>M*IDws)cF3OCC6JpoX(Y$$o6@uulkUM?iq{dX9LTm zBhBDC!48*H{y+BGh+(mqz{ic5i#vf{#n}6W{6Em5$Y0QZQ2$gDJRL&KR9nu#p3hjP ze2$Oemh?o;y$A#f!xwoYi6*yiz2F8ZDw@+8Ns_6^DOT55%DDD+IMdAlv3nvdnfpX^ z*cErRHq@N)1|mD|*pWCxEy6JFHd`@;hu(m-i08*6N=wyY%ZACR;K`-MxSxmf8xuXJT4;qb{S&{GEA zXO0_cghD)KW_6xawTm@w`X+%$iHF*<>711H=9Xz_)&8AV zR?j&*nd5!Gtqk=JtM<|3AGIJgB=KnARdvNXTTSD|UG>b#|5SH}6VAjKKyXPiGg2}$ zA}7JY1?ba2vxs0~NC?lt!G;MXd4ESovWJHYCoYcF%F2Jnq6_ozUf|`h%*3(GM6X(d z*rZ3!{E~s0^m6&l5IGib*LyLebi1e3+0Ray@IGFNv*!s1dGo7FcMd=C`{2=MSiDjk zX3O+`+e_91G?Uo$Xkv+b&lI5zQGK`RK51yIqLH<4b*u~&9&)nwQ3;`Vxyy5 zC<^&gB5y#2-2?Bhu8?jTryjuj`R8PY=?f2j#@GPOcr7bdO-b$5q7PuT@kb0oqbbGI zFe^1-ZTn^*u!t)$wl79UvO2!RMG9lRj2C9G8~S=W$(HdZ9^Uf~XN-i6jS9xo4J%&! z76P-KS-NE`q{*#k=M7XjNhr$eo6W21;|a;F<+g+Px}V=GQ3UM5f;DoU zEo^I_KhjmCe10--5mu9m-mHOQ&n%PgY=i<@s~rN}m61uBw+y@&>zTVIHa8@>Izu{> zT4zfBuW*|{g_ZE(yWN~G!9EpSA(cgju6zviaUmq-uN?@nSy-i`*BV#K9;k#{-FLEp zX~xMg8X9QF<<%s~C>6+R%+n&f4@BOPa}YF>A1&M0^yz_*vuooPgWYFC^m$Ub{UvVJ zi9j#wT$(*I$p=7HC`)s@;6*srA`eKTR{@7Bd<&8{=MkcO0u< zwTyOODwF*?l=yavkKI1}%-knS@K%%j_WeyR4|qJJb%lK(|z(tI3Qti>Z02gS&D z+zpKU^xYqROJaXZ@AaVfDZ7rb-1E<040V@czMJCfip?CM!)>|w;D+aLV{W2Re+V;$ zO{5Ln&^|k=-M$d=4<{A!g{nSk7wRJ|X@ppBkI3QFim?fpQJvtS>rcgEF3L&}q1-dw;}U)Q9K!6YW0|l5Molqx8A7poYUo}7Ksl1= zxnfqE2)QkQXDBDM!gn>rudL!2^Q;L#T7KGIT7^%ioDy7#^68EaOoI0E+_sg9wlo&e z(T*Zqw~0juX}U+C`ZmxDLXUrge#yDJ{1qU1kO+0V<~u=2!Im!#Dxa*pA#qI+vGsRP^z zLZ5ND&s32?g@#w^W}8+nsx96_a(+;y5jlX%s#8h&7n0|ph!+9`>!c6kW{trwiR_RH zl?@c>10+}(Z*@;1sJn3``sA)%uz@AB`MEStZ|86mpxAH;mViy>A7h;Q)1^H07+zDZ z0TN)nNIM=E67KeTHu~`$$!mRoGWnHA;QXo|=amJD%CZ?QrsZ!%t zChSm!Kke34EA`|-G*m*3Qwk`rDi6xbtI2w45nGRe)45D(a5ReyQQ;wAtBjO(=n~$C zD1T-OJ4OVXx87weXOtmu!(4JcSLCzNzzTB#L-^OzEAhF(;$Sz zE)t=cS-W&on2-1!gtR+JfLtj#-Z}GjFg=K68bofWDbdNYh68GIDj%1G5+j@Aa$2>R z64;9b5ovrNhkLR_o=VI)@8%xMP77KQyOJ+JzXD?1v|8&av1M&3ne(zNHMs7}s#w>w z766PKO^BzSoZTyq@&6zqRK|fUmZI8B6qvHvFl*fXnMG+==FMUccycn*1zYkoEK?J8 z-on5*5`wK}jO|Oz+;$YrEJpx~wvlqmOs>*QgIM)3=`QDada)m!$3Z_Zmnr1*c>+a3 zqK~L@zGX)U4W3gx{WO;PDHGhLv$t&q+;bz_zw%fvSGElpAqOXAgpB7;bi?AjMcHKD zwNS=qTQ1=$jqo^zsA%vr|Uh@jc`Btm8azpmSve}XJATPAo)|y zy+Us~!R_1AloZ9=0u%RkcWdBh$FXp8@vWg%a!)DyHA@QUNTXMq_#W6(%X`lk;n*n$ zq1LXI$@Rq9dKt|nrf;Bv{orQtgSQjWOUs=$VYA1BDEr8fD$`{+7WDY(-o3*VPb^3g za@}QZ8`{M~U(4xl8*qein$U-jV`iB^358I*yoF-XOe(G&e2OZUZW%_Ii9b;!^Q;h$ z2fnPYWx<@CfjQo)6s-og5~`Q+OVD?FPNrm{HohPt1~UlB1rE(W5b7ZntgFcm_A|#| zezWgovMExJFG-h#LRL78bozbn%G~`v4nx_qX$EWRR35i{y1-PdbA77(-+0PssCPfI z;kPwziRR_bHHziseI$3Q?;y=_w99O+PI)Xp{=>`phK;=R_xH9O<7a1F82S9SJSG#a z_TEpL@%6)O!|vZ_xp&xsneK+`N7DDVC=xDQM-jbT)EuCDgQ5)ub>P#D^!c$l*#93A zDC*+@FHfemmBc`we{wt$FDt6GHr7BPX|nq7lOfpj2$JWni*r{Ya)_6MC=kRNi7kgW z-Rh>UF0O^Ndv-6nMDV+Ijp92KTFwibB$N`E*Pi)ZkSp3NW+HCUPjD%cVVq7&La`_1 zhKcw&xcB)}(XO8KY=-*JE9n`+q5pmZiy%;vv@kmvGUv~hYh)R zGsp2=1DH!kBIvPbo*6x?7XTy4J}@I_c~Ecc-q2cMsxwcV_H5v2b9v<~#Wez_n2rAN zhsc5G1vgU<1g)r*@f>$0e#^h_1z}U&Y{Gx#g;hn9Mb@!)R4h z=o2Cof&I5#+3pw*m%e%|Rn@id*MpIz&`=Oq3zQKu2+(}pOjP;_g-y6kX{gyZiG?gl zlb=9b*^kJ`RXaTh4_FBcNq@C3w2x zJ`x$~_zIXAyb0Mgnrvv>C@%w8C`T*J*}XCt;Em5Q+45eX9*Z?UoX`>+Kh!MiXw<%Dq{<2!`E=jODeF#W z>XRUD)`1@Nd4gFY8lDFfUY$ z7#vgSXY8R_4hXj|6g9QDZu)Y&;X$Ir8Qs8@7%L>!@r0FBI8U0%wa{`u9xH7$E3Q$S z=dn_->bCm31=jaQ8zJF>z{%%w?I4;BWhATmtpl3sZ@UaMFv!?TClx{DQ%FzFs^s!e4!Q^0DW}Nx zGCrXZn}0c^+ZS3Z@9rQpM@aeza4W=kOPJ%FGw7(9|127ZLTTU-JS>ae*x?>iyMCm&9&TYL1irmlFS@GRR=!AwKl@uYc(l?{FUtarp1c zCtuL)-&-FR?L8K!7ws2mL~EdY@F4_VsUsZ8aMP~I16c%W!2+Ng^1*=$b<5@gC)mZe zJ!goY9c5O%dj~Nc1M6uI|4p%?vUo`8vqU^94JE?KjL%augY#ZXdv3aCy5q+07at3n zOkrop&qwiI6BPe@*Bq|{cjvhCulp0^s$MvCbzDGzPULIlb%&aK)le6I+A<{I%hLql z_!NRHFUMqjg+Y8`DuPXUag4^)hg{W zf|}0BZR&uz`T*_rj{^=KO<~--bUHR+9$x79m0AS0IE;Ql)DJTS-YkO_J&ULVh&pae zoNJ(yhOU89gssHSMn5(3U2B!-y_qdKi}ZX7uk7K|Z>zyyAZqMafpF$4wBK)(?>#;G z?9R~^w;V3`*GE<<~=Iv92M!NEDoa2vfJh0Lqm5{4AGh4!6A4%Qy)LuXw~rl|1B zdm`RC0q@pUOK+)B5#O)!I4?no>*>EW_ft{KNKQk{&c9T%>O*R>Rz*vPWmlg zn9EMzWX`cTweRbcYuxEU5<9X7n;`%$VL`_6#|*9 z-N@u_|Cj~p=5Wl39f86Xiv{mnA!6rGm+7#(gL3Uuo+*;;VcQ`sJ&N>_=8Sc6<79q# zvk?14aQ!{4s`C`tf(H7hHwj_VCHiylf9L~eG^IwM>4KIF`2pLF1}sttSpxQupuzY< zv`fb?mHN`XJ=M42%P5&OSC^Mw4JED3h)y^ph0{znMD}OTYr=Ak%k1`aGxND?-FsIk z+7;N*kix{xuA6QoV#&a&5izA`;cUhQaP#p!F-qVR6mAUOq!toTGwUrvTs{W3`*zR3 zMX{@fo0k8XP(tZkn<2Z*H{0>=krJ}qV;BFSt=7PQ7Sn^xs^sop)E<_88T9fgi}VHj zOdEu=5=}1F8;gLqNy`-*0&EvAfPWqcn-%b8g?hGHm&rF=mT2tz?2LP5G}cgV?*9jb zv?nWhP^GT`l|4$$nyhy!#J9xdZO-*-m&5|+Zjxukr5gLn^9-H>i;woGfTwet!(LB? zq$cN4!yj7`?f0@QL~Aus7r0BR?gpgIpa@q1e@iyN7N^$9JTq)Fz;$aT{#PWYeGxe9 zRkmD)*7!%rA1nWLnwzF82jccEPtoee-b^2hqGUD<_+&-e`4>Cqm>3#%0MHgy*(a_# zW=lAIBXo>C_lYyBW^(K_ZEf6XedA^esTYw`#Ze+Rgb|ln zKi0^$uYt}$5wffjTf$G|tpo~)R#UaYod*R9$j5$ps0w}~yQs2s{U95nCCcs94P~Bw zaaDENXf31)Q&)@)PxAB<(N&3DxZU)<4Pk72q@$-%KrIqK4n3(YF0SYq)Qhw5Zsh-7 zdk6cmuMx4)TW-a@c0?R?w_i5U>|+)BU}Ip=(_%Y1DSE7%@ahw+x+cndBCphIqm12R zi4ST=c~PRQe>V6(6w>T-0_BqZk_#;!{dd%Y!Pz-k-@BkM&gIm_HNPs9ZpNM2vy}rZ zRL&oS$}%E&P<7m)2yShJRf2(I$PO>i`c4-a1{;TW@a|6B(x~e*~sV zok|SQ!;65p2rgMR-A0!+GH9spbZ*#(-*4dDmTTKN(XlJKoBk9RK1y}(uR4kZNJDG) z$1w|A&l=Ll3~-3cfh-WrZh*PPl!^wQ+blU>v$hXQz?yk8?mc(LjBNWUabxt%)AG-- zm!s1cQ{4MbyMZP2%7IKVkS=Yp_*?)PMhabs&tVs?s)lbF(2c4- zhQthEZ+sSPMf8=ms5bHHSkMPKV>eB5bs&~%W!$fvM7@+8Y;-i+Gpo_xV@iFaH4y%M znQt6iVv7pa6_(sIITNcyB)80SqWcQKD@_PcZ1%~jPg#YeW$EOs=jK;Xb$~HD)$29t z1@?d)(9L;r%!~3dhg6nV`s@(9T+84X*wTwSh%c~=lz09VwQdIl8hWSz>93}bE_Dw$ z|FB7NCV?4PVLB-b)iPiwh5B;M2>K6>uH_NY!?dH?^vhJ7U5wQDjPz~!f-I}?Sd@p+ zY*F4=m}Th-%h#Rx`{C{IrgY-JvMG^??VYMtut403$tnQMP6pamzW1CoiVj6mZgcXX z$AZY!{8A|q@XZS&328IGskcH?q5!>=3L-Gmw~pSoSzTMr?(9&FJ(r0`ZnI*rRs%FY zBQ*6aGas*-Ln3NY{L>fN>%N#16an5u3D9 zY4HUU9YL3J4U92i0t9@1qddSiL-1eTdM-+) zg6;I=;R~Fq-eFYQL)GY>r%9vvLG_~<;T$|V%_{{pk1GHX>vbrj-G0gyzI?veg?$vC z>kdjJZj_spT9oM#UsTk2XaSaSP{j1Yxdb9mOCioEqy|XXPh+1pE%*j_x87Fs+d;ae z@${&B@oKw&?t*I0F=GZyFs80hhC8-*74?1gypjJg;^{8!kGDkM#$4B4oKPFj!uQUG-dCNPD literal 0 HcmV?d00001 From dcbbb4e62968137971603e035279a6cb5b2ad950 Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 5 Dec 2023 11:02:49 +0100 Subject: [PATCH 03/34] removed image due compile error --- ERCS/erc-7208.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 828e73e469d..247c9fc84cb 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -21,7 +21,7 @@ requires: 721 - 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. -![ODC Overview](../assets/erc-7208/erc-7208-overview.jpg) +[ODC Overview HERE] ## 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. @@ -429,7 +429,7 @@ The Metadata library includes functions to generate metadata, add extra **Proper **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. -![ODC use cases examples](../assets/erc-7208/erc-7208-example-use-cases.jpg) +[ODC use cases examples HERE] ### Wrapping of Assets (Example Property Manager) From b51d8e6bfe8f0f2936a98965a861e60b11d67d94 Mon Sep 17 00:00:00 2001 From: galimba Date: Wed, 13 Dec 2023 19:53:34 +0100 Subject: [PATCH 04/34] ERC-7208: update compatibility appendix --- assets/erc-7208/erc-7208-compat.md | 44 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md index eee7cbe5b1f..51381f062e8 100644 --- a/assets/erc-7208/erc-7208-compat.md +++ b/assets/erc-7208/erc-7208-compat.md @@ -1,38 +1,48 @@ -# On-Chain Data Container ERC -## Backwards Compatibility Analysis +## Appendix: Compatibility Analysis -**EIP-2309 (Consecutive batch minting)**: The ODC standard doesn't interfere with the batch minting process prescribed by EIP-2309. Instead, it offers an additional layer of customization for NFTs without affecting their creation process. +We provide a cherrypicked list of possible points of contact between ERC-7208 (on-chain data container) and other tokenization standards and proposals. -**EIP-2615 (Swap Orders)**: This standard does not disrupt the atomic swap functionality introduced by EIP-2615. ODCs may be involved in swap orders, with their properties intact. +**ERC-1400 (Security Token Standard)**: In aggregate provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations. ODCs can enhance ERC-1400 by offering more dynamic and flexible data management. ODCs allow for the storage and modification of properties related to security tokens, such as compliance information or ownership details. This integration could lead to more efficient and transparent security token offerings. Additionally, hooks and triggers can be implemented within the logic of specific use-cases so that compliance with regulatory frameworks is achieved automatically. -**EIP-2981 (Royalties)**: The EIP-2981 standard for royalties is preserved under this proposal. ODCs can have royalties specified as one of their properties, providing added flexibility. +**EIP-2309 (Consecutive batch minting)**: ODCs are compatible with EIP-2309, allowing for the batch minting process to be enriched with additional data. ODCs could store information related to each batch, such as metadata or batch-specific attributes, without disrupting the minting process. -**ERC-3643 (Permissioned Tokens)**: ERC-3643 proposal defines *Security Token interface* based on ERC-20 token standard with additional requirement for sender and receiver of the token to be approved by the token issuer. This interface relies on the *OnchainID* system to provide Identity information, process KYC and other credentials, providing that data on-chain for token holders for minting, burning, and recovery of assets. Compatibility with this interface can be implemented as an ODC **Property Manager**, with the added benefit of a more versatile on-chain identity management derived from alternative **Property Managers**. +**EIP-2615 (Swap Orders)**: ODCs can work alongside EIP-2615, enhancing swap orders with additional data capabilities. Properties within ODCs can store terms, conditions, or other relevant data for swap orders, facilitating more complex and informed swap transactions. Additionally, Swap Orders can be bundled together by Wrapper Property Manager, generating bundles of orders to be interacted with together. -**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. For use-cases where NFTs are involved, the current proposal for an ODC can be leveraged to separate the vault logic away from the storage. +**EIP-2981 (Royalties)**: ODCs can complement EIP-2981 as a Property Manager by providing a flexible way to handle royalties. Properties in ODCs can store and manage royalty information dynamically within the data and metadata, allowing for more complex royalty structures that can change over time or based on certain conditions. -**ERC-4885 (Fractional Ownership)**: ODCs can represent fractional ownership, offering compatibility with ERC-4885. Additional properties can define the conditions of fractional ownership. +**ERC-3643 (Permissioned Tokens)**: ERC-3643 proposal defines *Security Token interface* based on ERC-20 token standard with additional requirement for sender and receiver of the token to be approved by the token issuer. This interface relies on the *OnchainID* system to provide Identity information, process KYC and other credentials, providing that data on-chain for token holders for minting, burning, and recovery of assets. Compatibility with this interface can be implemented as an ODC **Property Manager**, with the added benefit of a more versatile on-chain identity management derived from alternative **Property Managers**. Implementing ERC-3643 tokens as Property Managers could lead to more robust permissioned tokens. ODC Properties can store and manage permissions, enhancing the control and flexibility of permissioned tokens. -**ERC-4886 (Provably Rare Tokens)**: The new standard doesn't disrupt the functionality of provably rare tokens, but offers an additional layer of customization by allowing the setting of specific properties. +**ERC-4337 (Account Abstraction)**: ODCs can provide a standardized method to store and manage the complex data structures required by abstracted accounts. This can include user preferences, access control lists, recovery options, and other customizable account features. The mutable states of abstracted accounts can be efficiently handled using ODCs. This, in turn, improves the adaptability and security of abstracted accounts. -**ERC-4907 (Shared Ownership)**: ODCs can represent shared ownership and are therefore compatible with ERC-4907. The proposed properties can provide additional controls for such tokens. +**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. ODCs can enhance EIP-4626 by providing a more dynamic data layer for tokenized vaults. Properties in ODCs can store information about the assets in the vault, conditions for access, or other relevant data, enabling more nuanced interactions with tokenized vaults. -**EIP-5050 (Interactive NFTs)**: The new standard compliments EIP-5050 by adding Properties, allowing for even richer interactions with NFTs. +**ERC-4885 (Fractional Ownership)**: ODCs can improve ERC-4885 Fractional Ownership by providing a standardized way to manage and track metadata related to the life-cycle of the tokens. ODC Properties can store details about each fractional owner and their ownership percentage, making the management of fractional ownership more efficient, versatile and transparent. -**EIP-5095 (Principal Tokens)**: ODCs would be fully compatible with EIP-5095, and additional properties could be implemented to further define the parameters of a loan. +**ERC-4886 (Proxy Ownership Register)**: Combining the security features of ERC-4886 with the data management capabilities of ERC-7208 could open up new use cases by working together to provide a more comprehensive solution for user security and experience. For example, the proxy address in ERC-4886 could be used to interact with various dApps while maintaining the user’s settings and preferences stored in an ERC-7208 ODC. This synergy allows users to have a secure, consistent, and personalized experience across the Ethereum ecosystem. Moreover, ERC-4886 addresses the issue of user confidence when interacting with smart contracts, as it separates the interaction address from the asset storage address. When combined with ERC-7208, it can ensure that users feel safe interacting with various applications, knowing that their settings are stored securely in an ODC and their assets are safe in a different address. -**EIP-5185 (Metadata Upgradeability)**: ODCs support metadata upgradeability. Their properties could serve as mutable metadata, making them compatible with EIP-5185. +**ERC-4907 (Shared Ownership)**: The integration of ERC-4907 as a Property Manager with ERC-7208 ODC storage can enhance the rental experience by allowing for additional rental-related data directly on-chain, such as rental terms, user permissions, and other customizable settings which would be self-contained within **Properties** and therefore automatically updated as metadata. ERC-4907's rental mechanism complements ERC-7208's ability to manage mutable on-chain data. By combining these two, NFTs can not only be rented out for specific periods but also have their properties or states dynamically managed and updated during the rental period using ODCs. This combination enhances security and compliance in NFT transactions, particularly for Real World Asset Tokenization. Rental agreements, regulatory compliance, intelectual property and user rights can be embedded within ODCs to ensure that the NFT usage adheres to predefined rules. -**EIP-5409 (ERC-1155 extension)**: The proposed ODC standard is compatible with the ERC-1155 extension proposed by EIP-5409 and can further enhance the utility of ERC-1155 tokens by allowing storage and modification of properties. +**EIP-5050 (Interactive NFTs)**: The integration of ERC-7208 with ERC-5050 could potentially enable new forms of interactive applications, where the data stored via ODC can be utilized in interactive NFT environments governed by either native ERC-5050 or a Property Manager implementation of ERC-5050. This could open up innovative use cases, especially in areas like gaming, digital art, and decentralized identity, where the interplay of secure data storage and interactive token functionalities is crucial. -**EIP-5505 (Asset-Backed NFTs)**: The proposed standard doesn't conflict with asset-backed NFTs and may provide additional controls or definitions for such tokens. +**EIP-5095 (Principal Tokens)**: An ERC-5095 Property Manager implementation could potentially be used to represent specific financial states or obligations as part of a broader data container structure. This way, ERC-5095 tokens could be part of a larger ODC structure, representing a financial component within a multi-faceted on-chain agreement or asset. Compliance, Identity, and predefined rules can be included as part of the logic once it is abstracted away from the storage, which is of particular interest for use cases referrent to Realt World Asset tokenization. -**EIP-5560 (Redeemable NFTs)**: ODCs can be redeemable and therefore compatible with EIP-5560. The properties associated with ODCs can provide additional control mechanisms for redeemable tokens. +**EIP-5185 (Metadata Upgradeability)**: ODCs align well with EIP-5185's focus on metadata upgradeability. Properties in ODCs can be used to store and update metadata, allowing for more flexible and dynamic metadata management for tokens. However, the main benefit for implementing EIP-5185 as a Property Manager is the access to dynamically metadata upgradeability by tapping into the stored data within the ODC. -**EIP-5633 (Composable Soulbound NFTs)**: ODCs can be composable and soulbound, thus compatible with EIP-5633. Furthermore, properties can provide further configuration for these types of tokens. +**EIP-5505 (Asset-Backed NFTs)**: The Wrapper and Fractionalizer Property Managers within ERC-7208 ODCs can be used to ensure that the fractionalization adheres to relevant regulations and custom rules, providing a compliant and flexible framework for ERC-5505 tokens, if they are implemented as ODCs. + +**EIP-5560 (Redeemable NFTs)**: ERC-5560 and ERC-7208 complement each other's capability to tokenize real-world assets. ODCs can be used to manage the data and rules surrounding the redemption process of a physical asset represented by an NFT, including tracking the redemption status, ownership history, and other relevant metadata of the tokenized assets. By leveraging both standards together, digital tokens can not only represent ownership of physical assets but also provide a standardized and regulated mechanism for their redemption. + +**EIP-5633 (Composable Soulbound NFTs)**: **ERC-6960 (Dual Layer Token Standard)**: **ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: +**ERC-7558 (Minimal Lockable Range NFTs)**: + +**ERC-7562 (Account Abstraction Validation Scope Rules)**: + +**ERC-7564 (Contract wallet management NFT)**: + +**ERC-7574 (Authentication SBT using Credential)**: \ No newline at end of file From 2f1890e27153397ab02e9a219414a34e0fbaad68 Mon Sep 17 00:00:00 2001 From: galimba Date: Wed, 13 Dec 2023 21:47:19 +0100 Subject: [PATCH 05/34] ERC-7208: updating compatibility with other standards --- assets/erc-7208/erc-7208-compat.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md index 51381f062e8..00a67cc0310 100644 --- a/assets/erc-7208/erc-7208-compat.md +++ b/assets/erc-7208/erc-7208-compat.md @@ -33,16 +33,8 @@ We provide a cherrypicked list of possible points of contact between ERC-7208 (o **EIP-5560 (Redeemable NFTs)**: ERC-5560 and ERC-7208 complement each other's capability to tokenize real-world assets. ODCs can be used to manage the data and rules surrounding the redemption process of a physical asset represented by an NFT, including tracking the redemption status, ownership history, and other relevant metadata of the tokenized assets. By leveraging both standards together, digital tokens can not only represent ownership of physical assets but also provide a standardized and regulated mechanism for their redemption. -**EIP-5633 (Composable Soulbound NFTs)**: +**EIP-5633 (Composable Soulbound NFTs)**: ERC-7208 ODCs can be utilized to manage the additional data and rules associated with ERC-5633 soulbound tokens, such as identity management, access and usage rights, and specific account associations. -**ERC-6960 (Dual Layer Token Standard)**: +**ERC-6960 (Dual Layer Token Standard)**: By implementing ERC-6960 as an ODC, the two-level classification system complements ERC-7208's focus on real-world asset tokenization. The `mainId` and `subId`` structure of ERC-6960 can be utilized to represent various layers of ownership and characteristics of a real-world asset within an ODC framework, providing more nuanced control and representation of assets. This synergy can enable more sophisticated management and fractionalization of assets, adhering to regulatory frameworks and custom management rules. -**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: - -**ERC-7558 (Minimal Lockable Range NFTs)**: - -**ERC-7562 (Account Abstraction Validation Scope Rules)**: - -**ERC-7564 (Contract wallet management NFT)**: - -**ERC-7574 (Authentication SBT using Credential)**: \ No newline at end of file +**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: ERC-7540 vaults's are focused on asynchronous deposit and redemption. Integrating ERC-7540, either by Wrapping or by Property Manager, will complement ERC-7208's ODCs and facilitate more complex financial products. Comnplex DeFi products like undercollateralized loans, insurance products, or tokenized stocks often require operations to be handled in a non-instantaneous manner. However, the nature of these products requires adhering to regulatory compliance and identity management solutions. This can easily be achieved by integrating ODCs Identity Property Managers. From af29ab9f5e3d33d9d942d7899eb20bf5fe7c87d4 Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 19 Dec 2023 14:50:23 +0100 Subject: [PATCH 06/34] Update ERC-7208: Apply suggestions from code review Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> --- ERCS/erc-7208.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 247c9fc84cb..b7aa5aff9e1 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -13,22 +13,22 @@ requires: 721 ## Abstract -- 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. +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 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. +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 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. -[ODC Overview HERE] + ## 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 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](./eip-721.md) 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 in 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. +NFTs conforming to standards such as [ERC-721](./eip-721.md) 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. @@ -51,6 +51,7 @@ A case-by-case limited analysis is provided in the 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. @@ -341,7 +342,7 @@ The *Registry* is a smart contract for managing the internal governance by manag 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** +#### Registry Management Functions ```solidity /** @@ -400,7 +401,7 @@ The current EIP introduces a Metadata library that is designed to standardize th The Metadata library includes base properties for [ERC-721](./eip-721.md) 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** +#### ODC Metadata Functions ```solidity /** @@ -412,7 +413,7 @@ The Metadata library includes functions to generate metadata, add extra **Proper function generateMetadata(bytes32 prop, uint256 mtid) external view returns (Metadata.ExtraProperties memory); ``` -**Examples of Properties and Restrictions**: +#### 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. @@ -429,7 +430,7 @@ The Metadata library includes functions to generate metadata, add extra **Proper **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. -[ODC use cases examples HERE] + ### Wrapping of Assets (Example Property Manager) @@ -590,7 +591,7 @@ The presence of mutable Properties can be used to implement security measures. I **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. -This ERC requires further discussions + ## Copyright From e39da517e343a1f1af4d1efe0623f4ca2f4b461e Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 19 Dec 2023 15:35:33 +0100 Subject: [PATCH 07/34] ERC:7208: minor updates as per editor request --- ERCS/erc-7208.md | 138 +++++++++++++------------- assets/erc-7208/erc-7208-overview.jpg | Bin 110725 -> 0 bytes assets/erc-7208/erc-7208-overview.svg | 4 + 3 files changed, 72 insertions(+), 70 deletions(-) delete mode 100644 assets/erc-7208/erc-7208-overview.jpg create mode 100644 assets/erc-7208/erc-7208-overview.svg diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index b7aa5aff9e1..a7b68e34403 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -1,31 +1,35 @@ --- eip: 7208 title: On-Chain Data Container -description: Abstracting logic away from storage for RWA Tokenization +description: Abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 status: Draft type: Standards Track category: ERC created: 2023-06-09 -requires: 721 +requires: 165, 721 --- ## Abstract -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. +On-chain Data Containers are smart contracts that inherit from [ERC-721](./eip-721.md) 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. -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. - ## 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](./eip-721.md) 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. @@ -46,7 +50,7 @@ This EIP proposes a series of interfaces for storing and accessing data on-chain - **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 appendix. +A case-by-case limited analysis is provided in the [appendix](../assets/erc-7208/erc-7208-compat.md). ## Specification @@ -64,30 +68,6 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S **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. -### The DataStorage Library (example - not part of the ERC) -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()`. - - ### ODC Interface An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the incorporation of **Properties** in its internal storage. The **Properties** MAY have **Restrictions**. @@ -132,7 +112,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param category category ID to consult * @return An array with all the respective property IDs */ - function getCategoryProperties(bytes32 category) external view returns (bytes32[] memory) + function propertiesOfCategory(bytes32 category) external view returns (bytes32[] memory) ``` ```solidity @@ -141,7 +121,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param mtid ODC token ID * @return An array with all the enabled property IDs */ - function getAllProperties(uint256 mtid) external view returns (bytes32[] memory) + function propertiesOf(uint256 mtid) external view returns (bytes32[] memory) ``` ```solidity @@ -161,7 +141,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param prop property ID to inquire * @return An array with all the ODC token IDs that have the property attached */ - function getAllTokensWithProperty(address account, bytes32 prop) external view returns (uint256[] memory) + function tokensWithProperty(address account, bytes32 prop) external view returns (uint256[] memory) ``` ```solidity @@ -231,7 +211,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param prop property ID * @return An array with all the requested restrictions (of type Restriction) */ - function getRestrictions(uint256 mtid, bytes32 prop) external view returns (Restriction[] memory) + function restrictionsOf(uint256 mtid, bytes32 prop) external view returns (Restriction[] memory) ``` ### Properties @@ -304,7 +284,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param r the restriction to add * @return uint256 The index of the newly added restriction */ - function add( + function addRestriction( Layout storage l, uint256 mtid, bytes32 property, @@ -320,7 +300,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco * @param property identifier for the property * @param ridx restriction index */ - function remove( + function removeRestriction( Layout storage l, uint256 mtid, bytes32 property, @@ -398,7 +378,7 @@ Non-fungible tokens (NFTs) have rapidly gained prominence in the Ethereum ecosys 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](./eip-721.md) 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 base properties for [ERC-721](./eip-721.md) 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 @@ -410,9 +390,57 @@ The Metadata library includes functions to generate metadata, add extra **Proper * @param mtid ODC token ID * @return The generated metadata */ - function generateMetadata(bytes32 prop, uint256 mtid) external view returns (Metadata.ExtraProperties memory); + function getMetadata(bytes32 prop, uint256 mtid) 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. @@ -430,9 +458,6 @@ The Metadata library includes functions to generate metadata, add extra **Proper **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: @@ -548,31 +573,6 @@ Fractionalizer is a Property Manager that enables the creation of fraction token ) external returns (address) ``` -## 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 - -See Nexera Protocol (nexeraprotocol dot com) ## Security Considerations @@ -591,8 +591,6 @@ The presence of mutable Properties can be used to implement security measures. I **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 Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/erc-7208/erc-7208-overview.jpg b/assets/erc-7208/erc-7208-overview.jpg deleted file mode 100644 index 0dc3e921ead2182298e60895b1735d2c32964195..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110725 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

P2eHpy-c!aJR*dwH`3(!6MPA&DNocP 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 diff --git a/assets/erc-7208/erc-7208-overview.svg b/assets/erc-7208/erc-7208-overview.svg new file mode 100644 index 00000000000..049fd93c3d3 --- /dev/null +++ b/assets/erc-7208/erc-7208-overview.svg @@ -0,0 +1,4 @@ + + + +ODC Contract
List of restrictions with related type & data  
List of restrictions with related type & data  
List of properties which can be enabled/disabled and store any data
List of properties which can be enabled/disabled and store any data
RestrictionsEmpty restriction (used a s a placeholder)Transfer restriction (dissalows transfer of ODC)Lock ERC20 restrictions (locks an ERC20 Property till specified timestamp)ODC TokenDEX Property Manager
  1. POOL_CREATOR (pool_address) Property
  2. LIQUIDITY_PROVIDED (pool_aadress) Property
    adds LOCK_ERC20_RESTRICTION
POOL_CREATOR (pool_address) Property...
KYC Property Manager
  1. KYC_PASSED PROPERTY
    adds TRANSFER_RESTRICTION
KYC_PASSED PROPERTY...
Some DEX
in the ecosystem
Some DEX...
Some KYC Verificator
in the ecosystem
Some KYC Verificator...
Fractionalizer Property Manager
  1. FRACTIONS_BALANCE(address) Property
    adds UNDERLYING_ASSET_RESTRICTION
FRACTIONS_BALANCE(address) Property...
Some Marketplace
in the ecosystem
Some Marketplace...
PropertyRegistry
getCategoryInfoForProperty() - finds a category of a property
getCategoryInfoForProperty() - finds a category of a property
getCategoryInfo() - returns a list of properties and if the category can be splited
getCategoryInfo() - returns a list of properties and if the category can be sp...
Manages KYCed Property
Manages KYCed Property
Manages fractionalization rules and Properties
Manages fractionalization rules and Properties
Manages categoriesand data governance
User
User
Off-chain
Off-chain
On-Chain
On-Chain
getUri()
JSON with PROPERTY_DATA
JSON with PROPERTY_DATA
Metadata generated
from on-chain data
Metadata generated...
Manages DEX interactions and Properties
Manages DEX interactions and Properties
ERC20 Property Manager
  1. Balance (address) Property
  2. Allowance (address) Property
Balance (address) Property...
Manages ERC20 interactions and Properties
Manages ERC20 interactions and Properties
ERC1155 Property Manager
  1. Balance (address, id) Property
  2. Allowance (address, id) Property
Balance (address, id) Property...
Manages ERC1155 interactions and Properties
Manages ERC1155 interactions and Properties
Text is not SVG - cannot display
\ No newline at end of file From 7b160d3219e1982f97cb4087993496d1e89f0128 Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 19 Dec 2023 16:42:13 +0100 Subject: [PATCH 08/34] ERC-7802: removing link to appendix due to html error --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index a7b68e34403..fca7c152572 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -50,7 +50,7 @@ This EIP proposes a series of interfaces for storing and accessing data on-chain - **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 [appendix](../assets/erc-7208/erc-7208-compat.md). +A case-by-case limited analysis is provided in the compatibility appendix. ## Specification From 17043346a5bdd50971a8f8a4e22335ddb140c28b Mon Sep 17 00:00:00 2001 From: galimba Date: Fri, 24 May 2024 10:48:00 +0200 Subject: [PATCH 09/34] erc-7208 update: rename mtid-odc erc-7208 update: removed todo erc-7208 update: update metadata provider interface erc-7208 update: add mutation types --- ERCS/erc-7208.md | 348 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 271 insertions(+), 77 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index fca7c152572..738348c3098 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -72,26 +72,260 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S An ODC MUST extend the functionality of [ERC-721](./eip-721.md) 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. + +```solidity +/** + * @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); +``` + +```solidity +/** + * @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`. + +```solidity +/** + * @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; +} +``` + + + +```solidity +/** + * @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); +} +``` + +```solidity +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 + } +} +``` + +```solidity +/** + * @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); +} +``` + +```solidity +/** + * @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); +} +``` + +```solidity +interface IMetadataProvider { + function metadataOf() view; +} +``` + +```solidity +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); +} +``` + + ```solidity /** * @notice Queries whether a given ODC token has a specific property - * @param mtid ODC token ID + * @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 mtid, bytes32 prop) external view returns (bool) + function hasProperty(uint256 ouid, bytes32 prop) external view returns (bool) ``` ```solidity /** * @notice Adds a given property to an existing ODC token - * @param mtid ODC token ID + * @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 mtid, + uint256 ouid, bytes32 prop, IMetaRestrictions.Restriction[] calldata restrictions ) external returns (uint256[] memory) @@ -100,10 +334,10 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Removes an existing property from an existing ODC - * @param mtid ODC token ID + * @param ouid ODC unique ID * @param prop property ID (property to remove) */ - function removeProperty(uint256 mtid, bytes32 prop) external + function removeProperty(uint256 ouid, bytes32 prop) external ``` ```solidity @@ -118,15 +352,15 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Retrieves all enabled properties of a given ODC - * @param mtid ODC token ID + * @param ouid ODC unique ID * @return An array with all the enabled property IDs */ - function propertiesOf(uint256 mtid) external view returns (bytes32[] memory) + function propertiesOf(uint256 ouid) external view returns (bytes32[] memory) ``` ```solidity /** - * @notice Retrieves a given user's ODC token ID that has a specific property attached + * @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 @@ -136,7 +370,7 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** - * @notice Retrieves all the ODC token IDs owned by a given user that have a specific property attached + * @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 @@ -147,18 +381,18 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Checks if a specific property exists in a given ODC token - * @param mtid ODC token ID + * @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 mtid, bytes32 prop) external view returns (bool) + function exists(uint256 ouid, bytes32 prop) external view returns (bool) ``` ```solidity /** * @notice Merges the given categories' related properties from one ODC token to another - * @param from origin ODC token ID - * @param to target ODC token ID + * @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( @@ -168,23 +402,23 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Splits an ODC token from its specific categories and mints a new one with the related properties attached - * @param mtid origin ODC token ID + * @param ouid origin ODC unique ID * @param categories category IDs to split - * @return newMtid the resulting (newly minted) ODC token ID + * @return newOuid the resulting (newly minted) ODC token ID */ - function split(uint256 mtid, bytes32[] calldata categories) external returns (uint256 newMtid) + function split(uint256 ouid, bytes32[] calldata categories) external returns (uint256 newOuid) ``` ```solidity /** * @notice Adds a new restriction to a given ODC property - * @param mtid ODC token ID + * @param ouid ODC unique ID * @param prop property ID * @param restr the restriction to add * @return uint256 The restriction's id */ function addRestriction( - uint256 mtid, + uint256 ouid, bytes32 prop, Restriction calldata restr ) external returns (uint256) { @@ -193,12 +427,12 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Removes a restriction identified by its index from a given ODC's property - * @param mtid ODC token ID + * @param ouid ODC unique ID * @param prop property ID * @param ridx restriction index */ function removeRestriction( - uint256 mtid, + uint256 ouid, bytes32 prop, uint256 ridx ) external @@ -207,54 +441,14 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco ```solidity /** * @notice Retrieves all restrictions attached to a given ODC's property - * @param mtid ODC token ID + * @param ouid ODC unique ID * @param prop property ID * @return An array with all the requested restrictions (of type Restriction) */ - function restrictionsOf(uint256 mtid, bytes32 prop) external view returns (Restriction[] memory) + function restrictionsOf(uint256 ouid, bytes32 prop) external view returns (Restriction[] memory) ``` -### 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. -```solidity -/** - * @notice Gets a Property Data point of the ODC. - * @dev This function allows anyone to get a property of the ODC. - * @param tokenId The 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 tokenId, - bytes32 propertyKey, - bytes32 dataKey - ) external view returns (bytes32); -``` - -```solidity -/** - * @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 tokenId The 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 tokenId, - 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`. ### Restrictions @@ -279,14 +473,14 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco /** * @dev Adds a restriction to a given ODC's property * @param l storage layout - * @param mtid ODC token ID + * @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 mtid, + uint256 ouid, bytes32 property, IMetaRestrictions.Restriction memory r ) internal returns (uint256) @@ -296,13 +490,13 @@ An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the inco /** * @dev Removes a restriction identified by its index from a given ODC's property * @param l storage layout - * @param mtid ODC token ID + * @param ouid ODC unique ID * @param property identifier for the property * @param ridx restriction index */ function removeRestriction( Layout storage l, - uint256 mtid, + uint256 ouid, bytes32 property, uint256 ridx ) internal @@ -387,10 +581,10 @@ The Metadata library includes functions to generate metadata, add extra **Proper /** * @notice Generates metadata for a given property of a ODC token * @param prop property ID of ODC token - * @param mtid ODC token ID + * @param ouid ODC unique ID * @return The generated metadata */ - function getMetadata(bytes32 prop, uint256 mtid) external view returns (Metadata.ExtraProperties memory); + function getMetadata(bytes32 prop, uint256 ouid) external view returns (Metadata.ExtraProperties memory); ``` ## Rationale @@ -475,7 +669,7 @@ The transferring or interaction with multiple individual tokens often leads to a * @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 existingMtidToUse If different than 0, it represents the ODC to wrap assets on. If 0, new mNFT is minted. + * @param existingOuidToUse If different than 0, it represents the ODC to wrap assets on. If 0, new mNFT is minted. */ struct WrapData { address[] tokens; @@ -483,7 +677,7 @@ The transferring or interaction with multiple individual tokens often leads to a uint256[] ids; uint256[] types; uint256[] unlockTimestamps; - uint256 existingMtidToUse; + uint256 existingOuidToUse; } ``` @@ -509,13 +703,13 @@ The transferring or interaction with multiple individual tokens often leads to a * @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 mtid The ODC id associated. + * @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 mtid, uint256[] calldata restrictionIds, address[] calldata tokens, uint256[] calldata types, uint256[] calldata ids) external; + function unwrap(uint256 ouid, uint256[] calldata restrictionIds, address[] calldata tokens, uint256[] calldata types, uint256[] calldata ids) external; ``` ```solidity @@ -539,7 +733,7 @@ Fractionalizer is a Property Manager that enables the creation of fraction token * @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 mtid The id of ODC to lock. + * @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. */ @@ -547,7 +741,7 @@ Fractionalizer is a Property Manager that enables the creation of fraction token string calldata name, string calldata symbol, uint256 amountToMint, - uint256 mtid, + uint256 ouid, address governor, GovernanceDeployer.GovernanceData calldata data ) external returns (address) @@ -560,7 +754,7 @@ Fractionalizer is a Property Manager that enables the creation of fraction 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 mtid The id of ODC to lock. + * @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( @@ -568,7 +762,7 @@ Fractionalizer is a Property Manager that enables the creation of fraction token string calldata symbol, string calldata baseUri, uint256[] calldata idsToMint, - uint256 mtid, + uint256 ouid, address governor ) external returns (address) ``` From 2e84511b45b6e8b53c069a444fefd4830a49ff2a Mon Sep 17 00:00:00 2001 From: galimba Date: Fri, 5 Jul 2024 17:41:19 +0200 Subject: [PATCH 10/34] ERC-7208 Update: Applies fixes as per community feedback ERC7208:update Abstract ERC7208:update Motivation ERC7208:update interfaces ERC7208:update rationale ERC7208:update grammar ERC7208:update assets ERC7208:update reference implementation ERC7208:update removed erc721 dependency ERC7208:update fix comment ERC7208:update fixes to interfaces ERC7208:update fixes to interfaces ERC7208:update fixes to interfaces ERC7208:update fixes to rationale ERC7208:update fixes to implementation --- ERCS/erc-7208.md | 953 ++++++------------ assets/erc-7208/erc-7208-compat.md | 36 +- assets/erc-7208/erc-7208-overview.svg | 2 +- .../erc-7208/erc-7208-technical-overview.svg | 4 + 4 files changed, 326 insertions(+), 669 deletions(-) create mode 100644 assets/erc-7208/erc-7208-technical-overview.svg diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 738348c3098..0b9904085ae 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -8,782 +8,455 @@ status: Draft type: Standards Track category: ERC created: 2023-06-09 -requires: 165, 721 +requires: 165 --- ## Abstract -On-chain Data Containers are smart contracts that inherit from [ERC-721](./eip-721.md) 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. +"On-chain Data Containers" (ODCs) are used for indexing and storing data in Smart Contracts called "Data Objects" (DOs). Information stored in Data Objects can be accessed and modified by the implementation of smart contracts called "Data Managers" (DMs). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for ODCs, the structures associated with DOs for abstracting storage, the DMs to access or modify the data, and finally the interfaces for compatibility Registries that enable Data Portability (horizontal mobility) between different implementations of this ERC. ## 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. +As the Ethereum ecosystem grows, so does the demand for on-chain functionalities. The market encourages a desire for broader adoption and more complex systems, so there is a constant need for improved efficiency. We have seen times where the market hype has driven an explosion of new standard token proposals. While each standard serves its purpose, most often requires more flexibility to manage interoperability with other standards. -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. +While such diversity spurs innovation, different projects will implement their bespoke solutions for interoperability, resulting in a highly fragmented landscape. The lack of a standard mechanism for adapting tokenized across ERC standards is accentuating the interoperability issues. -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. +We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - either Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens by utilizing different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols are required to implement each of those standards to be able to access and modify the on-chain data. Moreover, the immutability of smart contracts plays a role against future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable On-chain Adapters to enable the interaction between assets tokenized under different standards. -ODCs address some of the existing limitations with previous ERC token contracts by separating the logic from the storage. +We aim to abstract the on-chain data handling from both the logical implementation and the ERC interfaces exposing the underlying data. This EIP proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. -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. +- **Data Abstraction**: We propose a standardized interface for enabling developers to separate the data storage code from the underlying token utility logic, reducing the need for supporting and implementing multiple inherited -and often clashing- interfaces to achieve asset compatibility. The data (and therefore the assets) can be stored independently of the logic that governs such data. -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. +- **Standard Neutrality**: A neutral approach must enable the underlying data of any tokenized asset to transition seamlessly between different token standards. This will significantly improve interoperability among different standards, reducing fragmentation in the landscape. Our proposal aims to separate the storage of data representing an underlying asset from the standard interface used for representing the token. -NFTs conforming to standards such as [ERC-721](./eip-721.md) 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. +- **Consistent Interface**: A uniform interface of primitive functions abstracts the data storage from the use case, irrespective of the underlying token's standard or the interface used for exposing such data. Both data as well as metadata can be stored on-chain, and exposed through the same functions. -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. +- **Data Portability**: We provide a mechanism for the Horizontal Mobility of data between implementations of this standard, incentivizing the implementation of interoperable solutions and standard adapters. -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 +**ODC**: A uniquely identifiable data container that is used for indexing Data Objects or storing Data Points. -An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the incorporation of **Properties** in its internal storage. The **Properties** MAY have **Restrictions**. +**ODC Implementation**: One or many Smart Contracts implementing the ODC access-management logic. -### Properties +**Data Point**: A uniquely identifiable unit of information. -**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. +**Data Object**: One or many Smart Contracts implementing the low-level storage management of stored Data Points -```solidity -/** - * @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); -``` - -```solidity -/** - * @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; -``` +**Data Manager**: One or many Smart Contracts implementing the high-level logic and end-user interface for managing the Data Points. -- **getProperty**: This function MUST retrieve a specific `dataValue` of an ODC, identifiable through the `tokenId`, `propertyKey`, and `dataKey` parameters. +**Data Point Registry**: One or many Smart Contracts that define a space of compatible or interoperable Data Points. +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. -- **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`. - -```solidity -/** - * @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; -} -``` - +### ODC + * ODC SHOULD manage internal IDs for each data container. + * ODC SHOULD manage the access of Data Managers to Data Objects. + * ODC SHOULD use the IODC interface: ```solidity -/** - * @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); -} -``` - -```solidity -interface IODCMetadata { - function metadataOf(uint256 odcid) external view returns(string calldata); -} -interface IODCTypes { +interface IODC { /** - * @notice Enum of operations which PropertyManager can initiate on ODC + * @notice Verifies if DataManager is allowed to write specific DataPoint on specific DataObject + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @return if write access is allowed */ - enum PMOperation { - MODIFY_PROPERTY_DATA, - ADD_RESTRICTION, - REMOVE_RESTRICTION - } - /** - * - */ - enum Mutatation { - SPLIT, - MERGE, - ATTACH, - DETACH - } -} -``` + function isApprovedDataManager(DataPoint dp, address dm) external view returns(bool); -```solidity -/** - * @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 + * @notice Defines if DataManager is allowed to write specific DataPoint + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @param approved if DataManager should be approved for the DataPoint + * @dev Function should be restricted to DataPoint maintainer only */ - function maintenerOf(bytes32 category) external view returns(address); + function allowDataManager(DataPoint dp, address dm, bool approved) external; + /** - * @notice Returns category of specified property + * @notice Verifies if DataObject is allowed to add Hooks to the DataPoint + * @param dp Identifier of the DataPoint + * @param dobj Address of DataObject + * @return if write access is allowed */ - function categoryOf(bytes32 property) external view returns(bytes32 category); + function isApprovedDataObject(DataPoint dp, address dobj) external view returns(bool); + /** - * @notice Returns all properties of specified category + * @notice Defines if DataObject is allowed to add Hooks to the DataPoint + * @param dp Identifier of the DataPoint + * @param dobj Address of DataObject + * @param approved if DataManager should be approved for the DataPoint + * @dev Function should be restricted to datapoint maintainer only */ - function propertiesOf(bytes32 categoy) external view returns(bytes32[] memory properties); + function allowDataObject(DataPoint dp, address dobj, bool approved) external; + /** - * @notice Returns metadata provider for specific property + * @notice Reads stored data + * @param dobj Identifier of DataObject + * @param dp Identifier of the datapoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data */ - function metadataProviderOf(bytes32 property) external view returns(IMetadataProvider); + function read(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external view returns(bytes memory); + /** - * @notice Returns if mutation is allowed for the category + * @notice Store data + * @param dobj Identifier of DataObject + * @param dp Identifier of the datapoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + * @dev Function should be restricted to allowed DMs only */ - function isAllowed(Mutatation mutation, bytes32 category) external view returns(bool); + function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); } ``` +### Data Objects + + * Data Object SHOULD implement the logic directly related to handling the data stored on Data Points. + * Data Object SHOULD implement the logic for transfering management of its Data Points to a different ODC Implementation. + * Data Object SHOULD use the IDataObject interface: + ```solidity -/** - * @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 { +interface IDataObject { /** - * @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 + * @notice Reads stored data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data */ - function isPropertyManagerAllowed(address pm, PMOperation op, uint256 odcid, bytes32 prop) external returns(bool); -} -``` + function read(DataPoint dp, bytes4 operation, bytes calldata data) external view returns(bytes memory); -```solidity -interface IMetadataProvider { - function metadataOf() view; -} -``` + /** + * @notice Store data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + */ + function write(DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); -```solidity -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 + * @notice Sets ODC Implementation + * @param dp Identifier of the DataPoint + * @param newImpl address of the new ODC implementation */ - error Restricted(uint256 odcid, uint rid, string restrictionReason); - error OperationNotAllowedForPropertyManager(address pm, PMOperation op, uint256 odcid, bytes32 prop); - error MutationNotAllowed(Mutatation mutation, bytes32 category); + function setOdcImplementation(DataPoint dp, address newImpl) external; } ``` +Data Objects can receive `read()` or `write()` requests when a Data Manager is requesting access to a Data Point. -```solidity -/** - * @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) -``` +The function `setODCImplementation()` SHOULD enable the delegation of the the management function to an IODC implementation. -```solidity -/** - * @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) -``` -```solidity -/** - * @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 -``` +### Data Points -```solidity -/** - * @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) -``` +* Data Point SHOULD be `bytes32` storage units. +* Data Point SHOULD use a 4 bytes prefix for storing information relevant to the compatibility with other Data Points. +* Data Point SHOULD use the last 20 bytes for storage identifying which Registry allocated them. +* The RECOMMENDED internal structure of the Data Point is as follows: ```solidity /** - * @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) + * RECOMMENDED internal DataPoint structure on the Reference Implementation: + * 0xPPPPVVRRIIIIIIIIHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * - Prefix (bytes4) + * -- PPPP - Type prefix (i.e. 0x4450 - ASCII representation of letters "DP") + * -- VV - Verison of DataPoint specification (i.e. 0x00 for the reference implementation) + * -- RR - Reserved + * - Registry-local identifier + * -- IIIIIIII - 32 bit implementation-specific id of the DataPoint + * - Chain ID (bytes4) + * -- HHHHHHHH - 32 bit of chain identifier + * - REGISTRY Address (bytes20) + * -- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - Address of Registry which allocated the DataPoint +**/ ``` -```solidity -/** - * @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) -``` +### Data Point Registry -```solidity -/** - * @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) -``` + * Data Point Registry SHOULD store Data Point access management data for Data Managers and Data Objects + * Data Point Registry SHOULD use the IDataPointRegistry interface: ```solidity -/** - * @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) -``` +interface IDataPointRegistry { -```solidity -/** - * @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 Verifies if an address has an Admin role for a DataPoint + * @param dp DataPoint + * @param account Account to verify + */ + function isAdmin(DataPoint dp, address account) external view returns (bool); -```solidity -/** - * @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 Allocates a DataPoint to an owner + * @param owner Owner of the new DataPoint + * @dev Owner SHOULD be granted Admin role during allocation + */ + function allocate(address owner) external payable returns (DataPoint); -```solidity -/** - * @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 Transfers a DataPoint to an owner + * @param dp Data Point to be transferred + * @param owner Owner of the new DataPoint + */ + function transferOwnership(DataPoint dp, address newOwner) external; -```solidity -/** - * @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 Grant permission to grant/revoke other roles on the DataPoint inside an ODC Implementation + * This is useful if DataManagers are deployed during lifecycle of the application. + * @param dp DataPoint + * @param account New admin + * @return If the role was granted (otherwise account already had the role) + */ + function grantAdminRole(DataPoint dp, address account) external returns (bool); -```solidity -/** - * @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) + /** + * @notice Revoke permission to grant/revoke other roles on the DataPoint inside an ODC Implementation + * @param dp DataPoint + * @param account Old admin + * @dev If an owner revokes Admin role from himself, he can add it again + * @return If the role was revoked (otherwise account didn't had the role) + */ + function revokeAdminRole(DataPoint dp, address account) external returns (bool); +} ``` +### Data Managers + * Data Manager MAY use read() or DataObject.read() to read data form Data Objects + * Data Manager MAY use write() to write data to Data Objects + * Data Manager MAY share Data Point with other Data Managers + * Data Manager MAY use multiple Data Points + * Data Manager MAY implement the logic for requesting Data Points from a Data Point Registry. +Data Managers are independent smart contracts that implement the business logic. They can either `read()` from a DataObject address, and `write()` through an ODC Implementation managing the delegated storage of the Data Points. -### 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. -```solidity -/** - * @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; - } -``` +## Rationale -```solidity -/** - * @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) -``` +The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with flexibility to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, in order for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. -```solidity -/** - * @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 -``` +Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their own addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. +Data Objects offer flexibility in storing mutable on-chain data that can be modified as per the requirements of each specific use case. This enables the Data Managers to hold mutable states in delegated storage and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through most other standardized interfaces. +As the Data Points can be set to respond to a specific ODC implementation, Data Managers can decide to migrate the complete storage of a Data Object from one ODC implementation to another. +By leveraging multiple implementations of the IODC interface, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). -### 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: +## Backwards Compatibility -### Categories and Registry +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 Data Points or owned in Vaults as Data Objects, and later exposed through any implementation of Data Managers. -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. +See Reference Implementation. -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*. +## Reference Implementation -#### Registry Management Functions +We present an example implementation of Asset Vaults, a deterministic Asset Vault Factory, and a Data Object specialized in managing Asset Vaults. The following implementation can be used for wrapping multiple assets under the management of a Data Object, which in turn may be exposed through any Data Manager interface. -```solidity -/** - * @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); -``` -```solidity -/** - * @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); -``` +**Example Vault Interface** ```solidity -/** - * @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); -``` +pragma solidity ^0.8.0; +interface IVault { + /** + * @notice Executes a state-changing call on a target + * @param target Contract to call + * @param data Data sent to the target, including function selector + * @param value Native coin value sent with the call + * @dev Access to this function SHOULD be protected + */ + function execute(address target, bytes calldata data, uint256 value) external returns (bytes memory); -```solidity -/** - * @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 Executes a static call (non state-changing) on a target + * @param target Contract to call + * @param data Data sent to the target, including function selector + */ + function executeStatic(address target, bytes calldata data) external view returns (bytes memory); +} ``` +**Example Vault Factory Interface** ```solidity -/** - * @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](./eip-721.md) 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. +pragma solidity ^0.8.0; -#### ODC Metadata Functions +import "IVault.sol"; -```solidity -/** - * @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); +interface IVaultFactory { + function deploy(bytes calldata data) external returns (IVault); + function deployDeterministic(bytes32 salt, bytes calldata data) external returns (IVault); + function computeVaultAddress(address deployer, bytes32 salt, bytes calldata data) external view returns (address); +} ``` -## 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()`. +**Example Vault Data Object contract** +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -#### Examples of Properties and Restrictions +import "IVaultFactory.sol"; +import "BaseDataObject.sol"; -**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. +contract VaultDataObject is IERC1271, BaseDataObject { + using EnumerableSet for EnumerableSet.AddressSet; -**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. + bytes4 public constant VAULT_FOR_SALT_SELECTOR = bytes4(keccak256("vaultForSalt(bytes32)")); //vaultForSalt(bytes32) returns(address) + bytes4 public constant ALL_VAULTS_SELECTOR = bytes4(keccak256("allVaults()")); //allVaults() returns(address[] memory) -**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. + bytes4 public constant DEPLOY_VAULT_SELECTOR = bytes4(keccak256("deployVault(address,bytes)")); //deployVault(address factory, bytes calldata data) + bytes4 public constant DEPLOY_DETERMINISTIC_VAULT_SELECTOR = bytes4(keccak256("deployDeterministicVault(address,bytes32,bytes)")); //deployDeterministicVault(address factory, bytes32 salt, bytes calldata data) + bytes4 public constant GRANT_SIGNATURE_VALIDATION_SELECTOR = bytes4(keccak256("grantSignatureValidation(address,bytes32,address)")); //grantSignatureValidation(address vault, bytes32 hash, address signer) + bytes4 public constant REVOKE_SIGNATURE_VALIDATION_SELECTOR = bytes4(keccak256("revokeSignatureValidation(address,bytes32,address)")); //revokeSignatureValidation(address vault, bytes32 hash, address signer) + bytes4 public constant VAULT_EXECUTE_SELECTOR = bytes4(keccak256("vaultExecute(address,address,bytes,uint256)")); //vaultExecute(address vault, address target, bytes calldata data, uint256 value) + bytes4 private constant ERC1271_INVALID_SIGNATURE = 0xffffffff; -**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. + error UnknownVault(); + error UnknownSalt(); + error UnknownDataPoint(); + error DeloyedVaultAlreadyRegistered(); -**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. + event SignatureValidationAdded(address vault, bytes32 hash, address signer); + event SignatureValidationRevoked(address vault, bytes32 hash, address signer); -**Oracle Subscription**: An oracle system can stream data periodically into the Property (i.e. asset price, weather condition, metrics, etc) + struct SignatureVerificationData { + mapping(address vault => mapping(address signer => bool valid)) validSigners; + } -**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. + struct DpData { + EnumerableSet.AddressSet deployedVaults; + mapping(bytes32 salt => address vault) deployedDeterministicVaults; + mapping(bytes32 hash => SignatureVerificationData) validSignatures; + } + mapping(uint256 dpIdx => DpData) private dpDataStorage; + mapping(address vault => DataPoint) internal vaultsToDataPoints; -### Wrapping of Assets (Example Property Manager) + /** + * @inheritdoc IERC1271 + */ + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) { + ... + } -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: + function computeDeterministicVaultAddress(DataPoint dp, address factory, bytes32 salt, bytes calldata data) external view returns (address) { + ... + } -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. + function datapointOfVault(address vault) public view returns (DataPoint) { + DataPoint dp = vaultsToDataPoints[vault]; + if (DataPoint.unwrap(dp) == bytes32(0)) revert UnknownVault(); + return dp; + } + function dispatchRead(DataPoint dp, bytes4 operation, bytes calldata data) internal view virtual override returns (bytes memory) { + if (operation == VAULT_FOR_SALT_SELECTOR) { + bytes32 salt = abi.decode(data, (bytes32)); + return abi.encode(_vaultForSalt(dp, salt)); + } else if (operation == ALL_VAULTS_SELECTOR) { + return abi.encode(_allVaults(dp)); + } else { + revert UnknownReadOperation(operation); + } + } -```solidity -/** - * @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; + function dispatchWrite(DataPoint dp, bytes4 operation, bytes calldata data) internal virtual override returns (bytes memory) { + if (operation == DEPLOY_VAULT_SELECTOR) { + (address factory, bytes memory factoryData) = abi.decode(data, (address, bytes)); + return abi.encode(_deployVault(dp, factory, factoryData)); + } else if (operation == DEPLOY_DETERMINISTIC_VAULT_SELECTOR) { + (address factory, bytes32 salt, bytes memory factoryData) = abi.decode(data, (address, bytes32, bytes)); + return abi.encode(_deployDeterministicVault(dp, factory, salt, factoryData)); + } else if (operation == GRANT_SIGNATURE_VALIDATION_SELECTOR) { + (address vault, bytes32 hash, address signer) = abi.decode(data, (address, bytes32, address)); + _grantSignatureValidation(dp, vault, hash, signer); + return ""; + } else if (operation == REVOKE_SIGNATURE_VALIDATION_SELECTOR) { + (address vault, bytes32 hash, address signer) = abi.decode(data, (address, bytes32, address)); + _revokeSignatureValidation(dp, vault, hash, signer); + return ""; + } else if (operation == VAULT_EXECUTE_SELECTOR) { + (address vault, address target, bytes memory vaultCallData, uint256 value) = abi.decode(data, (address, address, bytes, uint256)); + return _vaultExecute(dp, vault, target, vaultCallData, value); + } else { + revert UnknownWriteOperation(operation); + } } +} ``` -```solidity -/** - * @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); -``` +**Example ERC-20 Data Manager implementation** ```solidity -/** - * @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); -``` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -```solidity -/** - * @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; -``` +contract ERC20DataManager is IERC20, IERC20Metadata, IERC20Errors, Ownable, ERC20Approvals, ERC20Transfers, ERC20Burnable, ERC20Mintable, ERC20Metadata { + bytes4 internal BALANCE_OF_SELECTOR = bytes4(keccak256("balanceOf(address)")); + bytes4 internal TOTAL_SUPPLY_SELECTOR = bytes4(keccak256("totalSupply()")); + bytes4 internal MINT_SELECTOR = bytes4(keccak256("mint(address,uint256)")); + bytes4 internal BURN_SELECTOR = bytes4(keccak256("burn(address,uint256)")); + bytes4 internal TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,address,uint256)")); -```solidity -/** - * @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 -``` + DataPoint internal immutable datapoint; + IDataObject public immutable fungibleDO; + IODC public odc; + mapping(address account => mapping(address spender => uint256)) private _allowances; -### Fractionalization (Example Property Manager) + constructor( + bytes32 _datapoint, + address _odc, + address _fungibleDO, + string memory name_, + string memory symbol_ + ) Ownable(msg.sender) ERC20Metadata(name_, symbol_) { + datapoint = DataPoint.wrap(_datapoint); + odc = IODC(_odc); + fungibleDO = IDataObject(_fungibleDO); + } -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. + function totalSupply() external view override returns (uint256) { + return abi.decode(fungibleDO.read(datapoint, TOTAL_SUPPLY_SELECTOR, ""), (uint256)); + } + function balanceOf(address account) external view override returns (uint256) { + return abi.decode(fungibleDO.read(datapoint, BALANCE_OF_SELECTOR, abi.encode(account)), (uint256)); + } -```solidity -/** - * @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) -``` + function _checkMinter() internal view override { + _checkOwner(); + } -```solidity -/** - * @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) + function _writeTransfer(address from, address to, uint256 amount) internal override { + if (from == address(0)) { + odc.write(address(fungibleDO), datapoint, MINT_SELECTOR, abi.encode(to, amount)); + } else if (to == address(0)) { + odc.write(address(fungibleDO), datapoint, BURN_SELECTOR, abi.encode(from, amount)); + } else { + odc.write(address(fungibleDO), datapoint, TRANSFER_SELECTOR, abi.encode(from, to, amount)); + } + } +} ``` - ## 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. - +No specific security considerations are derived from this ERC. ## Copyright diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md index 00a67cc0310..571ef9f9121 100644 --- a/assets/erc-7208/erc-7208-compat.md +++ b/assets/erc-7208/erc-7208-compat.md @@ -3,38 +3,18 @@ We provide a cherrypicked list of possible points of contact between ERC-7208 (on-chain data container) and other tokenization standards and proposals. -**ERC-1400 (Security Token Standard)**: In aggregate provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations. ODCs can enhance ERC-1400 by offering more dynamic and flexible data management. ODCs allow for the storage and modification of properties related to security tokens, such as compliance information or ownership details. This integration could lead to more efficient and transparent security token offerings. Additionally, hooks and triggers can be implemented within the logic of specific use-cases so that compliance with regulatory frameworks is achieved automatically. +**ERC-1400 (Security Token Standard)**: In aggregate provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations. ERC-7208 can enhance ERC-1400 by offering more dynamic and flexible data management. Data Objects enable the storage and modification of on-chain data related to security tokens, such as compliance information or ownership details. In the case of assets that are already issued under ERC-1400, they can be wrapped into a Vault Data Object and exposed through any Data Manager interface (including ERC-3643 and others). Alternative, if the asset is issued with native Data Point storage, the integration could lead to more efficient and transparent security token offerings. The modular and adaptable nature of ERC-7208 enable transparent enhancements to the internal logic of individual ERC-1400 tokens. -**EIP-2309 (Consecutive batch minting)**: ODCs are compatible with EIP-2309, allowing for the batch minting process to be enriched with additional data. ODCs could store information related to each batch, such as metadata or batch-specific attributes, without disrupting the minting process. +**EIP-2309 (Consecutive batch minting)**: ERC-7208 is compatible with EIP-2309, allowing for the batch minting process to be enriched with additional data. ODCs could store information related to each batch, such as metadata or batch-specific attributes, without disrupting the minting process. This information may be stored on Data Points, internally within the ODC Implementation, or locally at the Data Manager exposing the EIP-2309 interface. -**EIP-2615 (Swap Orders)**: ODCs can work alongside EIP-2615, enhancing swap orders with additional data capabilities. Properties within ODCs can store terms, conditions, or other relevant data for swap orders, facilitating more complex and informed swap transactions. Additionally, Swap Orders can be bundled together by Wrapper Property Manager, generating bundles of orders to be interacted with together. +**EIP-2981 (Royalties)**: ERC-7208 can complement EIP-2981 as a Data Manager by providing a flexible way to handle royalties. Data Objects can store and manage the low level storage of royalty information dynamically and independently from the Data Manager's implemented interface. This enables a complex royalty structure that can change over time or based on certain conditions, like embedding compliance checks on the functions modifying the storage and simultaneously exposing multiple interfaces for accessing the storage. For instance, by leveraging ERC-7208, an individual royalty based NFT can be traded in a compliant manner, concurrently under both an ERC-721 interface as well as an ERC-20 through the use of Data Managers. -**EIP-2981 (Royalties)**: ODCs can complement EIP-2981 as a Property Manager by providing a flexible way to handle royalties. Properties in ODCs can store and manage royalty information dynamically within the data and metadata, allowing for more complex royalty structures that can change over time or based on certain conditions. +**ERC-3643 (Security Tokens)**: ERC-3643 defines a *Security Token interface for Regulated Exchanges* based on ERC-20 token standard. The ERC-7208 can be used for wrapping buckets of tokens (irrespective of their ERC) and adapting their logic to the ERC-3643. Additionally, a Data Object storing native ERC-3643 tokens can be used for improving the compliance logic and enabling the trading of underlying securities simultaneously through multiple interfaces that respond to different regulatory frameworks. Moreover, the separation of the storage enables the logic to implement functionalities that were not initially a part of the original ERC, such as identity-based recovery of assets, role-based access control, the introduction of cross-chain support, etc. -**ERC-3643 (Permissioned Tokens)**: ERC-3643 proposal defines *Security Token interface* based on ERC-20 token standard with additional requirement for sender and receiver of the token to be approved by the token issuer. This interface relies on the *OnchainID* system to provide Identity information, process KYC and other credentials, providing that data on-chain for token holders for minting, burning, and recovery of assets. Compatibility with this interface can be implemented as an ODC **Property Manager**, with the added benefit of a more versatile on-chain identity management derived from alternative **Property Managers**. Implementing ERC-3643 tokens as Property Managers could lead to more robust permissioned tokens. ODC Properties can store and manage permissions, enhancing the control and flexibility of permissioned tokens. +**ERC-4337 (Account Abstraction)**: ERC-7208 can provide a standardized method to store and manage the complex data structures required by abstracted accounts. This can include user preferences, access control lists, recovery options, and other customizable account features. The mutable states of abstracted accounts can be efficiently handled using Data Objects. This, in turn, improves the adaptability and security of abstracted accounts. Additionally, an ERC-7208 implementation supporting meta-transactions and Data Points separated by chain-id can be developed to fully abstract account management across blockchains. -**ERC-4337 (Account Abstraction)**: ODCs can provide a standardized method to store and manage the complex data structures required by abstracted accounts. This can include user preferences, access control lists, recovery options, and other customizable account features. The mutable states of abstracted accounts can be efficiently handled using ODCs. This, in turn, improves the adaptability and security of abstracted accounts. +**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from a single ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. ODCs can enhance EIP-4626 by providing a more dynamic data layer for tokenized vaults. Data Objects and ODCs can store information about the assets in the vault, conditions for access, or other relevant data, enabling more nuanced interactions with tokenized vaults. Additionally, the Data Object can store more than a single ERC-20, greatly increasing the capabilities of Tokenized Vaults. -**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. ODCs can enhance EIP-4626 by providing a more dynamic data layer for tokenized vaults. Properties in ODCs can store information about the assets in the vault, conditions for access, or other relevant data, enabling more nuanced interactions with tokenized vaults. +**ERC-4907 (Shared Ownership)**: The integration of ERC-4907 as a Data Manager with ERC-7208 Data Object storage can enhance the rental experience by allowing for additional rental-related data directly on-chain, such as rental terms, user permissions, and other customizable settings which would be self-contained within Data Points and therefore automatically updated as metadata. ERC-4907's rental mechanism complements ERC-7208's ability to manage mutable on-chain data. By combining these two, NFTs can not only be rented out for specific periods but also have their traits or states dynamically managed and updated during the rental period. This combination enhances security and compliance in NFT transactions, particularly for Real World Asset Tokenization. Rental agreements, regulatory compliance, intelectual property and user rights can be embedded within Data Objects to ensure that the NFT usage adheres to predefined rules. -**ERC-4885 (Fractional Ownership)**: ODCs can improve ERC-4885 Fractional Ownership by providing a standardized way to manage and track metadata related to the life-cycle of the tokens. ODC Properties can store details about each fractional owner and their ownership percentage, making the management of fractional ownership more efficient, versatile and transparent. - -**ERC-4886 (Proxy Ownership Register)**: Combining the security features of ERC-4886 with the data management capabilities of ERC-7208 could open up new use cases by working together to provide a more comprehensive solution for user security and experience. For example, the proxy address in ERC-4886 could be used to interact with various dApps while maintaining the user’s settings and preferences stored in an ERC-7208 ODC. This synergy allows users to have a secure, consistent, and personalized experience across the Ethereum ecosystem. Moreover, ERC-4886 addresses the issue of user confidence when interacting with smart contracts, as it separates the interaction address from the asset storage address. When combined with ERC-7208, it can ensure that users feel safe interacting with various applications, knowing that their settings are stored securely in an ODC and their assets are safe in a different address. - -**ERC-4907 (Shared Ownership)**: The integration of ERC-4907 as a Property Manager with ERC-7208 ODC storage can enhance the rental experience by allowing for additional rental-related data directly on-chain, such as rental terms, user permissions, and other customizable settings which would be self-contained within **Properties** and therefore automatically updated as metadata. ERC-4907's rental mechanism complements ERC-7208's ability to manage mutable on-chain data. By combining these two, NFTs can not only be rented out for specific periods but also have their properties or states dynamically managed and updated during the rental period using ODCs. This combination enhances security and compliance in NFT transactions, particularly for Real World Asset Tokenization. Rental agreements, regulatory compliance, intelectual property and user rights can be embedded within ODCs to ensure that the NFT usage adheres to predefined rules. - -**EIP-5050 (Interactive NFTs)**: The integration of ERC-7208 with ERC-5050 could potentially enable new forms of interactive applications, where the data stored via ODC can be utilized in interactive NFT environments governed by either native ERC-5050 or a Property Manager implementation of ERC-5050. This could open up innovative use cases, especially in areas like gaming, digital art, and decentralized identity, where the interplay of secure data storage and interactive token functionalities is crucial. - -**EIP-5095 (Principal Tokens)**: An ERC-5095 Property Manager implementation could potentially be used to represent specific financial states or obligations as part of a broader data container structure. This way, ERC-5095 tokens could be part of a larger ODC structure, representing a financial component within a multi-faceted on-chain agreement or asset. Compliance, Identity, and predefined rules can be included as part of the logic once it is abstracted away from the storage, which is of particular interest for use cases referrent to Realt World Asset tokenization. - -**EIP-5185 (Metadata Upgradeability)**: ODCs align well with EIP-5185's focus on metadata upgradeability. Properties in ODCs can be used to store and update metadata, allowing for more flexible and dynamic metadata management for tokens. However, the main benefit for implementing EIP-5185 as a Property Manager is the access to dynamically metadata upgradeability by tapping into the stored data within the ODC. - -**EIP-5505 (Asset-Backed NFTs)**: The Wrapper and Fractionalizer Property Managers within ERC-7208 ODCs can be used to ensure that the fractionalization adheres to relevant regulations and custom rules, providing a compliant and flexible framework for ERC-5505 tokens, if they are implemented as ODCs. - -**EIP-5560 (Redeemable NFTs)**: ERC-5560 and ERC-7208 complement each other's capability to tokenize real-world assets. ODCs can be used to manage the data and rules surrounding the redemption process of a physical asset represented by an NFT, including tracking the redemption status, ownership history, and other relevant metadata of the tokenized assets. By leveraging both standards together, digital tokens can not only represent ownership of physical assets but also provide a standardized and regulated mechanism for their redemption. - -**EIP-5633 (Composable Soulbound NFTs)**: ERC-7208 ODCs can be utilized to manage the additional data and rules associated with ERC-5633 soulbound tokens, such as identity management, access and usage rights, and specific account associations. - -**ERC-6960 (Dual Layer Token Standard)**: By implementing ERC-6960 as an ODC, the two-level classification system complements ERC-7208's focus on real-world asset tokenization. The `mainId` and `subId`` structure of ERC-6960 can be utilized to represent various layers of ownership and characteristics of a real-world asset within an ODC framework, providing more nuanced control and representation of assets. This synergy can enable more sophisticated management and fractionalization of assets, adhering to regulatory frameworks and custom management rules. - -**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: ERC-7540 vaults's are focused on asynchronous deposit and redemption. Integrating ERC-7540, either by Wrapping or by Property Manager, will complement ERC-7208's ODCs and facilitate more complex financial products. Comnplex DeFi products like undercollateralized loans, insurance products, or tokenized stocks often require operations to be handled in a non-instantaneous manner. However, the nature of these products requires adhering to regulatory compliance and identity management solutions. This can easily be achieved by integrating ODCs Identity Property Managers. +**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: ERC-7540 vaults's are focused on asynchronous deposit and redemption. Integrating ERC-7540, either by Wrapping into a Data Object or by exposing a 7208 Data Manager, will facilitate more complex financial products. DeFi products like undercollateralized loans, insurance products, or tokenized stocks often require operations to be handled in a non-instantaneous manner. However, the nature of these products requires adhering to regulatory compliance and identity management solutions. This can easily be achieved by implementing the use of on-chain adapters that enhance the logic while keeping the data secure. diff --git a/assets/erc-7208/erc-7208-overview.svg b/assets/erc-7208/erc-7208-overview.svg index 049fd93c3d3..13cc55680e0 100644 --- a/assets/erc-7208/erc-7208-overview.svg +++ b/assets/erc-7208/erc-7208-overview.svg @@ -1,4 +1,4 @@ -ODC Contract
List of restrictions with related type & data  
List of restrictions with related type & data  
List of properties which can be enabled/disabled and store any data
List of properties which can be enabled/disabled and store any data
RestrictionsEmpty restriction (used a s a placeholder)Transfer restriction (dissalows transfer of ODC)Lock ERC20 restrictions (locks an ERC20 Property till specified timestamp)ODC TokenDEX Property Manager
  1. POOL_CREATOR (pool_address) Property
  2. LIQUIDITY_PROVIDED (pool_aadress) Property
    adds LOCK_ERC20_RESTRICTION
POOL_CREATOR (pool_address) Property...
KYC Property Manager
  1. KYC_PASSED PROPERTY
    adds TRANSFER_RESTRICTION
KYC_PASSED PROPERTY...
Some DEX
in the ecosystem
Some DEX...
Some KYC Verificator
in the ecosystem
Some KYC Verificator...
Fractionalizer Property Manager
  1. FRACTIONS_BALANCE(address) Property
    adds UNDERLYING_ASSET_RESTRICTION
FRACTIONS_BALANCE(address) Property...
Some Marketplace
in the ecosystem
Some Marketplace...
PropertyRegistry
getCategoryInfoForProperty() - finds a category of a property
getCategoryInfoForProperty() - finds a category of a property
getCategoryInfo() - returns a list of properties and if the category can be splited
getCategoryInfo() - returns a list of properties and if the category can be sp...
Manages KYCed Property
Manages KYCed Property
Manages fractionalization rules and Properties
Manages fractionalization rules and Properties
Manages categoriesand data governance
User
User
Off-chain
Off-chain
On-Chain
On-Chain
getUri()
JSON with PROPERTY_DATA
JSON with PROPERTY_DATA
Metadata generated
from on-chain data
Metadata generated...
Manages DEX interactions and Properties
Manages DEX interactions and Properties
ERC20 Property Manager
  1. Balance (address) Property
  2. Allowance (address) Property
Balance (address) Property...
Manages ERC20 interactions and Properties
Manages ERC20 interactions and Properties
ERC1155 Property Manager
  1. Balance (address, id) Property
  2. Allowance (address, id) Property
Balance (address, id) Property...
Manages ERC1155 interactions and Properties
Manages ERC1155 interactions and Properties
Text is not SVG - cannot display
\ No newline at end of file +
DataObject
DataObject
DataManager
On-chain Data Container
DataManager
DataManager
DataObject
Buckets of assets
and asset data
Access Management
Business Logic and
user interface for
interacting with the data
\ No newline at end of file diff --git a/assets/erc-7208/erc-7208-technical-overview.svg b/assets/erc-7208/erc-7208-technical-overview.svg new file mode 100644 index 00000000000..d1ab5cc3cc7 --- /dev/null +++ b/assets/erc-7208/erc-7208-technical-overview.svg @@ -0,0 +1,4 @@ + + + +
Access Management
ODC_ID => DM_ID => DP_ID
...
...
ODC Smart Contract
ODC ID
Management
DataManager (DM)
Implements business logic
Access DM
Manage ODC
User
Manage DM <=> DataObject relationship
DM Mainteiner
Storage
DataPoint_DM1_1
odc_id1
odc_id2
DataPoint_DM1_2
odc_id1
odc_id2
DataPoint_DM2_1
odc_id1
odc_id2
DataPoint_DM2_2
odc_id1
odc_id2
DataPoint Logic
Performs logic directly related
to storage management
Multiple DataObject (DO)
Contracts
DataObject (DO)
Contract
ODC implementation
(ERC-7208)

DataManager (DM)
Implements business logic
DataManager (DM)
Implements business logic
DataManager (DM)
Implements usecase logic
Data is stored in DataPoints
DataObject exposes low level
Data Management interface
ODC implementation
manages access
and indexes DataObjects
DataPoint Registry

Should provide information if an account can grant access to DP for DMs and DOs

function isAdmin(DataPoint, account) returns (bool)
Verify DP Maintainer
is Admin of DataPoint
Allocate DataPoint
DataManagers Implement
business logic
(user-facing interfaces)
DP Registry implementation
separates the space of
compatible DataPoints
\ No newline at end of file From 6820abd77deada8ceb56406ca5b1b9a5d436275b Mon Sep 17 00:00:00 2001 From: galimba Date: Sun, 7 Jul 2024 22:01:12 +0200 Subject: [PATCH 11/34] erc7208 update: fix example + grammar erc7208 update: grammar fix erc7208 update: grammar fix --- ERCS/erc-7208.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 0b9904085ae..15464629d9a 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -14,7 +14,7 @@ requires: 165 ## Abstract -"On-chain Data Containers" (ODCs) are used for indexing and storing data in Smart Contracts called "Data Objects" (DOs). Information stored in Data Objects can be accessed and modified by the implementation of smart contracts called "Data Managers" (DMs). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for ODCs, the structures associated with DOs for abstracting storage, the DMs to access or modify the data, and finally the interfaces for compatibility Registries that enable Data Portability (horizontal mobility) between different implementations of this ERC. +"On-chain Data Containers" (ODCs) are used for indexing and storing data in Smart Contracts called "Data Objects" (DOs). Information stored in Data Objects can be accessed and modified by implementing smart contracts called "Data Managers" (DMs). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for ODCs, the structures associated with DOs for abstracting storage, the DMs to access or modify the data, and finally the interfaces for compatibility Registries that enable Data Portability (horizontal mobility) between different implementations of this ERC. @@ -24,7 +24,7 @@ As the Ethereum ecosystem grows, so does the demand for on-chain functionalities While such diversity spurs innovation, different projects will implement their bespoke solutions for interoperability, resulting in a highly fragmented landscape. The lack of a standard mechanism for adapting tokenized across ERC standards is accentuating the interoperability issues. -We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - either Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens by utilizing different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols are required to implement each of those standards to be able to access and modify the on-chain data. Moreover, the immutability of smart contracts plays a role against future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable On-chain Adapters to enable the interaction between assets tokenized under different standards. +We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens using different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such On-chain Adapters. We aim to abstract the on-chain data handling from both the logical implementation and the ERC interfaces exposing the underlying data. This EIP proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. @@ -42,7 +42,7 @@ We aim to abstract the on-chain data handling from both the logical implementati ### Terms -**ODC**: A uniquely identifiable data container that is used for indexing Data Objects or storing Data Points. +**ODC**: A uniquely identifiable data structure used for indexing Data Objects or storing Data Points. **ODC Implementation**: One or many Smart Contracts implementing the ODC access-management logic. @@ -56,7 +56,7 @@ We aim to abstract the on-chain data handling from both the logical implementati 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. -### ODC +### ODC Interface * ODC SHOULD manage internal IDs for each data container. * ODC SHOULD manage the access of Data Managers to Data Objects. @@ -121,7 +121,7 @@ interface IODC { } ``` -### Data Objects +### Data Object Interface * Data Object SHOULD implement the logic directly related to handling the data stored on Data Points. * Data Object SHOULD implement the logic for transfering management of its Data Points to a different ODC Implementation. @@ -161,7 +161,7 @@ Data Objects can receive `read()` or `write()` requests when a Data Manager is r The function `setODCImplementation()` SHOULD enable the delegation of the the management function to an IODC implementation. -### Data Points +### Data Point Structure * Data Point SHOULD be `bytes32` storage units. * Data Point SHOULD use a 4 bytes prefix for storing information relevant to the compatibility with other Data Points. @@ -185,7 +185,7 @@ The function `setODCImplementation()` SHOULD enable the delegation of the the ma **/ ``` -### Data Point Registry +### Data Point Registry Interface * Data Point Registry SHOULD store Data Point access management data for Data Managers and Data Objects * Data Point Registry SHOULD use the IDataPointRegistry interface: @@ -234,7 +234,7 @@ interface IDataPointRegistry { } ``` -### Data Managers +### Data Managers Contracts * Data Manager MAY use read() or DataObject.read() to read data form Data Objects * Data Manager MAY use write() to write data to Data Objects @@ -247,9 +247,9 @@ Data Managers are independent smart contracts that implement the business logic. ## Rationale -The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with flexibility to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, in order for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. +The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with many options to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. -Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their own addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. +Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. Data Objects offer flexibility in storing mutable on-chain data that can be modified as per the requirements of each specific use case. This enables the Data Managers to hold mutable states in delegated storage and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through most other standardized interfaces. @@ -400,7 +400,7 @@ contract VaultDataObject is IERC1271, BaseDataObject { } ``` -**Example ERC-20 Data Manager implementation** +**Example Fungible Token Data Manager implementation** ```solidity // SPDX-License-Identifier: MIT From a905885acdd075d347dc159ac7eb539252b09b8c Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 9 Jul 2024 14:09:26 +0200 Subject: [PATCH 12/34] erc7208 update: example --- ERCS/erc-7208.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 167b9289118..969ba206541 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -261,6 +261,8 @@ Data Managers are independent smart contracts that implement the business logic. The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with many options to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. +Data Manager implementations decide which Data Points they will be using. Their allocation is managed through a Data Point Registry, and the access to the Data Point is managed by passing through the ODC Implementation. + Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. Data Objects offer flexibility in storing mutable on-chain data that can be modified as per the requirements of each specific use case. This enables the Data Managers to hold mutable states in delegated storage and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through most other standardized interfaces. @@ -278,11 +280,16 @@ See Reference Implementation. ## Reference Implementation -We present an example implementation of Asset Vaults, a deterministic Asset Vault Factory, and a Data Object specialized in managing Asset Vaults. The following implementation can be used for wrapping multiple assets under the management of a Data Object, which in turn may be exposed through any Data Manager interface. +We present an example implementation of Asset Vaults, a deterministic Asset Vault Factory, and a Data Object specialized in managing Asset Vaults. The following implementation assumes the existance of both IDataPointRegistry and IODC implementations. + +The Vault Data Object may hold multiple assets locked under management, which in turn may be exposed through a Data Manager interface implementing a logic equivalent to the fractionalization of the Vault. **Example Vault Interface** +Asset Vaults are an example smart contract designed to implement the minimimalistic approach at asset management. Any user can deploy Asset Vaults through the factory. The role of an Asset Vault smart contract is to manage assets on behalf of the owner. We recommend Asset Vaults be `Ownable2Step` for security reasons. + + ```solidity pragma solidity ^0.8.0; interface IVault { @@ -301,9 +308,12 @@ interface IVault { * @param data Data sent to the target, including function selector */ function executeStatic(address target, bytes calldata data) external view returns (bytes memory); + ... } ``` +Vault Factories are example smart contracts that facilitate the deployment of Asset Vaults. + **Example Vault Factory Interface** ```solidity pragma solidity ^0.8.0; @@ -314,9 +324,12 @@ interface IVaultFactory { function deploy(bytes calldata data) external returns (IVault); function deployDeterministic(bytes32 salt, bytes calldata data) external returns (IVault); function computeVaultAddress(address deployer, bytes32 salt, bytes calldata data) external view returns (address); + ... } ``` +A Vault Data Object is an implementation of Base Data Object for managing assets through Asset Vaults. As an implementation of IDataObject, this smart contract must implement `read()` and `write()` functions related to handling the data stored on Data Points under management. The logic of `read()` can be implemented with the use of `dispatchRead()` and the `write()` logic with `dispatchWrite()`. In this example, the Vault Data Object also implements some signature verification logic. + **Example Vault Data Object contract** ```solidity // SPDX-License-Identifier: MIT @@ -354,6 +367,7 @@ contract VaultDataObject is IERC1271, BaseDataObject { EnumerableSet.AddressSet deployedVaults; mapping(bytes32 salt => address vault) deployedDeterministicVaults; mapping(bytes32 hash => SignatureVerificationData) validSignatures; + ... } mapping(uint256 dpIdx => DpData) private dpDataStorage; @@ -409,9 +423,13 @@ contract VaultDataObject is IERC1271, BaseDataObject { revert UnknownWriteOperation(operation); } } + ... } ``` +Finally, we expose a user-facing example Data Manager contract for managing the Data Object. The Data Manager will be implementing `read()` functions for checking the users' `balanceOf()` directly from the Data Object, and `write()` functions through the IODC implementation for the `transfer()` logic. Since multiple Data Managers can make use of the same Data Point, it is possible for several concurrent Data Manager implementations to share a Data Point where they delegate their storage. + + **Example Fungible Token Data Manager implementation** ```solidity @@ -468,7 +486,13 @@ contract ERC20DataManager is IERC20, IERC20Metadata, IERC20Errors, Ownable, ERC2 ## Security Considerations -No specific security considerations are derived from this ERC. +The access control is separated in three layers: + +* **Layer 1**: The Data Point Registry allocates for Data Managers and manages ownerhsip (admin/write rights) of Data Points. +* **Layer 2**: The ODC smart contract implements Access Control by managing Approvals of Data Managers to Data Points. It uses the Data Point Registry to verify who can grant/revoke this access. +* **Layer 3**: The Data Manager exposes functions that can perform `write` operations on the Data Point by calling the ODC implementation. + +No further security considerations are derived specificly from this ERC. ## Copyright From de4d2c0232aab5c5e8246f640fe4d36c2da99bf3 Mon Sep 17 00:00:00 2001 From: galimba Date: Mon, 22 Jul 2024 15:08:56 +0200 Subject: [PATCH 13/34] erc7208 update: fix to ODC interface --- ERCS/erc-7208.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index c5cc463ca6b..3dd6fca0d5a 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -87,23 +87,6 @@ interface IODC { */ function allowDataManager(DataPoint dp, address dm, bool approved) external; - /** - * @notice Verifies if DataObject is allowed to add Hooks to the DataPoint - * @param dp Identifier of the DataPoint - * @param dobj Address of DataObject - * @return if write access is allowed - */ - function isApprovedDataObject(DataPoint dp, address dobj) external view returns(bool); - - /** - * @notice Defines if DataObject is allowed to add Hooks to the DataPoint - * @param dp Identifier of the DataPoint - * @param dobj Address of DataObject - * @param approved if DataManager should be approved for the DataPoint - * @dev Function should be restricted to datapoint maintainer only - */ - function allowDataObject(DataPoint dp, address dobj, bool approved) external; - /** * @notice Reads stored data * @param dobj Identifier of DataObject From abd039caa13eef12ebf551d1f52b0777d506b05b Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 30 Jul 2024 15:15:42 +0200 Subject: [PATCH 14/34] erc7208 update: ODC -> DataIndex --- ERCS/erc-7208.md | 274 +++++++---------------------------------------- 1 file changed, 36 insertions(+), 238 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 3dd6fca0d5a..a617497fc97 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -15,22 +15,22 @@ requires: 165 -"On-chain Data Containers" (ODCs) are used for indexing and storing data in Smart Contracts called "Data Objects" (DOs). Information stored in Data Objects can be accessed and modified by implementing smart contracts called "Data Managers" (DMs). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for ODCs, the structures associated with DOs for abstracting storage, the DMs to access or modify the data, and finally the interfaces for compatibility Registries that enable Data Portability (horizontal mobility) between different implementations of this ERC. - +"On-chain Data Containers" (ODCs) are a series of interfaces used for indexing and managing data in Smart Contracts called "Data Object" (DO). Information stored in Data Objects can be accessed and modified by implementing smart contracts called "Data Manager" (DM). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for access management through "Data Index" (DI) implementations, the structures associated with "Data Points" (DP) for abstracting storage, the Data Managers to access or modify the data, and finally the "Data Point Registries" (DPR) interfaces for compatibility that enable data portability (horizontal mobility) between different implementations of this ERC. ## Motivation -As the Ethereum ecosystem grows, so does the demand for on-chain functionalities. The market encourages a desire for broader adoption and more complex systems, so there is a constant need for improved efficiency. We have seen times where the market hype has driven an explosion of new standard token proposals. While each standard serves its purpose, most often requires more flexibility to manage interoperability with other standards. +As the Ethereum ecosystem grows, so does the demand for on-chain functionalities. The market encourages a desire for broader adoption through more complex systems and there is a constant need for improved efficiency. We have seen times where the market hype has driven an explosion of new standard token proposals. While each standard serves its purpose, most often requires more flexibility to manage interoperability with other standards. + -While such diversity spurs innovation, different projects will implement their bespoke solutions for interoperability, resulting in a highly fragmented landscape. The lack of a standard mechanism for adapting tokenized across ERC standards is accentuating the interoperability issues. +While the diversity of standards spurs innovation, different projects will implement their bespoke solutions for interoperability, resulting in a highly fragmented landscape. The lack of a unified mechanism for adapting interaction between assets issued through different ERC standards is accentuating interoperability issues that lead to fragmentation. -We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens using different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such On-chain Adapters. +We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens, using different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such on-chain adapters. -We aim to abstract the on-chain data handling from both the logical implementation and the ERC interfaces exposing the underlying data. This EIP proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. +We aim to abstract the on-chain data handling from both the logical implementation and the ERC interfaces exposing the underlying data. This ERC proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. - **Data Abstraction**: We propose a standardized interface for enabling developers to separate the data storage code from the underlying token utility logic, reducing the need for supporting and implementing multiple inherited -and often clashing- interfaces to achieve asset compatibility. The data (and therefore the assets) can be stored independently of the logic that governs such data. @@ -46,14 +46,11 @@ We aim to abstract the on-chain data handling from both the logical implementati ### Terms +**Data Index (DI) Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access-management through the indexing of Data Objects. -**ODC**: A uniquely identifiable data structure used for indexing Data Objects or storing Data Points. - -**ODC Implementation**: One or many Smart Contracts implementing the ODC access-management logic. - -**Data Point**: A uniquely identifiable unit of information. +**Data Point**: A uniquely identifiable unit of information indexed by Data Index, managed by a Data Manager through a Data Object, and provided by a Data Point Registry. -**Data Object**: One or many Smart Contracts implementing the low-level storage management of stored Data Points +**Data Object**: One or many Smart Contracts implementing the low-level storage management of stored Data Points. **Data Manager**: One or many Smart Contracts implementing the high-level logic and end-user interface for managing the Data Points. @@ -62,14 +59,14 @@ We aim to abstract the on-chain data handling from both the logical implementati 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. -### ODC Interface +### Data Index (DI) Interface - * ODC SHOULD manage internal IDs for each data container. - * ODC SHOULD manage the access of Data Managers to Data Objects. - * ODC SHOULD use the IODC interface: + * DataIndex SHOULD manage the access of Data Managers to Data Objects. + * DataIndex SHOULD manage internal IDs for each user. + * DataIndex SHOULD use the IDataIndex interface: ```solidity -interface IODC { +interface IDataIndex { /** * @notice Verifies if DataManager is allowed to write specific DataPoint on specific DataObject * @param dp Identifier of the DataPoint @@ -109,12 +106,13 @@ interface IODC { function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); } ``` +The **Data Index (DI)** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()` or any other method), the **Data Index** should be used for validating access to the data. ### Data Object Interface * Data Object SHOULD implement the logic directly related to handling the data stored on Data Points. - * Data Object SHOULD implement the logic for transfering management of its Data Points to a different ODC Implementation. + * Data Object SHOULD implement the logic for transfering management of its Data Points to a different Data Index Implementation. * Data Object SHOULD use the IDataObject interface: ```solidity @@ -138,18 +136,18 @@ interface IDataObject { function write(DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); /** - * @notice Sets ODC Implementation + * @notice Sets DataIndex Implementation * @param dp Identifier of the DataPoint - * @param newImpl address of the new ODC implementation + * @param newImpl address of the new DataIndex implementation */ - function setOdcImplementation(DataPoint dp, address newImpl) external; + function setDIImplementation(DataPoint dp, address newImpl) external; } ``` +**Data Objects** are entrusted with the management of transactions that affect the storage of **Data Points**. -Data Objects can receive `read()` or `write()` requests when a Data Manager is requesting access to a Data Point. - -The function `setODCImplementation()` SHOULD enable the delegation of the the management function to an IODC implementation. +**Data Objects** can receive `read()`, `write()` or any other custom requests from a **Data Manager** requesting access to a **Data Point**. +As such, **Data Objects** respond to a gating mechanism given by a single **Data Index**. The function `setDIImplementation()` SHOULD enable the delegation of the the management function to an `IDataIndex` implementation. ### Data Point Structure @@ -176,6 +174,8 @@ The function `setODCImplementation()` SHOULD enable the delegation of the the ma **/ ``` +**Data Points** are the low-level structure abstracting information. **Data Points** are allocated by a **Data Point Registry**, and this information should be stored within its internal structure. Each **Data Point** should have a unique identifier provided by the **Data Point Registry** when instantiated. + ### Data Point Registry Interface @@ -208,7 +208,7 @@ interface IDataPointRegistry { function transferOwnership(DataPoint dp, address newOwner) external; /** - * @notice Grant permission to grant/revoke other roles on the DataPoint inside an ODC Implementation + * @notice Grant permission to grant/revoke other roles on the DataPoint inside a Data Index Implementation * This is useful if DataManagers are deployed during lifecycle of the application. * @param dp DataPoint * @param account New admin @@ -217,7 +217,7 @@ interface IDataPointRegistry { function grantAdminRole(DataPoint dp, address account) external returns (bool); /** - * @notice Revoke permission to grant/revoke other roles on the DataPoint inside an ODC Implementation + * @notice Revoke permission to grant/revoke other roles on the DataPoint inside a Data Index Implementation * @param dp DataPoint * @param account Old admin * @dev If an owner revokes Admin role from himself, he can add it again @@ -226,6 +226,8 @@ interface IDataPointRegistry { function revokeAdminRole(DataPoint dp, address account) external returns (bool); } ``` +The **Data Point Registry** is a smart contract entrusted with **Data Point** access control. **Data Managers** may request the allocation of **Data Points** to the **Data Point Registry**. Access-control to those **Data Points** is also managed by the **Data Point Registry**. + ### Data Manager Contract @@ -236,7 +238,7 @@ interface IDataPointRegistry { * Data Manager MAY use multiple Data Points * Data Manager MAY implement the logic for requesting Data Points from a Data Point Registry. -Data Managers are independent smart contracts that implement the business logic. They can either `read()` from a DataObject address, and `write()` through an ODC Implementation managing the delegated storage of the Data Points. +**Data Managers** are independent smart contracts that implement the business logic or "high-level" data management. They can either `read()` from a **Data Object** address, and `write()` through a **Data Index** Implementation managing the delegated storage of the **Data Points**. ## Rationale @@ -245,236 +247,32 @@ Data Managers are independent smart contracts that implement the business logic. The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with many options to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. -Data Manager implementations decide which Data Points they will be using. Their allocation is managed through a Data Point Registry, and the access to the Data Point is managed by passing through the ODC Implementation. +Data Manager implementations decide which Data Points they will be using. Their allocation is managed through a Data Point Registry, and the access to the Data Point is managed by passing through the Data Index Implementation. Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. Data Objects offer flexibility in storing mutable on-chain data that can be modified as per the requirements of each specific use case. This enables the Data Managers to hold mutable states in delegated storage and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through most other standardized interfaces. -As the Data Points can be set to respond to a specific ODC implementation, Data Managers can decide to migrate the complete storage of a Data Object from one ODC implementation to another. -By leveraging multiple implementations of the IODC interface, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). +As the Data Points can be set to respond to a specific Data Index implementation, Data Managers can decide to migrate the complete storage of a Data Object from one Data Index implementation to another. +By leveraging multiple implementations of the `IDataIndex` interface, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). ## 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 Data Points or owned in Vaults as Data Objects, and later exposed through any implementation of Data Managers. - -See Reference Implementation. - +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 tokens under other ERCs can be wrapped as Data Points and managed by Data Objects, and later exposed through any implementation of Data Managers. Each interoperability integration will require a compatibility analysis, depending on the use case. ## Reference Implementation -We present an example implementation of Asset Vaults, a deterministic Asset Vault Factory, and a Data Object specialized in managing Asset Vaults. The following implementation assumes the existance of both IDataPointRegistry and IODC implementations. - -The Vault Data Object may hold multiple assets locked under management, which in turn may be exposed through a Data Manager interface implementing a logic equivalent to the fractionalization of the Vault. - - -**Example Vault Interface** - -Asset Vaults are an example smart contract designed to implement the minimimalistic approach at asset management. Any user can deploy Asset Vaults through the factory. The role of an Asset Vault smart contract is to manage assets on behalf of the owner. We recommend Asset Vaults be `Ownable2Step` for security reasons. - - -```solidity -pragma solidity ^0.8.0; -interface IVault { - /** - * @notice Executes a state-changing call on a target - * @param target Contract to call - * @param data Data sent to the target, including function selector - * @param value Native coin value sent with the call - * @dev Access to this function SHOULD be protected - */ - function execute(address target, bytes calldata data, uint256 value) external returns (bytes memory); - - /** - * @notice Executes a static call (non state-changing) on a target - * @param target Contract to call - * @param data Data sent to the target, including function selector - */ - function executeStatic(address target, bytes calldata data) external view returns (bytes memory); - ... -} -``` - -Vault Factories are example smart contracts that facilitate the deployment of Asset Vaults. - -**Example Vault Factory Interface** -```solidity -pragma solidity ^0.8.0; - -import "IVault.sol"; +We present an example implementation showcasing the abstraction of the storage from the logic that governs the data on a fungible token scenario. -interface IVaultFactory { - function deploy(bytes calldata data) external returns (IVault); - function deployDeterministic(bytes32 salt, bytes calldata data) external returns (IVault); - function computeVaultAddress(address deployer, bytes32 salt, bytes calldata data) external view returns (address); - ... -} -``` - -A Vault Data Object is an implementation of Base Data Object for managing assets through Asset Vaults. As an implementation of IDataObject, this smart contract must implement `read()` and `write()` functions related to handling the data stored on Data Points under management. The logic of `read()` can be implemented with the use of `dispatchRead()` and the `write()` logic with `dispatchWrite()`. In this example, the Vault Data Object also implements some signature verification logic. - -**Example Vault Data Object contract** -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "IVaultFactory.sol"; -import "BaseDataObject.sol"; - -contract VaultDataObject is IERC1271, BaseDataObject { - using EnumerableSet for EnumerableSet.AddressSet; - - bytes4 public constant VAULT_FOR_SALT_SELECTOR = bytes4(keccak256("vaultForSalt(bytes32)")); //vaultForSalt(bytes32) returns(address) - bytes4 public constant ALL_VAULTS_SELECTOR = bytes4(keccak256("allVaults()")); //allVaults() returns(address[] memory) - - bytes4 public constant DEPLOY_VAULT_SELECTOR = bytes4(keccak256("deployVault(address,bytes)")); //deployVault(address factory, bytes calldata data) - bytes4 public constant DEPLOY_DETERMINISTIC_VAULT_SELECTOR = bytes4(keccak256("deployDeterministicVault(address,bytes32,bytes)")); //deployDeterministicVault(address factory, bytes32 salt, bytes calldata data) - bytes4 public constant GRANT_SIGNATURE_VALIDATION_SELECTOR = bytes4(keccak256("grantSignatureValidation(address,bytes32,address)")); //grantSignatureValidation(address vault, bytes32 hash, address signer) - bytes4 public constant REVOKE_SIGNATURE_VALIDATION_SELECTOR = bytes4(keccak256("revokeSignatureValidation(address,bytes32,address)")); //revokeSignatureValidation(address vault, bytes32 hash, address signer) - bytes4 public constant VAULT_EXECUTE_SELECTOR = bytes4(keccak256("vaultExecute(address,address,bytes,uint256)")); //vaultExecute(address vault, address target, bytes calldata data, uint256 value) - bytes4 private constant ERC1271_INVALID_SIGNATURE = 0xffffffff; - - error UnknownVault(); - error UnknownSalt(); - error UnknownDataPoint(); - error DeloyedVaultAlreadyRegistered(); - - event SignatureValidationAdded(address vault, bytes32 hash, address signer); - event SignatureValidationRevoked(address vault, bytes32 hash, address signer); - - struct SignatureVerificationData { - mapping(address vault => mapping(address signer => bool valid)) validSigners; - } - - struct DpData { - EnumerableSet.AddressSet deployedVaults; - mapping(bytes32 salt => address vault) deployedDeterministicVaults; - mapping(bytes32 hash => SignatureVerificationData) validSignatures; - ... - } - - mapping(uint256 dpIdx => DpData) private dpDataStorage; - mapping(address vault => DataPoint) internal vaultsToDataPoints; - - /** - * @inheritdoc IERC1271 - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) { - ... - } - - function computeDeterministicVaultAddress(DataPoint dp, address factory, bytes32 salt, bytes calldata data) external view returns (address) { - ... - } - - function datapointOfVault(address vault) public view returns (DataPoint) { - DataPoint dp = vaultsToDataPoints[vault]; - if (DataPoint.unwrap(dp) == bytes32(0)) revert UnknownVault(); - return dp; - } - - function dispatchRead(DataPoint dp, bytes4 operation, bytes calldata data) internal view virtual override returns (bytes memory) { - if (operation == VAULT_FOR_SALT_SELECTOR) { - bytes32 salt = abi.decode(data, (bytes32)); - return abi.encode(_vaultForSalt(dp, salt)); - } else if (operation == ALL_VAULTS_SELECTOR) { - return abi.encode(_allVaults(dp)); - } else { - revert UnknownReadOperation(operation); - } - } - - function dispatchWrite(DataPoint dp, bytes4 operation, bytes calldata data) internal virtual override returns (bytes memory) { - if (operation == DEPLOY_VAULT_SELECTOR) { - (address factory, bytes memory factoryData) = abi.decode(data, (address, bytes)); - return abi.encode(_deployVault(dp, factory, factoryData)); - } else if (operation == DEPLOY_DETERMINISTIC_VAULT_SELECTOR) { - (address factory, bytes32 salt, bytes memory factoryData) = abi.decode(data, (address, bytes32, bytes)); - return abi.encode(_deployDeterministicVault(dp, factory, salt, factoryData)); - } else if (operation == GRANT_SIGNATURE_VALIDATION_SELECTOR) { - (address vault, bytes32 hash, address signer) = abi.decode(data, (address, bytes32, address)); - _grantSignatureValidation(dp, vault, hash, signer); - return ""; - } else if (operation == REVOKE_SIGNATURE_VALIDATION_SELECTOR) { - (address vault, bytes32 hash, address signer) = abi.decode(data, (address, bytes32, address)); - _revokeSignatureValidation(dp, vault, hash, signer); - return ""; - } else if (operation == VAULT_EXECUTE_SELECTOR) { - (address vault, address target, bytes memory vaultCallData, uint256 value) = abi.decode(data, (address, address, bytes, uint256)); - return _vaultExecute(dp, vault, target, vaultCallData, value); - } else { - revert UnknownWriteOperation(operation); - } - } - ... -} -``` - -Finally, we expose a user-facing example Data Manager contract for managing the Data Object. The Data Manager will be implementing `read()` functions for checking the users' `balanceOf()` directly from the Data Object, and `write()` functions through the IODC implementation for the `transfer()` logic. Since multiple Data Managers can make use of the same Data Point, it is possible for several concurrent Data Manager implementations to share a Data Point where they delegate their storage. - - -**Example Fungible Token Data Manager implementation** - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract ERC20DataManager is IERC20, IERC20Metadata, IERC20Errors, Ownable, ERC20Approvals, ERC20Transfers, ERC20Burnable, ERC20Mintable, ERC20Metadata { - bytes4 internal BALANCE_OF_SELECTOR = bytes4(keccak256("balanceOf(address)")); - bytes4 internal TOTAL_SUPPLY_SELECTOR = bytes4(keccak256("totalSupply()")); - bytes4 internal MINT_SELECTOR = bytes4(keccak256("mint(address,uint256)")); - bytes4 internal BURN_SELECTOR = bytes4(keccak256("burn(address,uint256)")); - bytes4 internal TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,address,uint256)")); - - DataPoint internal immutable datapoint; - IDataObject public immutable fungibleDO; - IODC public odc; - mapping(address account => mapping(address spender => uint256)) private _allowances; - - constructor( - bytes32 _datapoint, - address _odc, - address _fungibleDO, - string memory name_, - string memory symbol_ - ) Ownable(msg.sender) ERC20Metadata(name_, symbol_) { - datapoint = DataPoint.wrap(_datapoint); - odc = IODC(_odc); - fungibleDO = IDataObject(_fungibleDO); - } - - function totalSupply() external view override returns (uint256) { - return abi.decode(fungibleDO.read(datapoint, TOTAL_SUPPLY_SELECTOR, ""), (uint256)); - } - - function balanceOf(address account) external view override returns (uint256) { - return abi.decode(fungibleDO.read(datapoint, BALANCE_OF_SELECTOR, abi.encode(account)), (uint256)); - } - - function _checkMinter() internal view override { - _checkOwner(); - } - - function _writeTransfer(address from, address to, uint256 amount) internal override { - if (from == address(0)) { - odc.write(address(fungibleDO), datapoint, MINT_SELECTOR, abi.encode(to, amount)); - } else if (to == address(0)) { - odc.write(address(fungibleDO), datapoint, BURN_SELECTOR, abi.encode(from, amount)); - } else { - odc.write(address(fungibleDO), datapoint, TRANSFER_SELECTOR, abi.encode(from, to, amount)); - } - } -} -``` ## Security Considerations The access control is separated in three layers: * **Layer 1**: The Data Point Registry allocates for Data Managers and manages ownerhsip (admin/write rights) of Data Points. -* **Layer 2**: The ODC smart contract implements Access Control by managing Approvals of Data Managers to Data Points. It uses the Data Point Registry to verify who can grant/revoke this access. -* **Layer 3**: The Data Manager exposes functions that can perform `write` operations on the Data Point by calling the ODC implementation. +* **Layer 2**: The Data Index smart contract implements Access Control by managing Approvals of Data Managers to Data Points. It uses the Data Point Registry to verify who can grant/revoke this access. +* **Layer 3**: The Data Manager exposes functions that can perform `write` operations on the Data Point by calling the Data Index implementation. No further security considerations are derived specificly from this ERC. From 5840a805357454882dbc6c43c8e280d94a36fa39 Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 09:52:33 +0200 Subject: [PATCH 15/34] erc7208 update: diagrams --- ERCS/erc-7208.md | 4 +++- assets/erc-7208/erc-7208-compat.md | 20 +++++++++---------- assets/erc-7208/erc-7208-overview.svg | 2 +- .../erc-7208/erc-7208-technical-overview.svg | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index a617497fc97..20c07d95f04 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -1,7 +1,7 @@ --- eip: 7208 title: On-Chain Data Container -description: Abstracting logic away from storage +description: ERC interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 status: Draft @@ -108,6 +108,8 @@ interface IDataIndex { ``` The **Data Index (DI)** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()` or any other method), the **Data Index** should be used for validating access to the data. +The mechanism for ID managamenent determines a space of compatibility between implementations. + ### Data Object Interface diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md index 571ef9f9121..8a9a5fe6961 100644 --- a/assets/erc-7208/erc-7208-compat.md +++ b/assets/erc-7208/erc-7208-compat.md @@ -1,20 +1,20 @@ -## Appendix: Compatibility Analysis +## Appendix: Interoperability Analysis -We provide a cherrypicked list of possible points of contact between ERC-7208 (on-chain data container) and other tokenization standards and proposals. +We provide a cherrypicked list of possible points of contact between ERC-7208 (on-chain data containers) and other tokenization standards and proposals. -**ERC-1400 (Security Token Standard)**: In aggregate provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations. ERC-7208 can enhance ERC-1400 by offering more dynamic and flexible data management. Data Objects enable the storage and modification of on-chain data related to security tokens, such as compliance information or ownership details. In the case of assets that are already issued under ERC-1400, they can be wrapped into a Vault Data Object and exposed through any Data Manager interface (including ERC-3643 and others). Alternative, if the asset is issued with native Data Point storage, the integration could lead to more efficient and transparent security token offerings. The modular and adaptable nature of ERC-7208 enable transparent enhancements to the internal logic of individual ERC-1400 tokens. +**ERC-1400 (Security Token Standard)**: This ERC provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations. ERC-7208 can enhance ERC-1400 by offering more dynamic and flexible data management architecture. **Data Objects** enable the storage and modification of on-chain data related to security tokens, such as compliance information or ownership details. In the case of assets that are already issued under ERC-1400, they can be wrapped into a **Vault Data Object** and exposed through any **Data Manager** interface (including ERC-3643 and others). Alternative, if the asset is issued with native **Data Point** storage, the integration could lead to more efficient and transparent security token offerings. The modular and adaptable nature of ERC-7208 enable transparent enhancements to the internal logic of individual ERC-1400 tokens. -**EIP-2309 (Consecutive batch minting)**: ERC-7208 is compatible with EIP-2309, allowing for the batch minting process to be enriched with additional data. ODCs could store information related to each batch, such as metadata or batch-specific attributes, without disrupting the minting process. This information may be stored on Data Points, internally within the ODC Implementation, or locally at the Data Manager exposing the EIP-2309 interface. +**EIP-2309 (Consecutive batch minting)**: ERC-7208 is compatible with EIP-2309, allowing for the batch minting process to be enriched with additional data. **Data Objects** could store information related to each batch, such as metadata or batch-specific attributes, without disrupting the minting process. This information may be stored on **Data Points**, and interacted with through a **Data Manager** exposing the EIP-2309 interface. -**EIP-2981 (Royalties)**: ERC-7208 can complement EIP-2981 as a Data Manager by providing a flexible way to handle royalties. Data Objects can store and manage the low level storage of royalty information dynamically and independently from the Data Manager's implemented interface. This enables a complex royalty structure that can change over time or based on certain conditions, like embedding compliance checks on the functions modifying the storage and simultaneously exposing multiple interfaces for accessing the storage. For instance, by leveraging ERC-7208, an individual royalty based NFT can be traded in a compliant manner, concurrently under both an ERC-721 interface as well as an ERC-20 through the use of Data Managers. +**EIP-2981 (Royalties)**: ERC-7208 can complement EIP-2981 as a **Data Manager** by providing a flexible way to handle royalties. **Data Objects** can store and manage the low level storage of royalty information dynamically and independently from the interface used by the end user. This enables a complex royalty structure that can change over time or based on arbitrary conditions, like embedding compliance checks and simultaneously exposing multiple interfaces for accessing the underlying asset. For instance, by leveraging ERC-7208, an individual royalty based NFT can be traded in a compliant manner, concurrently under both an ERC-721 interface as well as an ERC-20 through the use of **Data Managers**. -**ERC-3643 (Security Tokens)**: ERC-3643 defines a *Security Token interface for Regulated Exchanges* based on ERC-20 token standard. The ERC-7208 can be used for wrapping buckets of tokens (irrespective of their ERC) and adapting their logic to the ERC-3643. Additionally, a Data Object storing native ERC-3643 tokens can be used for improving the compliance logic and enabling the trading of underlying securities simultaneously through multiple interfaces that respond to different regulatory frameworks. Moreover, the separation of the storage enables the logic to implement functionalities that were not initially a part of the original ERC, such as identity-based recovery of assets, role-based access control, the introduction of cross-chain support, etc. +**ERC-3643 (Security Tokens)**: ERC-3643 defines a *Security Token interface for Regulated Exchanges* based on ERC-20 token standard. The ERC-7208 can be used for wrapping buckets of tokens (irrespective of their ERC) and adapting their logic to the ERC-3643. Additionally, a **Data Object** storing native ERC-3643 tokens can be used for improving the compliance logic and enabling the trading of underlying securities simultaneously through multiple interfaces that respond to different regulatory frameworks. Moreover, the separation of the storage enables the logic to implement functionalities that were not initially a part of the original ERC, such as identity-based recovery of assets, role-based access control, the introduction of cross-chain support, etc. -**ERC-4337 (Account Abstraction)**: ERC-7208 can provide a standardized method to store and manage the complex data structures required by abstracted accounts. This can include user preferences, access control lists, recovery options, and other customizable account features. The mutable states of abstracted accounts can be efficiently handled using Data Objects. This, in turn, improves the adaptability and security of abstracted accounts. Additionally, an ERC-7208 implementation supporting meta-transactions and Data Points separated by chain-id can be developed to fully abstract account management across blockchains. +**ERC-4337 (Account Abstraction)**: ERC-7208 can provide a standardized method to store and manage the complex data structures required by abstracted accounts. This can include user preferences, access control lists, recovery options, and other customizable account features. The mutable states of abstracted accounts can be efficiently handled using **Data Objects**. This, in turn, improves the adaptability and security of abstracted accounts. Additionally, an ERC-7208 implementation supporting meta-transactions and **Data Points** separated by chain-id can be developed to fully abstract account management across blockchains. -**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from a single ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. ODCs can enhance EIP-4626 by providing a more dynamic data layer for tokenized vaults. Data Objects and ODCs can store information about the assets in the vault, conditions for access, or other relevant data, enabling more nuanced interactions with tokenized vaults. Additionally, the Data Object can store more than a single ERC-20, greatly increasing the capabilities of Tokenized Vaults. +**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from a single ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. ERC-7208 can enhance EIP-4626 by providing a more dynamic data layer for tokenized vaults. **Data Objects** store information about the assets in the vault, conditions for access, or other relevant data, enabling more nuanced interactions with tokenized vaults. Additionally, the **Data Point** can store more than a single ERC-20, greatly increasing the capabilities of Tokenized Vaults. -**ERC-4907 (Shared Ownership)**: The integration of ERC-4907 as a Data Manager with ERC-7208 Data Object storage can enhance the rental experience by allowing for additional rental-related data directly on-chain, such as rental terms, user permissions, and other customizable settings which would be self-contained within Data Points and therefore automatically updated as metadata. ERC-4907's rental mechanism complements ERC-7208's ability to manage mutable on-chain data. By combining these two, NFTs can not only be rented out for specific periods but also have their traits or states dynamically managed and updated during the rental period. This combination enhances security and compliance in NFT transactions, particularly for Real World Asset Tokenization. Rental agreements, regulatory compliance, intelectual property and user rights can be embedded within Data Objects to ensure that the NFT usage adheres to predefined rules. +**ERC-4907 (Shared Ownership)**: The integration of ERC-4907 as a **Data Manager** with ERC-7208 **Data Point** storage can enhance the rental experience by allowing for additional rental-related data directly on-chain, such as rental terms, user permissions, and other customizable settings which would be self-contained within **Data Points** and therefore automatically updated as metadata. ERC-4907's rental mechanism complements ERC-7208's ability to manage mutable on-chain data. By combining these two, NFTs can not only be rented out for specific periods but also have their traits or states dynamically managed and updated during the rental period. This combination enhances security and compliance in NFT transactions, particularly for *Real World Asset Tokenization*. Rental agreements, regulatory compliance, intelectual property and user rights can be embedded within Data Objects to ensure that the NFT usage adheres to predefined rules. -**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: ERC-7540 vaults's are focused on asynchronous deposit and redemption. Integrating ERC-7540, either by Wrapping into a Data Object or by exposing a 7208 Data Manager, will facilitate more complex financial products. DeFi products like undercollateralized loans, insurance products, or tokenized stocks often require operations to be handled in a non-instantaneous manner. However, the nature of these products requires adhering to regulatory compliance and identity management solutions. This can easily be achieved by implementing the use of on-chain adapters that enhance the logic while keeping the data secure. +**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: ERC-7540 vaults's are focused on asynchronous deposit and redemption. Integrating ERC-7540, either by Wrapping into a **Data Object** or by exposing an ERC-7208 **Data Manager**, will facilitate more complex financial products. DeFi products like undercollateralized loans, insurance products, or tokenized stocks often require operations to be handled in a non-instantaneous manner. However, the nature of these products requires adhering to regulatory compliance and identity management solutions. This can easily be achieved by implementing the use of on-chain adapters that enhance the logic while keeping the data secure. diff --git a/assets/erc-7208/erc-7208-overview.svg b/assets/erc-7208/erc-7208-overview.svg index 13cc55680e0..1e4a1e7a4ea 100644 --- a/assets/erc-7208/erc-7208-overview.svg +++ b/assets/erc-7208/erc-7208-overview.svg @@ -1,4 +1,4 @@ -
DataObject
DataObject
DataManager
On-chain Data Container
DataManager
DataManager
DataObject
Buckets of assets
and asset data
Access Management
Business Logic and
user interface for
interacting with the data
\ No newline at end of file +
DataObject
DataObject
DataManager
Data Index
 &
Data Point Registry
DataManager
DataManager
DataObject
Asset data Storage
Access Management
Business Logic and
user interface for
interacting with the data
(expose any ERC interface)
\ No newline at end of file diff --git a/assets/erc-7208/erc-7208-technical-overview.svg b/assets/erc-7208/erc-7208-technical-overview.svg index d1ab5cc3cc7..638390a5d3a 100644 --- a/assets/erc-7208/erc-7208-technical-overview.svg +++ b/assets/erc-7208/erc-7208-technical-overview.svg @@ -1,4 +1,4 @@ -
Access Management
ODC_ID => DM_ID => DP_ID
...
...
ODC Smart Contract
ODC ID
Management
DataManager (DM)
Implements business logic
Access DM
Manage ODC
User
Manage DM <=> DataObject relationship
DM Mainteiner
Storage
DataPoint_DM1_1
odc_id1
odc_id2
DataPoint_DM1_2
odc_id1
odc_id2
DataPoint_DM2_1
odc_id1
odc_id2
DataPoint_DM2_2
odc_id1
odc_id2
DataPoint Logic
Performs logic directly related
to storage management
Multiple DataObject (DO)
Contracts
DataObject (DO)
Contract
ODC implementation
(ERC-7208)

DataManager (DM)
Implements business logic
DataManager (DM)
Implements business logic
DataManager (DM)
Implements usecase logic
Data is stored in DataPoints
DataObject exposes low level
Data Management interface
ODC implementation
manages access
and indexes DataObjects
DataPoint Registry

Should provide information if an account can grant access to DP for DMs and DOs

function isAdmin(DataPoint, account) returns (bool)
Verify DP Maintainer
is Admin of DataPoint
Allocate DataPoint
DataManagers Implement
business logic
(user-facing interfaces)
DP Registry implementation
separates the space of
compatible DataPoints
\ No newline at end of file +
Access Management
ID => DM_ID => DP_ID
...
...
DataIndex (DI)
Smart Contract
ID Management
DataManager (DM)
Implements business logic
Access DM
Provides ID
User
Manage DM <=> DataObject relationship
DM Mainteiner
Storage
DataPoint_DM1_1
id_1
id_2
DataPoint_DM1_2
id_1
id_2
DataPoint_DM2_1
id_1
id_2
DataPoint_DM2_2
id_1
id_2
DataPoint Logic
Performs logic directly related
to storage management
Multiple DataObject (DO)
Contracts
DataObject (DO)
Contract
ERC-7208
Technical Overview
DataManager (DM)
Implements business logic
DataManager (DM)
Implements business logic
DataManager (DM)
Implements usecase logic
Data is stored in DataPoints
DataObject exposes low level
Data Management interface
DataIndex implementation
manages access
and indexes DataObjects
DataPoint Registry

Should provide information if an account can grant access to DP for DMs and DOs

function isAdmin(DataPoint, account) returns (bool)
Verify DP Maintainer
is Admin of DataPoint
Allocate DataPoint
DataManagers Implement
business logic
(user-facing interfaces)
DP Registry implementation
separates the space of
compatible DataPoints
\ No newline at end of file From 1d7275f3ddf1059c50e0e7e1577fc87c1d5c8022 Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 10:11:43 +0200 Subject: [PATCH 16/34] erc7208 update: grammar --- ERCS/erc-7208.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 20c07d95f04..a2389b7c980 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -24,13 +24,13 @@ requires: 165 As the Ethereum ecosystem grows, so does the demand for on-chain functionalities. The market encourages a desire for broader adoption through more complex systems and there is a constant need for improved efficiency. We have seen times where the market hype has driven an explosion of new standard token proposals. While each standard serves its purpose, most often requires more flexibility to manage interoperability with other standards. -While the diversity of standards spurs innovation, different projects will implement their bespoke solutions for interoperability, resulting in a highly fragmented landscape. The lack of a unified mechanism for adapting interaction between assets issued through different ERC standards is accentuating interoperability issues that lead to fragmentation. +The diversity of standards spurs innovation. Different projects will implement their bespoke solutions for interoperability. The absence of a unified adapter mechanism driving the interactions between assets issued under different ERC standards is causing interoperability issues. This, in turn, is leading to fragmentation. -We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens, using different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such on-chain adapters. +We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens through the use of different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such on-chain adapters. -We aim to abstract the on-chain data handling from both the logical implementation and the ERC interfaces exposing the underlying data. This ERC proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. +We aim to abstract the on-chain data handling from the logical implementation and the ERC interfaces exposing the underlying data. The current ERC proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. - **Data Abstraction**: We propose a standardized interface for enabling developers to separate the data storage code from the underlying token utility logic, reducing the need for supporting and implementing multiple inherited -and often clashing- interfaces to achieve asset compatibility. The data (and therefore the assets) can be stored independently of the logic that governs such data. @@ -46,17 +46,17 @@ We aim to abstract the on-chain data handling from both the logical implementati ### Terms -**Data Index (DI) Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access-management through the indexing of Data Objects. +**Data Index (DI) Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access management through the indexing of Data Objects. -**Data Point**: A uniquely identifiable unit of information indexed by Data Index, managed by a Data Manager through a Data Object, and provided by a Data Point Registry. +**Data Point**: A uniquely identifiable unit of information indexed by a Data Index, managed by a Data Manager through a Data Object, and provided by a Data Point Registry. **Data Object**: One or many Smart Contracts implementing the low-level storage management of stored Data Points. -**Data Manager**: One or many Smart Contracts implementing the high-level logic and end-user interface for managing the Data Points. +**Data Manager**: One or many Smart Contracts implementing the high-level logic and end-user interfaces for managing Data Points. **Data Point Registry**: One or many Smart Contracts that define a space of compatible or interoperable Data Points. -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. +The keywords "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. ### Data Index (DI) Interface @@ -106,7 +106,7 @@ interface IDataIndex { function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); } ``` -The **Data Index (DI)** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()` or any other method), the **Data Index** should be used for validating access to the data. +The **Data Index (DI)** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()`, or any other method), the **Data Index** should be used for validating access to the data. The mechanism for ID managamenent determines a space of compatibility between implementations. @@ -147,7 +147,7 @@ interface IDataObject { ``` **Data Objects** are entrusted with the management of transactions that affect the storage of **Data Points**. -**Data Objects** can receive `read()`, `write()` or any other custom requests from a **Data Manager** requesting access to a **Data Point**. +**Data Objects** can receive `read()`, `write()`, or any other custom requests from a **Data Manager** requesting access to a **Data Point**. As such, **Data Objects** respond to a gating mechanism given by a single **Data Index**. The function `setDIImplementation()` SHOULD enable the delegation of the the management function to an `IDataIndex` implementation. @@ -240,7 +240,7 @@ The **Data Point Registry** is a smart contract entrusted with **Data Point** ac * Data Manager MAY use multiple Data Points * Data Manager MAY implement the logic for requesting Data Points from a Data Point Registry. -**Data Managers** are independent smart contracts that implement the business logic or "high-level" data management. They can either `read()` from a **Data Object** address, and `write()` through a **Data Index** Implementation managing the delegated storage of the **Data Points**. +**Data Managers** are independent smart contracts that implement the business logic or "high-level" data management. They can either `read()` from a **Data Object** address and `write()` through a **Data Index** Implementation managing the delegated storage of the **Data Points**. ## Rationale @@ -261,7 +261,7 @@ By leveraging multiple implementations of the `IDataIndex` interface, this stand ## 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 tokens under other ERCs can be wrapped as Data Points and managed by Data Objects, and later exposed through any implementation of Data Managers. Each interoperability integration will require a compatibility analysis, depending on the use case. +This ERC is intended to augment the functionality of existing token standards without introducing breaking changes. As such, it does not present any backward compatibility issues. Already deployed tokens under other ERCs can be wrapped as Data Points and managed by Data Objects, and later exposed through any implementation of Data Managers. Each interoperability integration will require a compatibility analysis, depending on the use case. ## Reference Implementation @@ -270,13 +270,13 @@ We present an example implementation showcasing the abstraction of the storage f ## Security Considerations -The access control is separated in three layers: +The access control is separated into three layers: -* **Layer 1**: The Data Point Registry allocates for Data Managers and manages ownerhsip (admin/write rights) of Data Points. +* **Layer 1**: The Data Point Registry allocates for Data Managers and manages ownership (admin/write rights) of Data Points. * **Layer 2**: The Data Index smart contract implements Access Control by managing Approvals of Data Managers to Data Points. It uses the Data Point Registry to verify who can grant/revoke this access. * **Layer 3**: The Data Manager exposes functions that can perform `write` operations on the Data Point by calling the Data Index implementation. -No further security considerations are derived specificly from this ERC. +No further security considerations are derived specifically from this ERC. ## Copyright From f2fcedbc18baa4e1e305a476727898333cc0bfa5 Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 10:34:54 +0200 Subject: [PATCH 17/34] erc7208 update: grammar --- ERCS/erc-7208.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index a2389b7c980..cebd744c994 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -46,7 +46,7 @@ We aim to abstract the on-chain data handling from the logical implementation an ### Terms -**Data Index (DI) Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access management through the indexing of Data Objects. +**Data Index Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access management through the indexing of Data Objects. **Data Point**: A uniquely identifiable unit of information indexed by a Data Index, managed by a Data Manager through a Data Object, and provided by a Data Point Registry. @@ -59,7 +59,7 @@ We aim to abstract the on-chain data handling from the logical implementation an The keywords "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. -### Data Index (DI) Interface +### Data Index Interface * DataIndex SHOULD manage the access of Data Managers to Data Objects. * DataIndex SHOULD manage internal IDs for each user. @@ -106,7 +106,7 @@ interface IDataIndex { function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); } ``` -The **Data Index (DI)** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()`, or any other method), the **Data Index** should be used for validating access to the data. +The **Data Index** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()`, or any other method), the **Data Index** should be used for validating access to the data. The mechanism for ID managamenent determines a space of compatibility between implementations. From d48304c3fbb9bd45d6830d45e218981e556a6f6c Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 15:44:05 +0200 Subject: [PATCH 18/34] erc7208 update: ref impl --- ERCS/erc-7208.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index cebd744c994..8b4d1d3f23b 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -265,7 +265,11 @@ This ERC is intended to augment the functionality of existing token standards wi ## Reference Implementation -We present an example implementation showcasing the abstraction of the storage from the logic that governs the data on a fungible token scenario. +We present an **educational example** implementation showcasing two types of tokens (Fungible and Semi-Fungible) sharing the same storage. The abstraction of the storage from the logic is achieved through the use of **Data Objects**. A factory is used for deploying fungible token contracts that share storage with each semi-fungible NFT representing a collection of fractions. Note that if a `transfer()` is called by either interface (Fungible or Semi-Fungible), both interfaces are emitting an event. + + +**This example has not been audited and should not be used in production environments.** + ## Security Considerations From 7d3e6e2c9879604c4706899cfa4e4713b51407ed Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 20:11:28 +0200 Subject: [PATCH 19/34] erc7208 update: diagrams --- assets/erc-7208/erc-7208-overview.svg | 2 +- assets/erc-7208/erc-7208-technical-overview.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/erc-7208/erc-7208-overview.svg b/assets/erc-7208/erc-7208-overview.svg index 1e4a1e7a4ea..bd9fa53b8fb 100644 --- a/assets/erc-7208/erc-7208-overview.svg +++ b/assets/erc-7208/erc-7208-overview.svg @@ -1,4 +1,4 @@ -
DataObject
DataObject
DataManager
Data Index
 &
Data Point Registry
DataManager
DataManager
DataObject
Asset data Storage
Access Management
Business Logic and
user interface for
interacting with the data
(expose any ERC interface)
\ No newline at end of file +
DataObject
DataObject
DataManager
Data Index
 &
Data Point Registry
DataManager
DataManager
DataObject
Asset data Storage
Access Management
Business Logic and
user interface for
interacting with the data
(expose any ERC interface)
\ No newline at end of file diff --git a/assets/erc-7208/erc-7208-technical-overview.svg b/assets/erc-7208/erc-7208-technical-overview.svg index 638390a5d3a..cef28c2fa26 100644 --- a/assets/erc-7208/erc-7208-technical-overview.svg +++ b/assets/erc-7208/erc-7208-technical-overview.svg @@ -1,4 +1,4 @@ -
Access Management
ID => DM_ID => DP_ID
...
...
DataIndex (DI)
Smart Contract
ID Management
DataManager (DM)
Implements business logic
Access DM
Provides ID
User
Manage DM <=> DataObject relationship
DM Mainteiner
Storage
DataPoint_DM1_1
id_1
id_2
DataPoint_DM1_2
id_1
id_2
DataPoint_DM2_1
id_1
id_2
DataPoint_DM2_2
id_1
id_2
DataPoint Logic
Performs logic directly related
to storage management
Multiple DataObject (DO)
Contracts
DataObject (DO)
Contract
ERC-7208
Technical Overview
DataManager (DM)
Implements business logic
DataManager (DM)
Implements business logic
DataManager (DM)
Implements usecase logic
Data is stored in DataPoints
DataObject exposes low level
Data Management interface
DataIndex implementation
manages access
and indexes DataObjects
DataPoint Registry

Should provide information if an account can grant access to DP for DMs and DOs

function isAdmin(DataPoint, account) returns (bool)
Verify DP Maintainer
is Admin of DataPoint
Allocate DataPoint
DataManagers Implement
business logic
(user-facing interfaces)
DP Registry implementation
separates the space of
compatible DataPoints
\ No newline at end of file +
Access Management
ID => DM_ID => DP_ID
...
...
DataIndex (DI)
Smart Contract
ID Management
DataManager (DM)
Implements business logic
Access DM
Provides ID
User
Manage DM <=> DataObject relationship
DM Mainteiner
Storage
DataPoint_DM1_1
id_1
id_2
DataPoint_DM1_2
id_1
id_2
DataPoint_DM2_1
id_1
id_2
DataPoint_DM2_2
id_1
id_2
DataPoint Logic
Performs logic directly related
to storage management
Multiple DataObject (DO)
Contracts
DataObject (DO)
Contract
ERC-7208
Technical Overview
DataManager (DM)
Implements business logic
DataManager (DM)
Implements business logic
DataManager (DM)
Implements usecase logic
Data is stored in DataPoints
DataObject exposes low level
Data Management interface
DataIndex implementation
manages access
and indexes DataObjects
DataPoint Registry

Should provide information if an account can grant access to DP for DMs and DOs

function isAdmin(DataPoint, account) returns (bool)
Verify DP Maintainer
is Admin of DataPoint
Allocate DataPoint
DataManagers Implement
business logic
(user-facing interfaces)
DP Registry implementation
separates the space of
compatible DataPoints
\ No newline at end of file From e1cd754b16a4b3e8e54e75548d38f81e67c83537 Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 20:24:09 +0200 Subject: [PATCH 20/34] erc7208 update: example implementation --- assets/erc-7208/contracts/DataIndex.sol | 95 ++++ .../erc-7208/contracts/DataPointRegistry.sol | 78 +++ ...icERC1155WithERC20FractionsDataManager.sol | 452 ++++++++++++++++++ .../MinimalisticERC20FractionDataManager.sol | 157 ++++++ ...alisticERC20FractionDataManagerFactory.sol | 12 + .../MinimalisticFungibleFractionsDO.sol | 380 +++++++++++++++ .../contracts/interfaces/IDataIndex.sol | 49 ++ .../contracts/interfaces/IDataObject.sol | 37 ++ .../interfaces/IDataPointRegistry.sol | 91 ++++ .../IFractionTransferEventEmitter.sol | 20 + .../IFungibleFractionsOperations.sol | 103 ++++ .../contracts/interfaces/IIDManager.sol | 27 ++ .../erc-7208/contracts/utils/ChainidTools.sol | 48 ++ .../erc-7208/contracts/utils/DataPoints.sol | 67 +++ .../contracts/utils/OmnichainAddresses.sol | 77 +++ 15 files changed, 1693 insertions(+) create mode 100644 assets/erc-7208/contracts/DataIndex.sol create mode 100644 assets/erc-7208/contracts/DataPointRegistry.sol create mode 100644 assets/erc-7208/contracts/datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol create mode 100644 assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManager.sol create mode 100644 assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManagerFactory.sol create mode 100644 assets/erc-7208/contracts/dataobjects/MinimalisticFungibleFractionsDO.sol create mode 100644 assets/erc-7208/contracts/interfaces/IDataIndex.sol create mode 100644 assets/erc-7208/contracts/interfaces/IDataObject.sol create mode 100644 assets/erc-7208/contracts/interfaces/IDataPointRegistry.sol create mode 100644 assets/erc-7208/contracts/interfaces/IFractionTransferEventEmitter.sol create mode 100644 assets/erc-7208/contracts/interfaces/IFungibleFractionsOperations.sol create mode 100644 assets/erc-7208/contracts/interfaces/IIDManager.sol create mode 100644 assets/erc-7208/contracts/utils/ChainidTools.sol create mode 100644 assets/erc-7208/contracts/utils/DataPoints.sol create mode 100644 assets/erc-7208/contracts/utils/OmnichainAddresses.sol diff --git a/assets/erc-7208/contracts/DataIndex.sol b/assets/erc-7208/contracts/DataIndex.sol new file mode 100644 index 00000000000..f1cb864cbec --- /dev/null +++ b/assets/erc-7208/contracts/DataIndex.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "./interfaces/IDataIndex.sol"; +import "./interfaces/IDataObject.sol"; +import "./interfaces/IIDManager.sol"; +import "./interfaces/IDataPointRegistry.sol"; + +/** + * @title Data Index contract + * @notice Minimalistic implementation of a Data Index contract + */ +contract DataIndex is IDataIndex, IIDManager, AccessControl { + /// @dev Error thrown when the sender is not an admin of the DataPoint + error InvalidDataPointAdmin(DataPoint dp, address sender); + + /// @dev Error thrown when the DataManager is not approved to interact with the DataPoint + error DataManagerNotApproved(DataPoint dp, address dm); + + /// @dev Error thrown when the dataIndex identifier is incorrect + error IncorrectIdentifier(bytes32 diid); + + /** + * @notice Event emitted when DataManager is approved for DataPoint + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @param approved if DataManager is approved + */ + event DataPointDMApprovalChanged(DataPoint dp, address dm, bool approved); + + /// @dev Mapping of DataPoint to DataManagers allowed to write to this DP (in any DataObject) + mapping(DataPoint => mapping(address dm => bool allowed)) dmApprovals; + + /** + * @notice Restricts access to the function, allowing only DataPoint admins + * @param dp DataPoint to check ownership of + */ + modifier onlyDPOwner(DataPoint dp) { + (uint32 chainId, address registry, ) = DataPoints.decode(dp); + ChainidTools.requireCurrentChain(chainId); + bool isAdmin = IDataPointRegistry(registry).isAdmin(dp, msg.sender); + if (!isAdmin) revert InvalidDataPointAdmin(dp, msg.sender); + _; + } + + /** + * @notice Allows access only to DataManagers which was previously approved + * @param dp DataPoint to check DataManager approval for + */ + modifier onlyApprovedDM(DataPoint dp) { + bool approved = dmApprovals[dp][msg.sender]; + if (!approved) revert DataManagerNotApproved(dp, msg.sender); + _; + } + + /// @dev Sets the default admin role + constructor() { + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + } + + ///@inheritdoc IDataIndex + function isApprovedDataManager(DataPoint dp, address dm) external view returns (bool) { + return dmApprovals[dp][dm]; + } + + ///@inheritdoc IDataIndex + function allowDataManager(DataPoint dp, address dm, bool approved) external onlyDPOwner(dp) { + dmApprovals[dp][dm] = approved; + emit DataPointDMApprovalChanged(dp, dm, approved); + } + + ///@inheritdoc IDataIndex + function read(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external view returns (bytes memory) { + return IDataObject(dobj).read(dp, operation, data); + } + + ///@inheritdoc IDataIndex + function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external onlyApprovedDM(dp) returns (bytes memory) { + return IDataObject(dobj).write(dp, operation, data); + } + + ///@inheritdoc IIDManager + function diid(address account, DataPoint) external pure returns (bytes32) { + return bytes32(uint256(uint160(account))); + } + + ///@inheritdoc IIDManager + function ownerOf(bytes32 _diid) external view returns (uint32, address) { + if (_diid & 0xFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000 != 0) revert IncorrectIdentifier(_diid); // Require first 12 bytes empty, leaving only 20 bytes of address non-empty + address account = address(uint160(uint256(_diid))); + if (account == address(0)) revert IncorrectIdentifier(_diid); + return (ChainidTools.chainid(), account); + } +} diff --git a/assets/erc-7208/contracts/DataPointRegistry.sol b/assets/erc-7208/contracts/DataPointRegistry.sol new file mode 100644 index 00000000000..e090083e939 --- /dev/null +++ b/assets/erc-7208/contracts/DataPointRegistry.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {DataPoints, DataPoint} from "./utils/DataPoints.sol"; +import {IDataPointRegistry} from "./interfaces/IDataPointRegistry.sol"; + +/** + * @title DataPointRegistry contract + * @notice Contract for managing the creation, transfer and access control of DataPoints + */ +contract DataPointRegistry is IDataPointRegistry { + /** + * @notice DataPoint access data + * @param owner Owner of the DataPoint + * @param isAdmin Mapping of isAdmin status for each account + */ + struct DPAccessData { + address owner; + mapping(address => bool) isAdmin; + } + + /// @dev Counter for DataPoint allocation + uint256 private counter; + + /// @dev Access data for each DataPoint + mapping(DataPoint => DPAccessData) private accessData; + + /// @inheritdoc IDataPointRegistry + function isAdmin(DataPoint dp, address account) public view returns (bool) { + return accessData[dp].isAdmin[account]; + } + + /// @inheritdoc IDataPointRegistry + function allocate(address owner) external payable returns (DataPoint) { + if (msg.value > 0) revert NativeCoinDepositIsNotAccepted(); + uint256 newCounter = ++counter; + if (newCounter > type(uint32).max) revert CounterOverflow(); + DataPoint dp = DataPoints.encode(address(this), uint32(newCounter)); + DPAccessData storage dpd = accessData[dp]; + dpd.owner = owner; + dpd.isAdmin[owner] = true; + emit DataPointAllocated(dp, owner); + return dp; + } + + /// @inheritdoc IDataPointRegistry + function transferOwnership(DataPoint dp, address newOwner) external { + DPAccessData storage dpd = accessData[dp]; + address currentOwner = dpd.owner; + if (msg.sender != currentOwner) revert InvalidDataPointOwner(dp, msg.sender); + dpd.owner = newOwner; + emit DataPointOwnershipTransferred(dp, currentOwner, newOwner); + } + + /// @inheritdoc IDataPointRegistry + function grantAdminRole(DataPoint dp, address account) external returns (bool) { + DPAccessData storage dpd = accessData[dp]; + if (msg.sender != dpd.owner) revert InvalidDataPointOwner(dp, msg.sender); + if (!isAdmin(dp, account)) { + dpd.isAdmin[account] = true; + emit DataPointAdminGranted(dp, account); + return true; + } + return false; + } + + /// @inheritdoc IDataPointRegistry + function revokeAdminRole(DataPoint dp, address account) external returns (bool) { + DPAccessData storage dpd = accessData[dp]; + if (msg.sender != dpd.owner) revert InvalidDataPointOwner(dp, msg.sender); + if (isAdmin(dp, account)) { + dpd.isAdmin[account] = false; + emit DataPointAdminRevoked(dp, account); + return true; + } + return false; + } +} diff --git a/assets/erc-7208/contracts/datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol b/assets/erc-7208/contracts/datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol new file mode 100644 index 00000000000..cf8cb6daa74 --- /dev/null +++ b/assets/erc-7208/contracts/datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Arrays.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC1155Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +import "../interfaces/IDataIndex.sol"; +import "../interfaces/IDataObject.sol"; +import "./MinimalisticERC20FractionDataManagerFactory.sol"; +import "./MinimalisticERC20FractionDataManager.sol"; + +/** + * Deployment process + * 1. Allocate DataPoint via IDataPointRegistry.allocate() + * 2. Deploy ERC1155WithERC20FractionsDataManager (or an extending contract) + * 3. Grant Admin role on the DataPoint to the deployed contract + */ +contract MinimalisticERC1155WithERC20FractionsDataManager is IFractionTransferEventEmitter, IERC1155, IERC1155Errors, ERC165, Ownable { + using Arrays for uint256[]; + + error IncorrectId(uint256 id); + event ERC20FractionDataManagerDeployed(uint256 id, address dm); + + string private _name; + string private _symbol; + string private _defaultURI = ""; + DataPoint internal immutable datapoint; + IDataObject public immutable fungibleFractionsDO; + IDataIndex public dataIndex; + mapping(address account => mapping(address operator => bool)) private _operatorApprovals; + mapping(uint256 id => address erc20dm) public fractionManagersById; + mapping(address erc20dm => uint256 id) public fractionManagersByAddress; + MinimalisticERC20FractionDataManagerFactory erc20FractionsDMFactory; + + modifier onlyMinter() { + _checkMinter(); + _; + } + + constructor( + bytes32 _dp, + address _dataIndex, + address _fungibleFractionsDO, + address _erc20FractionsDMFactory, + string memory name_, + string memory symbol_ + ) Ownable(msg.sender) { + _name = name_; + _symbol = symbol_; + datapoint = DataPoint.wrap(_dp); + dataIndex = IDataIndex(_dataIndex); + fungibleFractionsDO = IDataObject(_fungibleFractionsDO); + erc20FractionsDMFactory = MinimalisticERC20FractionDataManagerFactory(_erc20FractionsDMFactory); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC1155MetadataURI).interfaceId || super.supportsInterface(interfaceId); + } + + function setDefaultURI(string calldata defaultURI) external onlyOwner { + _setDefaultURI(defaultURI); + } + + function totalSupply() public view returns (uint256) { + return abi.decode(fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.totalSupplyAll.selector, ""), (uint256)); + } + + function totalSupply(uint256 id) public view returns (uint256) { + return abi.decode(fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.totalSupply.selector, abi.encode(id)), (uint256)); + } + + function balanceOf(address account, uint256 id) public view returns (uint256) { + return abi.decode(fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.balanceOf.selector, abi.encode(account, id)), (uint256)); + } + + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view returns (uint256[] memory) { + return + abi.decode( + fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.balanceOfBatchAccounts.selector, abi.encode(accounts, ids)), + (uint256[]) + ); + } + + function uri(uint256) public view virtual returns (string memory) { + return _defaultURI; + } + + function name() public view returns (string memory) { + return _name; + } + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll(address account, address operator) public view virtual returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the zero address. + */ + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC1155InvalidOperator(address(0)); + } + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) public virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); + } + _safeTransferFrom(from, to, id, value, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); + } + _safeBatchTransferFrom(from, to, ids, values, data); + } + + /** + * @dev Transfers a `value` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `value` amount. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + _updateWithAcceptanceCheck(from, to, id, value, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + * - `ids` and `values` must have the same length. + */ + function _safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { + if (ids.length != values.length) { + revert ERC1155InvalidArrayLength(ids.length, values.length); + // DO NOT remove this check without refactoring ERC1155WithERC20FractionsDataManager._update() which relies on it! + } + + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + if (ids.length == 1 && values.length == 1) { + uint256 id = ids.unsafeMemoryAccess(0); + uint256 value = values.unsafeMemoryAccess(0); + _updateWithAcceptanceCheck(from, to, id, value, data); + } else { + _updateWithAcceptanceCheck(from, to, ids, values, data); + } + } + + /** + * @dev Version of {_update} that performs the token acceptance check by calling + * {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it + * contains code (eg. is a smart contract at the moment of execution). + * + * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any + * update to the contract state after this function would break the check-effect-interaction pattern. Consider + * overriding {_update} instead. + */ + function _updateWithAcceptanceCheck(address from, address to, uint256 id, uint256 value, bytes memory data) internal virtual { + _update(from, to, id, value); + if (to != address(0)) { + address operator = _msgSender(); + _doSafeTransferAcceptanceCheck(operator, from, to, id, value, data); + } + } + + /** + * @dev Version of {_update} that performs the token acceptance check by calling + * {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it + * contains code (eg. is a smart contract at the moment of execution). + * + * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any + * update to the contract state after this function would break the check-effect-interaction pattern. Consider + * overriding {_update} instead. + */ + function _updateWithAcceptanceCheck(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal virtual { + _update(from, to, ids, values); + if (to != address(0)) { + address operator = _msgSender(); + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data); + } + } + + /** + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` + * (or `to`) is the zero address. + * + * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received} + * or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. + * - `ids` and `values` must have the same length. + * + * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead. + */ + function _updateInternal(address from, address to, uint256 id, uint256 value) internal virtual { + address operator = _msgSender(); + + _writeTransfer(from, to, id, value); + + emit TransferSingle(operator, from, to, id, value); + } + + /** + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` + * (or `to`) is the zero address. + * + * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received} + * or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. + * - `ids` and `values` must have the same length. + * + * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead. + * NOTE: Array length check is not performed in this function and must be performed in the caller + */ + function _updateInternal(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual { + address operator = _msgSender(); + + _writeTransferBatch(from, to, ids, values); + emit TransferBatch(operator, from, to, ids, values); + } + + /** + * @dev Performs an acceptance check by calling {IERC1155-onERC1155Received} on the `to` address + * if it contains code at the moment of execution. + */ + function _doSafeTransferAcceptanceCheck(address operator, address from, address to, uint256 id, uint256 value, bytes memory data) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + // Tokens rejected + revert ERC1155InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + /** + * @dev Performs a batch acceptance check by calling {IERC1155-onERC1155BatchReceived} on the `to` address + * if it contains code at the moment of execution. + */ + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + // Tokens rejected + revert ERC1155InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + function _setDefaultURI(string memory defaultURI) internal virtual { + _defaultURI = defaultURI; + } + + function _checkMinter() internal view { + _checkOwner(); + } + + function _writeTransfer(address from, address to, uint256 id, uint256 value) internal virtual { + if (from == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.mint.selector, abi.encode(to, id, value)); + } else if (to == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.burn.selector, abi.encode(from, id, value)); + } else { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.transferFrom.selector, abi.encode(from, to, id, value)); + } + } + + function _writeTransferBatch(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual { + if (from == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.batchMint.selector, abi.encode(to, ids, values)); + } else if (to == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.batchBurn.selector, abi.encode(from, ids, values)); + } else { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.batchTransferFrom.selector, abi.encode(from, to, ids, values)); + } + } + + function mint(address to, uint256 id, uint256 value, bytes memory data) public virtual onlyMinter { + _mint(to, id, value, data); + } + + function batchMint(address, uint256[] memory, uint256[] memory, bytes memory) public pure { + revert("Batch mint not supported"); // it will be very expensive anyway + } + + function fractionTransferredNotify(address from, address to, uint256 value) external { + uint256 id = fractionManagersByAddress[_msgSender()]; + if (id == 0) revert WrongTransferNotificationSource(); + emit TransferSingle(_msgSender(), from, to, id, value); + } + + function _mint(address to, uint256 id, uint256 value, bytes memory data) internal virtual { + if (id == 0) revert IncorrectId(id); + _deployERC20DMIfNotDeployed(id, data); + _updateWithAcceptanceCheck(address(0), to, id, value, data); + } + + function _update(address from, address to, uint256 id, uint256 value) internal { + _updateInternal(from, to, id, value); + _erc20TransferNotify(id, from, to, value); + } + + function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal { + _updateInternal(from, to, ids, values); + for (uint256 i; i < ids.length; i++) { + uint256 id = ids.unsafeMemoryAccess(i); + uint256 value = ids.unsafeMemoryAccess(i); // We have an array length check in ERC1155Transfers._safeBatchTransferFrom() + _erc20TransferNotify(id, from, to, value); + } + } + + function _deployERC20DMIfNotDeployed(uint256 id, bytes memory data) internal { + if (fractionManagersById[id] != address(0)) return; // Already deployed + (string memory name_, string memory symbol_) = _prepareNameAndSymbol(data, id); + _deployERC20DM(id, name_, symbol_); + } + + function _afterDeployERC20DM(address deployedDM) internal virtual {} + + function _prepareNameAndSymbol(bytes memory data, uint256 id) private view returns (string memory, string memory) { + string memory name_; + string memory symbol_; + if (data.length != 0) { + (name_, symbol_) = abi.decode(data, (string, string)); + } else { + name_ = string.concat(name(), " ", Strings.toString(id)); + symbol_ = string.concat(symbol(), "-", Strings.toString(id)); + } + return (name_, symbol_); + } + + function _deployERC20DM(uint256 id, string memory name_, string memory symbol_) private { + address erc20dm = erc20FractionsDMFactory.deploy(id); + MinimalisticERC20FractionDataManager(erc20dm).initialize( + DataPoint.unwrap(datapoint), + address(dataIndex), + address(fungibleFractionsDO), + address(this), + id, + name_, + symbol_ + ); + + fractionManagersById[id] = erc20dm; + fractionManagersByAddress[erc20dm] = id; + + dataIndex.allowDataManager(datapoint, erc20dm, true); + + _afterDeployERC20DM(erc20dm); + + emit ERC20FractionDataManagerDeployed(id, erc20dm); + } + + function _erc20TransferNotify(uint256 id, address from, address to, uint256 value) private { + IFractionTransferEventEmitter(fractionManagersById[id]).fractionTransferredNotify(from, to, value); + } +} diff --git a/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManager.sol b/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManager.sol new file mode 100644 index 00000000000..93aa92d016c --- /dev/null +++ b/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManager.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "../interfaces/IDataIndex.sol"; +import "../interfaces/IDataObject.sol"; +import "../interfaces/IFungibleFractionsOperations.sol"; +import "../interfaces/IFractionTransferEventEmitter.sol"; + +contract MinimalisticERC20FractionDataManager is IFractionTransferEventEmitter, IERC20, IERC20Errors, OwnableUpgradeable { + uint8 private constant DECIMALS = 0; + + DataPoint internal datapoint; + IDataObject public fungibleFractionsDO; + IDataIndex public dataIndex; + address public erc1155dm; + uint256 public erc1155ID; + + string private _name; + string private _symbol; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + modifier onlyTransferNotifier() { + if (_msgSender() != erc1155dm) revert WrongTransferNotificationSource(); + _; + } + + function initialize( + bytes32 _datapoint, + address _dataIndex, + address _fungibleFractionsDO, + address _erc1155dm, + uint256 _erc1155ID, + string memory name_, + string memory symbol_ + ) external initializer { + __Ownable_init_unchained(_msgSender()); + __MinimalisticERC20FractionDataManager_init_unchained(_datapoint, _dataIndex, _fungibleFractionsDO, _erc1155dm, _erc1155ID, name_, symbol_); + } + + function __MinimalisticERC20FractionDataManager_init_unchained( + bytes32 _datapoint, + address _dataIndex, + address _fungibleFractionsDO, + address _erc1155dm, + uint256 _erc1155ID, + string memory name_, + string memory symbol_ + ) internal onlyInitializing { + datapoint = DataPoint.wrap(_datapoint); + dataIndex = IDataIndex(_dataIndex); + fungibleFractionsDO = IDataObject(_fungibleFractionsDO); + erc1155dm = _erc1155dm; + erc1155ID = _erc1155ID; + _name = name_; + _symbol = symbol_; + } + + function decimals() external pure returns (uint8) { + return DECIMALS; + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } + + function totalSupply() external view override returns (uint256) { + return abi.decode(fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.totalSupply.selector, abi.encode(erc1155ID)), (uint256)); + } + + function balanceOf(address account) external view override returns (uint256) { + return abi.decode(fungibleFractionsDO.read(datapoint, IFungibleFractionsOperations.balanceOf.selector, abi.encode(account, erc1155ID)), (uint256)); + } + + function fractionTransferredNotify(address from, address to, uint256 amount) external onlyTransferNotifier { + emit Transfer(from, to, amount); + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 value) public returns (bool) { + if (_msgSender() == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[_msgSender()][spender] = value; + emit Approval(_msgSender(), spender, value); + return true; + } + + function transfer(address to, uint256 amount) external virtual override returns (bool) { + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _beforeTokenTransfer(_msgSender(), to, amount); + + _writeTransfer(_msgSender(), to, amount); + + emit Transfer(_msgSender(), to, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) { + _spendAllowance(from, _msgSender(), amount); + _beforeTokenTransfer(from, to, amount); + + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + + _writeTransfer(from, to, amount); + + emit Transfer(from, to, amount); + return true; + } + + function _spendAllowance(address owner, address spender, uint256 amount) internal { + uint256 currentAllowance = _allowances[owner][spender]; + if (currentAllowance != type(uint256).max) { + if (currentAllowance < amount) { + revert ERC20InsufficientAllowance(spender, currentAllowance, amount); + } + unchecked { + _allowances[owner][spender] = currentAllowance - amount; + } + } + } + + function _writeTransfer(address from, address to, uint256 amount) internal { + if (from == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.mint.selector, abi.encode(to, erc1155ID, amount)); + } else if (to == address(0)) { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.burn.selector, abi.encode(from, erc1155ID, amount)); + } else { + dataIndex.write(address(fungibleFractionsDO), datapoint, IFungibleFractionsOperations.transferFrom.selector, abi.encode(from, to, erc1155ID, amount)); + } + IFractionTransferEventEmitter(erc1155dm).fractionTransferredNotify(from, to, amount); + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} +} diff --git a/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManagerFactory.sol b/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManagerFactory.sol new file mode 100644 index 00000000000..b030746d24d --- /dev/null +++ b/assets/erc-7208/contracts/datamanagers/MinimalisticERC20FractionDataManagerFactory.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/Create2.sol"; +import "./MinimalisticERC20FractionDataManager.sol"; + +contract MinimalisticERC20FractionDataManagerFactory { + function deploy(uint256 id) external returns (address) { + bytes32 salt = keccak256(abi.encodePacked(msg.sender, id)); + return Create2.deploy(0, salt, type(MinimalisticERC20FractionDataManager).creationCode); + } +} diff --git a/assets/erc-7208/contracts/dataobjects/MinimalisticFungibleFractionsDO.sol b/assets/erc-7208/contracts/dataobjects/MinimalisticFungibleFractionsDO.sol new file mode 100644 index 00000000000..a15ccf6b048 --- /dev/null +++ b/assets/erc-7208/contracts/dataobjects/MinimalisticFungibleFractionsDO.sol @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/utils/Arrays.sol"; +import "../interfaces/IIDManager.sol"; +import "../interfaces/IFungibleFractionsOperations.sol"; +import "../interfaces/IDataPointRegistry.sol"; +import "../interfaces/IDataIndex.sol"; +import "../interfaces/IDataObject.sol"; +import "../utils/OmnichainAddresses.sol"; + +/** + * @title Minimalistic Fungible Fractions Data Object + * @notice DataObject with base funtionality of Fungible Fractions (Can be used for ERC1155-Compatible DataManagers) + * @dev This contract exposes base functionality of Fungible Fraction tokens, including + * balanceOf, totalSupply, exists, transferFrom, mint, burn and their batch variants. + * + * NOTE: This contract is expected to be used by a DataManager contract, which could + * implement a fungible token interface and provide more advanced features like approvals, + * access control, metadata management, etc. As may be an ERC1155 token. + * + * This contract only emit basic events, it is expected that the DataManager contract will + * emit the events for the token operations + */ +contract MinimalisticFungibleFractionsDO is IDataObject { + using Arrays for uint256[]; + using Arrays for address[]; + using EnumerableSet for EnumerableSet.UintSet; + + /** + * @notice Error thrown when the msg.sender is not the expected caller + * @param dp The DataPoint identifier + * @param sender The msg.sender address + */ + error InvalidCaller(DataPoint dp, address sender); + + /** + * @notice Error thrown when the DataPoint is not initialized with a DataIndex implementation + * @param dp The DataPoint identifier + */ + error UninitializedDataPoint(DataPoint dp); + + /// @dev Error thrown when the operation arguments are wrong + error WrongOperationArguments(); + + /** + * @notice Error thrown when the read operation is unknown + * @param selector The operation selector + */ + error UnknownReadOperation(bytes4 selector); + + /** + * @notice Error thrown when the write operation is unknown + * @param selector The operation selector + */ + error UnknownWriteOperation(bytes4 selector); + + /** + * @notice Error thrown when the balance is insufficient + * @param diid The DataIndex identifier + * @param id The id of the token + * @param balance The current balance + * @param value The requested amount + */ + error InsufficientBalance(bytes32 diid, uint256 id, uint256 balance, uint256 value); + + /** + * @notice Error thrown when the total supply is insufficient + * @param id The id of the token + * @param totalSupply The current total supply + * @param value The requested amount + * @dev This should never happen because we've already checked "from" balance + */ + error InsufficientTotalSupply(uint256 id, uint256 totalSupply, uint256 value); + + /// @dev Error thrown when the params length mismatch + error ArrayLengthMismatch(); + + /** + * @notice Event emitted when the DataIndex implementation is set + * @param dp The DataPoint identifier + * @param dataIndexImplementation The DataIndex implementation address + */ + event DataIndexImplementationSet(DataPoint dp, address dataIndexImplementation); + + /** + * @notice Data structure for storing Fungible Fractions data + * @param totalSupplyAll Total supply of all tokens + * @param totalSupply Mapping of token id to total supply + * @dev Data related to the DataPoint as a whole + */ + struct DpData { + uint256 totalSupplyAll; + mapping(uint256 id => uint256 totalSupplyOfId) totalSupply; + } + + /** + * @notice Data structure for storing Fungible Fractions data of a user + * @param ids Enumerable set of object (ERC1155 token) ids + * @param balances Mapping of object (ERC1155 token) id to balance of the user owning diid + * @dev Data related to a specific user of a DataPoint (user identified by his DataIndex id) + */ + struct DiidData { + EnumerableSet.UintSet ids; + mapping(uint256 id => uint256 value) balances; + } + + /** + * @notice Data structure to store DataPoint data + * @param dataIndexImplementation The DataIndex implementation set for the DataPoint + * @param dpData The DataPoint data + * @param dataIndexData Mapping of diid to user data + */ + struct DataPointStorage { + IDataIndex dataIndexImplementation; + DpData dpData; + mapping(bytes32 diid => DiidData) diidData; + } + + /// @dev Mapping of DataPoint to DataPointStorage + mapping(DataPoint => DataPointStorage) private dpStorages; + + /** + * @notice Modifier to check if the caller is the DataIndex implementation set for the DataPoint + * @param dp The DataPoint identifier + */ + modifier onlyDataIndex(DataPoint dp) { + DataPointStorage storage dps = _dataPointStorage(dp); + if (address(dps.dataIndexImplementation) != msg.sender) revert InvalidCaller(dp, msg.sender); + _; + } + + /// @inheritdoc IDataObject + function setDIImplementation(DataPoint dp, IDataIndex newImpl) external { + DataPointStorage storage dps = dpStorages[dp]; + if (address(dps.dataIndexImplementation) == address(0)) { + // Registering new DataPoint + // Should be called by DataPoint Admin + if (!_isDataPointAdmin(dp, msg.sender)) revert InvalidCaller(dp, msg.sender); + } else { + // Updating the DataPoint + // Should be called by current DataIndex or DataPoint Admin + if ((address(dps.dataIndexImplementation) != msg.sender) && !_isDataPointAdmin(dp, msg.sender)) revert InvalidCaller(dp, msg.sender); + } + dps.dataIndexImplementation = newImpl; + emit DataIndexImplementationSet(dp, address(newImpl)); + } + + // =========== Dispatch functions ============ + /// @inheritdoc IDataObject + function read(DataPoint dp, bytes4 operation, bytes calldata data) external view returns (bytes memory) { + return _dispatchRead(dp, operation, data); + } + + /// @inheritdoc IDataObject + function write(DataPoint dp, bytes4 operation, bytes calldata data) external onlyDataIndex(dp) returns (bytes memory) { + return _dispatchWrite(dp, operation, data); + } + + function _dispatchRead(DataPoint dp, bytes4 operation, bytes calldata data) internal view virtual returns (bytes memory) { + if (operation == IFungibleFractionsOperations.balanceOf.selector) { + (address account, uint256 id) = abi.decode(data, (address, uint256)); + return abi.encode(_balanceOf(dp, account, id)); + } else if (operation == IFungibleFractionsOperations.balanceOfBatchAccounts.selector) { + (address[] memory accounts, uint256[] memory ids) = abi.decode(data, (address[], uint256[])); + return abi.encode(_balanceOfBatchAccounts(dp, accounts, ids)); + } else if (operation == IFungibleFractionsOperations.totalSupply.selector) { + return abi.encode(_totalSupply(dp, abi.decode(data, (uint256)))); + } else if (operation == IFungibleFractionsOperations.totalSupplyAll.selector) { + if (data.length != 0) revert WrongOperationArguments(); + return abi.encode(_totalSupplyAll(dp)); + } else if (operation == IFungibleFractionsOperations.exists.selector) { + return abi.encode(_exists(dp, abi.decode(data, (uint256)))); + } else { + revert UnknownReadOperation(operation); + } + } + + function _dispatchWrite(DataPoint dp, bytes4 operation, bytes calldata data) internal virtual returns (bytes memory) { + if (operation == IFungibleFractionsOperations.transferFrom.selector) { + (address from, address to, uint256 id, uint256 value) = abi.decode(data, (address, address, uint256, uint256)); + _transferFrom(dp, from, to, id, value); + return ""; + } else if (operation == IFungibleFractionsOperations.mint.selector) { + (address to, uint256 id, uint256 value) = abi.decode(data, (address, uint256, uint256)); + _mint(dp, to, id, value); + return ""; + } else if (operation == IFungibleFractionsOperations.burn.selector) { + (address from, uint256 id, uint256 value) = abi.decode(data, (address, uint256, uint256)); + _burn(dp, from, id, value); + return ""; + } else if (operation == IFungibleFractionsOperations.batchTransferFrom.selector) { + (address from, address to, uint256[] memory ids, uint256[] memory values) = abi.decode(data, (address, address, uint256[], uint256[])); + _batchTransferFrom(dp, from, to, ids, values); + return ""; + } else { + revert UnknownWriteOperation(operation); + } + } + + // =========== Logic implementation ============ + + function _balanceOf(DataPoint dp, address account, uint256 id) internal view returns (uint256) { + bytes32 diid = _tryDiid(dp, account); + if (diid == 0) return 0; + (bool success, DiidData storage od) = _tryDiidData(dp, diid); + return success ? od.balances[id] : 0; + } + + function _balanceOfBatchAccounts(DataPoint dp, address[] memory accounts, uint256[] memory ids) internal view returns (uint256[] memory balances) { + if (accounts.length != ids.length) revert ArrayLengthMismatch(); + balances = new uint256[](accounts.length); + for (uint256 i; i < accounts.length; i++) { + uint256 id = ids.unsafeMemoryAccess(i); + address account = accounts.unsafeMemoryAccess(i); + bytes32 diid = _tryDiid(dp, account); + if (diid == 0) { + balances[i] = 0; + } else { + (bool success, DiidData storage od) = _tryDiidData(dp, diid); + balances[i] = success ? od.balances[id] : 0; + } + } + } + + function _totalSupply(DataPoint dp, uint256 id) internal view returns (uint256) { + (bool success, DpData storage dd) = _tryDpData(dp); + return success ? dd.totalSupply[id] : 0; + } + + function _totalSupplyAll(DataPoint dp) internal view returns (uint256) { + (bool success, DpData storage dd) = _tryDpData(dp); + return success ? dd.totalSupplyAll : 0; + } + + function _exists(DataPoint dp, uint256 id) internal view returns (bool) { + (bool success, DpData storage dd) = _tryDpData(dp); + return success ? (dd.totalSupply[id] > 0) : false; + } + + function _transferFrom(DataPoint dp, address from, address to, uint256 id, uint256 value) internal virtual { + bytes32 diidFrom = _diid(dp, from); + bytes32 diidTo = _diid(dp, to); + DiidData storage diiddFrom = _diidData(dp, diidFrom); + DiidData storage diiddTo = _diidData(dp, diidTo); + _decreaseBalance(diiddFrom, id, value, dp, diidFrom); + _increaseBalance(diiddTo, id, value, dp, diidTo); + } + + function _mint(DataPoint dp, address to, uint256 id, uint256 value) internal virtual { + bytes32 diidTo = _diid(dp, to); + DiidData storage diiddTo = _diidData(dp, diidTo); + _increaseBalance(diiddTo, id, value, dp, diidTo); + + DpData storage dpd = _dpData(dp); + dpd.totalSupply[id] += value; + dpd.totalSupplyAll += value; + } + + function _burn(DataPoint dp, address from, uint256 id, uint256 value) internal virtual { + bytes32 diidFrom = _diid(dp, from); + DiidData storage diiddFrom = _diidData(dp, diidFrom); + _decreaseBalance(diiddFrom, id, value, dp, diidFrom); + DpData storage dpd = _dpData(dp); + uint256 totalSupply = dpd.totalSupply[id]; + if (totalSupply < value) revert InsufficientTotalSupply(id, totalSupply, value); + unchecked { + totalSupply -= value; + } + dpd.totalSupply[id] = totalSupply; + uint256 totalSupplyAll = dpd.totalSupplyAll; + if (totalSupplyAll < value) revert InsufficientTotalSupply(id, totalSupplyAll, value); + unchecked { + totalSupplyAll -= value; + } + dpd.totalSupplyAll = totalSupplyAll; + } + + function _batchTransferFrom(DataPoint dp, address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual { + if (ids.length != values.length) revert ArrayLengthMismatch(); + bytes32 diidFrom = _diid(dp, from); + bytes32 diidTo = _diid(dp, to); + DiidData storage diiddFrom = _diidData(dp, diidFrom); + DiidData storage diiddTo = _diidData(dp, diidTo); + for (uint256 i; i < ids.length; i++) { + uint256 id = ids.unsafeMemoryAccess(i); + uint256 value = values.unsafeMemoryAccess(i); + _decreaseBalance(diiddFrom, id, value, dp, diidFrom); + _increaseBalance(diiddTo, id, value, dp, diidTo); + } + } + + function _increaseBalance(DiidData storage diidd, uint256 id, uint256 value, DataPoint, bytes32) private { + diidd.balances[id] += value; + diidd.ids.add(id); // if id is already in the set, this call will return false, but we don't care + } + + function _decreaseBalance(DiidData storage diidd, uint256 id, uint256 value, DataPoint, bytes32 diidFrom) private { + uint256 diidBalance = diidd.balances[id]; + if (diidBalance < value) { + revert InsufficientBalance(diidFrom, id, diidBalance, value); + } else { + unchecked { + diidBalance -= value; + } + diidd.balances[id] = diidBalance; + if (diidBalance == 0) { + diidd.ids.remove(id); + } + } + } + + // =========== Helper functions ============ + + function _isDataPointAdmin(DataPoint dp, address account) internal view returns (bool) { + (uint32 chainId, address registry, ) = DataPoints.decode(dp); + ChainidTools.requireCurrentChain(chainId); + return IDataPointRegistry(registry).isAdmin(dp, account); + } + + function _diid(DataPoint dp, address account) internal view returns (bytes32) { + return IIDManager(msg.sender).diid(account, dp); + } + + function _tryDiid(DataPoint dp, address account) internal view returns (bytes32) { + try IIDManager(msg.sender).diid(account, dp) returns (bytes32 diid) { + return diid; + } catch { + return 0; + } + } + + function _dpData(DataPoint dp) internal view returns (DpData storage) { + DataPointStorage storage dps = _dataPointStorage(dp); + return dps.dpData; + } + + function _diidData(DataPoint dp, bytes32 diid) internal view returns (DiidData storage) { + DataPointStorage storage dps = _dataPointStorage(dp); + return dps.diidData[diid]; + } + + function _tryDpData(DataPoint dp) internal view returns (bool success, DpData storage) { + (bool found, DataPointStorage storage dps) = _tryDataPointStorage(dp); + if (!found) { + return (false, dps.dpData); + } + return (true, dps.dpData); + } + + function _tryDiidData(DataPoint dp, bytes32 diid) internal view returns (bool success, DiidData storage) { + (bool found, DataPointStorage storage dps) = _tryDataPointStorage(dp); + if (!found) { + return (false, dps.diidData[bytes32(0)]); + } + DiidData storage diidd = dps.diidData[diid]; + if (diidd.ids.length() == 0) { + // Here we use length of ids array as a flag that there is no data for the diid + return (false, diidd); + } + return (true, diidd); + } + + function _dataPointStorage(DataPoint dp) private view returns (DataPointStorage storage) { + DataPointStorage storage dps = dpStorages[dp]; + if (address(dps.dataIndexImplementation) == address(0)) { + revert UninitializedDataPoint(dp); + } + return dpStorages[dp]; + } + + function _tryDataPointStorage(DataPoint dp) private view returns (bool success, DataPointStorage storage) { + DataPointStorage storage dps = dpStorages[dp]; + if (address(dps.dataIndexImplementation) == address(0)) { + return (false, dps); + } + return (true, dps); + } +} diff --git a/assets/erc-7208/contracts/interfaces/IDataIndex.sol b/assets/erc-7208/contracts/interfaces/IDataIndex.sol new file mode 100644 index 00000000000..45dc3a6b290 --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IDataIndex.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../utils/DataPoints.sol"; + +/** + * @title Data Index interface + * @notice The interface defines functions to manage the access control of DataManagers to + * DataPoints as well as to the data related to these DataPoints in specific dataObjects + */ +interface IDataIndex { + /** + * @notice Verifies if DataManager is allowed to write in specific DataPoint + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @return if write access is allowed + */ + function isApprovedDataManager(DataPoint dp, address dm) external view returns (bool); + + /** + * @notice Defines if DataManager is allowed to write in specific DataPoint + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @param approved if DataManager should be approved for the DataPoint + * @dev Function SHOULD be restricted to DataPoint maintainer only + */ + function allowDataManager(DataPoint dp, address dm, bool approved) external; + + /** + * @notice Reads stored data + * @param dobj Identifier of DataObject + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data + */ + function read(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external view returns (bytes memory); + + /** + * @notice Stores data + * @param dobj Identifier of DataObject + * @param dp Identifier of the DataPoint + * @param operation Write operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + * @dev Function SHOULD be restricted to allowed DMs only + */ + function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns (bytes memory); +} diff --git a/assets/erc-7208/contracts/interfaces/IDataObject.sol b/assets/erc-7208/contracts/interfaces/IDataObject.sol new file mode 100644 index 00000000000..6f5430f14d6 --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IDataObject.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../utils/DataPoints.sol"; +import "./IDataIndex.sol"; + +/** + * @title Data Object Interface + * @notice Interface defines functions to manage the DataIndex implementation and the data + * stored in DataObjects and associated with DataPoints + */ +interface IDataObject { + /** + * @notice Reads stored data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data + */ + function read(DataPoint dp, bytes4 operation, bytes calldata data) external view returns (bytes memory); + + /** + * @notice Store data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + */ + function write(DataPoint dp, bytes4 operation, bytes calldata data) external returns (bytes memory); + + /** + * @notice Sets DataIndex Implementation + * @param dp Identifier of the DataPoint + * @param newImpl address of the new DataIndex implementation + */ + function setDIImplementation(DataPoint dp, IDataIndex newImpl) external; +} diff --git a/assets/erc-7208/contracts/interfaces/IDataPointRegistry.sol b/assets/erc-7208/contracts/interfaces/IDataPointRegistry.sol new file mode 100644 index 00000000000..6e9483064d9 --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IDataPointRegistry.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {DataPoint} from "../utils/DataPoints.sol"; + +/** + * @title Data Point Registry Interface + * @notice Interface defines functions to manage creation, transfer and access control of DataPoints + */ +interface IDataPointRegistry { + /** + * @notice Event emitted when a DataPoint is allocated + * @param dp DataPoint identifier + * @param owner Owner of the DataPoint + */ + event DataPointAllocated(DataPoint indexed dp, address owner); + + /** + * @notice Event emitted when ownership of a DataPoint is transferred + * @param dp DataPoint identifier + * @param previousOwner Previous owner + * @param newOwner New owner + */ + event DataPointOwnershipTransferred(DataPoint indexed dp, address previousOwner, address newOwner); + + /** + * @notice Event emitted when Admin role is granted + * @param dp DataPoint identifier + * @param account Account granted with Admin role + */ + event DataPointAdminGranted(DataPoint indexed dp, address account); + + /** + * @notice Event emitted when Admin role is revoked + * @param dp DataPoint identifier + * @param account Account revoked from Admin role + */ + event DataPointAdminRevoked(DataPoint indexed dp, address account); + + /// @dev Error thrown when DataPoint allocation counter overflows + error CounterOverflow(); + + /// @dev Error thrown when native coin is sent in allocation + error NativeCoinDepositIsNotAccepted(); + + /** + * @notice Error thrown when caller is not the owner of the DataPoint + * @param dp DataPoint identifier + * @param owner Invalid owner + */ + error InvalidDataPointOwner(DataPoint dp, address owner); + + /** + * @notice Verifies if an address has an Admin role for a DataPoint + * @param dp DataPoint + * @param account Account to verify + */ + function isAdmin(DataPoint dp, address account) external view returns (bool); + + /** + * @notice Allocates a DataPoint to an owner + * @param owner Owner of the new DataPoint + * @dev Owner SHOULD be granted Admin role during allocation + */ + function allocate(address owner) external payable returns (DataPoint); + + /** + * @notice Transfers ownership of a DataPoint to a new owner + * @param dp DataPoint identifier + * @param newOwner New owner + */ + function transferOwnership(DataPoint dp, address newOwner) external; + + /** + * @notice Grant permission to grant/revoke other roles on the DataPoint inside an DataIndex Implementation + * This is useful if DataManagers are deployed during lifecycle of the application. + * @param dp DataPoint identifier + * @param account New admin + * @return If the role was granted (otherwise account already had the role) + */ + function grantAdminRole(DataPoint dp, address account) external returns (bool); + + /** + * @notice Revoke permission to grant/revoke other roles on the DataPoint inside an DataIndex Implementation + * @param dp DataPoint identifier + * @param account Old admin + * @dev If an owner revokes Admin role from himself, he can add it again + * @return If the role was revoked (otherwise account didn't had the role) + */ + function revokeAdminRole(DataPoint dp, address account) external returns (bool); +} diff --git a/assets/erc-7208/contracts/interfaces/IFractionTransferEventEmitter.sol b/assets/erc-7208/contracts/interfaces/IFractionTransferEventEmitter.sol new file mode 100644 index 00000000000..fd80a33a39e --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IFractionTransferEventEmitter.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Fraction Transfer Event Emitter interface + * @notice Interface defines functions to emit events when fractions are transferred + */ +interface IFractionTransferEventEmitter { + /// @dev Emmited when caller is not one of expected addresses + error WrongTransferNotificationSource(); + + /** + * @notice Notifies about a fraction transfer + * @param from Address from which the fraction is transferred + * @param to Address to which the fraction is transferred + * @param value Amount of the fraction transferred + * @dev Should emit an event with the information about the transfer + */ + function fractionTransferredNotify(address from, address to, uint256 value) external; +} diff --git a/assets/erc-7208/contracts/interfaces/IFungibleFractionsOperations.sol b/assets/erc-7208/contracts/interfaces/IFungibleFractionsOperations.sol new file mode 100644 index 00000000000..9442f1c3fa0 --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IFungibleFractionsOperations.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Fungible Fractions Operations Interface + * @notice Provides the operations of the FungibleFractionsDO to interact with + * fungible fractions tokens and their associated data + */ +interface IFungibleFractionsOperations { + /** + * @notice Operation used to get the balance of an account + * @param account The account address + * @param id The id of the token + * @return The balance of the account + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @notice Operation used to get the balance of an account + * @param account The account address + * @param ids The ids of the tokens + * @return The balance of the account + */ + function balanceOfBatch(address account, uint256[] calldata ids) external view returns (uint256[] memory); + + /** + * @notice Operation used to get the balance of multiple accounts + * @param accounts The account addresses + * @param ids The ids of the tokens + * @return The balance of the accounts + */ + function balanceOfBatchAccounts(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); + + /** + * @notice Operation used to get the total supply of a token + * @param id The id of the token + * @return The total supply of the token + */ + function totalSupply(uint256 id) external view returns (uint256); + + /** + * @notice Operation used to get the total supply of all ids tokens + * @return The total supply of all ids tokens + */ + function totalSupplyAll() external view returns (uint256); + + /** + * @notice Operation used to check if an id exists + * @param id The id of the token + * @return True if the id exists, false otherwise + */ + function exists(uint256 id) external view returns (bool); + + /** + * @notice Operation used to transfer tokens from one account to another + * @param from The account to transfer from + * @param to The account to transfer to + * @param id The id of the token + * @param value The amount of tokens to transfer + */ + function transferFrom(address from, address to, uint256 id, uint256 value) external; + + /** + * @notice Operation used to transfer tokens from one account to another + * @param from The account to transfer from + * @param to The account to transfer to + * @param ids The ids of the tokens + * @param values The amounts of tokens to transfer + */ + function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values) external; + + /** + * @notice Operation used to mint tokens + * @param to The account to mint to + * @param id The id of the token + * @param value The amount of tokens to mint + */ + function mint(address to, uint256 id, uint256 value) external; + + /** + * @notice Operation used to burn tokens + * @param from The account to burn from + * @param id The id of the token + * @param value The amount of tokens to burn + */ + function burn(address from, uint256 id, uint256 value) external; + + /** + * @notice Operation used to mint tokens + * @param to The account to mint to + * @param ids The ids of the tokens + * @param values The amounts of tokens to mint + */ + function batchMint(address to, uint256[] calldata ids, uint256[] calldata values) external; + + /** + * @notice Operation used to burn tokens + * @param from The account to burn from + * @param ids The ids of the tokens + * @param values The amounts of tokens to burn + */ + function batchBurn(address from, uint256[] calldata ids, uint256[] calldata values) external; +} diff --git a/assets/erc-7208/contracts/interfaces/IIDManager.sol b/assets/erc-7208/contracts/interfaces/IIDManager.sol new file mode 100644 index 00000000000..ea162eb17fa --- /dev/null +++ b/assets/erc-7208/contracts/interfaces/IIDManager.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {DataPoint} from "../utils/DataPoints.sol"; + +/** + * @title ID Manager Interface + * @notice Interface defines functions to build DataIndex user identifiers and get information about them + */ +interface IIDManager { + /** + * @notice Provides DataIndex id for a specific account for a specific DataPoint + * @param account Address of the user + * @param dp DataPoint the id should be linked with + * @return DataIndex identifier + * @dev If no token available, this function REVERTS + */ + function diid(address account, DataPoint dp) external view returns (bytes32); + + /** + * @notice Provides information about owner of specific DataIndex id + * @param diid DataIndex id to get info for + * @return chainid of owner's address + * @return owner's address + */ + function ownerOf(bytes32 diid) external view returns (uint32, address); +} diff --git a/assets/erc-7208/contracts/utils/ChainidTools.sol b/assets/erc-7208/contracts/utils/ChainidTools.sol new file mode 100644 index 00000000000..163b6a7877e --- /dev/null +++ b/assets/erc-7208/contracts/utils/ChainidTools.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title ChainidTools library + * @notice This library provides helper to convert uint256 chainid provided by block.chainid to uint32 + * chainid used accross this DataIndex implementation + */ +library ChainidTools { + /// @dev Error thrown when chainid is not supported + error UnsupportedChain(uint256 chainId); + + /// @dev Error thrown when chainid is not the current chain + error UnexpectedChain(uint32 expected, uint32 requested); + + /// @dev Error thrown when chainid is not the expected chain + error DifferentChainExpected(uint256 chainId); + + /** + * @notice Converts block.chainid to uint32 chainid + * @return uint32 chainid + */ + function chainid() internal view returns (uint32) { + if (block.chainid < type(uint32).max) { + return uint32(block.chainid); + } + revert UnsupportedChain(block.chainid); + } + + /** + * @notice Requires current chain to be the same as requested + * @param chainId Requested chain ID + */ + function requireCurrentChain(uint32 chainId) internal view { + uint32 currentChain = uint32(block.chainid); + if (currentChain != chainId) revert UnexpectedChain(currentChain, chainId); + } + + /** + * @notice Requires current chain to be different than requested + * @param chainId Requested chain ID + */ + function requireNotCurrentChain(uint32 chainId) internal view { + if (block.chainid == 31337) return; // Allow LayerZero Endpoint Mock to be used on Hardhat testnet + uint32 currentChain = uint32(block.chainid); + if (currentChain == chainId) revert DifferentChainExpected(chainId); + } +} diff --git a/assets/erc-7208/contracts/utils/DataPoints.sol b/assets/erc-7208/contracts/utils/DataPoints.sol new file mode 100644 index 00000000000..a89b0c50239 --- /dev/null +++ b/assets/erc-7208/contracts/utils/DataPoints.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ChainidTools} from "./ChainidTools.sol"; + +/// @dev DataPoint is a 32 bytes structure which contains information about data point +type DataPoint is bytes32; + +/** + * DataPoint structure: + * 0xPPPPVVRRIIIIIIIIHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * - Prefix (bytes4) + * -- PPPP - Type prefix (0x4450) - ASCII representation of letters "DP" + * -- VV - Version of DataPoint specification, currently 0x00 + * -- RR - Reserved byte (should be 0x00 in current specification) + * - Registry-local identifier + * -- IIIIIIII - 32 bit implementation-specific id of the DataPoint + * - Chain ID (bytes4) + * -- HHHHHHHH - 32 bit of chain identifier + * - Registry Address (bytes20) + * -- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - Address of Registry which allocated the DataPoint + * + * !!! COMPATIBILITY REQUIREMENTS !!! + * - Registry address MUST be located in last 20 bytes of the DataPoint in ALL DataPoint implementations + * - PREFIX 0x44500000 SHOULD be used only by implementations with same DataPoint structure + */ + +/** + * @title DataPoints library + * @notice Library with utility functions to encode and decode DataPoint + */ +library DataPoints { + /// @dev represent PPPPVVRR prefix + bytes4 internal constant PREFIX = 0x44500000; + + /// @dev Error thrown when DataPoint structure is not supported + error UnsupportedDataPointStructure(); + + /** + * @notice Encode DataPoint + * @param registry Address of the registry which allocated the DataPoint + * @param id 32 bit implementation-specific id of the DataPoint + * @return Encoded DataPoint + */ + function encode(address registry, uint32 id) internal view returns (DataPoint) { + return + DataPoint.wrap( + bytes32((uint256(uint32(PREFIX)) << 224) | (uint256(id) << 192) | (uint256(ChainidTools.chainid()) << 160) | uint256(uint160(registry))) + ); + } + + /** + * @notice Decode DataPoint + * @param dp DataPoint to decode + * @return chainid Chain ID of the DataPoint + * @return registry Address of the registry which allocated the DataPoint + * @return id 32 bit implementation-specific id of the DataPoint + */ + function decode(DataPoint dp) internal pure returns (uint32 chainid, address registry, uint32 id) { + uint256 dpu = uint256(DataPoint.unwrap(dp)); + bytes4 prefix = bytes4(uint32(dpu >> 224)); + if (prefix != PREFIX) revert UnsupportedDataPointStructure(); + registry = address(uint160(dpu)); + chainid = uint32(dpu >> 160); + id = uint32(dpu >> 192); + } +} diff --git a/assets/erc-7208/contracts/utils/OmnichainAddresses.sol b/assets/erc-7208/contracts/utils/OmnichainAddresses.sol new file mode 100644 index 00000000000..25897074b6e --- /dev/null +++ b/assets/erc-7208/contracts/utils/OmnichainAddresses.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ChainidTools} from "./ChainidTools.sol"; + +/// @dev OmnichainAddress is a structure that represents address on specific chain +type OmnichainAddress is bytes32; + +/** + * OmnichainAddress structure: + * 0xPPPPVVRRRRRRRRRRHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * - Prefix (bytes4): + * -- PPPP - Type prefix (0x4F41) - ASCII representation of letters "OA" + * -- VV - Version of OmnichainAddress specification, currently 0x00 + * -- RR - Reserved byte + * - Reserved bytes (bytes4) + * -- RRRRRRRR - Reserved bytes (should be 0x00000000 in current specification) + * - Chain ID (bytes4) + * -- HHHHHHHH - 32 bit of chain identifier + * - User Address (bytes20) + * -- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - Address of the user + * + * Note: cheap conversion to `address` is possible: + * `address account = address(uint160(uint256(OmnichainAddress.unwrap(omnichainAddress))))` + * but it ignores chainid and prefix, so must be used with care + * + * !!! COMPATIBILITY REQUIREMENTS !!! + * - PREFIX 0x4F410000 SHOULD be used only by implementations with same OmnichainAddress structure + * - Requirements for other implementations: + * -- User Identifier MUST be persistent between compatible DataIndex implementations (so that user can use same ID in all compatible implementations) + * -- Any of compatible DataIndex implementation SHOULD be able to find owner of ID issued by any other compatible implementation + */ + +/** + * @title OmnichainAddresses library + * @notice Library with utility functions to encode and decode OmnichainAddress + */ +library OmnichainAddresses { + /// @dev represent PPPPVVRR prefix + bytes4 internal constant PREFIX = 0x4F410000; + + /// @dev Error thrown when OmnichainAddress structure is not supported + error UnsupportedOmnichainAddressesStructure(); + + /** + * @notice Encode OmnichainAddress + * @param account Address of the user + * @return Encoded OmnichainAddress + */ + function encode(address account) internal view returns (OmnichainAddress) { + return encode(ChainidTools.chainid(), account); + } + + /** + * @notice Encode OmnichainAddress + * @param chainid Chain ID to encode + * @param account Address of the user + * @return Encoded OmnichainAddress + */ + function encode(uint32 chainid, address account) internal pure returns (OmnichainAddress) { + return OmnichainAddress.wrap(bytes32((uint256(uint32(PREFIX)) << 224) | (uint256(chainid) << 160) | uint256(uint160(account)))); + } + + /** + * @notice Decode OmnichainAddress + * @param oa OmnichainAddress to decode + * @return chainid Chain ID of the OmnichainAddress + * @return account Address of the user + */ + function decode(OmnichainAddress oa) internal pure returns (uint32 chainid, address account) { + uint256 oau = uint256(OmnichainAddress.unwrap(oa)); + bytes4 prefix = bytes4(uint32(oau >> 224)); + if (prefix != PREFIX) revert UnsupportedOmnichainAddressesStructure(); + account = address(uint160(oau)); + chainid = uint32(oau >> 160); + } +} From ebeaa4a159c6b30f33520790a6544938053bc5fc Mon Sep 17 00:00:00 2001 From: galimba Date: Thu, 1 Aug 2024 21:03:55 +0200 Subject: [PATCH 21/34] erc7208 update: move to review --- ERCS/erc-7208.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 8b4d1d3f23b..dfd26b7dd2d 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -4,7 +4,7 @@ title: On-Chain Data Container description: ERC interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-06-09 @@ -270,6 +270,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** +See `../assets/erc-7208/contracts` ## Security Considerations From a536b58f6c809dfc4d99608a1dff166bd26c7277 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 21 Aug 2024 15:19:44 +0200 Subject: [PATCH 22/34] Fix description --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 128bb0ca8ac..fa7a69f9fa8 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -1,7 +1,7 @@ --- eip: 7208 title: On-Chain Data Container -description: ERC interoperability by abstracting logic away from storage +description: Interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 status: Review From dc24b487bea15549b349854e8c85a65562ebb882 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 21 Aug 2024 18:00:57 +0200 Subject: [PATCH 23/34] Add readme with a list of contracts --- assets/erc-7208/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 assets/erc-7208/README.md diff --git a/assets/erc-7208/README.md b/assets/erc-7208/README.md new file mode 100644 index 00000000000..2a29db61c0d --- /dev/null +++ b/assets/erc-7208/README.md @@ -0,0 +1,27 @@ +# Reference implementation of ERC-7208 and usage examples +## List of contracts +### Interfaces +- [IDataIndex](./interfaces/IDataIndex.sol) - Interface of Data Index +- [IDataObject](./interfaces/IDataObject.sol) - Interface of Data Object +- [IDataPointRegistry](./interfaces/IDataPointRegistry.sol) - Interface of Data Point Registry +- [IIDManager](./interfaces/IIDManager.sol) - Interface for buildinq and quering Data Index user identifiers + +### Implementation +- [DataIndex](DataIndex.sol) - Data Index (implements `IDataIndex` and `IIDManager`) +- [DataPointRegistry](DataPointRegistry.sol) - Data Point Registry (implements `IDataPointRegistry`) +- [DataPoints](./utils/DataPoints.sol) - Library implementing DataPoint type and its encode/decode functions +- [OmnichainAddress](./utils/OmnichainAddress.sol) - Library implementing OmnichainAddress type which combines address and chain id +- [ChainidTools](./utils/ChainidTools.sol) - Library implementing utility functions to work with chain ids + +### Usage examples +- [IFractionTransferEventEmitter](./interfaces/IFractionTransferEventEmitter.sol) - Interface used for DataManagers communication to emit ERC20 Transfer events +- [IFungibleFractionsOperations](./interfaces/IFungibleFractionsOperations.sol) - Interface defines DataObject operations, which can be called by DataManager +- [MinimalisticFungibleFractionsDO](./dataobjects/MinimalisticFungibleFractionsDO.sol) - DataObject implements data storage and related logic for token with Fungible Fractions (like ERC1155) +- [MinimalisticERC1155WithERC20FractionsDataManager](./datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol) - DataManager implements token with fungible fractions with ERC1155 interface, linked to a DataManager which implements ERC20 interface for same token +- [MinimalisticERC20FractionDataManager](./datamanagers/MinimalisticERC20FractionDataManager.sol) - implements token with ERC20 interface, linked to a DataManager which implements ERC1155 interface for same token +- [MinimalisticERC20FractionDataManagerFactory](./datamanagers/MinimalisticERC20FractionDataManagerFactory.sol) - factory of DataManagers implementing ERC20 interface for token with fungible fractions + + + + + From 0225d9e1a5e83e70ca081d5db76da202e9833626 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 21 Aug 2024 18:02:39 +0200 Subject: [PATCH 24/34] Remove empty lines --- assets/erc-7208/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/assets/erc-7208/README.md b/assets/erc-7208/README.md index 2a29db61c0d..734eb14655b 100644 --- a/assets/erc-7208/README.md +++ b/assets/erc-7208/README.md @@ -19,9 +19,4 @@ - [MinimalisticFungibleFractionsDO](./dataobjects/MinimalisticFungibleFractionsDO.sol) - DataObject implements data storage and related logic for token with Fungible Fractions (like ERC1155) - [MinimalisticERC1155WithERC20FractionsDataManager](./datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol) - DataManager implements token with fungible fractions with ERC1155 interface, linked to a DataManager which implements ERC20 interface for same token - [MinimalisticERC20FractionDataManager](./datamanagers/MinimalisticERC20FractionDataManager.sol) - implements token with ERC20 interface, linked to a DataManager which implements ERC1155 interface for same token -- [MinimalisticERC20FractionDataManagerFactory](./datamanagers/MinimalisticERC20FractionDataManagerFactory.sol) - factory of DataManagers implementing ERC20 interface for token with fungible fractions - - - - - +- [MinimalisticERC20FractionDataManagerFactory](./datamanagers/MinimalisticERC20FractionDataManagerFactory.sol) - factory of DataManagers implementing ERC20 interface for token with fungible fractions \ No newline at end of file From 989dbf7a3a20afe37fb6ac4e4703c42809de6715 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 21 Aug 2024 18:07:06 +0200 Subject: [PATCH 25/34] Move readme to correct folder --- assets/erc-7208/{ => contracts}/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename assets/erc-7208/{ => contracts}/README.md (88%) diff --git a/assets/erc-7208/README.md b/assets/erc-7208/contracts/README.md similarity index 88% rename from assets/erc-7208/README.md rename to assets/erc-7208/contracts/README.md index 734eb14655b..ebb1427e9da 100644 --- a/assets/erc-7208/README.md +++ b/assets/erc-7208/contracts/README.md @@ -1,14 +1,14 @@ # Reference implementation of ERC-7208 and usage examples ## List of contracts ### Interfaces -- [IDataIndex](./interfaces/IDataIndex.sol) - Interface of Data Index +- [IDataIndex](/interfaces/IDataIndex.sol) - Interface of Data Index - [IDataObject](./interfaces/IDataObject.sol) - Interface of Data Object - [IDataPointRegistry](./interfaces/IDataPointRegistry.sol) - Interface of Data Point Registry - [IIDManager](./interfaces/IIDManager.sol) - Interface for buildinq and quering Data Index user identifiers ### Implementation -- [DataIndex](DataIndex.sol) - Data Index (implements `IDataIndex` and `IIDManager`) -- [DataPointRegistry](DataPointRegistry.sol) - Data Point Registry (implements `IDataPointRegistry`) +- [DataIndex](./DataIndex.sol) - Data Index (implements `IDataIndex` and `IIDManager`) +- [DataPointRegistry](./DataPointRegistry.sol) - Data Point Registry (implements `IDataPointRegistry`) - [DataPoints](./utils/DataPoints.sol) - Library implementing DataPoint type and its encode/decode functions - [OmnichainAddress](./utils/OmnichainAddress.sol) - Library implementing OmnichainAddress type which combines address and chain id - [ChainidTools](./utils/ChainidTools.sol) - Library implementing utility functions to work with chain ids From 177e5704fc6ed38c3efedc28708ff4337c0aebb2 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 21 Aug 2024 18:09:23 +0200 Subject: [PATCH 26/34] Fix link to IDataIndex --- assets/erc-7208/contracts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/erc-7208/contracts/README.md b/assets/erc-7208/contracts/README.md index ebb1427e9da..fe0c96ec00b 100644 --- a/assets/erc-7208/contracts/README.md +++ b/assets/erc-7208/contracts/README.md @@ -1,7 +1,7 @@ # Reference implementation of ERC-7208 and usage examples ## List of contracts ### Interfaces -- [IDataIndex](/interfaces/IDataIndex.sol) - Interface of Data Index +- [IDataIndex](./interfaces/IDataIndex.sol) - Interface of Data Index - [IDataObject](./interfaces/IDataObject.sol) - Interface of Data Object - [IDataPointRegistry](./interfaces/IDataPointRegistry.sol) - Interface of Data Point Registry - [IIDManager](./interfaces/IIDManager.sol) - Interface for buildinq and quering Data Index user identifiers From 62d0881704818f2d2195981da707bf88dd3745d9 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Thu, 22 Aug 2024 14:08:17 +0200 Subject: [PATCH 27/34] Fix the link --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index fa7a69f9fa8..fb5ebbc3718 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -271,7 +271,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** -See `../assets/erc-7208/contracts` +See [contracts](../assets/erc-7208/contracts/README.md) ## Security Considerations From 74aeffc98ed921f4e10d63452f69efa2c32d7890 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Thu, 22 Aug 2024 15:07:02 +0200 Subject: [PATCH 28/34] fix link to OmnichainAddresses lib --- assets/erc-7208/contracts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/erc-7208/contracts/README.md b/assets/erc-7208/contracts/README.md index fe0c96ec00b..2a0ea7707ba 100644 --- a/assets/erc-7208/contracts/README.md +++ b/assets/erc-7208/contracts/README.md @@ -10,7 +10,7 @@ - [DataIndex](./DataIndex.sol) - Data Index (implements `IDataIndex` and `IIDManager`) - [DataPointRegistry](./DataPointRegistry.sol) - Data Point Registry (implements `IDataPointRegistry`) - [DataPoints](./utils/DataPoints.sol) - Library implementing DataPoint type and its encode/decode functions -- [OmnichainAddress](./utils/OmnichainAddress.sol) - Library implementing OmnichainAddress type which combines address and chain id +- [OmnichainAddresses](./utils/OmnichainAddresses.sol) - Library implementing OmnichainAddress type which combines address and chain id - [ChainidTools](./utils/ChainidTools.sol) - Library implementing utility functions to work with chain ids ### Usage examples From 8af3a0dbdc458d8cfe09c8fa4f8f009089277057 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Mon, 26 Aug 2024 11:33:07 +0200 Subject: [PATCH 29/34] Change reference imlementation link from README to a contract Trying to fix Continuous Integration / HTMLProofer error this way --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index fb5ebbc3718..7bd98c0be50 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -271,7 +271,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** -See [contracts](../assets/erc-7208/contracts/README.md) +See [contracts](../assets/erc-7208/contracts/DataIndex.sol) ## Security Considerations From a3d3ee7f82c26bb63466f4c483b227f4289d0eb2 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 28 Aug 2024 20:32:30 +0200 Subject: [PATCH 30/34] Replace reference implementation link from assets to separate repository --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 7bd98c0be50..0c94c8d1a9b 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -271,7 +271,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** -See [contracts](../assets/erc-7208/contracts/DataIndex.sol) +See [Minimalistic Data Index](https://github.com/NexeraProtocol/Minimalistic-Data-Index) implementation. ## Security Considerations From 6ef9a27e981788604059d1919a2a1789e81aebbd Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 28 Aug 2024 20:33:17 +0200 Subject: [PATCH 31/34] Revert Draft => Review change, to merge other changes first. --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 0c94c8d1a9b..f13481b3a0d 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -4,7 +4,7 @@ title: On-Chain Data Container description: Interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 -status: Review +status: Draft type: Standards Track category: ERC created: 2023-06-09 From 3bc07883dad4c3e452b08c61e4f62ac27b7106da Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 28 Aug 2024 20:46:03 +0200 Subject: [PATCH 32/34] Revert "Revert Draft => Review change, to merge other changes first." This reverts commit 6ef9a27e981788604059d1919a2a1789e81aebbd. --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index f13481b3a0d..0c94c8d1a9b 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -4,7 +4,7 @@ title: On-Chain Data Container description: Interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-06-09 From 64e64440e4c8ebe9e8eecfb5137a9465338e262b Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Thu, 5 Sep 2024 20:13:22 +0200 Subject: [PATCH 33/34] Revert "Replace reference implementation link from assets to separate repository" This reverts commit a3d3ee7f82c26bb63466f4c483b227f4289d0eb2. --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 0c94c8d1a9b..7bd98c0be50 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -271,7 +271,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** -See [Minimalistic Data Index](https://github.com/NexeraProtocol/Minimalistic-Data-Index) implementation. +See [contracts](../assets/erc-7208/contracts/DataIndex.sol) ## Security Considerations From f9c65e86409deae2a26214108fca7ad77ee3a7eb Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Thu, 5 Sep 2024 20:20:55 +0200 Subject: [PATCH 34/34] Link to a README instead of a contract --- ERCS/erc-7208.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 7bd98c0be50..fb5ebbc3718 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -271,7 +271,7 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** -See [contracts](../assets/erc-7208/contracts/DataIndex.sol) +See [contracts](../assets/erc-7208/contracts/README.md) ## Security Considerations

9q1qNVZ2T@bm)v^po3MC_=_T6kS+S6Mx$34? ze=!3CaZ@GK#0Cg`cxI2iZ`*fDw7v}LD>!3{rG<;)ziXQDa!Eodmk!h~b8Ez6;|kgX zc_WC!RW!T8&zv}L4G6Zb-yBW4Uzo9W5}X)Q@dPux9y215su1pPzrmez_!jyEMG;YhFw~nM!2;c|3Vg zx`%j0DoU<*Dg{JlBX@Fb|B=h!PMG%0X{~QFi}|k~N4;UbLs24j{Vn1@+z5B-rT(f zN@M?$j3V$(GPi%d#2-EXQ7($rbW7fdB~?C)s^J04x7LnhkL`FjQQ;+vp|x>N1|P1~ z;%ybr@nseH(dU7^xhZ`^WSr%3Bzbw?m@XRDLffLxm_A*NBrW6{NJ$y$-`4x4By#Qj z{N;~6{IF-{6O33o8}&biX4>!3D6jv! zhFj++iFXYDpW4I!ISzvR>sbdZL4W7AH%llvNfBjqG|EG!KLb1B#L(irkYRHcQ@XN_ zA{K@-g7Vk~=uWI*{OjIwgbC5fo&5-_Ke9&O$%-1X=Kn7wjr%YOStbd|(70Djlea4m`tvUJ!pK*M=yf;Lvu2&`8 z5FQxIYuoQgff3c2O0>%XsVQoBhf`Dhe*@SkmfJf%6ew!A{d4;__;dR|fbzM|@>?TF z-B-_ehG9V3klLklzCgIR{@@ksPl)TfjjX!N5hB>#%de(0;>ED@59Q4g+>ni7ja9&& zsAa~puhMh1Lxh1v*xD|GRP7lHdN&_ke%z)78ntYE*FS--B+lXV!-C{(n5kUOzbm2| zbZs8THeUy@-5AZG|D?O|`FGed_2u(MO;ohXA?6HEsD0gUGv5t&ThPbmy=EO2Uc^%-&U!?X@Ertq0CY8D2$E_FU7 z1V zMvEUFSGP#%uZ?!^ryE@O-M!FNN}rnrG8e4nksf{=&G%{NbXYo??Mz%BV)Na{dA8}z z&Xs{k_1v2!jD8+UA6b?u>I|d>MDSWaZ#U@V0hKzr4xoA%Vl|mCC=!2kP&_X)P8+wp z;{*E{{%XDcXW9D*$(^Mcs`s{m3d3bEHO#;$rI?6z*hpOqT(R9+Vy@Dbmrd@MFy(-t}QyVjh1B>BesW*rlh#LXhU&m>yfM@=)%{L;R1ze;&Q%%s^^eJbu1moVnaOvvpZ3S`FjYT^RMDNCys>Yk8lc7zN>w-@G;a(bfD9%^7Tmal zNAGEEGKBSN96FKg>{JH?(?wBwV@x{kG&Od1Y`u=5MR=O44^FpRMIDmi=ozdAzMONs zh3?$ucl`!9DMUaLQ-oJ@zn7v`cOK}qGtm;gZlaheJ`~H-9=%=3h${#1Fb<>#;TwOR zE){8xDXSwgU9oIMgs#z?29Te3)O;IlLl!=NLUv3XJWn#(4xv_3q8>Ztxt7ck;a~G= zQ`dGe5v2%CQQvm5fl&DX@z6YAO5Aomtk{YY`(wiM`EtAX4E+_(lg~Yj-}Zj&F>RTd zR{9bj<3mJyHuF{J)cSsDD{%wNU}1_<5emd~XPIW1n z?Xdbv>u30sxLgQzUR_X2u5GRu^ewRcAa@y(`V_spWcpiwAQYy+-uf2l1paQ1I3VT1kL$gM-G3K!pzLq0p2?acEB_ zs%EMBAsb{gB>aP}(bIv9U+YjU|2Foi%4j6RbhdHQrNT_Q1PC<^Nj;!sypMV}Mf8cD zC=3zA#_qF6QD-kZ#B!-@=*QU28;`hPniUdJ$_71&{bKEC2C2$YAtx^+Cwb9I!d1*T zZMQo(!LUxlKp75YUUO=Wk8=TA347(nulX89RtuYHjhJar^3>LGrWyNLHYN?g6L3AW z+v3kOpEuSq4>>_TjvU4?P7BOF2|7o|0f^+JE3GW4nV+?)kkDCLhi!hu#s#IO4s*Y^ z2lMh0C-^hd2p__NkN@m_EI>G|avpz<2y8L~_f&zRR%pKg()L$|2vT`H%bDrgz-ouh z8|1#BoEoaEXmBpN(;)MAG5I`=*N@k_HZFa>|C$K0d8^J|g4A80?m1Vi{K`ZVnvRJU zO6*6GByKoysh7PSWjJKy3TL6~h{TY4DS_;*5At#9peNw{pN`Gbr1WWJiNX7 zFZ2G)rP#G~9V5!E+u)<*0R{wN6MZQWPUv$)2ort2FO}N)%jku>@ULRS_`}%&&K=A! z2{PkLrn;(wG>oEdrv?qx(QRXv4a`p&i@yO`cFhsQAA5X4Rzj~@=9_;5R{mwgyEmG= zI5+}|8UR-b!DuB5S)+!Z00Z|wC*ew9l%uRvBK>ISE>4C;u0Tw?626HfM$L2(^qc#0 zBHX^us$*^XONLzgI^djEbG6k4R~&5HMMEQ`?N%E_MJt`APDOw22K%$AjdmS?b zC@JDkL_JD-5dxC~3a)hDq!+IolUZ7{JmEs7If3!6V}7pvW@!AS1|qrGL4alfypoh{ zhaqFUR&GY1=<8WNYcyr?b!P`N&9*^kQW96{&*-GU^u}^kW_45EABNmM%o=*2=hlq9 z@mY_(UaScJgs%KN3w-q0`*U>95wTTNbG%vgm4uXCe5r_=2k~UoK4I5h$_g#vb3F8# zNxWmMc6A&s2CT?X_UNTl2vNe$j#eu4mq<#f;G<*jGtKn_|LWl&c0) zs{;-JavBq+ocd|)92Ji2Hm}5pI=V25DfPFSwOn_MB~1O)sM@zI z<^*zvP_dOY`nw?%Rud z)yCk^%Ep(+?H_e%U#0lM#h%hk@@z|hTN4=dUlr|di{z*oK}LN0y4FGs282gIB{?v5 z5&nQ6?)&Y*-sg*I^3+t_04x{j0N9r&NoT1FZ(PwYRsXvjGc$#@yC(#akjXrJ)PjcFtcRkAduFLIuNwBgfKmKnjBS+brSsN0* zKIqmU4>2LBM%$xu^IEKXyzmj_B6*MviVs`0N!YlN-w%D%|K5)6U!g& z0$n&koI}tPGHz@IJw_Y%d!O}?cKVS?6?`~)qWE~(-i@JZuccykPNa1(c6VOAGuJE- zEdJ@t&9tnT`-9WtDigK#E?AOzaB$^zm5<61k+Q8T?Hes~E3^91Uc%(q&~2*RbLjFR z0=W-F&0w68z=1tQmK#-X!P`$8%)fL;Ukygfa5H=}Lgm^=bhw|ARA6r@ZZoXnf-spd z>7K}@idi5ZhIE;{dYuvgd=9mdQqPW3b#ZpnY;I~Phdo-Ds;P0=o1JYwKX8UU_i1u@ zCpF)^oaX{_(?Kzm@j5L$w`#7rL*}nh$eTY%4-EPlGniC=a5mT*QrDMCel!3e;`pBj zta&GR_Y^AB$bvBFKatOOEiWu<5Y7i!F0jWYcjKdmT{v>rUF8{eZrM#v>&HE7FkD~X z4PMX_qy$ysTwaml?u)w66!Oe7D;PD(g&-VWpg||D(QAs%v0%!(_{}dTs9Z??7xK$# zs*A*?u^w6h?9sC)Va28Kcl6&dgmq zu=zdupzaE_ga7*C_HXcold?*11Tq=k4F$=GPx5`HM|N??6VCNzshZEic(6dSc}{{D_A1WJaT_S ztOJ3r%h?DK5yFPwO+A&Oq@`fdA%e(#aYzVKtsC7X+PzX6v!<8*J=2>?zGS27#W zqWG(Q6?cg}mg!2m7xtFLCnEd1enPq7r;Oy{XSbiaP2YFE7uHYj5J=t3_W0-ePI7nL zl~o(fUzdd7MoKBe#fKh==T<_0a9qsk14L^lYp8eFo2hLYvTZO{5xX8Pd+lzrUJ8TM zg848yNFfX~k~Q@d_Ns(sPjHV*ynz_TMlKi*qm!PK8xKzdi)~nUMNTz8T^g`*@J7A0m%&zp-#k(MN_d-RuN4<1e7sA=$tALGc%*2XLb1-Hw^<8~!q z75tYq&v(j}*-O$A=a+^IlsiGs5jGlFpM5ID=EIYsamRd(vXg}P#NVoirVm75nOyob z?OXX#H4Y}u@>+dBfBCV~(+WE2w_;l%FJaw{bFLPk@Vxl*i5~22En(0tOFtzRFyZx6UBg|q&Jl~PVU-xdgY@!F37d#u@19;%g$;iD0LnHuOnmNn=0_W4%^Xweu}fj13k)L#g&4Da^8sQ%#t7#_ zA?V(ldB|qlnWfHIC3XSUKrwX&|EgKr<^V9dK?%RRxP5JPISu)8hdP;Z&2-u_mg%C_ zVls~ztUo=Admni-J2VeH1!EaFBPCrPt6(FzXScOt@Ky8Y~Lc>Zm+uT-EVj9v%JI^z43e;aH;-aS03$5{y#Oab3n1sGa^kY z;<$t=jPc0^t^uUfh_o8f8urZ8Ur4H1ZTOXT@=>-{SQ#JuWkg!+Z*;R~=f31}lrRZWm)8G!o*Rb@KbQAQU;U~B0FGu1(BncBKy zM1p`V%Aqew-MQ?Un^y1<{8htIV)lQ^E5}IQG_MlBqfRZVd1qN8G+zS z4Z08pznS#j9k+XfiHbs=u#Q8vX74aqdh%L2>oFr8CY>^Ip287gs8}UR;QJ4AP7FUu zV9hK3tnY2D$<%y&vo~c*ZC4)nCZ_nQb$!Aa3!=;ga^z1ualdK~i^&Ow??hG+?+)_bUcDL2kQNl|kQpoG+O zymfWw@aQiu4t^F#w77m41u!7-V}(p|0dkN3bH}N8UTGN%05^zZ=}Dv-(59x71gMD^vQPbXYeErWyFkZG z6Ggr#z9%)c*e9%~F-&%<%g2`Z}_Q2my@fuM=uEI~@p}=})6WlEC*@H$Z+x=%#tKm2`l`S_*?O z9S_Y@Hk}$7Zq^u7D$I03!G|OGzX7qnGM~(8eAOp(IR>ETjE3KnP3P1~x01xPWS+mo z=%(|_aHxi^b7uhAj!lvRK1+@rBJ)?;Z5ti8(qAV}VW24(Ne=CaBt07bP9nz%bur)u zDFLAAOq+8(qOH7z4a44P?j%%B$ZEF%!ZW#{_Xx^!6`uf_x8%WTd6N}24FyB-hZC;u$cHE@)fzHTD%r|xnr`hH9fQplQ ztK^ye+%TV7l-((lA9=AHkb?a6r=wUC_Zq!jP~8i z5;MvPbK52*MZFOP0k9gisjGt#poF?KV&j%K8^|j z5CY_sCOKW}JNEluaNIimH^6!8G;~M!e+x#RYhTZ#Tx(xXD~UJ=DS$WkWrN$Uwav`Y ztqc_My4Q@wgHWrRN39(f$JaCcD{q2lYh7;xel>ac9;{p-UcE<^zpXJ_y1u-1+CKjL z{=k^#zp?ZGi#_fycJn)SSO%k-{jJlyh+8!_>_nc;9n0)xFoLcze^u1+#n)qK=JMDK z3OW2A5MLC@{*LrFa(stepj!S$g71)DV;6)cp=6Kek=C64YXvCt4JwUZo5F7Z%5GC? zCXNW6tR?wRpnG@wA0X=P_TNG9^~yOSbsq%^ezn~4?4Vx$H$by}Adi7$)bL~whmntD zP>f7HH+2HA#}U`#5U^WKx(8;;&I)L9q~z%o;bIotRf^SC(_CV8$_;u-)?{S9EwF7j>i{8}7iAn#RVJ=wBvVeSpR4wIpPkNmJK=HdFvq-E4(Ws-E%4f8yElb(oz_O*SwnYvxxlXi zYZ|oI>*=xe5Hh$C`?iPK}c~vgO9-fj778CF?c7L`>0Z(CO z7m|gO8n2XPJ_JC_HaM#a;ggf!9*nqzj8TzAPcQjX2_!6L*Di+ius)U*(g4TTb$=j? zEGT=EakS#t9L12OOjy&+FU52FwD7Hh={b&&#`N<@EE?!pCk55&XqHr+J`gLiwIv1( zSt*f3q{>+HZrX)y0RT(~NK7yJ@Vw^9KF$qM>XX8g;VMbhS(hO8;fL)RU0d`t`taAF z5gNqXpnMvP$SB7HzoMbDPH3#@)W!<|brcV8QCr)8nw1zX_>i*-03fH&(^u*9mbjz6 z7;EnXa;J$3=a5fQF|l6Y3qx-rFPaO>b<)$Yv0%pNo`Nu;%NtEBGF)mlpX3izE-ZPQ z6uE7*o<43=1f{jZ@#?rfyjAMg$`=(!>vI)~?S58#XWqL^nsYSdPk(HYo!SweD&j;L z&cr8~C_eo(!kU<3>`AgOr-a4x%t>4yC+$g28=*w>=B*P4Ty-7yY7%$-`nZ)@RGxoh zH(>}u1-%WyC?sB)(e-;6IsXRJqj^vMs2*^^TXK@yYxGt$#a9%@2U9}o*@dbBm!tx> z^Yx!sGTe6sSzUMIJ6$@^n5q=fc@?wlU_D3tpkdmW>3_xEXK+8bqP?e^=crqvh`zLQ zNQRr1eN-!H?lgWoCTn4s-!&Q&H_b9hqgW#r znOtX}n!k^}=*XiPM=H6gPQI-FU`pNT(8GNVf=etw zq-(S1gIQ*F!2dQRV0y5a%$&LIQ|>M*(QjtUe;WV(@|;9?Cs_lMS4ad=$r*lZ{zBF) z$+Qm2i-lCZ@Lg=;MCr#Gmk2;f2oMM0I3LpN^yQWbzJbgn%8Q8_4BRV0OPzHRbthJ^ z#2qZ<;MOY+(cEO_DxIQ)P+@zudXed&9Nw+L=Vw9xu~_-bpE4^n?VKzJds(M(?Z?A= z!z75d_xNgR42rb!0+E~66UCxJ-S3S8wkz69ai&tcL{yLd9H296x!-wRpJ(hC8eT<^urY zL&s6)Pc3h?+58|WyaGR8y2<2fnl^?FU{v7e#+2dfqYFxSh2|2?oqk^t?3KP{X=8eT zzsKadSV`&JzZ6z+millDlMPl$2J{6JRks%BGuiGFgBgl&F-f@&q(8N){XVQUr z;XH{_^COu=7LhNVVe^~AlbVk-Y^B55QkPdBw%Rp38SbnV+MrXXyA5sY$dP=#wV6EF z!`!HLYz($RQ}5EM{6L)KlWHLIOz7~S&a)@hw>>Mw#4=+NJq(5&SlR>J*OmIvLjUHSPs>Z}_E2lWJ!2sA^6s zil#1L5l`|gZkPpXEbFL3Z2Z=Ze8F{gdy+E8^;6e1<3D9z{F+qzl+W4?Ix81Dz6fNm z(Bf2ydl#fco}4cv(OtYmj2&<|$OQ;-#fXx&V3S?D({MtWSVDNSMS7X-u}srL^=FAU!88L(KK}c`-_r4q?1Iu7jz;L>rnkT%eMeR#3~n29oklwAjwp zmZ`TJ9o!V2!ObH^=g5`T!W5Ik9TQu1OFYkl2TWDSd?>>XB8{?YglUjNM+;k6BCtqe z(S+Y=ds|D{k)HWFBI5~P1ixM)fMNP*593ii&Z|t`>M&G(HOL<$nEijTy(DWe-~riQ zQh{Vy7zeQ5?D#Yi4bi_;NIsy`7$mrwemg>HNtG0(_aO?mL{DQ#VqKLBC@HZgTA~s= zb_E0wZRX%x`(RqSvc&2ERsoCLX)>Et+}AdQ$qGc>FM8v4TY~giX~_;4XhX!4KaJn{ zw$9KGnk2;*$1vI#LhGJZeF-DW2c03D$Bm$$^Q&s!$Qqei46%OGQ|+6KY-{T#3(waS zU48ck5aE~atZ;QH%Xa!ZvG=?MiXo5_pNu$#2(^Y})3unm# zqF9}Q%|>JC=Kkv>=1MOgIXta6&aAjV`Rps@qj?jRf6Qr$82mE5gCERTQPJX*wjlo@t z@_6io0HzR83I_-iBk;EwJ;1WN#7s3S&^sL$z&n5)f{zWL4`CSpPiw~&LB-)5KPZJd z6^l+~nJRRh#0V_#u6er2l)ce~Z0HT+Wv)@*+E?fo>Fi$QgAODGj)gj1SB{{y3*lQH zF`9?yp3jl{T>%cOb5$p5wWOLh&>_iL((&{75W4OcDZknv3s_vR z@%kK3*T7*SLNg?Bj8x-Umbm$l{kIRz>abaD*4UOg56+4vl{x?$@TQSD%iCAG&$t4I z{57chTL05=Nj?&-8k@O+tETnJY-SN2hk*t}=3ld^uNiYVxQZEiVe#N_=mF5zi&{EW zn#tZ(AC^OgTp)hvfsL7D$PPKlHAp&b#?wfWYWTS|4wbYieE@_bMFhWpGc$nb>R25= z3y*dF1lHf`M|ct@k1gsG&6ghG9$`S6)v!X*hu`VF)%;LW#R!*P)7xL8(l|YopYFzS z-P%phzJ|Kg@6>DzuEq)R1F zsSwAaZ#8OEwbiRf4<|;nVjrMaucjwGp@YoChEL#8*}8#{?fUtmR)}dl`bcHE=8&9p z1u5gOt4LE=^=N;5m?*Ky2{<(O296I~_~2H+J^J$MCNW<1*sHzfw&&xC>5Hkyz07O@ zzjieSa#XjK%C@XGGc1xKcBq`fzH~Lvt|6eEzeJzbNcUO!4$&n?;fkWajABv~+cWu9 zfsoitB6M2DV^pUdL`bZpm5eK^zhB1W-ew#YJYMnNImw*EVyJ4v^ESX7bOa2DP zyc)b>{x8m9a_X`l*)C;Ql1(ma1Ch}z@nI6AL9SjdbeE!)*Tcl-zs^47C3gzFT^#%n zPQq2_O~~iFkEg!BC6!aEVg1LKYgx|m{ylGB_D1uY>c9_?SPz*3a;6!oCHG|APXD-N z?T0TitWcriRc=^Md4LS0-QQR~b9>Y)_C17OYqDN} zX8CG)DjMfKhsu@8awPGv^j53_b}DQw6)|l{9gp}*Bu5?%PwdDknkTzFHb(*7e+Nm2 zsN4S;$fF1@x-b-w|Gxm3wd3f^Sm2<6R5aDot+NuV6ke2w0> zN|vLm*l-ShpcEN**yg4g^<_@Y2moe1$)8uIej`Dj@Jw9{=W`gRd>sDQgxd_}$qrDk z?aTAp!Iilux4iY@-%Wxw|C3Pghrs!V5c8)8+h0BYJAw5t!s9dcp_s}lxq)AuELm-1x`{r~VA@qy%h>yQM(ys!J@Zfd zk2;E{n|#$PosJR>W|o*r5$*q1GITt9@o3AAA=lC1^fGnzBlo3orMmcIjuLbuEo)i7 z@}$xMHnv`J4I_O2vC3!IjP%{v>dHouA3KZT6pxs>*xG^(W=5_vY9CKGdHyOzWM14e zdtd3ab*p83fB#Wiupx?AR{MBH)bm#*il}Y&z6wQrh$80v8&QLeng#u&zE(Hw=2B5r zB28!q@J+PvQ8AF6-P8A6uSh8ppOOpp4~2!GZqUXRPTEmUhAfk*L>+e2Ag|Q!+Z7hB zXLnqGGo$bc7aP_MR(D@-ibv=TRxF74(7v0oBe1M$l+(5R*qh=TqQ~k#=(-ZWiN0W7 zMKyKLoCk*@P^GmXQH67HIIB?H`+srdJyyR$G5*KpvH!qXIh4_I z)9c7Oac~q3@#Qx_>Rsa5-4(-l=&y$apdRua^<3+l{R{bjQ2*EKZr53CvibArodPK} z`O$8puRa0HJH~at{^v_ztkDPF5KpQ7eNh#?s$%Jof0yFFKPR?SgQf-|&$0$m%&_jp z&(JQYUkG&QsO7Y1xS*UJe`&1MI=eHhWmlN^i3&3)x&m~rs>uAB2ltjtZ(uhzN?yfK z@S3d%MZ|y3R)n$=Y3#w(9=NY=s{tZc(PEO8vHN9USgiaLI|zBMM)8|8?~J z_i_wG#g$|=%HyI@J5WtgmX{QDggf)z$IQd=h=gbJ_Gcul4<{MxA1|%G8+#|l-!Dyw z(g;cnC6uA;;U5*;8Or`vLHR0=$PJd-`;ZFjC)0*>(Q}L5+OGaWBxyv*wW78!f{uivp%cQb-mnAvU?PkrAq8O&QGZ4_P+@yCdlR9-Op3LMr^CspK}Czi;? z2fyGl)T1(V1rP%Q7d*ZdC;92OuJ(q|JgcX*#|X8(Rvuhx;j+g%V(@^_BrY%V2r>w= z80t^pd@LrR495)im{uj9GHun{*+VXTYAa?Fv6r(2R-d*=)v|NOiIhuT_3pua;Ct%n z$V_>E#?FJz&g7_|*Ni-J9<3IpIBvYX^TEl7;S~>>M>b86zX7?Hz7Gr*hJFLchVTFL zP;h5xsD~750jn+*j$XU8m9~ZhV!^x`p4l@kauFidRGSQ3(>5Dm@4^$p2oX6M^Rud5 z4C_(pOg*)2Z-CEE#if5P4tE%bb~+-aW4_pM5Hw4DE6jNp*CrpAH@ANq~g-m}L7tH!#`~F;! zETNa0@)8dcM0bFrdyoTPC&)5>s4C^)A|oWAt?I)k&=uoq6=%q`4E-n#X`$VuX00h5 z91l?2=F-k4!YnKz#L>J9o*w&`mGe60IQGkls>40xt7Nte33^ljFHG>5jbvqmR&jO? zyVT5uw_=kQI6Odss!bA47bz(k#i<_>@D&3wZl3tKTK7JO9jeT#aDw^}G?{*`%y^0} z@k`;dX@WX|i~yfmG1q;LrV18 zCfDLSX;22CGucY6pq#c_H{X!WgYeYG%DfDskKT}FEHZ=*Xh>4El-2QqN@mpn11viT zk*!HCkgBJE2~P;#70Ey1{__?r=9fqo*K|8Hyv9IXI6f1$_?4Ym>!r@O{v{R>L<=Z zlro-#@Vb7q91)4TSI$On@m#TT+5DAD@`zC0{I}sg!`u>%p|Dm=?Cu_<|1XK%fsYFuSr8RiR0HxG%V*q3>gv_pN-kpd+j*@LQL$|xk1wX&r}fB z6hvSZ!XmUmpvb&g<)q*0?0gHwqL^*^+U()wThYd%{(An16j`ld;``-M*9)=y5mhHz z44O0q=O)&EqK%-oj5!ZX4~|Htho}FFIaZsc-IOuc<7K!~Rf`|=j0n*zU{_=)(oygs z3cs9oqKBA;pmneE%=16~k%RpnSr|WxjhUnNR_4Nm;(=fcKKciy$Nv_aF4!2eTBoOZ zm7q)y$(r0!7mR9KxdGt(@_-dw>R@DOHpZ+)A)5KA+61Jh_O#nCk&4`mZ&mKYkO zrE1)6ed3K=0}q-IYp_rl6CWLsF{mxRlH4USCbUtG(F95L|2pOVfO0W2al=HNj{PGV z-$v&$;(^?jP?nz%x@*UHze_rMjxR8(#p#F#tJAp{hMj zfisOOKgwRoQuLf+QYX`_>SBqP2j4RW&dFjoTM3yFFEE9k}eNTk)FEq}81a zPHM_TVNNs!inLI5VaJ@l4aHPFR1>af1I$iozr2{;y|?$I5hBE!_A(XoaCtZ1(w<@{b?hc?{M`GWrGslPe%m z?4Ic2G;kXn274;tESP9Q$NBkABO8gR+%sUN7LU&Gf^ zC2zH~VOO)iMDkn%i(ONJl@e&_G;$=ZS1{;k;1zb0F7TGgKoK-P;p*SatQ6ok(J{7d z_spX66H$){>k3gDUw`_+<~nPxJhG0mOR=z&FlZ+kw{X5|p>4@zkMOD0*JbQgw>{g| zTK&PBThQ?eZ?5Ko{HI|W3D1FQc22#?SLUCFT8B#ZnX*Rd`y)7!sCnMBV-CAkX2KwA z=Pj&G(pa*>|;-{@#gx^N=gEf7H;Q|s&)+5pd3*}tSZWI z_&}qdvr5ZVtdzf75F)8d3jSWK!#N^94hyAWWd1U4jX#kID%A(uldLnrR{++4)^<*_ zgn!K352!?sg=Ab}>_Fnwp9>_4*hzlL(p-8I6bZ=4jY$Q62QTQ#uZZ35Ok&DFW*;EB`Xbx5U81E~-h$OyXqV zDsV#WxZUMZ&Ho|3MYBbvp7W5P)U)1)Y`$AP(i}uBo|AQ*=f>lL>%wK9o(!Go+(sQ; zmuue+*p;VJU*5^viFdrWD<@ZlxC6HBB&&n*#QOjq82_#^RxO&{blS>?1jQ$sK04{$ znOHm0bwthed~qCv1T`qWHfr#4@dl9__6v9lSuNE-KvFyQU|G&`Ml9~qp1O4BYXoZb zkq4b_HJ36rnlWTM6BspAA%6uh& z7V?ruSk=M7o~F8=Ti7F*agvQGbE9F1qig72EO*mO+hcX_gwzoGkao1Vy`vK$#A`mQ zNDJVL*MEd8SJ#vX7T{CY^F|wKbE*naU?QjKln{z0Gi*;lyvoM{N)&XX+x%;J`STJB zflwS8Jq?u#b4m8N0|Wq=m%-h}jT%v7O?sRPcc+pf?nb+j5XWB0mp0zejgk(fP(;CL z?XAM#*z5~zX#+{oBzR&9=VE8d9MA?r%9}nQy&hAUSpp-ILsZTJJs~TZ^e7EG;>;*@ zRI&Yxyy>}j`WI~!L_!&L#D3~{1?<*4N$PQaEuzf>PtKZ8!O&~`VQRR#_|swI!4ff^ z>*+9F835~AUb_PQojB-kV=43krgsrvY2gT9i|q1gJ*)DmQDZtv2oL~|fi_PgQ^y;& zCIGux!ulUwU#<;2#t+?U6t#Ba%SmeThe8unR2>v z&A!#7W2)4Ri#PPKAvOM>%b#SGp8z2JkfJ|Mt%*5EvH8!&CRe)()Mn`(lxc~Sq*Zu4 z>^fla2{M39*~7>GQQyjCKI06#!8gEmGZVmnBU-*3eAzR^Rl?nWD@C}`)uGLVyEtoj z7~bK;(lf0u!uLuJKe6XE^;xO4oxClzxSC9SPT zz0m$f>l>EcPCgcz38IU433w6&<26OiL9yeOKcH~GMTOj7RJzjP(J|5BYe{{wLpG9K zf(_{)3;Me|%@u5U?b*uV8(X6{L)+|2TOCBb=RV|?-qyN2Z;W_3zUXYWa5~O-GKK50 zE~6ye2NwfJ?I#m@1bF;ujB7}iC9u)Oqb4F$yIlop7VKzNE~A#y$kebK?~gYpx$!Sv zjf&A04}31}#tN1P9or}*Ot(iM9mGTV?7LQK)}PJ0T73vz(8ErLVXl@Jls)#~iIV5M2uu6Zn_8JK_kv0i)qW)tSyqXoIz>A>Q3Tw<`H;5T=J4RpXwj6iTrdnHbSG^v%Zi;Kt;ttLeS!w+e zKvJi^_b9CCe*MeyW~iirnIdKQSwmh`>Z#9{BR_n5ag#bRgi!VH+V!UjSg-E=H=WyC zB1mrav&B9-CTK1k+Zy@i3g2_5C*G zTYQUMPJYrJPW66E5a1=ybF~Kv0x|-qaIa(scEi}I2N?md^G#71I$4lnkfw+j zKO!hcO(-`|y^vUwsNwJP`Rp{55?M-HyR%z4%2?UEm_SHJ=Pe^mE&0YfMd4ej?g?e0 zAkrAMsD`dab1x5BsO+ZO;$DsRdAv;PSAL73kUpYT3Fe$wve4HF=frpR%Nk1%H*B6; zA6@XiifS^vWzx{LPQ9 zXZgHcT10A+Yiz?;zR)s68D7Ew+;?P`{qY!h@8B`LKrF0juht5}#U!RC$B^L_go2@? zcM*44{NGb=`QM|$sj=5bD6n!iquPit$EZqMd|bs62$XX*mIet z;F(DGOjP)vM7d{T^7u3H%`+$7vMMm$Oe_jGF*V0sHg@n;Sl|;OfoT;Xd;@E(egdYv zB+i~z&S{2AFD?%xFUUfPl-KI%%QS!PG@f$-CUnuHKEqRhz#H3rhWEw~0#85$0*|Bs zk|Y7LvffMXnUU(5@lW=e&pZqw1%jtBo0akj-0>f=B+ltTz6MKUUkdpxu|gT`PRqw% z)|D~j-SWkpruh4s%kn@nE@o;{c>c#po*^W>ewNGzkxczuLdoO*1p@9fgqUXtGXI9~ z8*AA!R?X*Pz+Lh4kd6w%QsdcOUi-oJ$>JPx!dUBIqy9U+ZeREt^1MJy2WZU^%Y%+vZLY8QS@K?hP;B5DM|C=u<|{86 zDT3wRF$l0}-PqfGXSA1pGSY+a<2A=D4?Ax6y1nAB%hLrh*`PrPzXhy53m7C-d!kKC z7A}eaBqDfZt=~Gi*lB5dAOFtrYo0eq=-?T`us=k?YY2qjBCjHD2mQBtxm$&Tu)k+^ z5E{X2>^Ts^q0ooFT-{H^I=UeyqH~n0xW8K%5VzcF_MDpz48EzAR;Zm11IU%zr$~NI zIu_2jCa*N+f6~rd5`-sXCuu340+AsgR6&KnOH@p~xYovXn0*CbfxzTDx;X>>^a}vz2YTySS1!vKaiJ;YS4dQ( zqE3yl#wE?0ESlc3)2?}3c8ZIESHfp9SFIPAh@ur?b%~+L%x( zvWS&B{R{FUVIG<~)4_p_c}^4XlZ;^3$Wi-ir-$`;T}>!8A0U`kstWVU*@5@TyL$$w zxpE4l324C^3aHcaY><3CU(}^>htfaPft7bfvRv|4w9}K zE`_`O_#@zt>!;?2NtfV9mDgV|h~H|Dzty@$L%zx>9l4R!tIwNthfXSHkWR4|oLIQ> z1M0+>1WfJ?@bq}Mof^?Cix>hPoMBzdC<4_3MSl9IYSI%RF)Mefnzd~UvMLBe)Q zxGl--QfJV+^hFg3T1jq@kYfwW!~~LEsLFh*ysoygF`H_BWgfmAy)BwLU2aR~O^Jc7 zp|8S$^`6XdL^VrGrYrpIACNsR!8h|O$=D3kz`zKr03DN<*lIWI5gTI}Nw+f_`{=gf zMdg;^`eG&?KmB;=83|Zu5rl}EuaWUR)s{U~w?fqn)VdX=iW$! zcfSC%Cb2(IXYQ$Y+e+w%4d~N|E2}8bKG|EIV+F{=V(MskAKif8UfdM!o%kcve>_5+ ztjov&1y(`afyGGBXLHA^wfX{iifS`P~+N0hBi{KJHG;1a9SsnFbbJ{4Hi-coxga*`lyZ0s|>H=1;-RFSlfo-=#~i z94XRdRy`6$<%^hv{l8+S1D;#U1v#@%BAVRC%HJOheVxvCG^DJKOJ?rKkA7Z#?!`&0 zT8VvxB4@ZFug3>VW93G}2G0GuDphWqa%`G}Ong6jKLpFslb9p%+?k={NyE|`3^cvx zrojzq8rR_Ey;+p9DAeBwxxf;Us z`L$Irm~iG~H<}AYJHw+Xe-g6dA(l)?pZAbWNCV5N>eTOR+Q6Udrpe6%NEc*R$zDrr z0e@_rh<#r8>4>;gQNZ=)WTg+DfRa*_Jvr^pPXh~nVkDOnazN8N7!~q{DlQTlM0 znNgC~I7r0fX@L8oR;iPtMPHInHl_LKc-4tF%eRf@jG7KPO_E+u`9vMPoOE80Om79) zlCD~jsXf=ZbZ#T*ebWU~l|=yQF$hV7d@yulg^u3{`T?ZM>g~X5evOrJcBfWAg1fic z_C^c3e$Cb;Rz+jU$+?}Tb06)iX~Cv`j|lHTcTpinViODGPKKa+KgR3 zZ`AXa+jYCE&bb5kO3;By{z$IC^@pi)Pynm@5m(_qGXJ3DxKW3NbubK$rqLmI|46Jp z$w#DjBplS1hM`FRn4HrYbh~SB-&L3xV&Zmum8h3z*u6b}00~EE z^&PGOL5TKOv3hxae~CA~qctEFT{rt?2yxeGe7k>a85=Ku?3;}?abq1xgB)1{|Bbw$n`QtPc@upbP}j@m>J5!(JOhMlNZ zqcp3G%WIcP{lIR&?w$FA-w20+zcr$W4}(Tq^{$Kj4taPW3(2X6!>2M}$_Uylw~kHgBx-H0xMfj#E<*hGLo2sZQN3a!Ho%zAQV6Y3)GD z0RNtx@{BEEl$YPJq@0+chlhdsSWG<#O{yVYb=H55L7zfYxgWRLTYa>Cir$hh}&Q{8?sTU)&MlTJOe6P(^H+-EQgsv1uu= za`+b@qquPRX>{zZ(vhC|2{)!X*E{!c8{{2{oHC?wYhL{aPs5Y|xhY5#i(vA@ra$_t zl?tVm!h*`nx+IrfJsws%psp=F)r~PTjio|>vgw*ZpeSMXh23^>Re6{uEN`4gYnmSk z8U91IZ{#nbylQHPDzHBSoI4=JDZnF<7<2&pWp%gvBT* zMy-}AlEWGt!?WWz=SF7MQATs+mvPN6%y!H9jn3=?q$TDFrY~LRD~$Syff*Akv(??~ zq*pDfpu9DzOp-gwJ{UA&%I=JarB+`oJ~j_OX?9^+Qj0r*OE|3cC;WCjJKu7k2wo%G z6P0NF0i%dh%C$ zd*lj(oUWD1W@w~*y#-b;46$#7>coHJ?(|Wv+1}AZI31c>%9nw>6E>@#YN-G%}ON zq9U;Z7fYJm@``^Q7Ezs+e2>i87VvKL-y{3AlWx8^zvsj+K)ffLo*t^f_hX!_J2+%s z@`kqCS-=uP$)n(DS~;eJe!xlH9AD9+irm6qRcR|g@#vE?54~>fpk5^xTT1uYN11cW zv1%5vV3E&)0~Yq%WpNe7yIng*juF7Iy3Zo4OL9s{<+w@&5MJ35}b-I_N{p^k>%fDahI?4sgFdG-e``j)vg-yl zRS&vLQ21Kjp;`i-rW9HF@$n5AQK(>%us*zIrMy?J^263iP&m`0$~d;uN5x<*^%#HW z2F4%Awe`iB`-#SCJBtX@LF(|QVFy9AWUbZXemRr_*iSUhyd&{cO5(=@WH~?BK__bY z3xz^;qTYi3=VtkzxjG`e*+JWcYf?f{rrnSy_d%k&HzATxsO}L4!_3J8d@J67mk{7(clDDdrYwwm&48Lp42i5)6D%2AT`fSmq zXeltFo`K!=gla8l?9amU9Ezlh1nZy*aoGpgb&9yWW4H z?dvn0o|{~**=YQb)1=k>W}>nNYo`Cm8Zz2a1~ctPdb~cS{Ms-@Mijp=ZMNlN_ssz(k?=retoQi28r^*Yq&o;^zjXQTf(4h5Ip}(_ZzFueSteyM5?*8)U+nAW6jv;Ne zmuHnXL;?5Zw>yLXRP5U`g_TNX2mO$EWSL5ZCeebCSgP-^3v(oR21g&jhf<51&T=Zm!(pQJt$QToaHa8N>yu5s;hJeI!nrjXYVqeB*?X< z0(0z{S*Bb*F$dfaRcuet$zUw3KZ;XH>vYoOcXuHFNaYEBHRQB-gzAv zOxqf~d0%t14ZEJN;DYu~M|9`(Y5L=qV;Xz14-&i=vm zjX@^mhSR>}XfZcfd_a8F+f#;lLfT4ps)Cqmo+iN4?O013eJAMXW9p1`1!ubR&%2mY z%`nZQ7ZeUgrOB^!WNh^O%B5{YEP4PVb&l`dt95Npdu(E?o$OhdInKn>jLu_faeFJe z=m&viNs+7nQ6k@XNQ?pVpE{jaDiT0&*gAsMR?2sWp6HeuaF!)2Xn^C#wwEQu3;CdxeUL&PK+9H4!EYrtwuTi#6YR93FvSDf@{$ z*Umc?s)0mNT-OJ12HUTnYglYTxz!}?fQVRS&{)F_k(%wp*P9(HHXq<+)tS>o@Hrn@ z*pM#o15T;ePZJpu9wqrWx?3E*pGY3$$o9a2-}Iks7Yvqp~?pCbB7|{DMp&5mqlh z$J6U2?&rVDD|T1Sa|bq+pgFbtib5e3QLh~TGwz?hRl{%MJ;uHW9UZ(C z{%VuIZR3-@LF?_1zjEO9VIB26!dprx@7%pZK<*^q7XYXt4Nd>t8QNWS%pJH@f&xU{ zkDhNuAqxNU)ZgbqwY)q3gSoBVJcunmp!-NB<%N|VI0>vjF1ikKc6ZOsTiZY26ZkOs z=K*&A7INzVwJ-Zr5NT=g+$$cCTqXEUXaBioKRjQJlackey}Si90KqD3IR89vqJ+Y^ zg;LA}v5NXVKZGOBNXPDHFnftG>9a{n@}ZU1PfnB zCQiir162}bw*T749&WZ0tTe@rE;vXzS31*tA?hqq6}5>4vFhN_y?ne{tH+o7qdsHVG7J^(mJeLzuPFPe4Z)C6 zp5Lv+eoug62%<2}QrE%$0-PJz>ZnwOFs`UvE*+ncCT9>;XU?*bn2bu7TPl3q8)RaU z&PeBxbt1`S&;hSNk>RTvqS5I=e?1C%R$@gVdiU7Rhvq-(pj?XBe9NgQqlDJ`M;+au zXAr@FCCg`(S;XgHadEp@`b&8-iQ{>!4C7cV4{~pHOqkWhKblfd#cyNZrnngNil-Q! zub{sxW?-S%<5?_%m`?cO$rkzeyblKWAO1jXu&grPMEo?gm!_L-rPDVZC1pXTny<47 z-4_k;E83mlM(#H){Au=hc>5b& zWrH*~GzM9@=fmp}BE%JrU%nCNQ=fvc(``W+NV;#iTu(4VF(5_<(Z;`)H>zsYy%}S9 ztMI+)T^nNb?}SGj+=_o`p8MO{mZ4iidHIT?$ojzO97kXd zy*LP*-frXgdtx?I%yn(2!HyZA$-2~-b+_}FnDjk!w)%KGiih64L-`y>C>*p|r+`xp zcBZ}+Y=E9)T%G5h&F9p}!!!qp-&7I#I(?Kh%UgA>XYf~L&qAG?69f!Zrzn^~wd;Nw z-0p$#G+}U*b(?S`Mhm|A=PL(Mt5+XFw$~(tvIEvox(i0wfcd?7_D*bs-BEb`j`4lT zYV5M^@*vZ>Xfr^3Oh49q=AZ6n;c3F`t`I)$%eEn>9v5R&p_(3K27B5N@!Sy&;@a3a z1&=`rd{F0tC#7_zjqDld6(?Vom{FkNrcx4luvT+Z-{4I`Ps2W9(q9#peMS%F>iO(uZw~vF^e&`n?fn@?E$% z;vhpi_>&`m63godr%+EXq<`~ts@myE<}qXNMwI)Z{qpw&^C3Glr>p8_M{+w{lFHhw z<#x`I$&w1C^WGCoh(^eLx15c4=U1pk#O}qJ>v?WL z>n1fUl7J|eG&iX6p#|6HiMQ0HKv=G>nwoQ8UdP)dImoKB?Ie`L!?+Ivv6 zDWWOjHN=BJ>q~mFXNRI(yw`&GLRXKt;gEzZ9jq$FTW%!MtMb2~ra-}xR+X=93(oju zA{+@-qpc>ND*|;inr_=NWCjz~ui7hIwfGtGa6!m6a9p9;|JX2iKtu<4?lBM>#&$gc z7j%89!sAEAe%q_UbO1VUw;iI6hn3b& z%GzgtUaNzS3ID!6x5x*(cP9L2iu2NZu$v3*9W>&aFBjt(cqOW8^;y*alFno-qG!&- zuF?3+?+(fIw^S)1p!o*NH7r)gl{LiP;p>U8$vsW3ff%=y0GSFsPj#N>J_)|V%sT~c zQ~U?{NWU+pcY3@8R#e_>Wv^?K*97wY1y8J3lq#a`qQuc6A(JZs$XqG`GPx?x(}DyNiRFYJ3r%4Boio^NB4OAs79Pt68NeoxIR zAxeh8UnOG%qGmu8j4V92O5XcI72Y+VcWQV;x}19;GdV_{Fgk{@G+ZW~gfyxMt6`cv zBTyYTu&$&{H6tD+lz2}4?7=eXdGHFmMyM&7QEU+mZQ={#Q*5@5LnmvP;}=FeoS*hP zQlmDk1P2CR1SVHIJ<`J1pKriGb||SO!xbS)`#Zw#pJFND&C6u5dUK#^*I_MIy*>1F ziZtte1Qn9a$Q}jyTzhVBu=eYL|8ape2_r;QjL7qPcH?T1op9%NEQ;@b6^x+niP*IC z{t9Gwum}$2BP)e?XpSBso{%Z1W9LIp3dPeE`|Byqro3UHC4UaqG&xVArM&uTHwr>~ zI&;ZstfW|}CC7`Xb;~nPS(PHA({?;>>_!;A zP9$YV@f+t~Pg3W6Q8QN6v7&~%CZ5YnqDYIth*_**AOYB98HcS?mpIs|+A9ZV`f!Si ztHM#w&>@U1!>?cxLb5#5@7aYd96E*V}JY&htWxv$QpNk#94h&yM~AB zEp4gt{n3U!9j0Swg{1?9>PE)AhNI-d9AuAP4LVu>jA|Xz;u}plMo*$r(FCrls4V~V zl&?{oxF2~Y2JD_^N;^>38E~Q(CX?jYZ&H? zs|8jpC@74~Pa4mW>(K}yfT?i0jw=NPNo9yQZAb@S(zl$eGo?SQhF^#qd%bkOFH-4CJ;vB@6}tAT6f!%iP`6x0_x#NcB5Z`7sin z-x-zHN5O2Fdp29<6by9%jiq}G3r|dz4r4l-+Ms}wr5c=^_k(dJwb9%6`WXAgYQ?=Nx}_D^|}SJm8a{k0zQ z0%Kkb*U^@@;x)3@sI+p(Sz3b?9d$kct5r=Z6uU%=w{fzL%^^L!w9Ycx4GLaBS2!Z! z8i@FGr)Mk-k=9-AJryu!>>LR zZ&m-3H4G1qpW08o4^0sR8MU6UvphEQm?KBtmcC0Woi2;Bf~bzw^%*K!nS}Z?2@Oul z=iraTwB_Cs^aZh=Wvyt&9PWQ~-ai%Yz?aNWoh6T}`bIA`*>K?yl}>5l25O_6M|vd_ zze7H=qKW|2O068jB%3&Zeojw3?PD+U(+~353W&ZRdx|^Rm}+{r#?XhQ&3Hx|9z^=o zd|%Nd*YngHX=^A$t=9tIBsXn3YSrzb_7Vt-Su zxA@#VgIm_nm|lc_y^}+DhtgIX(dQ+!pIRkDyHPrSeOc-$bhIUj!kdFJ2fp)}4jsX= zL07KO0k>r{SeLd`e2!D58Uy;wmFwZ*33dh`yu%eOb2(zUuTy<0u|;WPrU^4$(dVpA zlgW9fJtyh15Q|v+M9V26USB|8tR37J+G#9!P{V-cxU-}D9*FkV zul&~or2}I8t-XyJ?X-Ek5@gAL0gAQ3Q-DjskwpCF#Qoz~qSEcJ&KHqA3{mmi^!RHa zkvxA>IsPlvpkUi-t#xIP%EN0VS`kHVFyZRlUu_Z1{kO-f&2^REDUTOKZ1X25=sCs* zwbspy#|gBXqIT+X2Z?L-6Z=UHiDZ(&g2XXF!NDLP!@|M9!azcqpa9TVFxZqF8p#wA znwV5(ZpH6sU~$+v#kE3GQcKje2ezms&ENEMg*M^Ra9>EfUj8e*>Gv_>$IUcZX!vRU z%0@D!e$`MLfb>^q$n7h5E-j2-Y;}64YXXXO?20o`D>X;kst&n3!Y%y@(9P|;-Ib3($Ea>!rr>_K6L^ZX!8*@(l0dTWTl(8e z825mz4Dbj%di->wkQ9zpkJR!774mSzd6KV0jQ`By84kWck6hY>UGPZ*PS*;%YulRJ zm*xSD+{#yvP@Ej99hmHsqHMAKvOlv3st~ACT(JvC%)JwC)Dd7{C9Y|%4p%(((Q;e% zHnxLCX=yDsE3Sy;=7o`v)w_gn)h83uP)P=cpvp%8s0196FKlsYcpUkykO0bJ}1fj=};J z&KsxuwvnQTrWD`Cvb23p4e^p8K(|fp1t5XAH?wHvLm19mDNxR*VCB)>%Mx1VG~z8o zRkqn6>r~3t;h{GW=x)5<+<&p%zF6L33itLy;3l1$M>?a09Ls`U6oug+UQm<=rw*G3 zMX3#c*u;LamJj1IDbfbNIkSl~n&XDUUkgZ-xD8tHi{aw6kS^#I|!1!{7Ob!2M`@#-UjQ4 zTYml67;ZUykb2kkA>e#D^f<{G{*cb^trB8ZFxpO)wu6+B%o%~gA-z&qm_%%2?z}8~ z7Q1-XT=JQ11=><7@mDy2kun`ZWD6FI+p)CuL+OL~HHpddM~^Q75Pu5T9f9?ZkA&We z${m>qi(eBkC>(ME716Wf$%>^BaxNXxKzNvBmsh2TBU~^3ggdVPPaT=)MDZ z<|NCRZW2hlh<2gQ_;7nJNmQR*NU5NOzZui=@%T#Is1)Nw95L)~9@^rtHX$;7Ld`Up zWfcm_e6&gB$-ZXE$buCY`^2TWKtgqD>>;c0jfA;L`AeQZo}56%H@Y39yt`B zw>(Q!2{m5JSV6*TvHA6huU>s3=2}FePyjmIX~A$tM+AP7v=voL=-{|iqK#riKXf*< z0_+ainMjm=HK@7`(=qj9XDr`dEV%yf3CU1cXbupgi+%Idh_`9UxG79?|BL0S*l12@ zmKGBEPsl82%u&Q3`yoQ!id!o9D0V1{Zu717KQ*pU^3Y-U!mR^QkXhM_P}&ebEIC^m zo?T`YjMGhu_2L*>u#P_EsB8KcK+l`4=X=6$9C9szuWt&J7i?CQRyqI84BXuQ5#ejBVdhe(!%xMSzunS&MlukxuB|(D_!Y08S5J;d%L{ zdRzF4h_o9)IWe4000dokP9^hYO9;fr_nIC)*Dw5-J>@Fm}ML?XJJxme(;Z zd{ZlLRcFvz!UI#8K+PkO-raJ;tVHXW$YZzn7AHO_U!4Qp7Vh1{iz_GuNl^HA+ znPjacFbq&fKU5RDh0E}k#%UhjY97!-+-)Q|>e&apQ~g2mD%@Ha(<9O#t5cEI8f_6e zQKZP6H7~v14hj?-zD0l4FpZ>-sgR6NR05O0IqR34Yq{J-BhOnqj>%#f7GG%qixRDd z$MPzL7&bW=N&>?T-VGD?Zox@rp($@pp+%#wUnqoMK^;a($`o6{RVzL_w$4hR#Ckmr z6~Sua_xCZ9a~v%5G^sP1JsDDxy+~kgkNj4&Nkn8CyR{@|2rCeQVnh)pG=0DxY`7_{ zpgM5XD>$7(P1d0d7stW%wv-?oCW`{&*lhwxvAp)G{n2=>_lH$5p0dY@9nCg$z`|gG2B^aGXot zz!MFd&1f_qt5l(ZN~@M-YfHk;7ig_wm{olh#-JqYS@MUZ2XA?!<3KNl!N$}97v&a1 zd-)_0M?3-AngT--s2AbpIoo~r5(}Fc5q52{_B=Bjp{6kv;M5o1{gStKw#l&4xQj1* zC&X8~?ElBz)%g)CsBT~&yx#>8=tSw|f7>`eC&=MgSZi!^N_mEVE+c)*COIDub3;WiE z$R990TJMvxSG$814hxKLgab;6rH_dD@vuipG0JW8AYnYRlof!Z5S$4t-W87TVXn5; zSiP!|8C&YmbiQOW?`TKw3-6tf+@<-gvAUMyGL5#`f*VQRJ7uBX;f?xiU^Ks0-{g1~ z$&3)j!g!Eq&^!5dY`>k5X@Suu2M87_awaCBUxBU#PO*m!25zK#+4 zEyDII+#d7H2FfRc(r)~;W;q`=2J3_i6U|A|&F=CXp?+T_Ztu`<8GqRjGecr?gw6OS z<99c-EG(CU%(N3!^p*OCAKibWc!unI#n;~cbE;LAETc|UZ} zB=3WdnN1xH6bQFLIyx=^ax(&_t4k$o_$0fFpJ7Qo!}^UL^%*_kGkPuvdirPd>d)x^ zVe`%tU?(IRsI+tw>c>8~BsU-0Y~MQbj@UZ0pwg@RxJvv|)6A8Nwum&m+T*FzUq>)V zqn9yX4EP04wGiwQQ>k{$H>_28L%dxzcNc%Dee@{%^WTgNX^LRpi8R2a^ofh~foz~)a)yjs`e6`ohf(0L&0z}k8Jt4mV zB0-jSeoy14i{I5CuL~xFEWbP4(>1?d7RmWTwRw8LS%&PQ537Hk_!vGrSg> zJpk#-8?)^`yeC`@{Xt+WJFiOZ@A;NK$qiYBbO(EG>#&*l_rElSbbhT5cVfPFTgghkD7N;SEV;C>WirsGj&7qH4cbE z)}SS14ZzlVn$3sjd`YPGu?S}^ic;I=GtQ95>$NBmg}xiYYj~&G78?&@KHF$y3*Jib zZR)ZA_>HLLT}~;h^TQ~ug#+{m8h}`st}CsdKQHym72UsBtazQftA5suHPIERwYZIR zLDZduvyWcqd%I7r_z4~=*)YYt(*XIOxT|Qm8*o1f()uii?}Nu-CgZ4CTNOz)5wYO7 ze)g8_NEP<^&)MGqfOg#3jgICou<)zp>2m3`wSIO|nv$*JKLFktQQO+BRZ zz0Hf&ZPEkNq0dIDqzX$&d|0k0YWMQ0FU+k3pGt&oNmwlNpeArHEjP7>zo6e}HI`Bo zF>_mFs~%X!0g6X{uKNXVnufgpn)^K7tqxC8_)9W(gM_v@H2|-Rk%i|%U=^~YU!*1& z^Ods3j%iq;q~}smV24qi@M=rtQ;8t#K_Vws`nB;BdUt1)zjiPNz41VOH|7V(lOL-{ zIgRhsiVO3h7Y6Q1T&GSOR<~d__Af*zB%?!+R|v z=1^q-79V#byo7ca{RXk-ev9@{ zrkJJ9r*dck^W-+%9VNwfuwAcZnoL-jkCjOAP;{p>tawz12Ne>{$b&Adgod+|@;0jq zbQ#L-P7l0+1#=%=K7lGC{G5Z;fC9bW?(%W&cabYEAH^-QNsWp(2i}a%BHk24GD3u3 z)3&w5B}J;vzkbzK>6Rd#$4Add+EeGd|IqU^`Slg#JE3?H$$Na^m!?FkFwL_P1w?m@ z<%ZIpTFNqh?hG)MKUIBEx72qxzILK(j!xf^E4*^B3>BTOoV$~QANGVIDr7s_!;Cfv2HAjr`DJs7tLqj8 z`tI!FP_V0r+|uL8Gj`{p)O@6*A4^beA$`$3yapPdsVkr@Io|Dl zc~?AJl}pvrrOAFQ`3rCt`ru7^xN28zG}789oqsl>MUzP11{*?+yWv|+ZoaT-{ZjoZ zK0xwBwiuK2k)oJ3U;Lg62RZL{Un0}5iK1o#h8ZTTHi~O5rNo4sHP_4W7odfB?16TB zwg1x#Sb5cGX{@Y|X(6<*`%&1YbWk?L7X?xe-^JE5DdAB`Kl-ghv}ZZ*BBzoA_z(`r z1m8(o`KF+#8i|5b!?bEX;4oHK!FH0LyQe5*ArB@Rns1|cjyFMLVju`t8;homWG-=7?QwDv z=Q#K6T+|c4BmQ}>+51rHUjXJjS8T1#+a>IKcCmK<$`l#7E2WB)hWOtIE7hKg2(oA?h}5q5w!%FI%##CM}JVx*27j-XuY@g~Af5RM|{&113JqknGtN z&DiF}k0(Ml%LUQxGUf#b zB_YC+59#_(HJrDPv7204PW#3egN{+ON{SWVJK3Hrp9ZSE@U$AM!z8QGkRbzn(R6i) zHgb^;G;m&{x>QHDIzGxE5YrR^?mu}TZ}G&dd9>}HnQAeOsF{H(j5~|=)p)F)~&*y!GT1`~X^iuQnQVX-y#W;%sO@ZWDjO4D`q8Gq` zEp>8rMwnwBbr-OGk!Yz7{G;p>_NwfCI)XirR0rmY5n*2&<1-%26`c4(zxw?^@sD@X z0kYN9$2=P38r8vf@!AM~5~6gHAx%^0tG)>LLodQ=@$tqmXIHrKN!&umaF48IPe?Qa zyrV6=;=|rZii`W+U?u9{9qRs_OIYM}IF4nkxE1`P&C+L-y#8r7Vp>Fa%uvf?uVZeK zXeg03_hcl~#uDFfuQL49~4yidZD-#s8^qf<6MHnBoDM|2oAZf4cg8&1h)DN5{e;{iT|&$>yB$8 zdHW#*k^rG3C}4tymH;ORg22&CARuZ8V)RZa3I+rOk&b{Rp_hOl9EM&FqkY2pPkR{v-`|?Yh3^ zyK5V#Nqka{8EltFRHu~?eG7~@8kOqrRDiO}N^$m#2)A$j+YjI)Wy1gNI7)~+!&dXo zhaEB;Z#9VZk#j%*?M|MVJI|*923oqtT>T6jk2)iNvX)GPHNP>Y(uK7fJoeimmy*W$ z?K)33bb9-l1q*|`Jge%K=xOtJ*0x(8v1Q&&9({RHPh!A5C1KzZ75C-bM`yn%ABn;A z`6?6OheTknmerO2qzXA7rd22(DbdgE2_Ko}71cPG&ibP5_^{Yr)*Ux=-J0a#Hk>%e z^Amx(8)q{|d@*>RQopjOQZj6rP~r(KGfT_ua>9zw-5p0}9DoG_R)6kXKJE*?PYzDb zwt4{;J*7PvtpoHiRRIuIznVWH-qILQn~wR4z9WoNP3tm(yM*sweE{QVZzkD{U%lz5 z#zRU2p&S||Tlw!Ft`9+d;`cRYo;LPiR8tuZ+LR7=NB-BD2ax=BU(!XLow6L9qU}9J zK-4Q%Vjc$r3QIo6d;RRYAVm~x(NHK!7wT0NhI#up{cMQAd&^fui1mbVo+p~s+A`9D z0d*ZOJ0ua>qJaqm8-?3lc7fpi)G_1G7NzyiFXyAf@@!0I54dS`j5xl2=7Wa5Ac^4N z%)z{m+6K1Lis6UUx`p7jK27*bw|Wo0b3#75RB@!7K7Y^|ZtAoY6o;NX*MWEZ^ev#= zNL9Y1F;Pud{4ZJrQw{sjU=pd567#Fl1FA^9!FY3MgE#A?7Jj--6`-%96;Prp=AptW zt*?;?T%W%2GF5&U#%}?kW^SB5>`fmbX9{}wjDiTh7ztL)Z#f^~S1m#0+WxP1uV)3a z6X6geK^uJk?R)C%%@bnl$EO}hFH2sx;F4cChRwBlb&v`FfORq)&&%oh};Pl3V`nPG%`X%EouVn<^vQ@f?QRdp2SOG5fec{6mh64Y)*-rj@^$&i^ zmL4n6K5jx++8{a7Zvc8TrckMIW&5)k~_THtJp34Q>PcJWVF-;@>rO zVQIxm-F#mRB)@P7@OfdmLc`umc5ACxoJ95Nl}I`Dw|k$oj+$|`)MUCSzL8ynoVTz9 z5FW2k+nqq>Tlwxs_yG-$;FT{b+El z9}zbI86DIycBg zZbJsUE$QBDuG32qE?s>Id=bZxAJT+Uz82A-bitjKYa#Y97#XrhD+Iof{qK=?5|^pFKAKpeW#?65sDpELx~j^q91-h zUC(f9$2L3y^co4RRutS@mN>TH`ZnsQja{R!V$4)y#(?uv&2uS$`wkg%Uf7N-CEtfZ z$7f}EsMg%putgH!a0LVGGeTXt{x!1JzA(u#^NPuajl8UVUkp(ScC$tbeVyAMoKbra z2ay=1`-sau^3O)}R!Yqa3X@>9GNXP%nx#Vq&_3J4w;zVpPV_QZQk@Y62xjgf3f{Ws!P0ED;jI4za&v-=_J1qacq*u z^{?l1pU6J_z5*-+3iwVp+5fbMXmPJbGE0x$Du!1T@76v0wk$}uH_p_sk=0QM$+H$h;m1WQ>$Bz1H?i8`jQ7JJoSnf)Yq^_yWN6Iu;ZI zoxCU>Azl|1bnb!jTsfm7)i4b%o7X-gk+2r$f^Ui^W8!s zOOAw@XDJ%-H`loD_&K@_3T58Co0rENwYH?Dx`(Hb0!onl1N5bj$^|TE$9NQOMma3O zY=0@ddkcOGkqOCejR>dtjMo?}DZO0k1*8=zO?7Hb6W?sPXBl&K_e^}A>7?Zeih4<( z5LFm^GpSok$Ck}G!l1P_>UFYw7t4AoV&p5@V&(sX_sA#j+&6l=DJbFR24!0t5UxFsh)L zj!^x!J#P#~RnYUMGa;kxhcK{k1Lj$TJ6Opbu2#%Qy z9VV7g*FCLO!#=(ln~7j^o~gooV()gcW_SQhL9AG+lTIXtvhuPvTk!12H1#KtFK*tp`pyp|wu1thu19}ZJF?XU+%^KrGky73A ztLrU@Fs{iO@2_!^^1qzZem&^b-R13mr5-47##bEAzjWfoSO9$%xgOhc+RpgRU%8O> zAeqv6Uelx0{kMM;WUXF|edcfRQ_d-LBk-^QU!Q5NWx{+Y;9+TbRZut{0M1UXkOJ8d zCJXwuqMwncm1`^*`D=#U|FhqUF5*tZp1*c%cNceKeyZrlsb}P~1$Ac&`uetyRITlW zf|j;hWJ=G+&IbVh-6~?_MW4HbaHfhn%9Wc|Fx@Xl3q<0TUr!1YZvmeg;{HFGqTOO2 zd^9zm1m!Ax_$Hqj&wYVxb6rc#{i2G@y>_=f$A-4wEllOH*6n?XHmQ@%q^*(k(;09- ze5f>E;Q@eFw%#2Q(PQ`Ef0g`qW=byao0}LdJ{NOJF*jO4+*0wYcz^&msdvS6DXfDv z?H^=wQ9C$SI|#`lb&G$>A?f~CM5yN|H6})tkmJI1IR8Y5ol;+umN_UMQa3!^N2%H9 z|B9lzbA1#u1h7Wi32vE8Lv6V-Tzczei~(|$fWJLyUWpy16H-xc3bx!y z|NHRG%@I1iIa>{=kd!+=!jP8zX0y0q&YMMiH z0E~BmIHfmnheXN7m*Gdz^J#|-Gx2>k|J6>KkO$h$U9qSSW~w;pzz~P zm&`&<{;wwiQMVjI0q7ugdYoGBFi}T1&N}nQmUbX3NQ;kR-hZ+krA1&VAXGiE7uKm( zDBjR7?Q?DD2LuWLx*n0@i@q_6k31y&TfH-Ed2u~tuDJC35#%-&ss>_ChH`=lO($7p zTRH@#IK`^w``IaUk-(ea$C;DMSFe!k4G8{!iExc-Yij#nA{WMcknev-$XeY1oaem^21@W5Q2^hg;$9I}?1}V-QqCwKOl@Aq48$EZJzT!yY!u|$lt(}DWXG7T z;rDu1dh3xj4&x%;?#)m|UT@pwjsT4iTR{7mccq$j#=sgp^&JjKku;AtLyNtxQ7;{{ zL8h!8+H*c1dW&9@p6mB?D}xPp|Fna2@3U-o0Gg%DlT;PLVPD5Y0thWwzpH1`h>v^v z&$|MN#l5Q9aG zphtj$tz+p-u7D4ua>-qxIDY^!dqLq`su#K*;9Ur(NBuzF+V%KXBucJISzM=jaC-2r zg|&M!vU42cvEMTv*BD(ua8Vcpe%Ymr@i9 z!Yg^o)wW+jlW0V_?gS*8Je^o=df+7S7b;`NW1c`W2hBjB9mk63@~42kL-^wmcxuCv z`Iyi2(?Pxx;Uys^^(YW=faCzhiF)#BLK^u0;Q|cp@<}u}mmPHb7_%J32&wd?qb3M-$KYzUr_MD);~ zTAAb^Y9tU>@NyH+VfX!IPn?wSI!H0&=f4o@*&v42Qn3wXoJDznWZfI-zz~GrP7dDo*R?DEH9b{8zU{-e^fc_8h{x+ zaQuK{p<@B@S4s0AH>#N|G^shny~RZ$^C2l`<|10l%s}SY_pdmyTOrVUIwyhSzh-|9 zh_QraWbNI!{lTViY(gRE6Ht`vi;!eOiHMIMQboagkFPthky++VQ=(>Wb!j%$HyYVh zw~nUDDOLgypmk@eoGg`|J6SIc)Jrr;&(RXFQ_#HFs%8X$?Xe#GSS~zYjf-U=p+n|} zRe7?F?ORH2ZKSIkKs#WM9TFo=k^^G3m{;1-9|ToU69il=`&7q$!1dgKY}uX}{gKP{ z(P)I^S{s^td%WVJm!lp5aKuwuLs8AR9%y4n5YrhA(@VE?cp&Pye?@Q!^9{d$zv06_ zHMDaT?g<+h`Q6>2Kxr&U>2WC+-%jkZS{&dWfE&uMt%1wEmHJ>UP`tZFwUK9Xlq{4~ zeFcEQ>X>lte#P@}z~{gy?-0iU=qMSddOL-m6O}cn3mXVFaBc8ifX~IY2@Xr((Ik(e z=?G*LkX$*9HoYcz$rb~Nlra6U?d_TD@RTkAF;JmI;*B^mz$@io1eVKsVwXVLFSpTYBQ-bR%8b}$R8=G@fs3rEe5`GPyD-z8T zK6u)h!GFG3e*h!-;j&H&3p+>=bT1JKP8YQL+EiMKf>=~LjjARVAvbjVG`yLo(Zh6G$@d5oeC5J<9HLab|*!(T3 zL?|WH;LM6NA)m&buga#ULoV_6e!AQNthhvmO zl&opQ5MaMjcwGFV@ArzAlcv^giRlR= zDO6g3s3DXA;G48T90Bsn0_{p=g(Wc~E2^Irq(Fktpt;s!uVjvnM-Xy4TtLF{p(hjj z{n)eQWLk`5<=)}_% zY?AjifNIxZ3*gF`n5&C99#d;q-oC0yXAxdk7XIv+@QbjN4Ate`5XVd1bMW!G@jK7# zi)K5sv(t-rqq&gFY4G5IA||Y(D{tP8eEJC;rS41u1<6qbW;NnAPGkc@Z5nP8mA@=3 zpJxwG6jYVJ`Q86UbkTwccRlWFJ9KBZs`STuf0u9hi@h7?JF{LD`RrzAOl&v>8T-6* zi|(Ai_%}fKnndi3`-S<2qMsl4*|KYutEz6D_xIb(U*t+2DX6kw|L(^e`>S%FOl4u{O8g>+fw$<->!}SEfCtq+m|~J K%-W5=2mc2G=G)-_ literal 0 HcmV?d00001 diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md new file mode 100644 index 00000000000..291a4de2236 --- /dev/null +++ b/assets/erc-7208/erc-7208-compat.md @@ -0,0 +1,31 @@ + +# On-Chain Data Container ERC +## Backwards Compatibility Analysis + +**EIP-2309 (Consecutive batch minting)**: The ODC standard doesn't interfere with the batch minting process prescribed by EIP-2309. Instead, it offers an additional layer of customization for NFTs without affecting their creation process. + +**EIP-2615 (Swap Orders)**: This standard does not disrupt the atomic swap functionality introduced by EIP-2615. ODCs may be involved in swap orders, with their properties intact. + +**EIP-2981 (Royalties)**: The EIP-2981 standard for royalties is preserved under this proposal. ODCs can have royalties specified as one of their properties, providing added flexibility. + +**EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. For use-cases where NFTs are involved, the current proposal for an ODC can be leveraged to separate the vault logic away from the storage. + +**ERC-4885 (Fractional Ownership)**: ODCs can represent fractional ownership, offering compatibility with ERC-4885. Additional properties can define the conditions of fractional ownership. + +**ERC-4886 (Provably Rare Tokens)**: The new standard doesn't disrupt the functionality of provably rare tokens, but offers an additional layer of customization by allowing the setting of specific properties. + +**ERC-4907 (Shared Ownership)**: ODCs can represent shared ownership and are therefore compatible with ERC-4907. The proposed properties can provide additional controls for such tokens. + +**EIP-5050 (Interactive NFTs)**: The new standard compliments EIP-5050 by adding Properties, allowing for even richer interactions with NFTs. + +**EIP-5095 (Principal Tokens)**: ODCs would be fully compatible with EIP-5095, and additional properties could be implemented to further define the parameters of a loan. + +**EIP-5185 (Metadata Upgradeability)**: ODCs support metadata upgradeability. Their properties could serve as mutable metadata, making them compatible with EIP-5185. + +**EIP-5409 (ERC-1155 extension)**: The proposed ODC standard is compatible with the ERC-1155 extension proposed by EIP-5409 and can further enhance the utility of ERC-1155 tokens by allowing storage and modification of properties. + +**EIP-5505 (Asset-Backed NFTs)**: The proposed standard doesn't conflict with asset-backed NFTs and may provide additional controls or definitions for such tokens. + +**EIP-5560 (Redeemable NFTs)**: ODCs can be redeemable and therefore compatible with EIP-5560. The properties associated with ODCs can provide additional control mechanisms for redeemable tokens. + +**EIP-5633 (Composable Soulbound NFTs)**: ODCs can be composable and soulbound, thus compatible with EIP-5633. Furthermore, properties can provide further configuration for these types of tokens. diff --git a/assets/erc-7208/erc-7208-encoding.md b/assets/erc-7208/erc-7208-encoding.md new file mode 100644 index 00000000000..98b61aacc09 --- /dev/null +++ b/assets/erc-7208/erc-7208-encoding.md @@ -0,0 +1,6 @@ + +# On-Chain Data Container ERC +## Examples of Property Encoding and other applications + + + From 0beb6dec9ffdc939f7b01e6b673bacd4e2c842ef Mon Sep 17 00:00:00 2001 From: galimba Date: Tue, 5 Dec 2023 10:52:51 +0100 Subject: [PATCH 02/34] ERC-7208: adding diagrams --- ERCS/erc-7208.md | 2 ++ assets/erc-7208/erc-7208-compat.md | 7 +++++++ assets/erc-7208/erc-7208-encoding.md | 6 ------ ...dPMs.jpg => erc-7208-example-use-cases.jpg} | Bin assets/erc-7208/erc-7208-overview.jpg | Bin 0 -> 110725 bytes 5 files changed, 9 insertions(+), 6 deletions(-) delete mode 100644 assets/erc-7208/erc-7208-encoding.md rename assets/erc-7208/{CoreAndPMs.jpg => erc-7208-example-use-cases.jpg} (100%) create mode 100644 assets/erc-7208/erc-7208-overview.jpg diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 78915211ea7..828e73e469d 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -21,6 +21,7 @@ requires: 721 - 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. +![ODC Overview](../assets/erc-7208/erc-7208-overview.jpg) ## 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. @@ -428,6 +429,7 @@ The Metadata library includes functions to generate metadata, add extra **Proper **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. +![ODC use cases examples](../assets/erc-7208/erc-7208-example-use-cases.jpg) ### Wrapping of Assets (Example Property Manager) diff --git a/assets/erc-7208/erc-7208-compat.md b/assets/erc-7208/erc-7208-compat.md index 291a4de2236..eee7cbe5b1f 100644 --- a/assets/erc-7208/erc-7208-compat.md +++ b/assets/erc-7208/erc-7208-compat.md @@ -8,6 +8,8 @@ **EIP-2981 (Royalties)**: The EIP-2981 standard for royalties is preserved under this proposal. ODCs can have royalties specified as one of their properties, providing added flexibility. +**ERC-3643 (Permissioned Tokens)**: ERC-3643 proposal defines *Security Token interface* based on ERC-20 token standard with additional requirement for sender and receiver of the token to be approved by the token issuer. This interface relies on the *OnchainID* system to provide Identity information, process KYC and other credentials, providing that data on-chain for token holders for minting, burning, and recovery of assets. Compatibility with this interface can be implemented as an ODC **Property Manager**, with the added benefit of a more versatile on-chain identity management derived from alternative **Property Managers**. + **EIP-4626 (Tokenized Vaults)**: Tokenized Vaults inherit from ERC-20 and ERC-2612 for approvals via EIP-712 secp256k1 signatures. For use-cases where NFTs are involved, the current proposal for an ODC can be leveraged to separate the vault logic away from the storage. **ERC-4885 (Fractional Ownership)**: ODCs can represent fractional ownership, offering compatibility with ERC-4885. Additional properties can define the conditions of fractional ownership. @@ -29,3 +31,8 @@ **EIP-5560 (Redeemable NFTs)**: ODCs can be redeemable and therefore compatible with EIP-5560. The properties associated with ODCs can provide additional control mechanisms for redeemable tokens. **EIP-5633 (Composable Soulbound NFTs)**: ODCs can be composable and soulbound, thus compatible with EIP-5633. Furthermore, properties can provide further configuration for these types of tokens. + +**ERC-6960 (Dual Layer Token Standard)**: + +**ERC-7540 (Asynchronous ERC-4626 Tokenized Vaults)**: + diff --git a/assets/erc-7208/erc-7208-encoding.md b/assets/erc-7208/erc-7208-encoding.md deleted file mode 100644 index 98b61aacc09..00000000000 --- a/assets/erc-7208/erc-7208-encoding.md +++ /dev/null @@ -1,6 +0,0 @@ - -# On-Chain Data Container ERC -## Examples of Property Encoding and other applications - - - diff --git a/assets/erc-7208/CoreAndPMs.jpg b/assets/erc-7208/erc-7208-example-use-cases.jpg similarity index 100% rename from assets/erc-7208/CoreAndPMs.jpg rename to assets/erc-7208/erc-7208-example-use-cases.jpg diff --git a/assets/erc-7208/erc-7208-overview.jpg b/assets/erc-7208/erc-7208-overview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0dc3e921ead2182298e60895b1735d2c32964195 GIT binary patch literal 110725 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