Open tonimateos opened 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);
}
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.
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.
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?
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
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);
}
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.
extendTokenURI
/ updateExtendedTokenURI
balanceOfUL
claimerOfULByIndex
extensionOfULByIndex
extensionOfULByClaimer
hasExtensionByClaimer
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.
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...
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);
}
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
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);
StorageMap
with a composite key hash(UL
, claimer
) mapping directly to the tokenURI
.StorageDoubleMap
, mapping each (UL
, index
) to claimer
.StorageMap
that tracks the number of extensions mapping eachUL
to the number of extensions.extendTokenURI / updateExtendedTokenURI
PolkadotULExtensions
with (UL
, claimer
) key and tokenURI
.ULExtensionCount
for UL
and with that number add claimer
and index
to ULToClaimers
for UL
.balanceOfUL
ULExtensionCount
for UL
.claimerOfULByIndex
ULToClaimers
for (UL
, index
) and return the claimer
.extensionOfULByIndex
claimer
from ULToClaimers
for (UL
, index
).UL
, claimer
) at the specified index to get the tokenURI
from PolkadotULExtensions
.extensionOfULByClaimer
tokenURI
from PolkadotULExtensions
using (UL
, claimer
).hasExtensionByClaimer
UL
, claimer
) key in PolkadotULExtensions
.StorageDoubleMap
with a double key hash(UL
, claimer
) mapping directly to the tokenURI
.StorageMap
that tracks the number of extensions mapping eachUL
to the number of extensions.extendTokenURI / updateExtendedTokenURI
PolkadotULExtensions
with (UL
, claimer
) key and tokenURI
.ULExtensionCount
for UL
balanceOfUL
ULExtensionCount
for UL
.claimerOfULByIndex (*)
extensionOfULByIndex (*)
extensionOfULByClaimer
tokenURI
from PolkadotULExtensions
using (UL
, claimer
).hasExtensionByClaimer
UL
, claimer
) key in PolkadotULExtensions
.(*) Index concept is removed. Users can get claims list:
getKeysPaged
by substrate whoever wants to retrieve the UL claims list can use such func. Similar to what is proposed here: https://substrate.stackexchange.com/questions/4083/how-can-i-get-all-items-in-a-storage-mapInterface 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.
So there's no way to query what extensions are available for an asset, only how many... :-(
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.
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
}
consider making universal location as indexed in the events
consider getStartingKey being part of the interface, so that it avoids complications with messeing up with the hashing
StorageMap
with a composite key hash(UL
, claimer
) mapping directly to the tokenURI
.StorageDoubleMap
, mapping each (UL
, index
) to claimer
.extendTokenURI / updateExtendedTokenURI
PolkadotULExtensions
with (UL
, claimer
) key and tokenURI
.UL
and with that number add claimer
and index=count+1
to ULToClaimers
for UL
.balanceOfUL
ULToClaimers
for a givenUL
using the count
method of the returned iterator from the StorageDoubleMap method iter_key_prefix
as can be seen here.claimerOfULByIndex
ULToClaimers
for (UL
, index
) and return the claimer
.extensionOfULByIndex
claimer
from ULToClaimers
for (UL
, index
).UL
, claimer
) at the specified index to get the tokenURI
from PolkadotULExtensions
.extensionOfULByClaimer
tokenURI
from PolkadotULExtensions
using (UL
, claimer
).hasExtensionByClaimer
UL
, claimer
) key in PolkadotULExtensions
.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);
}
-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:
StorageMap
with a composite key hash(UL
, claimer
) mapping directly to the tokenURI
.StorageDoubleMap
, mapping each (UL
, index
) to claimer
.StorageMap
that tracks the number of extensions mapping eachUL
to the number of extensions.extendTokenURI / updateExtendedTokenURI
ULExtensions
with hash(UL
, claimer
) key and tokenURI
.UL
from ULExtensionCount
by one and then with that number add claimer
and index
to ULToClaimers
for UL
.balanceOfUL
ULExtensionCount
for a givenUL
.claimerOfULByIndex
ULToClaimers
for (UL
, index
) and return the claimer
.extensionOfULByIndex
claimer
from ULToClaimers
for (UL
, index
).UL
, claimer
) at the specified index to get the tokenURI
from ULExtensions
.extensionOfULByClaimer
tokenURI
from ULExtensions
using (UL
, claimer
).hasExtensionByClaimer
UL
, claimer
) key in ULExtensions
.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);
}
NOTE: read methods involving index
revert on none
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: