-
Notifications
You must be signed in to change notification settings - Fork 123
Description
See the parent issue for higher-level context.
The asset preservation rules globally ensure that all assets are preserved within a given tx, so the place where asset validation in the kernel adds real assurances is during minting and burning, where the preservation rules are basically bypassed. This prevents users from bringing an invalid asset into circulation at the protocol level (e.g. a fungible asset whose amount exceeds the max).
However, once we have custom assets, users will also have the ability to mint invalid assets since the protocol won't be able to ensure validity. Then, contracts that rely on the kernel for validation may be vulnerable if they don't validate the custom assets before use. In the interest of uniformity, it seems better to push the responsibility of validation to users in general, which makes it at least clear. If contracts only need to validate some assets, it's easy to forget the cases where they do.
If we instead create hard-to-misuse APIs, the validation will happen naturally, both for "native" and custom assets, as all assets will be worked with uniformly. This uniform way is the pattern where each asset, both "native" and custom, has its own module. For instance, for fungible assets we'd have miden::standards::fungible_asset that could look like this:
# non-exhaustive set of examples
#! Inputs: [ASSET_KEY, ASSET_VALUE]
#! Outputs: [ASSET_KEY, ASSET_VALUE]
pub proc validate
# ...
end
#! Inputs: [amount]
#! Outputs: []
pub proc mint
exec.create
exec.::miden::protocol::faucet::mint
end
#! Inputs: [FUNGIBLE_ASSET_KEY, FUNGIBLE_ASSET_VALUE]
#! Outputs: [MERGED_FUNGIBLE_ASSET_VALUE]
pub proc add_to_account
exec.validate
exec.::miden::protocol::native_account::add_asset
end
#! Inputs: [FUNGIBLE_ASSET_KEY]
#! Outputs: [amount]
pub proc get_amount
exec.::miden::protocol::active_account::get_asset
exec.validate
exec.into_amount
end
#! Inputs: [amount]
#! Outputs: [FUNGIBLE_ASSET_KEY, FUNGIBLE_ASSET_VALUE]
pub proc create
exec.::miden::protocol::active_account::get_id
exec.::miden::protocol::faucet::has_callbacks
exec.from_amount
end
Many of these validate the provided asset, which may be unnecessary in some cases, so it may make sense to have _unchecked variants of these - though the default should be validation as the goal is hard-to-misuse APIs. This clearly makes safety opt-out, undermining the goal, but developers should have the choice.
The main point is that we could have the same pattern of wrappers for non_fungible_asset, but it would appropriately not have a get_amount but rather an is_contained check implementing the current has_non_fungible_asset check.
This moves the extension point of assets from miden::protocol::{native_account, active_account, faucet, ...} to the module defined by the asset.
This makes it easier for developers since all assets can in principle follow the same pattern of wrapping protocol APIs with their own logic.
It also makes it easy to further extend this pattern with composition at the user level, e.g. a contract implementing event tickets as NFTs would define an event_ticket module that wraps miden::standards::non_fungible_asset:
#! Gets an event ticket from its unique index.
#!
#! Inputs: [ticket_idx]
#! Outputs: [TICKET_ASSET_VALUE]
pub proc get_ticket
# create the asset key from an index
exec.from_idx
exec.::miden::standards::non_fungible_asset::get
end
I think this sets up the right structure where we can provide a good set of standards that is also easily extensible by users.
To make it clear: The overall trade-off is to weaken the guarantees provided by the tx kernel for native assets, in order to gain a better overall structure for assets that should make overall asset handling mistakes less likely.