Potential asset model #339
Replies: 4 comments 1 reply
-
This is great! Thank you! A few preliminary thoughts:
I wonder if this may complicate things. Specifically, in some cases we may need to do extra checks for asset-issuing accounts. For example, in the case of an account issuing NFTs, we need to ensure that the account can issue a given NFT only once. This could be done by recording all issued NFTs in a subtree of the account's storage and restricting access to this subtree only to kernel code. It may be better to clearly separate accounts which could issue assets from regular accounts (if we do this, we should come up with a name for such accounts - e.g., something like "issuers" or "faucets"). This can be done by enforcing some rules around account IDs. One scheme that I was thinking about could work as follows:
The reason for this scheme is that it would make it very easy to figure out which account is which. All we need to do is to use For example, the code to check if an account can issue NFTs could look like so:
The above check should take about 4 VM cycles. To create accounts which can issue assets, users will need to grind the first 32 bits of the account ID - but that shouldn't be too difficult (probably would take under 20 mins on most laptops).
I like the idea of separating vaults into sub-vaults - especially, for account vaults. This will give us flexibility to potentially optimize differently for different sub-vaults as I think number of assets in fungible asset vault would usually be pretty small, while the number of assets in a NFT-vault could be quite larger.
I am not sure about this yet. Being able to store assets in a single word (4 elements) could have a number of nice advantages. For example, in some situations it may be desirable to pass around a self-contained asset instead of vault roots (e.g., a procedure which takes an asset as a parameter). In a case of fungible assets this would be asset ID + amount, which would mean that asset ID could be 24 bytes and amount 8 bytes. This does make bridging assets from Ethereum more complicated, but the trade-off might be worth it. A couple of other advantages of fitting assets into a single word:
A scheme to store assets in a single words could work as follows:
The reason to set the least significant 32 bits of
It does mean that we'll need to impose an extra rule on fungible account IDs, to make sure they don't have these bits set to As in the case with fungible assets, the assets in the above scheme still have ~110 bit collision resistance (because we discard only 32 bits out of 256). |
Beta Was this translation helpful? Give feedback.
-
I like the idea, but I wonder if it should be the responsibility of the NFT-issuing account to ensure this, and just be specified in its account code. I would make the argument that users of an asset will need to inspect public account code anyway (or trust auditors), and that NFT-issuing accounts can just use standardized code that implements this functionality. We don't provide a similar protection to fungible assets (i.e. that an infinite amount of tokens can't be minted), so I'm hesitant to give NFT some special protections at the cost of additional complexity. Are there other motivations aside from this one to distinguish between asset and regular accounts? I think one potentially interesting effect of not making this distinction is that it allows native "social token" support, where a user account has the ability to issue things like IOUs or their own currency, without having to create a separate asset-issuing account whose ownership then needs to be established.
I do like this scheme, but I'd be more comfortable if we have a concrete idea of how we then handle bridged tokens that can't fit into 8 bytes (e.g. an ETH balance above ~18.5, which has 18 decimal places). Do we just throw out the least significant digits and have some dust accumulate in the bridge? That seems somewhat clunky to me, but maybe there is a better solution. |
Beta Was this translation helpful? Give feedback.
-
To summarize main open question:
|
Beta Was this translation helpful? Give feedback.
-
I've been thinking a bit about what procedures we might want to expose to the user to work with vaults and assets, and below is a rough sketch. Form the account context, we might want to do the following (these procedures could live in
We probably also need a way to manipulate individual assets as well as note vault contents. I actually think that we should probably use a different name for note vaults because now they are structurally different from account vaults. I've used
I'm probably missing quite a few, but it's a start. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
This note builds upon discussions #192 and #222, and outlines one potential mechanism for native asset support on Miden. We first discuss how assets are represented in accounts and notes, and then how they can be implemented in more detail in the VM.
Assets
Asset accounts can be used to represent fungible and non-fungible token contracts through the issuance of notes containing asset balances or item hashes, respectively. Item hashes can represent arbitrary metadata (e.g. ticket information, or a particular item's attributes). Any regular account can act as an asset by issuing these notes -- there is no distinction made in the state representation of these accounts. Below we sometimes refer to asset IDs, but this is just another name for account IDs that are being treated as assets.
Vaults
Assets reside in a vault, which is composed of a subvault for each asset type (fungible and non-fungible):
All keys and values are represented by 256-bit words so as to maintain compatibility with EVM-based assets that may be bridged to the rollup.
Account vault
Each account subvault is stored as a sparse Merkle trie. The hash of an account vault is equal to
hash([balance_subvault_root, item_subvault_root])
.Note vault
Each note subvault is stored as a list, where the hash of the subvault is given by a sequential hash of the contents. For example, to represent a note subvault containing three fungible assets, we would compute the hash according to:
balance_subvault_hash = hash(hash(hash(hash(h_0, h_a), h_b), h_c), 3)
, whereh_i = hash(amount_i, asset_i)
,h_0 = hash(0,0)
is used to mark the beginning of the vault, and the final hashed value3
indicates the number of assets. A similar sequential hash can be computed for non-fungible assets, whereh_i = hash(item_hash_i, asset_i)
.The hash of a note vault is equal to
hash([balance_subvault_hash, item_subvault_hash])
.Asset safety
Unlike EVM-based architectures, information about asset balances does not need to be stored in the storage of the smart contract account representing the asset, but can be distributed across user accounts, residing in each user's account vault. These vaults are simply another form of local account storage, so we need to ensure that an increase (decrease) in an account vault balance is matched with a decrease (increase) in a note vault balance. These safety checks do not apply when an asset account manipulates asset balances or item hashes that map to its own account ID.
These safety checks can be achieved by breaking transaction execution into three steps, as suggested in a previous discussion:
tx_program
(also referred to astx_script
) is executed, which is responsible for consuming notes and executing note programs (if they exist).Implementation in Miden VM
Asset creation
Asset balances can be created by calling a kernel function
mint_balance(amount)
, which addsamount
toaccountID
in the account's balance subvault. To move this newly created amount to a produced note, a kernel functioncreate_note(serial_num, program_root, vault_root)
must be called. A higher-level function such ascreate_asset_note(amount, recipient)
can be used to setprogram_root
to one that restricts consumption torecipient
, andvault_root
to one that contains only the amount of the asset.Item hashes can be created by calling
mint_item(hash)
, which adds a hash to the account's item subvault. The actual hash stored in the item subvault will look something likeH(hash, accountID)
so as to prevent collisions. Moving this item hash to a produced note can work similarly to an asset balance, as described above.Asset safety
Asset safety can be enforced through the following kernel functions (to be called in order):
execute_tx_prologue
,execute_tx_program
,execute_tx_epilogue
, each of which is documented below. For simplicity, only asset balances (i.e. fungible tokens) will be discussed below, but item hashes (i.e. non-fungible tokens) can be handled in a similar fashion.Instead of iterating through every asset in the account vault (which may be prohibitively large), we instead track changes made to account and note vaults, and operate on those difference values. This still places an upper bound on the amount of assets that can be processed, but for those rare instances in which this is an issue, processing can be split among multiple smaller vaults. With this approach, there are no explicit checks on modifications made to the account vault during code execution, and no overhead in repeatedly modifying vault balances.
Prologue:
execute_tx_prologue
We need to store a list of any assets that will be manipulated during transaction execution as well as their respective pre-execution amounts in a region of kernel memory. These values will be checked for consistency with the post-execution summed asset balances in the epilogue. As mentioned above, this bookkeeping needs to be done for both note and account vaults, and so we handle each case separately. When input notes are consumed, the whole content of their vault must be credited to the
account
, so all asset IDs in a note vault need to be added to this list. We also need to add the IDs of any asset that will be debited from the account (i.e. moved to an output note vault).h_0
if at the start)asset_i
byamount_i
asset_i
byamount_i
Execution:
execute_tx_program
At the end of the execution, an advice set needs to be constructed containing an ordered sequence of Merkle update proofs for each asset's final new balance. Note that the proof path for the first update will be identical to the committed vault (pre-execution), except that the root hash of the path will differ (the leaf is not contained in the Merkle proof). For subsequent updates, two proof paths will need to be provided: one demonstrating a path to the original asset balance, and another to its updated balance.
Epilogue:
execute_tx_epilogue
The epilogue needs to check the following:
In all three of these kernel functions, we do not keep track of any asset balances in which the asset ID of a vault matches the account ID of the transaction context. This allows assets to be issued or destroyed when code is executed in the context of an asset account.
Beta Was this translation helpful? Give feedback.
All reactions