freeverseio / laos

GNU General Public License v3.0
16 stars 6 forks source link

Definition of DAME #236

Open tonimateos opened 8 months ago

tonimateos commented 8 months ago

DAME = Dec. Asset Metadata Extender a.k.a. Decentralized Asset Identity

As a DApp developer that leverages DAME, I want the interface that LAOS will expose to extend asset metadata to be defined, so I can start building my DApp already.

SPEC'S ISSUE LINK

ACCEPTANCE:

ccubu commented 8 months ago

I leave here the first approach for the solidity interface. I decided to make the UL an struct because it was the only way I saw of conveying the UL string format into an easy to use interface for the params of the functions.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {
    /// @notice Enum to distinguish between different blockchain types
    /// @dev EVM for Ethereum Virtual Machine compatible blockchains, Polkadot for Polkadot/Kusama networks
    enum BlockchainType { EVM, Polkadot }

    /// @notice Struct representing a Universal Location (UL) for assets.
    /// @dev This struct is designed to uniquely identify assets across EVM and Polkadot/Kusama blockchains.
    /// @param blockchainType The type of blockchain (EVM or Polkadot).
    /// @param networkId Identifier for the broader blockchain network.
    /// @param chainId Specific to EVM blockchains for distinguishing between networks.
    /// @param paraId Identifier for a specific parachain in Polkadot/Kusama networks.
    /// @param genesisBlockHash First 32 bytes of the genesis block, used for Polkadot Relaychains other thank Polkadot/Kusama.
    /// @param evmPalletId For assets within EVM-compatible pallets on Polkadot/Kusama.
    /// @param contractAddress The smart contract address where the asset resides.
    /// @param tokenId Unique identifier of the asset within its contract.
    struct UniversalLocation {
        BlockchainType blockchainType;
        uint256 networkId;
        uint256 chainId; // Relevant only if blockchainType is EVM
        uint256 paraId;  // Relevant only if blockchainType is Polkadot
        bytes32 genesisBlockHash; // Relevant only if blockchainType is Polkadot and NetworkId is 0
        uint256 evmPalletId; // Relevant only if blockchainType is Polkadot
        address contractAddress;
        uint256 tokenId;
    }

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, UniversalLocation indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, UniversalLocation indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(UniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(UniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @return The number of extensions
    function balanceOfUL(UniversalLocation calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(UniversalLocation calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(UniversalLocation calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(UniversalLocation calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(UniversalLocation calldata uloc, address claimer) external view returns (bool);
}
tonimateos commented 8 months ago

Maybe consider 2 types of structs: UniversalLocationEVM, UniversalLocationPolkadot?

In the future we may support UniversalLocationCollection (to refer to a collection as opposed to an asset inside a collection), or others?

Basically, this would be using polymorphism: same method names, different input types.

ccubu commented 8 months ago

I considered it but if we split the structs, that would mean that we will have to duplicate the functions of the interface. Which at least for me was enough reason to stick to just one struct with a flag.

But I'm open to discussion.

ccubu commented 8 months ago

Another question I have is shall the tokenURI be a string or is it worth to have the tokenURI be a universal location, to force users to use LAOS to store their metadata?

tonimateos commented 8 months ago

Another question I have is shall the tokenURI be a string or is it worth to have the tokenURI be a universal location, to force users to use LAOS to store their metadata?

I suggest to use exactly the same we're using for tokenURI when minting & evolving. So, string

ccubu commented 8 months ago

As discussed before there is a lot of value on method overloading to support other types of universal locations so that's how the new interface could look:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {

    /// @notice Struct for Universal Location in EVM-based blockchains
    /// @param networkId Identifier for the EVM-based blockchain network
    /// @param chainId Chain ID for the specific EVM blockchain
    /// @param contractAddress Address of the smart contract
    /// @param tokenId Token ID within the contract
    struct EvmUniversalLocation {
        uint256 networkId;
        uint256 chainId;
        address contractAddress;
        uint256 tokenId;
    }

    /// @notice Struct for Universal Location in Polkadot-based blockchains
    /// @param networkId Identifier for the Polkadot-based blockchain network
    /// @param paraId Identifier for the specific parachain in the Polkadot network
    /// @param genesisBlockHash Hash of the genesis block, identifying the blockchain
    /// @param contractAddress Address of the smart contract
    /// @param tokenId Token ID within the contract
    struct PolkadotUniversalLocation {
        uint256 networkId;
        uint256 paraId;
        bytes32 genesisBlockHash;
        address contractAddress;
        uint256 tokenId;
    }

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, PolkadotUniversalLocation indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, PolkadotUniversalLocation indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(PolkadotUniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(PolkadotUniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @return The number of extensions
    function balanceOfUL(PolkadotUniversalLocation calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(PolkadotUniversalLocation calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(PolkadotUniversalLocation calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(PolkadotUniversalLocation calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(PolkadotUniversalLocation calldata uloc, address claimer) external view returns (bool);

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, EvmUniversalLocation indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, EvmUniversalLocation indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(EvmUniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(EvmUniversalLocation calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @return The number of extensions
    function balanceOfUL(EvmUniversalLocation calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(EvmUniversalLocation calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(EvmUniversalLocation calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(EvmUniversalLocation calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(EvmUniversalLocation calldata uloc, address claimer) external view returns (bool);

}
ccubu commented 8 months ago

The approach I would follow to implement the interface is heavily leaned towards being very fast on the searching but I had to made concessions on how I will storage the items, and right now I don't know what is most costly if using storage or cpu, I assumed that it's worse to have to iterate through arrays than just add a little more storage so that's the rationale behind my proposal.

I would store three items:

Reading the implementation details for each function will be enough to argument why I decided to use this storage items.

  1. extendTokenURI / updateExtendedTokenURI

    • Write to PolkadotULExtensions with (UL, claimer) key.
    • Add/update entry in ClaimerExtensions for the claimer.
    • If new extension, add claimer to ULToClaimers for UL.
  2. balanceOfUL

    • Directly retrieve the list of claimers from ULToClaimers for UL and return its length.
  3. claimerOfULByIndex

    • Directly access ULToClaimers for UL and return the claimer at the specified index.
  4. extensionOfULByIndex

    • Retrieve the list of claimers from ULToClaimers for UL.
    • Use the claimer at the specified index to get the tokenURI from PolkadotULExtensions.
  5. extensionOfULByClaimer

    • Directly retrieve the tokenURI from PolkadotULExtensions using (UL, claimer).
  6. hasExtensionByClaimer

    • Check for existence of (UL, claimer) key in PolkadotULExtensions.

After some reviewing with team the summary of this proposal is that we are making the writes more expensive, but the reads more cheap. Which could be the case where we are trying to solve a problem that we do not have yet. We still don't have enough facts to ensure what is better, so we'll continue looking to other proposals as per example making a more simple data structure but more computational expensive reads, and we will compare the solutions.

tonimateos commented 8 months ago

On the naming, may I suggest to unify the usage of UL everywhere? Also, to clarify the intention of the 2 structs, I would combine both goals (clarification & unification of UL) into:

struct PolkadotUniversalLocation --> struct ParachainNFTUL
struct EvmUniversalLocation --> struct SelfFinalityEvmNFTUL

I've also made it explicit that they refer to NFTs...

ccubu commented 8 months ago

Updated with new names interface

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {

    /// @notice Struct for Universal Location in EVM-based blockchains
    /// @param networkId Identifier for the EVM-based blockchain network
    /// @param chainId Chain ID for the specific EVM blockchain
    /// @param contractAddress Address of the smart contract
    /// @param tokenId Token ID within the contract
    struct SelfFinalityEvmNFTUL {
        uint256 networkId;
        uint256 chainId;
        address contractAddress;
        uint256 tokenId;
    }

    /// @notice Struct for Universal Location in Polkadot-based blockchains
    /// @param networkId Identifier for the Polkadot-based blockchain network
    /// @param paraId Identifier for the specific parachain in the Polkadot network
    /// @param genesisBlockHash Hash of the genesis block, identifying the blockchain
    /// @param contractAddress Address of the smart contract
    /// @param tokenId Token ID within the contract
    struct ParachainNFTUL {
        uint256 networkId;
        uint256 paraId;
        bytes32 genesisBlockHash;
        address contractAddress;
        uint256 tokenId;
    }

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, ParachainNFTUL indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, ParachainNFTUL indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(ParachainNFTUL calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(ParachainNFTUL calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @return The number of extensions
    function balanceOfUL(ParachainNFTUL calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(ParachainNFTUL calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(ParachainNFTUL calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(ParachainNFTUL calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(ParachainNFTUL calldata uloc, address claimer) external view returns (bool);

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, SelfFinalityEvmNFTUL indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token.
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, SelfFinalityEvmNFTUL indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(SelfFinalityEvmNFTUL calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location struct identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(SelfFinalityEvmNFTUL calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @return The number of extensions
    function balanceOfUL(SelfFinalityEvmNFTUL calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(SelfFinalityEvmNFTUL calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location struct identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(SelfFinalityEvmNFTUL calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(SelfFinalityEvmNFTUL calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location struct identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(SelfFinalityEvmNFTUL calldata uloc, address claimer) external view returns (bool);

}
ccubu commented 8 months ago

Droping a comment here from discord that summarizes the two approaches that we have right now

Approach A

3 StorageMaps
 - Extensions Map(PolkadotULExtensions): A StorageMap with a composite hashed key (UL, claimer) mapping directly to the tokenURI
 - Claimer to Extensions Map(ClaimerExtensions): A StorageMap, mapping each claimer to a list of tuples (UL, tokenURI)
 - UL to Claimers Map(ULToClaimers): A StorageMAp, mapping each UL to the list of claimers who made extensions

 Pros: All queries can be retrieved by direct lookup on the maps, as per substrate docs iterating over storage maps seems something to avoid

 Cons: By making queries more fast, writes are more expensive because we have more storage

 Approach B

 1 StorageDoubleMap & 1 StorageMap but we add pagination to interface
 - ULClaimerExtensions: A StorageDoubleMap where the key is (UL, claimer) mapping directly to the tokenURI
 - ULToClaims: A StorageValue where the key is UL mapping directly to how many claims that UL has

 Pros: Writes are cheaper because they store less things

 Cons: If we ask for the last 10 items on the list, do we need to iterate/load the items before that? 

Aslo this link is useful from substrate docs storage

tonimateos commented 8 months ago

As a DApp dev, I would also like to add "pagination-like" version of the queries (note the 's' in claimerS and extensionS):

claimersOfULByIndex(SelfFinalityEvmNFTUL calldata uloc, uint256 index, uint256 count) external view returns (address[] memory)

extensionsOfULByIndex(SelfFinalityEvmNFTUL calldata uloc, uint256 index, uint256 count) external view returns (string[] memory);
ccubu commented 8 months ago

Approach A

Data Structure:

Function Implementations:

magecnion commented 8 months ago

Approach B

Data Structure:

Function Implementations:

(*) Index concept is removed. Users can get claims list:

ccubu commented 8 months ago

Interface with UL as string:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, string uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, string uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @return The number of extensions
    function balanceOfUL(string calldata uloc) external view returns (uint256);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(string calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(string calldata uloc, address claimer) external view returns (bool);
}

Carla and I have been talking about the DAME approaches, we don't see the need of adding the indexes because we can access the same information through other means (listening to events, rpc with pagination). That's why we think that the approach B, would be the better one for our use case.

Our concerns are that we might be solving a problem that we still don't have and on top of that we will be polluting the storage with unwanted data.

We have decided to go with two functions to extend and update claims, as we think is an easier approach to understand, as it draws a parallelism with the minting precompiles.

On a side note on the details of the implementation for the functions, it looks like the StorageMap that was used to answer to the balanceOfUL function might not be needed since there is a function that returns all the keys k2 for a given k1, and it offers a count method more info. But in any case it's just an implementation detail, that does not alter our decision.

tonimateos commented 8 months ago

So there's no way to query what extensions are available for an asset, only how many... :-(

ccubu commented 8 months ago

So there's no way to query what extensions are available for an asset, only how many... :-(

But that's not true as stated on approach B, there is a way to get all the extensions for a given UL leveraging get_keys_paged and get_storage rpc calls to directly retrieve the StorageMap values.

What it is indeed true is that there is no way of retrieving the claims one by one.

Which again from a DappDev point of view I'd rather check how many extensions a given UL has, then do the approppriate batch calls to retrieve all the extensions, than go one by one.

Nonetheless, I understand that having them on the interface adds some explicit explanation, but if the calls are well documented I'd prefer the batch way through the RPC calls.

magecnion commented 8 months ago

This is script gets all the claims for a given UL:

const { ApiPromise, WsProvider } = require('@polkadot/api');

async function main() {
    const wsProvider = new WsProvider('ws://127.0.0.1:9944'); // Replace with your node's WebSocket address
    const api = await ApiPromise.create({ provider: wsProvider });

    // Rest of the code goes here
}

main().catch(console.error);

async function main() {
    // ...initialization code from above

    const startingKey = getStartingKey(UL); 
    const count = 10; // Number of keys to return per page
    const storageKeys = await api.rpc.state.getKeysPaged(startingKey, count); 

    // Fetch and log the storage values for each key
    for (const key of storageKeys) {
        const value = await api.rpc.state.getStorage(key);
        console.log(`Key: ${key.toHuman()}, Value: ${value.toHuman()}`);
    }
}

main().catch(console.error);

// To get the starting key you need to concatenate several hashes of the following values:
// 1. The pallet name "LaosEvolution"
// 2. The StorageDoubleMap name "PolkadotULExtensions"
// 3. The UL key value
// You might need to encode/decode to scale format used in substrate: https://substrate.stackexchange.com/questions/9893/how-to-properly-decode-a-scale-encoded-string-using-polkadot-js
async function getStartingKey(UL) {
    // 1. xxhash("LaosEvolution") https://github.com/polkadot-js/common/blob/master/packages/util-crypto/src/xxhash/index.ts
    // 2. xxhash("PolkadotULExtensions") https://github.com/polkadot-js/common/blob/master/packages/util-crypto/src/xxhash/index.ts
    // 3.a Get the hexdacimal of UL value
    // 3.b Blake2 128 of the value got before https://github.com/polkadot-js/common/blob/master/packages/util-crypto/src/blake2/index.ts
    // 3.c concat 3.a + 3.b
}
tonimateos commented 8 months ago

consider making universal location as indexed in the events

tonimateos commented 8 months ago

consider getStartingKey being part of the interface, so that it avoids complications with messeing up with the hashing

ccubu commented 8 months ago

Approach A

Data Structure:

Function Implementations:

Here is the solidity interface with the indexed UL:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, string indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, string indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Emits the ExtendedTokenURIUpdated event upon success.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @return The number of extensions
    function balanceOfUL(string calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location string identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(string calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location string identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(string calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(string calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(string calldata uloc, address claimer) external view returns (bool);
}
ccubu commented 8 months ago

-Since it looks like the methods iter_key_prefix.collect() and iter_key_prefix.count()(that were proposed to reduce the number of stored items) actually use iteration to get the specified values. We have decided to store 3 items again on the storage as described here:

Approach A

Data Structure:

Function Implementations:

With the solidity interface as follows:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @title Decentralized Asset Metadata Extender Interface
/// @author LAOS Team
/// @notice This interface allows users and developers to interact with the Decentralized Asset Metadata Extender (DAME)
interface IDecentralizedAssetMetadataExtender {

    /// @notice Emitted when a tokenURI is extended
    /// @param claimer The address of the claimer extending the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The URI of the token metadata that was extended
    event TokenURIExtended(address indexed claimer, string indexed uloc, string tokenURI);

    /// @notice Emitted when an extended tokenURI is updated
    /// @param claimer The address of the claimer updating the metadata
    /// @param uloc The Universal Location of the token as a string
    /// @param tokenURI The new URI of the token metadata
    event ExtendedTokenURIUpdated(address indexed claimer, string indexed uloc, string tokenURI);

    /// @notice Extends the metadata of a token.
    /// @dev Emits the TokenURIExtended event upon success.
    /// @dev Reverts if the UL has been extended previously.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The URI of the metadata to be extended.
    function extendTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Updates the extended metadata of a token.
    /// @dev Requires that the msg.sender is the original claimer.
    /// @dev Reverts if the UL has not been extended previously.
    /// @param uloc The Universal Location as a string identifying the token.
    /// @param tokenURI The new URI for the updated metadata.
    function updateExtendedTokenURI(string calldata uloc, string calldata tokenURI) external;

    /// @notice Returns the number of extensions made about a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @return The number of extensions
    function balanceOfUL(string calldata uloc) external view returns (uint256);

    /// @notice Returns the claimer for an extension at a given index
    /// @param uloc The Universal Location string identifying the asset
    /// @param index The index of the extension
    /// @return The address of the claimer
    function claimerOfULByIndex(string calldata uloc, uint256 index) external view returns (address);

    /// @notice Returns the tokenURI for an extension at a given index
    /// @param uloc The Universal Location string identifying the asset
    /// @param index The index of the extension
    /// @return The tokenURI of the extension
    function extensionOfULByIndex(string calldata uloc, uint256 index) external view returns (string memory);

    /// @notice Returns the tokenURI for an extension by a given claimer
    /// @dev Reverts if there is none.
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return The tokenURI of the extension
    function extensionOfULByClaimer(string calldata uloc, address claimer) external view returns (string memory);

    /// @notice Checks if a given claimer has extended a UL
    /// @param uloc The Universal Location as a string identifying the asset
    /// @param claimer The address of the claimer
    /// @return True if the claimer has an extension, false otherwise
    function hasExtensionByClaimer(string calldata uloc, address claimer) external view returns (bool);
}
ghost commented 7 months ago

NOTE: read methods involving index revert on none