-
Notifications
You must be signed in to change notification settings - Fork 124
Description
Based on the discussion in #1068 and the call from yesterday, we need to refactor our current asset model to enable two things:
- Faucets should be able to hook logic into asset transfers, e.g. faucet is called when an asset is added to an account vault (
on_asset_added_to_account) or added to a note (on_asset_added_to_note), and maybe others. For example, this enables faucets to implement block lists, which is required for compliant, private stablecoins. This will be discussed in a separate issue. - Prepare for allowing custom asset layouts.
Prepare means that we won't need to implement this full flexibility right away, but set everything in place so we can easily enable that flexibility later in a non-breaking way. Despite this, the second step is still the much larger piece of work.
Built-In Assets
If we don't enable full flexibility right away, we need an intermediary solution with which assets can be built. In essence, this will continue to be the existing Asset. At the asset layout level, this should be a specialized version of what later becomes the generalized definition of assets. This generalized definition will require assets to be represented as an ASSET_VAULT_KEY and an ASSET_VALUE. The core need for this split into key and value is to allow users up to 4 field elements of custom data, and more importantly allow asset definitions where ASSET_VALUE is the hash of the asset, e.g. the root of a merkle tree or a sequential hash.
The specialized version of fungible assets is then today's FungibleAsset expanded from one to two words:
ASSET_VAULT_KEY = [0, 0, faucet_id_suffix, faucet_id_prefix].ASSET_VALUE = [amount, 0, 0, 0].
Side Note: This would subsequently also allow us to consider representing larger amounts than the current FungibleAsset::MAX_AMOUNT by using more of the available space (see also #1951).
NonFungibleAsset expanded from one to two words would be:
ASSET_VAULT_KEY = [hash0, hash1, faucet_id_suffix, faucet_id_prefix].- The reason for including the hash into the vault key is to ensure vault key uniqueness.
ASSET_VALUE = [hash0, hash1, hash2, hash3].
In Rust code terms, after the expansion, this would be:
pub struct Asset {
key: AssetVaultKey,
value: AssetValue,
}
pub enum AssetValue {
Fungible(FungibleAsset),
NonFungible(NonFungibleAsset),
}Asset Model
We had two versions of approach 3: The SMT/double-tree approach and the flat approach.
With the expansion into two words, both flat and SMT approach are suitable. As discussed in the call, the flat approach seems to be sufficient, even though the SMT approach has the upper hand on the level of flexibility, due to managing an entire SMT. In my mind, the main advantage of the flat approach is that seems powerful enough and is easier to implement due to not requiring an SMT data structure in an Asset, and does not require merkle paths of length 64 and 128 for asset witnesses. I can't find many other major differences when comparing other properties between both approaches. Both should be able to allow representing complex assets, e.g. assets that need more than 4 felts.
The above fungible asset definition's vault key would be a specialized version of the generalized vault key, e.g. ASSET_VAULT_KEY = [asset_id_suffix = 0, asset_id_prefix = 0, faucet_id_suffix, faucet_id_prefix]. For non-fungible assets, the asset ID would be randomized by inclusion of the hash and prevent merging/splitting them.
Once we enable the generalized asset model, we'd overall have the following asset definitions:
- A "built-in" fungible asset and non-fungible asset as defined above - practically equivalent to what we have now.
- A custom asset for later use.
Or, in Rust terms:
pub struct Asset {
key: AssetVaultKey,
value: AssetValue,
}
pub enum AssetValue {
Fungible(FungibleAsset),
NonFungible(NonFungibleAsset),
Custom(CustomAsset),
}Open Questions
Non-Fungible Assets
I'm questioning whether the definition of non-fungible assets we have "built-in" the protocol would still be useful, once users have the ability to define their own asset layouts. So: Should we keep a "built-in" or "specialized" version of non-fungible assets in the protocol? This is mainly in the interest of reducing overall complexity.
Workload
One of the reasons for doing this incremental approach and "preparation" is to reduce the work load we have to do now. However, we need to prepare things in a non-breaking way for custom assets to avoid breaking changes. I haven't looked deep enough into the overall amount of work, but do wonder whether preparing things for custom assets doesn't mean implementing most of it already, in which case, going the implementation all the way may be fine. I think this may become a bit clearer when working on the one to two word expansion.
One advantage of the built-in asset is that we can use it for the purpose of fees, and even with custom assets, we'd need a solution for this anyway. The custom approach will likely have some benefits that the built-in way will not have, but maybe those benefits won't be needed, so maybe it is fine to have.
Next Steps
The immediate next step is to expand Assets from one to two words, as described above, as it is a major breaking change. Separately, I'll open an issue to discuss the design for hooks/callbacks for faucets which are much less breaking so can come a bit later.