From dcd7ffaf164f560820253f0be1813dfccde69097 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 27 Jan 2026 17:20:31 +0100 Subject: [PATCH 001/100] chore: use `get_balance` helper in account_delta --- .../asm/kernels/transaction/lib/account_delta.masm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm index 969438b4f5..fd1a6df1d4 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm @@ -511,7 +511,8 @@ pub proc add_asset exec.asset_vault::build_fungible_asset_vault_key swapw # => [ASSET, ASSET_KEY] - drop drop drop movdn.4 + exec.::$kernel::util::asset::get_balance_from_fungible_asset + movdn.4 # => [ASSET_KEY, amount] exec.add_fungible_asset @@ -541,7 +542,8 @@ pub proc remove_asset exec.asset_vault::build_fungible_asset_vault_key swapw # => [ASSET, ASSET_KEY] - drop drop drop movdn.4 + exec.::$kernel::util::asset::get_balance_from_fungible_asset + movdn.4 # => [ASSET_KEY, amount] exec.remove_fungible_asset From 67718c7a0d8cc5c0019b80efeff5c3c68b76e86b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 11:38:43 +0100 Subject: [PATCH 002/100] chore: Add `TryFrom for AssetVaultKey` --- .../src/asset/vault/vault_key.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 1d3d2a6914..d33b7e5943 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -7,6 +7,7 @@ use crate::Word; use crate::account::AccountType::FungibleFaucet; use crate::account::{AccountId, AccountIdPrefix}; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; +use crate::errors::AssetError; /// The key of an [`Asset`] in the asset vault. /// @@ -102,6 +103,23 @@ impl fmt::Display for AssetVaultKey { // CONVERSIONS // ================================================================================================ +impl TryFrom for AssetVaultKey { + type Error = AssetError; + + /// Attempts to convert the provided [`Word`] into an [`AssetVaultKey`]. + /// + /// # Errors + /// + /// Returns an error if: + /// - TODO(programmable_assets) + fn try_from(key: Word) -> Result { + // TODO(programmable_assets): Implement validation once the new structure of the asset vault + // key is defined. + + Ok(Self::new_unchecked(key)) + } +} + impl From for Word { fn from(vault_key: AssetVaultKey) -> Self { vault_key.0 From 2052f82a982c1822dedf79769b58bc1d6d476430 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 29 Jan 2026 12:52:44 +0100 Subject: [PATCH 003/100] feat: refactor `asset.masm` --- .../asm/kernels/transaction/lib/asset.masm | 285 ++++++++++++++---- crates/miden-protocol/asm/protocol/asset.masm | 2 + .../asm/shared_utils/util/asset.masm | 6 + crates/miden-protocol/src/asset/fungible.rs | 10 + crates/miden-protocol/src/asset/mod.rs | 10 + .../miden-protocol/src/asset/nonfungible.rs | 10 + .../src/transaction/kernel/memory.rs | 11 + .../src/kernel_tests/tx/test_asset.rs | 16 +- 8 files changed, 282 insertions(+), 68 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index d5f5d20580..39ad9cf0e4 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -3,172 +3,333 @@ use $kernel::account_id # ERRORS # ================================================================================================= +const ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE = "fungible asset vault key's account ID must be of type fungible faucet" + const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO="malformed fungible asset: `ASSET[1]` must be 0" const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed fungible asset: `ASSET[2]` and `ASSET[3]` must be a valid fungible faucet id" const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS="malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount" -const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" +const ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the fungible asset is not this faucet" -const ERR_NON_FUNGIBLE_ASSET_FORMAT_MOST_SIGNIFICANT_BIT_MUST_BE_ZERO="malformed non-fungible asset: the most significant bit must be 0" +const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE = "non-fungible asset vault key's account ID must be of type non-fungible faucet" -const ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the fungible asset is not this faucet" +const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungible asset is not this faucet" +const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET="account ID in asset vault key must be either of type fungible or non-fungible faucet" + # CONSTANT ACCESSORS # ================================================================================================= pub use ::$kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT +pub use ::$kernel::util::asset::ASSET_SIZE +pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET # PROCEDURES # ================================================================================================= #! Validates that a fungible asset is well formed. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to validate. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. #! #! Panics if: -#! - the asset is not well formed. +#! - the asset key or value are not well formed. pub proc validate_fungible_asset - # assert that ASSET[1] == ZERO - dup.2 not assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO - # => [ASSET] + exec.validate_fungible_asset_key + # => [ASSET_KEY, ASSET_VALUE] + + swapw exec.validate_fungible_asset_value swapw + # => [ASSET_KEY, ASSET_VALUE] +end - # assert that the tuple (ASSET[3], ASSET[2]) forms a valid account ID +#! Validates that a fungible asset value is well formed. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_VALUE] +#! +#! Where: +#! - ASSET_VALUE is the value of the asset to validate. +#! +#! Panics if: +#! - the asset value is not well formed. +proc validate_fungible_asset_value + # assert that ASSET_VALUE[1] == ZERO + dup.2 eq.0 assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO + # => [ASSET_VALUE] + + # assert that the tuple (ASSET_VALUE[3], ASSET_VALUE[2]) forms a valid account ID dup.1 dup.1 exec.account_id::validate - # => [ASSET] + # => [ASSET_VALUE] - # assert that the prefix (ASSET[3]) of the account ID is of type fungible faucet + # assert that the prefix (ASSET_VALUE[3]) of the account ID is of type fungible faucet dup exec.account_id::is_fungible_faucet assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID - # => [ASSET] + # => [ASSET_VALUE] - # assert that the max amount (ASSET[0]) of a fungible asset is not exceeded + # assert that the max amount (ASSET_VALUE[0]) of a fungible asset is not exceeded dup.3 lte.FUNGIBLE_ASSET_MAX_AMOUNT assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS - # => [ASSET] + # => [ASSET_VALUE] +end + +#! Validates that a fungible asset's vault key is well formed. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's faucet ID is not a fungible one. +pub proc validate_fungible_asset_key + exec.validate_asset_key + # => [ASSET_KEY] + + exec.is_fungible_asset + assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE + # => [ASSET_KEY] end #! Returns a boolean indicating whether the asset is fungible. #! -#! Inputs: [ASSET] -#! Outputs: [is_fungible_asset, ASSET] +#! Inputs: [ASSET_KEY] +#! Outputs: [is_fungible_asset, ASSET_KEY] #! #! Where: -#! - ASSET is the asset to check. +#! - ASSET_KEY is the vault key of the asset to check. #! - is_fungible_asset is a boolean indicating whether the asset is fungible. pub proc is_fungible_asset - # check the first element, it will be: - # - zero for a fungible asset - # - non zero for a non-fungible asset - dup.2 eq.0 - # => [is_fungible_asset, ASSET] + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup exec.account_id::is_fungible_faucet + # => [is_fungible_asset, ASSET_KEY] end #! Validates that a non fungible asset is well formed. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to validate. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. #! #! Panics if: -#! - the asset is not well formed. +#! - the asset key or value are not well formed. pub proc validate_non_fungible_asset - # assert that ASSET[3] is a valid account ID prefix + exec.validate_non_fungible_asset_key + swapw + # => [ASSET_VALUE, ASSET_KEY] + + exec.validate_non_fungible_asset_value + swapw + # => [ASSET_KEY, ASSET_VALUE] +end + +#! Validates that a non fungible asset is well formed. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_VALUE] +#! +#! Where: +#! - ASSET_VALUE is the value of the asset to validate. +#! +#! Panics if: +#! - the asset key or value are not well formed. +proc validate_non_fungible_asset_value + # assert that ASSET_VALUE[3] is a valid account ID prefix # hack: because we only have the prefix we add a 0 as the suffix which is always valid push.0 dup.1 exec.account_id::validate - # => [ASSET] + # => [ASSET_VALUE] - # assert that the account ID prefix ASSET[3] is of type non fungible faucet + # assert that the account ID prefix ASSET_VALUE[3] is of type non fungible faucet dup exec.account_id::is_non_fungible_faucet assert.err=ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID - # => [ASSET] + # => [ASSET_VALUE] +end + +#! Validates that a non-fungible asset's vault key is well formed. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's faucet ID is not a non-fungible one. +proc validate_non_fungible_asset_key + # => [ASSET_KEY] + # => [hash0', hash2, hash1, faucet_id_prefix] + + # hack: because we only have the prefix we add a 0 as the suffix which is always valid + push.0 dup.4 + # => [faucet_id_prefix, 0, ASSET_KEY] + + exec.account_id::validate + # => [ASSET_KEY] + + exec.is_non_fungible_asset + assert.err=ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE + + # => [ASSET_KEY] +end + +#! Validates that a fungible asset's vault key is well formed. +#! +#! TODO(expand_assets): +#! WARNING: For now, this only works for keys from fungible assets. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's account ID is neither of type fungible nor of type non-fungible. +proc validate_asset_key + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.1 dup.1 + # => [faucet_id_prefix, faucet_id_suffix, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + exec.account_id::validate + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup exec.account_id::is_fungible_faucet + # => [is_fungible_faucet, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.1 exec.account_id::is_non_fungible_faucet + # => [is_fungible_faucet, is_non_fungible_faucet, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + # assert the asset key's account ID is either of type fungible or type non-fungible + or assert.err=ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + # => [ASSET_KEY] end #! Returns a boolean indicating whether the asset is non-fungible. #! -#! Inputs: [ASSET] -#! Outputs: [is_non_fungible_asset, ASSET] +#! Inputs: [ASSET_KEY] +#! Outputs: [is_non_fungible_asset, ASSET_KEY] #! #! Where: -#! - ASSET is the asset to check. +#! - ASSET_KEY is the vault key of the asset to check. #! - is_non_fungible_asset is a boolean indicating whether the asset is non-fungible. pub proc is_non_fungible_asset - # check the first element, it will be: - # - zero for a fungible asset - # - non zero for a non-fungible asset - exec.is_fungible_asset not - # => [is_non_fungible_asset, ASSET] + # TODO(expand_assets): Eventually do: + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + # dup exec.account_id::is_non_fungible_faucet + # => [is_non_fungible_asset, ASSET_KEY] + + # => [ASSET_KEY] + dup.3 exec.account_id::is_non_fungible_faucet + # => [is_non_fungible_asset, ASSET_KEY] end #! Validates that an asset is well formed. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to validate. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. #! #! Panics if: -#! - the asset is not well formed. +#! - the asset key or value are not well formed. pub proc validate_asset # check if the asset is fungible exec.is_fungible_asset - # => [is_fungible_asset, ASSET] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] # if the asset is fungible, validate the fungible asset if.true exec.validate_fungible_asset + # => [ASSET_KEY, ASSET_VALUE] else # if the asset is non fungible, validate the non fungible asset exec.validate_non_fungible_asset + # => [ASSET_KEY, ASSET_VALUE] end - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] end #! Validates that a fungible asset is associated with the provided faucet_id. #! -#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET] -#! Outputs: [ASSET] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_prefix is the prefix of the faucet's account ID. -#! - ASSET is the asset to validate. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. pub proc validate_fungible_asset_origin + movdn.9 movdn.9 + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] + + # assert the fungible asset key and value are valid + exec.validate_fungible_asset + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] + # assert the origin of the asset is the faucet_id provided via the stack - dup.3 dup.3 - # => [asset_id_prefix, asset_id_suffix, faucet_id_prefix, faucet_id_suffix, ASSET] + exec.get_vault_key_faucet_id + # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] - exec.account_id::is_equal assert.err=ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN - # => [ASSET] + movup.11 movup.11 + # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE] - # assert the fungible asset is valid - exec.validate_fungible_asset - # => [ASSET] + exec.account_id::is_equal assert.err=ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN + # => [ASSET_KEY, ASSET_VALUE] end #! Validates that a non-fungible asset is associated with the provided faucet_id. #! -#! Inputs: [faucet_id_prefix, ASSET] -#! Outputs: [ASSET] +#! Inputs: [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_prefix is the prefix of the faucet's account ID. -#! - ASSET is the asset to validate. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. pub proc validate_non_fungible_asset_origin # assert the origin of the asset is the faucet_id prefix provided via the stack - dup.1 assert_eq.err=ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN - # => [ASSET] + dup.4 assert_eq.err=ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN + # => [ASSET_KEY, ASSET_VALUE] # assert the non-fungible asset is valid exec.validate_non_fungible_asset - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] +end + +#! Returns the faucet ID from an asset vault key. +#! +#! TODO(expand_assets): +#! WARNING: For now, this only works for keys from fungible assets. +#! WARNING: The faucet ID is not validated. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] +#! +#! Where: +#! - faucet_id is the account ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the faucet ID. +proc get_vault_key_faucet_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.1 dup.1 + # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] end diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index 5505fee44a..c10018e56b 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -4,6 +4,8 @@ use miden::protocol::account_id # ================================================================================================= pub use ::miden::protocol::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT +pub use ::miden::protocol::util::asset::ASSET_SIZE +pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET # ERRORS # ================================================================================================= diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 13df819405..4e7b59bc9c 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -6,6 +6,12 @@ # This is 2^63 - 2^31. See account_delta.masm for more details. pub const FUNGIBLE_ASSET_MAX_AMOUNT=0x7fffffff80000000 +# The number of elements in an asset, i.e. vault key and value. +pub const ASSET_SIZE = 8 + +# The offset of the asset value in an asset stored in memory. +pub const ASSET_VALUE_MEMORY_OFFSET = 4 + # PROCEDURES # ================================================================================================= diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index 95e7dfdcb6..bc093e0114 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -89,6 +89,16 @@ impl FungibleAsset { .expect("faucet ID should be of type fungible") } + /// Returns the asset's key encoded to a [`Word`]. + pub fn to_key_word(&self) -> Word { + *self.vault_key().as_word() + } + + /// Returns the asset's value encoded to a [`Word`]. + pub fn to_value_word(&self) -> Word { + Word::from(*self) + } + // OPERATIONS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 4d14998289..0fcd6b1620 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -145,6 +145,16 @@ impl Asset { } } + /// Returns the asset's key encoded to a [`Word`]. + pub fn to_key_word(&self) -> Word { + *self.vault_key().as_word() + } + + /// Returns the asset's value encoded to a [`Word`]. + pub fn to_value_word(&self) -> Word { + Word::from(*self) + } + /// Returns the inner [`FungibleAsset`]. /// /// # Panics diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index d48b26602b..394c43c276 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -125,6 +125,16 @@ impl NonFungibleAsset { AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS_BE]) } + /// Returns the asset's key encoded to a [`Word`]. + pub fn to_key_word(&self) -> Word { + *self.vault_key().as_word() + } + + /// Returns the asset's value encoded to a [`Word`]. + pub fn to_value_word(&self) -> Word { + Word::from(*self) + } + // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index d711c2c098..19962abfe9 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -434,6 +434,17 @@ pub const OUTPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 20; pub const OUTPUT_NOTE_DIRTY_FLAG_OFFSET: MemoryOffset = 21; pub const OUTPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 24; +// ASSETS +// ------------------------------------------------------------------------------------------------ + +/// The size of an asset's memory representation. +#[cfg(any(feature = "testing", test))] +pub const ASSET_SIZE: MemoryOffset = 8; + +/// The offset of the asset vaule in an asset's memory representation. +#[cfg(any(feature = "testing", test))] +pub const ASSET_VALUE_OFFSET: MemoryOffset = 4; + // LINK MAP // ------------------------------------------------------------------------------------------------ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 6299450263..20d23e1457 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -84,25 +84,29 @@ async fn test_validate_non_fungible_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; - - let non_fungible_asset = Word::from(NonFungibleAsset::mock(&[1, 2, 3])); + let non_fungible_asset = NonFungibleAsset::mock(&[1, 2, 3]); let code = format!( " use $kernel::asset begin - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} exec.asset::validate_non_fungible_asset # truncate the stack - swapw dropw + swapdw dropw dropw end - " + ", + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; - assert_eq!(exec_output.get_stack_word_be(0), non_fungible_asset); + assert_eq!(exec_output.get_stack_word_be(0), non_fungible_asset.to_key_word()); + assert_eq!(exec_output.get_stack_word_be(4), non_fungible_asset.to_value_word()); + Ok(()) } From acdc97bb137f99553864cdd71964c132d778a2d6 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 30 Jan 2026 13:41:25 +0100 Subject: [PATCH 004/100] feat: add `fungible_asset.masm` --- .../transaction/lib/fungible_asset.masm | 63 +++++++++++++++ .../src/kernel_tests/tx/test_asset_vault.rs | 79 ++++++++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm new file mode 100644 index 0000000000..240470cc77 --- /dev/null +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -0,0 +1,63 @@ +# Contains procedures for the built-in fungible asset. + +use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT + +# ERRORS +# ================================================================================================= + +const ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding the fungible asset to the vault would exceed the max amount" + +# PROCEDURES +# ================================================================================================= + +#! Merges two fungible assets. +#! +#! WARNING: This procedure assumes the assets have been validated. +#! +#! NOTE: we do not assert that faucet IDs match since this asset layout will soon no longer contain +#! them anyway +#! +#! Inputs: [ASSET_VALUE_0, ASSET_VALUE_1] +#! Outputs: [MERGED_ASSET_VALUE] +#! +#! Where: +#! - ASSET_VALUE_{0, 1} are the assets to merge. +#! - MERGED_ASSET_VALUE is the merged asset. +#! +#! Panics if: +#! - adding the two asset values would exceed FUNGIBLE_ASSET_MAX_AMOUNT. +pub proc merge + # extract amount from asset 1 + dup.7 + # => [amount_1, ASSET_VALUE_0, ASSET_VALUE_1] + + # compute max_add_amount = FUNGIBLE_ASSET_MAX_AMOUNT - current_amount + # this is the amount that can at most be added to still have a valid asset + push.FUNGIBLE_ASSET_MAX_AMOUNT dup.5 sub + # => [max_add_amount, amount_1, ASSET_VALUE_0, ASSET_VALUE_1] + + # assert it is safe to add the amounts together, i.e. amount_1 <= max_add_amount + lte assert.err=ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED + # => [ASSET_VALUE_0, ASSET_VALUE_1] + + exec.get_amount + # => [current_amount, ASSET_VALUE_1] + + # add the amounts + movup.4 add movdn.3 + # => [MERGED_ASSET_VALUE] +end + +#! Extracts the amount of a fungible asset. +#! +#! WARNING: This procedure assumes the asset has been validated. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [amount] +#! +#! Where: +#! - ASSET_VALUE are the asset from which to extract the amount. +#! - amount is the amount of the fungible asset. +pub proc get_amount + exec.::$kernel::util::asset::get_balance_from_fungible_asset +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 3749824d0e..554f94b04e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -7,7 +7,6 @@ use miden_protocol::asset::{ NonFungibleAsset, NonFungibleAssetDetails, }; -use miden_protocol::errors::AssetVaultError; use miden_protocol::errors::protocol::ERR_VAULT_GET_BALANCE_CAN_ONLY_BE_CALLED_ON_FUNGIBLE_ASSET; use miden_protocol::errors::tx_kernel::{ ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW, @@ -15,6 +14,7 @@ use miden_protocol::errors::tx_kernel::{ ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND, }; +use miden_protocol::errors::{AssetError, AssetVaultError}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, @@ -24,6 +24,7 @@ use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASS use miden_protocol::transaction::memory; use miden_protocol::{Felt, ONE, Word, ZERO}; +use crate::executor::CodeExecutor; use crate::kernel_tests::tx::ExecutionOutputExt; use crate::{TransactionContextBuilder, assert_execution_error}; @@ -543,3 +544,79 @@ async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { Ok(()) } + +/// Tests that adding two fungible assets results in the expected value. +#[tokio::test] +async fn test_merge_fungible_asset_success() -> anyhow::Result<()> { + let asset0 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT); + let asset1 = FungibleAsset::mock(FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT); + let merged_asset = asset0.unwrap_fungible().add(asset1.unwrap_fungible())?; + + // Check merging is commutative by checking asset0 + asset1 = asset1 + asset0. + for (asset_a, asset_b) in [(asset0, asset1), (asset1, asset0)] { + let code = format!( + " + use $kernel::fungible_asset + + begin + push.{ASSETA} + push.{ASSETB} + exec.fungible_asset::merge + # => [MERGED_ASSET] + + # truncate the stack + swapw dropw + end + ", + ASSETA = Word::from(asset_a), + ASSETB = Word::from(asset_b), + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await?; + + assert_eq!(exec_output.get_stack_word_be(0), Word::from(merged_asset)); + } + + Ok(()) +} + +/// Tests that adding two fungible assets fails when the added amounts exceed +/// [`FungibleAsset::MAX_AMOUNT`]. +#[tokio::test] +async fn test_merge_fungible_asset_fails_when_max_amount_exceeded() -> anyhow::Result<()> { + let asset0 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT); + let asset1 = FungibleAsset::mock(FungibleAsset::MAX_AMOUNT + 1 - FUNGIBLE_ASSET_AMOUNT); + + // Check merging fails for both asset0 + asset1 and asset1 + asset0. + for (asset_a, asset_b) in [(asset0, asset1), (asset1, asset0)] { + // Sanity check that the Rust implementation errors. + assert_matches!( + asset_a.unwrap_fungible().add(asset_b.unwrap_fungible()).unwrap_err(), + AssetError::FungibleAssetAmountTooBig(_) + ); + + let code = format!( + " + use $kernel::fungible_asset + + begin + push.{ASSETA} + push.{ASSETB} + exec.fungible_asset::merge + # => [MERGED_ASSET] + + # truncate the stack + swapw dropw + end + ", + ASSETA = Word::from(asset_a), + ASSETB = Word::from(asset_b), + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await; + + assert_execution_error!(exec_output, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); + } + + Ok(()) +} From 0374da3443312a72d06821433a50e7cd48f5546e Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 30 Jan 2026 13:42:05 +0100 Subject: [PATCH 005/100] feat: refactor `asset_vault.masm` --- .../kernels/transaction/lib/asset_vault.masm | 337 +++++++++--------- .../src/kernel_tests/tx/test_asset_vault.rs | 10 +- 2 files changed, 176 insertions(+), 171 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index 07f7d3dbb7..299709ffba 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -3,14 +3,13 @@ use miden::core::word use $kernel::account_id use $kernel::asset +use $kernel::fungible_asset use $kernel::memory use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT # ERRORS # ================================================================================================= -const ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding the fungible asset to the vault would exceed the max amount of 9223372036854775807" - const ERR_VAULT_ADD_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to add fungible asset to the asset vault due to the initial value being invalid" const ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="the non-fungible asset already exists in the asset vault" @@ -30,15 +29,15 @@ const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 # ACCESSORS # ================================================================================================= -#! Returns the ASSET associated with the provided asset vault key. +#! Returns the ASSET_VALUE associated with the provided asset vault key. #! #! Inputs: [ASSET_KEY, vault_root_ptr] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. pub proc get_asset # load the asset vault root from memory padw movup.8 mem_loadw_be @@ -49,7 +48,7 @@ pub proc get_asset # lookup asset exec.smt::get swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end #! Returns the _peeked_ ASSET associated with the provided asset vault key. @@ -69,12 +68,12 @@ end #! that the merkle paths are present prior to calling. #! #! Inputs: [ASSET_KEY, vault_root_ptr] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the retrieved ASSET. +#! - ASSET_VALUE is the retrieved asset. pub proc peek_asset # load the asset vault root from memory padw movup.8 mem_loadw_be @@ -86,15 +85,15 @@ pub proc peek_asset # lookup asset exec.smt::peek # OS => [ASSET_KEY, ASSET_VAULT_ROOT] - # AS => [ASSET] + # AS => [ASSET_VALUE] dropw # OS => [ASSET_VAULT_ROOT] - # AS => [ASSET] + # AS => [ASSET_VALUE] # this overwrites the vault root adv_loadw - # OS => [ASSET] + # OS => [ASSET_VALUE] # AS => [] end @@ -107,268 +106,254 @@ end #! If the amount to be added is zero and the asset does not already exist in the vault, the vault #! remains unchanged. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET'] +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE'] #! #! Where: #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. -#! - ASSET is the fungible asset to add to the vault. -#! - ASSET' is the total fungible asset in the account vault after ASSET was added to it. +#! - ASSET_KEY is the vault key of the fungible asset to add to the vault. +#! - ASSET_VALUE is the fungible asset to add to the vault. +#! - ASSET_VALUE' is the total fungible asset in the account vault after ASSET_VALUE was added to it. +#! +#! Locals: +#! - 0: vault_root_ptr #! #! Panics if: -#! - the total value of assets is greater than or equal to 2^63. +#! - the total value of assets is greater than or equal to FUNGIBLE_ASSET_MAX_AMOUNT. +@locals(1) pub proc add_fungible_asset - # Create the asset key from the asset. + # Get the current asset using `peek_asset`. # --------------------------------------------------------------------------------------------- - exec.build_fungible_asset_vault_key - # => [ASSET_KEY, faucet_id_prefix, faucet_id_suffix, 0, amount, vault_root_ptr] + # store the vault_root_ptr + movup.8 loc_store.0 + # => [ASSET_KEY, ASSET_VALUE] - movup.6 drop - # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - - # Get the asset vault root and read the current asset using the `push_smtpeek` decorator. - # --------------------------------------------------------------------------------------------- + dupw loc_load.0 movdn.4 + # => [ASSET_KEY, vault_root_ptr, ASSET_KEY, ASSET_VALUE] - padw dup.11 - # => [vault_root_ptr, pad(4), ASSET_KEY, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] + exec.peek_asset + # => [CUR_VAULT_VALUE, ASSET_KEY, ASSET_VALUE] - # the current asset may be the empty word if it does not exist and so its faucet id would be zeroes - # we therefore overwrite the faucet id with the faucet id from ASSET to account for this edge case - mem_loadw_be swapw - # => [ASSET_KEY, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] + # since we have peeked the value, we need to later assert that the actual value matches this + # one, so we'll keep a copy for later + swapw dupw.1 + # => [CUR_VAULT_VALUE, ASSET_KEY, CUR_VAULT_VALUE, ASSET_VALUE] - exec.smt::peek adv_loadw - # => [CUR_VAULT_VALUE, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - swapw - # => [VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - dupw.1 - # => [CUR_VAULT_VALUE, VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] + # the current vault value may be the empty word if it does not exist and so its faucet id would + # be zeros. We therefore overwrite the faucet id with the faucet id from ASSET_VALUE to account + # for this edge case drop drop - # => [[0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - movup.11 movup.11 - # => [[faucet_id_prefix, faucet_id_suffix, 0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, amount, vault_root_ptr] + dup.11 dup.11 + # => [CURRENT_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE, ASSET_VALUE] - # Check the new amount does not exceed the maximum allowed amount and add the two - # fungible assets together. - # --------------------------------------------------------------------------------------------- - - # arrange amounts - movup.3 movup.12 dup - # => [amount, amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] + movupw.3 + # => [ASSET_VALUE, CURRENT_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE] - # compute max_amount - cur_amount - push.FUNGIBLE_ASSET_MAX_AMOUNT dup.3 sub - # => [(max_amount - cur_amount), amount, amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, - # CUR_VAULT_VALUE, vault_root_ptr] + # Merge the assets. + # --------------------------------------------------------------------------------------------- - # assert amount + cur_amount < max_amount - lte assert.err=ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED - # => [amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] + exec.fungible_asset::merge + # => [MERGED_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE] - # add asset amounts - add movdn.3 - # => [ASSET', VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] + # store a copy of MERGED_ASSET_VALUE for returning + movdnw.2 dupw.2 + # => [MERGED_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] - # Create the asset key and insert the updated asset. + # Insert the merged asset. # --------------------------------------------------------------------------------------------- - # create the asset key to prepare insertion of the asset into the vault - dupw movdnw.3 - # => [ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] - dupw - # => [ASSET', ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] - push.0 swap.4 drop - # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] - swapw - # => [ASSET', ASSET_KEY', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] + # load the vault root + padw loc_load.0 mem_loadw_be + # => [VAULT_ROOT, MERGED_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] - # pad empty word for insertion - padw - # => [EMPTY_WORD, ASSET', ASSET_KEY', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] + movdnw.2 padw + # => [EMPTY_WORD, MERGED_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] # check if amount of new asset is zero # if it is zero, insert EMPTY_WORD to keep the merkle tree sparse - dup.7 eq.0 - # => [is_amount_zero, EMPTY_WORD, ASSET', ASSET_KEY', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] + dupw.1 + exec.fungible_asset::get_amount + eq.0 + # => [is_amount_zero, EMPTY_WORD, MERGED_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] # If is_amount_zero EMPTY_WORD remains. - # If !is_amount_zero ASSET' remains. + # If !is_amount_zero MERGED_ASSET_VALUE remains. cdropw - # => [EMPTY_WORD_OR_ASSET', ASSET_KEY', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] + # => [EMPTY_WORD_OR_MERGED_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] - # update asset in vault and assert the old value is equivalent to the value provided via the - # decorator + # update asset in vault exec.smt::set - # => [PREV_ASSET, VAULT_ROOT', CUR_VAULT_VALUE, ASSET', vault_root_ptr] + # => [PREV_VAULT_VALUE, NEW_VAULT_ROOT, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] + # assert PREV_VAULT_VALUE = CUR_VAULT_VALUE to make sure peek_asset returned the correct asset movupw.2 assert_eqw.err=ERR_VAULT_ADD_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID - # => [VAULT_ROOT', ASSET', vault_root_ptr] + # => [NEW_VAULT_ROOT, MERGED_ASSET_VALUE] # update the vault root - movup.8 mem_storew_be dropw - # => [ASSET'] + loc_load.0 mem_storew_be dropw + # => [MERGED_ASSET_VALUE] + # => [ASSET_VALUE'] end #! Add the specified non-fungible asset to the vault. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. -#! - ASSET is the non-fungible asset that is added to the vault. +#! - ASSET_KEY is the vault key of the non-fungible asset that is added to the vault. +#! - ASSET_VALUE is the non-fungible asset that is added to the vault. #! #! Panics if: #! - the vault already contains the same non-fungible asset. pub proc add_non_fungible_asset - # Build the asset key from the non-fungible asset. - # --------------------------------------------------------------------------------------------- - - dupw exec.build_non_fungible_asset_vault_key - # => [ASSET_KEY, ASSET, vault_root_ptr] - # Load VAULT_ROOT and insert asset. # --------------------------------------------------------------------------------------------- padw dup.12 - # => [vault_root_ptr, pad(4), ASSET_KEY, ASSET, vault_root_ptr] + # => [vault_root_ptr, pad(4), ASSET_KEY, ASSET_VALUE, vault_root_ptr] + mem_loadw_be swapw - # => [ASSET_KEY, VAULT_ROOT, ASSET, vault_root_ptr] + # => [ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr] + dupw.2 - # => [ASSET, ASSET_KEY, VAULT_ROOT, ASSET, vault_root_ptr] + # => [ASSET_VALUE, ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr] # insert asset into vault exec.smt::set - # => [OLD_VAL, VAULT_ROOT', ASSET, vault_root_ptr] + # => [OLD_VAL, VAULT_ROOT', ASSET_VALUE, vault_root_ptr] # assert old value was empty padw assert_eqw.err=ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS - # => [VAULT_ROOT', ASSET, vault_root_ptr] + # => [VAULT_ROOT', ASSET_VALUE, vault_root_ptr] # update the vault root movup.8 mem_storew_be dropw - # => [ASSET] + # => [ASSET_VALUE] end #! Add the specified asset to the vault. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET'] +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE'] #! #! Where: -#! - ASSET is the asset that is added to the vault. +#! - ASSET_KEY is the vault key of the asset that is added to the vault. +#! - ASSET_VALUE is the value of the asset that is added to the vault. #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. -#! - ASSET' final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. +#! - ASSET_VALUE' final asset in the account vault defined as follows: +#! - If ASSET_VALUE is a non-fungible asset, then ASSET_VALUE' is the same as ASSET_VALUE. +#! - If ASSET_VALUE is a fungible asset, then ASSET_VALUE' is the total fungible asset in the account vault +#! after ASSET_VALUE was added to it. #! #! Panics if: #! - the asset is not valid. -#! - the total value of two fungible assets is greater than or equal to 2^63. +#! - the total value of two fungible assets is greater than FUNGIBLE_ASSET_MAX_AMOUNT. #! - the vault already contains the same non-fungible asset. pub proc add_asset # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE, vault_root_ptr] # add the asset to the asset vault if.true # validate the fungible asset exec.asset::validate_fungible_asset - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE, vault_root_ptr] exec.add_fungible_asset - # => [ASSET'] + # => [ASSET_VALUE'] else # validate the non-fungible asset exec.asset::validate_non_fungible_asset - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE, vault_root_ptr] exec.add_non_fungible_asset - # => [ASSET'] + # => [ASSET_VALUE'] end end # REMOVE ASSET # ================================================================================================= -#! Remove the specified fungible asset from the vault. +#! Splits ASSET_VALUE off the existing asset in the vault associated with the ASSET_KEY. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET] +#! For instance, if ASSET_KEY points to a fungible asset with amount 100, and ASSET_VALUE has +#! amount 30, then a fungible asset with amount 70 remains in the vault. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the fungible asset to remove from the vault. +#! - ASSET_KEY is the asset vault key of the fungible asset to remove from the vault. +#! - ASSET_VALUE is the fungible asset that was removed from the vault. #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. #! #! Locals: -#! - 0..4: ASSET +#! - 0..4: ASSET_VALUE #! #! Panics if: #! - the amount of the asset in the vault is less than the amount to be removed. @locals(4) pub proc remove_fungible_asset - exec.build_fungible_asset_vault_key - # => [ASSET_KEY, ASSET, vault_root_ptr] - dupw movdnw.2 - # => [ASSET_KEY, ASSET, ASSET_KEY, vault_root_ptr] + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, vault_root_ptr] dup.12 movdn.4 - # => [ASSET_KEY, vault_root_ptr, ASSET, ASSET_KEY, vault_root_ptr] + # => [ASSET_KEY, vault_root_ptr, ASSET_VALUE, ASSET_KEY, vault_root_ptr] exec.peek_asset - # => [PEEKED_ASSET, ASSET, ASSET_KEY, vault_root_ptr] + # => [PEEKED_ASSET_VALUE, ASSET_VALUE, ASSET_KEY, vault_root_ptr] movdnw.2 - # => [ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - # store ASSET so we can return it later + # store ASSET_VALUE so we can return it later loc_storew_be.0 - # => [ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] dup.3 dup.12 - # => [peeked_amount, amount, ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [peeked_amount, amount, ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] # assert amount <= peeked_amount lte assert.err=ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW - # => [ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] - # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] dup.11 movup.4 - # => [amount, peeked_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [amount, peeked_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] # compute peeked_amount - amount sub - # => [new_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [new_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] movdn.3 - # => [[faucet_id_prefix, faucet_id_suffix, new_amount], ASSET_KEY, PEEKED_ASSET, vault_root_ptr] - # => [ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, new_amount], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] padw dup.7 - # => [new_amount, EMPTY_WORD, ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [new_amount, EMPTY_WORD, ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] eq.0 - # => [is_new_amount_zero, EMPTY_WORD, ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [is_new_amount_zero, EMPTY_WORD, ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] # If is_new_amount_zero EMPTY_WORD remains. - # If !is_new_amount_zero ASSET' remains. + # If !is_new_amount_zero ASSET_VALUE' remains. cdropw - # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] dup.12 padw movup.4 mem_loadw_be - # => [VAULT_ROOT, EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] + # => [VAULT_ROOT, EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] movdnw.2 - # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, VAULT_ROOT, PEEKED_ASSET, vault_root_ptr] + # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, VAULT_ROOT, PEEKED_ASSET_VALUE, vault_root_ptr] # update asset in vault and assert the old value is equivalent to the peeked value provided # via peek_asset exec.smt::set - # => [OLD_VALUE, NEW_VAULT_ROOT, PEEKED_ASSET, vault_root_ptr] + # => [OLD_VALUE, NEW_VAULT_ROOT, PEEKED_ASSET_VALUE, vault_root_ptr] # assert OLD_VALUE == PEEKED_ASSET movupw.2 assert_eqw.err=ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID @@ -379,53 +364,53 @@ pub proc remove_fungible_asset # => [NEW_VAULT_ROOT] loc_loadw_be.0 - # => [ASSET] + # => [ASSET_VALUE] end #! Remove the specified non-fungible asset from the vault. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET] +#! Note that the ASSET_VALUE is only needed to check against the asset that was removed from the +#! vault. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the non-fungible asset to remove from the vault. +#! - ASSET_KEY is the asset vault key of the non-fungible asset to remove from the vault. +#! - ASSET_VALUE is the non-fungible asset that was removed from the vault. #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. #! #! Panics if: #! - the non-fungible asset is not found in the vault. pub proc remove_non_fungible_asset - # build non-fungible asset key - dupw exec.build_non_fungible_asset_vault_key padw - # => [pad(4), ASSET_KEY, ASSET, vault_root_ptr] - # load vault root - dup.12 mem_loadw_be - # => [VAULT_ROOT, ASSET_KEY, ASSET, vault_root_ptr] + padw dup.12 mem_loadw_be + # => [VAULT_ROOT, ASSET_KEY, ASSET_VALUE, vault_root_ptr] # prepare insertion of an EMPTY_WORD into the vault at the asset key to remove the asset swapw padw - # => [EMPTY_WORD, ASSET_KEY, VAULT_ROOT, ASSET, vault_root_ptr] + # => [EMPTY_WORD, ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr] - # update asset in vault + # insert empty word into the vault to remove the asset exec.smt::set - # => [OLD_VAL, VAULT_ROOT', ASSET, vault_root_ptr] + # => [REMOVED_ASSET_VALUE, NEW_VAULT_ROOT, ASSET_VALUE, vault_root_ptr] - # assert old value was not empty (we only need to check ASSET[3] which is the faucet id) - eq.0 assertz.err=ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND drop drop drop - # => [VAULT_ROOT', ASSET, vault_root_ptr] + dupw.2 assert_eqw.err=ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND + # => [NEW_VAULT_ROOT, ASSET_VALUE, vault_root_ptr] # update the vault root movup.8 mem_storew_be dropw - # => [ASSET] + # => [ASSET_VALUE] end #! Remove the specified asset from the vault. #! -#! Inputs: [ASSET, vault_root_ptr] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE, vault_root_ptr] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to remove from the vault. +#! - ASSET_KEY is the asset vault key of the asset to remove from the vault. +#! - ASSET_VALUE is the value of the asset to remove from the vault. #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. #! #! Panics if: @@ -435,15 +420,15 @@ end pub proc remove_asset # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET, vault_root_ptr] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE, vault_root_ptr] # remove the asset from the asset vault if.true exec.remove_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] else exec.remove_non_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] end end @@ -453,25 +438,25 @@ end #! Builds the vault key of a non fungible asset. The asset is NOT validated and therefore must #! be a valid non-fungible asset. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_VALUE] #! Outputs: [ASSET_KEY] #! #! Where: -#! - ASSET is the non-fungible asset for which the vault key is built. +#! - ASSET_VALUE is the non-fungible asset for which the vault key is built. #! - ASSET_KEY is the vault key of the non-fungible asset. pub proc build_non_fungible_asset_vault_key # create the asset key from the non-fungible asset by swapping hash0 with the faucet id # => [faucet_id_prefix, hash2, hash1, hash0] swap.3 - # => [hash0, hash2, hash1 faucet_id_prefix] + # => [hash0, hash2, hash1, faucet_id_prefix] # disassemble hash0 into u32 limbs u32split swap - # => [hash0_lo, hash0_hi, hash2, hash1 faucet_id_prefix] + # => [hash0_lo, hash0_hi, hash2, hash1, faucet_id_prefix] # set the fungible bit to 0 u32and.INVERSE_FUNGIBLE_BITMASK_U32 - # => [hash0_lo', hash0_hi, hash2, hash1 faucet_id_prefix] + # => [hash0_lo', hash0_hi, hash2, hash1, faucet_id_prefix] # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part swap mul.0x0100000000 add @@ -483,11 +468,11 @@ end #! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must #! be a valid fungible asset. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET_KEY, ASSET] +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: -#! - ASSET is the fungible asset for which the vault key is built. +#! - ASSET_VALUE is the fungible asset for which the vault key is built. #! - ASSET_KEY is the vault key of the fungible asset. pub proc build_fungible_asset_vault_key # => [faucet_id_prefix, faucet_id_suffix, 0, amount] @@ -498,3 +483,23 @@ pub proc build_fungible_asset_vault_key dup.3 dup.3 # => [faucet_id_prefix, faucet_id_suffix, 0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] end + +#! TODO(expand_assets): Temporary procedure for building an asset vault key. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +pub proc build_asset_vault_key + # check the first element, it will be: + # - zero for a fungible asset + # - non zero for a non-fungible asset + dup.2 eq.0 + # => [is_fungible_asset, ASSET_VALUE] + + if.true + exec.build_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] + else + dupw exec.build_non_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] + end +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 554f94b04e..15ac66e1a9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -568,13 +568,13 @@ async fn test_merge_fungible_asset_success() -> anyhow::Result<()> { swapw dropw end ", - ASSETA = Word::from(asset_a), - ASSETB = Word::from(asset_b), + ASSETA = asset_a.to_value_word(), + ASSETB = asset_b.to_value_word(), ); let exec_output = CodeExecutor::with_default_host().run(&code).await?; - assert_eq!(exec_output.get_stack_word_be(0), Word::from(merged_asset)); + assert_eq!(exec_output.get_stack_word_be(0), merged_asset.to_value_word()); } Ok(()) @@ -609,8 +609,8 @@ async fn test_merge_fungible_asset_fails_when_max_amount_exceeded() -> anyhow::R swapw dropw end ", - ASSETA = Word::from(asset_a), - ASSETB = Word::from(asset_b), + ASSETA = asset_a.to_value_word(), + ASSETB = asset_b.to_value_word(), ); let exec_output = CodeExecutor::with_default_host().run(&code).await; From 9b4e83b368ebf2e29094514320705af8e2dc77cb Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 30 Jan 2026 16:04:10 +0100 Subject: [PATCH 006/100] feat: refactor `faucet.masm` --- .../asm/kernels/transaction/lib/faucet.masm | 102 +++++++++--------- .../src/kernel_tests/tx/test_faucet.rs | 18 ++-- 2 files changed, 64 insertions(+), 56 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index b2aad39f10..f514d7d359 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -5,23 +5,19 @@ use $kernel::asset_vault use $kernel::memory use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT -# ERRORS -# ================================================================================================= - -const ERR_FAUCET_BURN_NON_FUNGIBLE_ASSET_CAN_ONLY_BE_CALLED_ON_NON_FUNGIBLE_FAUCET="the burn_non_fungible_asset procedure can only be called on a non-fungible faucet" - # FUNGIBLE ASSETS # ================================================================================================== #! Mints a fungible asset associated with the fungible faucet the transaction is being executed #! against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [NEW_ASSET_VALUE] #! #! Where: -#! - amount is the amount of the fungible asset to mint. -#! - ASSET is the asset that was minted. +#! - ASSET_KEY is the vault key of the asset to mint. +#! - ASSET_VALUE is the value of the asset value to mint. +#! - NEW_ASSET_VALUE is ASSET_VALUE merged with the existing vault asset value, if any. #! #! Panics if: #! - the transaction is not being executed against a fungible faucet. @@ -32,25 +28,26 @@ pub proc mint_fungible_asset # that the asset is valid exec.account::get_id exec.asset::validate_fungible_asset_origin - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr - movdn.4 - # => [ASSET, input_vault_root_ptr] + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr] # add the asset to the input vault for asset preservation exec.asset_vault::add_fungible_asset - # => [ASSET] + # => [NEW_ASSET_VALUE] end #! Burns a fungible asset associated with the fungible faucet the transaction is being executed #! against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was burned. +#! - ASSET_KEY is the vault key of the asset to burn. +#! - ASSET_VALUE is the value of the asset value to burn. #! #! Panics if: #! - the transaction is not being executed against a fungible faucet. @@ -62,15 +59,15 @@ proc burn_fungible_asset # and that the asset is valid exec.account::get_id exec.asset::validate_fungible_asset_origin - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr - movdn.4 - # => [ASSET, input_vault_root_ptr] + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr] # remove the asset from the input vault for asset preservation exec.asset_vault::remove_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] end # NON-FUNGIBLE ASSETS @@ -79,11 +76,14 @@ end #! Mints a non-fungible asset associated with the non-fungible faucet the transaction is being #! executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [NEW_ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was minted. +#! - ASSET_KEY is the vault key of the asset to mint. +#! - ASSET_VALUE is the value of the asset value to mint. +#! - NEW_ASSET_VALUE is identical to ASSET_VALUE. This is to maintain API uniformity with +#! mint_fungible_asset. #! #! Panics if: #! - the transaction is not being executed against a non-fungible faucet. @@ -94,28 +94,29 @@ proc mint_non_fungible_asset # and that the asset is valid exec.account::get_id swap drop - # => [faucet_id_prefix, ASSET] + # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] exec.asset::validate_non_fungible_asset_origin - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr - movdn.4 - # => [ASSET, input_vault_root_ptr] + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr] # add the non-fungible asset to the input vault for asset preservation exec.asset_vault::add_non_fungible_asset - # => [ASSET] + # => [NEW_ASSET_VALUE] end #! Burns a non-fungible asset associated with the non-fungible faucet the transaction is being #! executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was burned. +#! - ASSET_KEY is the vault key of the asset to burn. +#! - ASSET_VALUE is the value of the asset value to burn. #! #! Panics if: #! - the transaction is not being executed against a non-fungible faucet. @@ -126,18 +127,18 @@ proc burn_non_fungible_asset # that the asset is valid exec.account::get_id swap drop - # => [faucet_id_prefix, ASSET] + # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] exec.asset::validate_non_fungible_asset_origin - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] # remove the non-fungible asset from the input vault for asset preservation exec.memory::get_input_vault_root_ptr - movdn.4 - # => [ASSET, input_vault_root_ptr] + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr] exec.asset_vault::remove_non_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] end # PUBLIC INTERFACE @@ -145,11 +146,15 @@ end #! Mint an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [NEW_ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was minted. +#! - ASSET_KEY is the vault key of the asset to mint. +#! - ASSET_VALUE is the value of the asset value to mint. +#! - For fungible assets: NEW_ASSET_VALUE is ASSET_VALUE merged with the existing vault asset +#! value, if any. +#! - For non-fungible assets: NEW_ASSET_VALUE is identical to ASSET_VALUE. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -162,26 +167,27 @@ end pub proc mint # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true # mint the fungible asset exec.mint_fungible_asset - # => [ASSET] + # => [NEW_ASSET_VALUE] else # mint the non-fungible asset exec.mint_non_fungible_asset - # => [ASSET] + # => [NEW_ASSET_VALUE] end end #! Burn an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was burned. +#! - ASSET_KEY is the vault key of the asset to burn. +#! - ASSET_VALUE is the value of the asset value to burn. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -195,15 +201,15 @@ end pub proc burn # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true # burn the fungible asset exec.burn_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] else # burn the non-fungible asset exec.burn_non_fungible_asset - # => [ASSET] + # => [ASSET_VALUE] end end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 57fd43f3d2..77660cebf6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -447,27 +447,29 @@ async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { exec.prologue::prepare_transaction # add non-fungible asset to the vault - exec.memory::get_input_vault_root_ptr push.{non_fungible_asset} + exec.memory::get_input_vault_root_ptr + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} exec.asset_vault::add_non_fungible_asset dropw # check that the non-fungible asset is presented in the input vault exec.memory::get_input_vault_root_ptr - push.{ASSET_KEY} + push.{NON_FUNGIBLE_ASSET_KEY} exec.asset_vault::get_asset - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} assert_eqw.err="input vault should contain the asset" # burn the non-fungible asset - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} call.mock_faucet::burn # assert the correct asset is returned - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} assert_eqw.err="burnt asset does not match expected asset" # assert the input vault has been updated and does not have the burnt asset exec.memory::get_input_vault_root_ptr - push.{ASSET_KEY} + push.{NON_FUNGIBLE_ASSET_KEY} exec.asset_vault::get_asset # the returned word should be empty, indicating the asset is absent padw assert_eqw.err="input vault should not contain burned asset" @@ -475,8 +477,8 @@ async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { dropw end "#, - ASSET_KEY = non_fungible_asset_burnt.vault_key(), - non_fungible_asset = Word::from(non_fungible_asset_burnt), + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset_burnt.to_key_word(), + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset_burnt.to_value_word(), ); tx_context.execute_code(&code).await?; From 09ac6173df593625de577487345d698a2d9f507e Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 30 Jan 2026 16:44:44 +0100 Subject: [PATCH 007/100] feat: refactor `account.masm` --- .../asm/kernels/transaction/lib/account.masm | 88 +++++++++++-------- .../src/asset/vault/vault_key.rs | 4 +- crates/miden-tx/src/host/tx_event.rs | 41 +++++---- 3 files changed, 71 insertions(+), 62 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/account.masm b/crates/miden-protocol/asm/kernels/transaction/lib/account.masm index 3161432585..df64de54b0 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/account.masm @@ -649,15 +649,16 @@ end #! Adds the specified asset to the account vault. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET'] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE'] #! #! Where: -#! - ASSET is the asset that is added to the vault. -#! - ASSET' final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. +#! - ASSET_KEY is the vault key of the asset that is added to the vault. +#! - ASSET_VALUE is the value of the asset that is added to the vault. +#! - ASSET_VALUE' final asset in the account vault defined as follows: +#! - If ASSET_VALUE is a non-fungible asset, then ASSET_VALUE' is the same as ASSET_VALUE. +#! - If ASSET_VALUE is a fungible asset, then ASSET_VALUE' is the total fungible asset in the account vault +#! after ASSET_VALUE was added to it. #! #! Panics if: #! - the asset is not valid. @@ -665,73 +666,82 @@ end #! added. #! - the vault already contains the same non-fungible asset. pub proc add_asset_to_vault - # duplicate the ASSET to be able to emit an event after an asset is being added - dupw - # => [ASSET, ASSET] + # duplicate the asset for the later event and delta update + dupw.1 dupw.1 + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, ASSET_VALUE] - # fetch the account vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, acct_vault_root_ptr, ASSET] + # push the account vault root ptr + exec.memory::get_account_vault_root_ptr movdn.8 + # => [ASSET_KEY, ASSET_VALUE, account_vault_root_ptr, ASSET_KEY, ASSET_VALUE] # emit event to signal that an asset is going to be added to the account vault emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT + # => [ASSET_KEY, ASSET_VALUE, account_vault_root_ptr, ASSET_KEY, ASSET_VALUE] # add the asset to the account vault exec.asset_vault::add_asset - # => [ASSET', ASSET] - - swapw - # => [ASSET, ASSET'] + # => [ASSET_VALUE', ASSET_KEY, ASSET_VALUE] - dupw exec.account_delta::add_asset - # => [ASSET, ASSET'] + movdnw.2 + # => [ASSET_KEY, ASSET_VALUE, ASSET_VALUE'] # emit event to signal that an asset is being added to the account vault emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT - dropw - # => [ASSET'] + # => [ASSET_KEY, ASSET_VALUE, ASSET_VALUE'] + + exec.account_delta::add_asset + # => [ASSET_VALUE'] end #! Removes the specified asset from the account vault. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to remove from the vault. +#! - ASSET_KEY is the asset vault key of the asset to remove from the vault. +#! - ASSET_VALUE is the value of the asset to remove from the vault. #! #! Panics if: #! - the fungible asset is not found in the vault. #! - the amount of the fungible asset in the vault is less than the amount to be removed. #! - the non-fungible asset is not found in the vault. pub proc remove_asset_from_vault - # fetch the vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, acct_vault_root_ptr] + # duplicate the asset for the later event and delta update + dupw.1 dupw.1 + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, ASSET_VALUE] + + # push the vault root ptr + exec.memory::get_account_vault_root_ptr movdn.8 + # => [ASSET_KEY, ASSET_VALUE, account_vault_root_ptr, ASSET_KEY, ASSET_VALUE] # emit event to signal that an asset is going to be removed from the account vault emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT + # => [ASSET_KEY, ASSET_VALUE, account_vault_root_ptr, ASSET_KEY, ASSET_VALUE] # remove the asset from the account vault exec.asset_vault::remove_asset - # => [ASSET] + # => [ASSET_VALUE, ASSET_KEY, ASSET_VALUE] - dupw exec.account_delta::remove_asset - # => [ASSET] + swapw + # => [ASSET_KEY, ASSET_VALUE, ASSET_VALUE] # emit event to signal that an asset is being removed from the account vault emit.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE, ASSET_VALUE] + + exec.account_delta::remove_asset + # => [ASSET_VALUE] end -#! Returns the ASSET associated with the provided asset vault key in the active account's vault. +#! Returns the ASSET_VALUE associated with the provided asset vault key in the active account's vault. #! #! Inputs: [ASSET_KEY] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. pub proc get_asset # get the vault root ptr exec.memory::get_account_vault_root_ptr movdn.4 @@ -743,18 +753,18 @@ pub proc get_asset # get the asset exec.asset_vault::get_asset - # => [ASSET] + # => [ASSET_VALUE] end -#! Returns the ASSET associated with the provided asset vault key in the active account's vault at +#! Returns the ASSET_VALUE associated with the provided asset vault key in the active account's vault at #! the beginning of the transaction. #! #! Inputs: [ASSET_KEY] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. pub proc get_initial_asset # get the vault root associated with the initial vault root of the native account exec.memory::get_account_initial_vault_root_ptr movdn.4 @@ -766,7 +776,7 @@ pub proc get_initial_asset # get the asset exec.asset_vault::get_asset - # => [ASSET] + # => [ASSET_VALUE] end # CODE diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index d33b7e5943..4e5bfd0811 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -111,9 +111,9 @@ impl TryFrom for AssetVaultKey { /// # Errors /// /// Returns an error if: - /// - TODO(programmable_assets) + /// - TODO(expand_assets) fn try_from(key: Word) -> Result { - // TODO(programmable_assets): Implement validation once the new structure of the asset vault + // TODO(expand_assets): Implement validation once the new structure of the asset vault // key is defined. Ok(Self::new_unchecked(key)) diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 1272248ba1..aece8e2ce1 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -193,28 +193,24 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultBeforeAddAsset | TransactionEventId::AccountVaultBeforeRemoveAsset => { - // Expected stack state: [event, ASSET, account_vault_root_ptr] - let asset_word = process.get_stack_word_be(1); - let asset = Asset::try_from(asset_word).map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_before_add_or_remove_asset", - source, - } - })?; + // Expected stack state: [event, ASSET_KEY, ASSET_VALUE, account_vault_root_ptr] + let asset_vault_key = process.get_stack_word_be(1); + let vault_root_ptr = process.get_stack_item(9); - let vault_root_ptr = process.get_stack_item(5); + let asset_vault_key = + AssetVaultKey::try_from(asset_vault_key).expect("TODO(expand_assets)"); let current_vault_root = process.get_vault_root(vault_root_ptr)?; on_account_vault_asset_accessed( base_host, process, - asset.vault_key(), + asset_vault_key, current_vault_root, )? }, TransactionEventId::AccountVaultAfterRemoveAsset => { - // Expected stack state: [event, ASSET] - let asset: Asset = process.get_stack_word_be(1).try_into().map_err(|source| { + // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_remove_asset", source, @@ -224,8 +220,9 @@ impl TransactionEvent { Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset }) }, TransactionEventId::AccountVaultAfterAddAsset => { - // Expected stack state: [event, ASSET] - let asset: Asset = process.get_stack_word_be(1).try_into().map_err(|source| { + // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] + + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_add_asset", source, @@ -240,8 +237,7 @@ impl TransactionEvent { let asset_key = process.get_stack_word_be(1); let vault_root_ptr = process.get_stack_item(5); - // TODO(expand_assets): Consider whether validation is necessary. - let asset_key = AssetVaultKey::new_unchecked(asset_key); + let asset_key = AssetVaultKey::try_from(asset_key).expect("TODO(expand_assets)"); let vault_root = process.get_vault_root(vault_root_ptr)?; on_account_vault_asset_accessed(base_host, process, asset_key, vault_root)? @@ -383,9 +379,9 @@ impl TransactionEvent { TransactionEventId::NoteBeforeAddAsset => { // Expected stack state: [event, ASSET, note_ptr, num_of_assets, note_idx] let note_idx = process.get_stack_item(7).as_int() as usize; + let asset_value = process.get_stack_word_be(1); - let asset_word = process.get_stack_word_be(1); - let asset = Asset::try_from(asset_word).map_err(|source| { + let asset = Asset::try_from(asset_value).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_note_before_add_asset", source, @@ -444,9 +440,12 @@ impl TransactionEvent { }, TransactionEventId::EpilogueBeforeTxFeeRemovedFromAccount => { - // Expected stack state: [event, FEE_ASSET] - let fee_asset = process.get_stack_word_be(1); - let fee_asset = FungibleAsset::try_from(fee_asset) + // Expected stack state: [event, FEE_ASSET_KEY, FEE_ASSET_VALUE] + + let fee_asset_key = process.get_stack_word_be(1); + let fee_asset_value = process.get_stack_word_be(5); + + let fee_asset = FungibleAsset::try_from(fee_asset_value) .map_err(TransactionKernelError::FailedToConvertFeeAsset)?; Some(TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount { fee_asset }) From aff2d9d35bc33e955a857e1558de771c03aa462e Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 30 Jan 2026 16:57:35 +0100 Subject: [PATCH 008/100] feat: refactor `account_delta.masm` --- .../transaction/lib/account_delta.masm | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm index fd1a6df1d4..8eaab8ab1f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm @@ -2,6 +2,7 @@ use $kernel::account use $kernel::asset use $kernel::asset_vault use $kernel::constants::STORAGE_SLOT_TYPE_VALUE +use $kernel::fungible_asset use $kernel::link_map use $kernel::memory use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT @@ -425,30 +426,30 @@ proc update_non_fungible_asset_delta movup.8 loc_store.1 # => [KEY, VALUE0, ...] # this stack state is equivalent to: - # => [ASSET, [was_added, 0, 0, 0], ...] + # => [ASSET_VALUE, [was_added, 0, 0, 0], ...] dup.4 neq.0 - # => [was_added_or_removed, ASSET, [was_added, 0, 0, 0], ...] + # => [was_added_or_removed, ASSET_VALUE, [was_added, 0, 0, 0], ...] # if the asset was added or removed (i.e. if was_added != 0), update the hasher if.true movup.4 - # => [was_added, ASSET, [0, 0, 0], ...] + # => [was_added, ASSET_VALUE, [0, 0, 0], ...] # convert was_added to a boolean # was_added is 1 if the asset was added and 0 - 1 if it was removed eq.1 - # => [was_added, ASSET, [0, 0, 0], ...] + # => [was_added, ASSET_VALUE, [0, 0, 0], ...] movdn.6 - # => [ASSET, [0, 0, was_added, 0], ...] + # => [ASSET_VALUE, [0, 0, was_added, 0], ...] push.DOMAIN_ASSET swap.8 drop - # => [ASSET, [0, 0, was_added, domain], RATE, RATE, PERM] + # => [ASSET_VALUE, [0, 0, was_added, domain], RATE, RATE, PERM] # drop previous RATE elements swapdw dropw dropw - # => [ASSET, [0, 0, was_added, domain], PERM] + # => [ASSET_VALUE, [0, 0, was_added, domain], PERM] exec.rpo256::permute # => [RATE, RATE, PERM] @@ -497,27 +498,32 @@ end #! #! Assumes the asset is valid, so it should be called after asset_vault::add_asset. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET is the asset. +#! - ASSET_KEY is the vault key of the asset that is added. +#! - ASSET_VALUE is the value of the asset that is added. pub proc add_asset # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true - exec.asset_vault::build_fungible_asset_vault_key swapw - # => [ASSET, ASSET_KEY] + swapw + # => [ASSET_VALUE, ASSET_KEY] - exec.::$kernel::util::asset::get_balance_from_fungible_asset + exec.fungible_asset::get_amount movdn.4 # => [ASSET_KEY, amount] exec.add_fungible_asset # => [] else + # asset key is unused for now + dropw + # => [ASSET_VALUE] + exec.add_non_fungible_asset # => [] end @@ -528,27 +534,32 @@ end #! Assumes the asset is valid, so it should be called after asset_vault::remove_asset #! (which would abort if the asset is invalid). #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET is the asset. +#! - ASSET_KEY is the vault key of the asset that is removed. +#! - ASSET_VALUE is the value of the asset that is removed. pub proc remove_asset # check if the asset is a fungible asset exec.asset::is_fungible_asset - # => [is_fungible_asset, ASSET, vault_root_ptr] + # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true - exec.asset_vault::build_fungible_asset_vault_key swapw - # => [ASSET, ASSET_KEY] + swapw + # => [ASSET_VALUE, ASSET_KEY] - exec.::$kernel::util::asset::get_balance_from_fungible_asset + exec.fungible_asset::get_amount movdn.4 # => [ASSET_KEY, amount] exec.remove_fungible_asset # => [] else + # asset key is unused for now + dropw + # => [ASSET_VALUE] + exec.remove_non_fungible_asset # => [] end @@ -630,7 +641,7 @@ end #! Adds the given non-fungible asset to the non-fungible asset vault delta. #! -#! ASSET must be a valid non-fungible asset. +#! ASSET_VALUE must be a valid non-fungible asset. #! #! If the key does not exist in the delta map, the non-fungible asset's was_added value is 0. #! When it is added to the account vault, was_added is incremented by 1; when it is removed from @@ -644,29 +655,29 @@ end #! 0 -> no change to the asset #! +1 -> asset was added #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET is the non-fungible asset to be added. +#! - ASSET_VALUE is the non-fungible asset to be added. pub proc add_non_fungible_asset dupw exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET, ASSET] + # => [non_fungible_delta_map_ptr, ASSET_VALUE, ASSET_VALUE] # retrieve the current delta # contains_key can be ignored because the default value is an empty word and the # was_added value is therefore 0 exec.link_map::get drop - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET] + # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] add.1 - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET] + # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] movupw.2 - # => [ASSET, was_added, 0, 0, 0, EMPTY_WORD] + # => [ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET, was_added, 0, 0, 0, EMPTY_WORD] + # => [non_fungible_delta_map_ptr, ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] exec.link_map::set drop # => [] @@ -674,33 +685,33 @@ end #! Removes the given non-fungible asset from the non-fungible asset vault delta. #! -#! ASSET must be a valid non-fungible asset. +#! ASSET_VALUE must be a valid non-fungible asset. #! #! See add_non_fungible_asset for documentation. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET is the non-fungible asset to be removed. +#! - ASSET_VALUE is the value of the asset that is removed. pub proc remove_non_fungible_asset dupw exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET, ASSET] + # => [non_fungible_delta_map_ptr, ASSET_VALUE, ASSET_VALUE] # retrieve the current delta # contains_key can be ignored because the default value is an empty word and the # was_added value is therefore 0 exec.link_map::get drop - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET] + # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] sub.1 - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET] + # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] movupw.2 - # => [ASSET, was_added, 0, 0, 0, EMPTY_WORD] + # => [ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET, was_added, 0, 0, 0, EMPTY_WORD] + # => [non_fungible_delta_map_ptr, ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] exec.link_map::set drop # => [] From 7137504b11845467135b927562e8a7c9f3e8f705 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 11:39:37 +0100 Subject: [PATCH 009/100] feat: refactor `epilogue.masm` --- .../asm/kernels/transaction/lib/epilogue.masm | 61 ++++++++++++------- .../asm/kernels/transaction/main.masm | 11 ++-- crates/miden-tx/src/host/tx_event.rs | 1 - 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 63a8f92a61..8fc298af00 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -1,5 +1,7 @@ use $kernel::account use $kernel::account_delta +use $kernel::asset::ASSET_SIZE +use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET use $kernel::asset_vault use $kernel::constants::NOTE_MEM_SIZE use $kernel::memory @@ -151,7 +153,7 @@ proc build_output_vault # output_notes_end_ptr] # compute the end pointer for output note asset looping - dup.3 mul.4 add swap + dup.3 mul.ASSET_SIZE add swap # => [assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, note_data_ptr, # output_notes_end_ptr] @@ -168,8 +170,14 @@ proc build_output_vault # num_assets, note_data_ptr, output_notes_end_ptr] # read the output note asset from memory - padw dup.5 mem_loadw_be - # => [ASSET, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, + # load the asset value + padw dup.5 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, + # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] + + # load the asset key + padw dup.9 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] # insert output note asset into output vault @@ -178,7 +186,7 @@ proc build_output_vault # note_data_ptr, output_notes_end_ptr] # increment assets_start_ptr and asses if we should loop again - add.4 dup.1 dup.1 neq + add.ASSET_SIZE dup.1 dup.1 neq # => [should_loop, assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, # note_data_ptr, output_notes_end_ptr] end @@ -277,19 +285,23 @@ end #! reference block as the faucet ID. #! #! Inputs: [fee_amount] -#! Outputs: [FEE_ASSET] +#! Outputs: [FEE_ASSET_KEY, FEE_ASSET_VALUE] #! #! Where: #! - fee_amount is the computed fee amount of the transaction in the native asset. -#! - FEE_ASSET is the fungible asset with amount set to fee_amount and the faucet ID set to the -#! native asset. +#! - FEE_ASSET_KEY is the asset vault key of the fee asset. +#! - FEE_ASSET_VALUE is the fungible asset with amount set to fee_amount and the faucet ID set to +#! the native asset. proc build_native_fee_asset exec.memory::get_native_asset_id # => [native_asset_id_prefix, native_asset_id_suffix, fee_amount] push.0 movdn.2 # => [native_asset_id_prefix, native_asset_id_suffix, 0, fee_amount] - # => [FEE_ASSET] + # => [FEE_ASSET_VALUE] + + exec.asset_vault::build_fungible_asset_vault_key + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] end #! Computes the fee of this transaction and removes the asset from the native account's vault. @@ -300,12 +312,11 @@ end #! check. That's okay, because the logic is entirely determined by the transaction kernel. #! #! Inputs: [] -#! Outputs: [FEE_ASSET] +#! Outputs: [FEE_ASSET_VALUE] #! #! Where: #! - fee_amount is the computed fee amount of the transaction in the native asset. -#! - FEE_ASSET is the fungible asset with amount set to fee_amount and the faucet ID set to the -#! native asset. +#! - FEE_ASSET_VALUE is the fee asset with the faucet ID set to the native asset. #! #! Panics if: #! - the account vault does not contain the computed fee. @@ -316,10 +327,10 @@ proc compute_and_remove_fee # build the native asset from the fee amount exec.build_native_fee_asset - # => [FEE_ASSET] + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT_EVENT - # => [FEE_ASSET] + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] # remove the fee from the native account's vault # note that this deliberately does not use account::remove_asset_from_vault, because that @@ -329,13 +340,13 @@ proc compute_and_remove_fee # commitment has already been computed and so any modifications done to the delta at this point # are essentially ignored. - # fetch the vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [FEE_ASSET, acct_vault_root_ptr] + # fetch the vault root ptr + exec.memory::get_account_vault_root_ptr movdn.8 + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, acct_vault_root_ptr] # remove the asset from the account vault exec.asset_vault::remove_fungible_asset - # => [FEE_ASSET] + # => [FEE_ASSET_VALUE] end # TRANSACTION EPILOGUE PROCEDURE @@ -355,18 +366,21 @@ end #! them in the fee and calculating is easiest when the operations are simple. #! #! Inputs: [] -#! Outputs: [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num] +#! Outputs: [ +#! OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, +#! FEE_ASSET_VALUE, tx_expiration_block_num +#! ] #! #! Where: #! - OUTPUT_NOTES_COMMITMENT is the commitment of the output notes. #! - ACCOUNT_UPDATE_COMMITMENT is the hash of the the final account commitment and account #! delta commitment. -#! - FEE_ASSET is the fungible asset used as the transaction fee. +#! - FEE_ASSET_VALUE is the fungible asset used as the transaction fee. #! - tx_expiration_block_num is the transaction expiration block number. #! #! Locals: #! - 0..4: OUTPUT_NOTES_COMMITMENT -#! - 4..8: FEE_ASSET +#! - 4..8: FEE_ASSET_VALUE #! - 8..12: ACCOUNT_DELTA_COMMITMENT #! #! Panics if: @@ -450,8 +464,9 @@ pub proc finalize_transaction # ------ Compute fees ------ exec.compute_and_remove_fee - # => [FEE_ASSET] + # => [FEE_ASSET_VALUE] + # TODO(programmable_assets): Decide what the public tx output of the fee asset looks like. # store fee asset in local loc_storew_be.4 dropw # => [] @@ -499,9 +514,9 @@ pub proc finalize_transaction # load fee asset from local padw loc_loadw_be.4 swapw - # => [ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num] + # => [ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num] # load output notes commitment from local padw loc_loadw_be.0 - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num] + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num] end diff --git a/crates/miden-protocol/asm/kernels/transaction/main.masm b/crates/miden-protocol/asm/kernels/transaction/main.masm index fe0c0d0383..6471f50fe2 100644 --- a/crates/miden-protocol/asm/kernels/transaction/main.masm +++ b/crates/miden-protocol/asm/kernels/transaction/main.masm @@ -54,7 +54,10 @@ const EPILOGUE_END_EVENT=event("miden::protocol::tx::epilogue_end") #! INPUT_NOTES_COMMITMENT, #! account_id_prefix, account_id_suffix, block_num, pad(1) #! ] -#! Outputs: [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num, pad(3)] +#! Outputs: [ +#! OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, +#! FEE_ASSET_VALUE, tx_expiration_block_num, pad(3) +#! ] #! #! Where: #! - BLOCK_COMMITMENT is the reference block for the transaction execution. @@ -65,7 +68,7 @@ const EPILOGUE_END_EVENT=event("miden::protocol::tx::epilogue_end") #! - OUTPUT_NOTES_COMMITMENT is the commitment to the notes created by the transaction. #! - ACCOUNT_UPDATE_COMMITMENT is the hash of the the final account commitment and account #! delta commitment. -#! - FEE_ASSET is the fungible asset used as the transaction fee. +#! - FEE_ASSET_VALUE is the fungible asset used as the transaction fee. @locals(1) proc main # Prologue @@ -167,11 +170,11 @@ proc main # execute the transaction epilogue exec.epilogue::finalize_transaction - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num, pad(16)] + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num, pad(16)] # truncate the stack to contain 16 elements in total repeat.13 movup.13 drop end - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num, pad(3)] + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num, pad(3)] emit.EPILOGUE_END_EVENT end diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index aece8e2ce1..aa22167193 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -442,7 +442,6 @@ impl TransactionEvent { TransactionEventId::EpilogueBeforeTxFeeRemovedFromAccount => { // Expected stack state: [event, FEE_ASSET_KEY, FEE_ASSET_VALUE] - let fee_asset_key = process.get_stack_word_be(1); let fee_asset_value = process.get_stack_word_be(5); let fee_asset = FungibleAsset::try_from(fee_asset_value) From a8086f15561948cbcbaeb50baba4df2c46bbaa29 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 15:36:15 +0100 Subject: [PATCH 010/100] feat: refactor `output_note.masm` --- .../asm/kernels/transaction/lib/memory.masm | 18 ++ .../kernels/transaction/lib/output_note.masm | 303 ++++++++---------- .../asm/standards/wallets/basic.masm | 20 +- .../src/kernel_tests/tx/test_active_note.rs | 13 +- .../src/kernel_tests/tx/test_output_note.rs | 134 +++++--- crates/miden-testing/src/utils.rs | 8 +- crates/miden-tx/src/host/tx_event.rs | 7 +- 7 files changed, 274 insertions(+), 229 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm index 52d4ba7c8b..ae7d62d7e5 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm @@ -1967,6 +1967,24 @@ pub proc set_output_note_num_assets mem_store end +#! Increments the number of assets in the output note by 1. +#! +#! Inputs: [note_ptr] +#! Outputs: [] +#! +#! Where: +#! - note_ptr is the memory address at which the output note data begins. +#! +#! Panics if: +#! - the number of assets exceeds the maximum allowed number of assets per note. +pub proc increment_output_note_num_assets + dup exec.get_output_note_num_assets add.1 + # => [num_assets + 1, note_ptr] + + swap exec.set_output_note_num_assets + # => [] +end + #! Returns the dirty flag for the assets commitment. #! #! This binary flag specifies whether the assets commitment stored in the specified note is diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index bac56500e6..f74738de5e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -1,13 +1,13 @@ use $kernel::account +use $kernel::asset +use $kernel::fungible_asset use $kernel::memory use $kernel::note -use $kernel::asset use $kernel::constants::MAX_OUTPUT_NOTES_PER_TX -use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT use $kernel::util::note::ATTACHMENT_KIND_NONE -use $kernel::util::note::ATTACHMENT_KIND_WORD use $kernel::util::note::ATTACHMENT_KIND_ARRAY -use miden::core::mem +use $kernel::asset::ASSET_SIZE +use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET use miden::core::word # CONSTANTS @@ -158,15 +158,15 @@ pub proc get_assets_info # word further to make the assets number even (the same way it is done in the # `note::compute_output_note_assets_commitment` procedure) movup.4 exec.memory::get_output_note_asset_data_ptr - # => [assets_data_ptr, ASSETS_COMMITMENT, num_assets] + # => [assets_start_ptr, ASSETS_COMMITMENT, num_assets] - dup dup.6 dup is_odd add - # => [padded_num_assets, assets_data_ptr, assets_data_ptr, ASSETS_COMMITMENT, num_assets] + movdn.4 dup.4 + # => [assets_start_ptr, ASSETS_COMMITMENT, assets_start_ptr, num_assets] - mul.4 add - # => [assets_end_ptr, assets_start_ptr, ASSETS_COMMITMENT, num_assets] + dup.6 mul.ASSET_SIZE add + # => [assets_end_ptr, ASSETS_COMMITMENT, assets_start_ptr, num_assets] - movdn.5 movdn.4 + movdn.5 # => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets] # store the assets data to the advice map using ASSETS_COMMITMENT as a key @@ -180,66 +180,52 @@ end #! Adds the ASSET to the note specified by the index. #! -#! Inputs: [note_idx, ASSET] +#! Inputs: [note_idx, ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: #! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. +#! - ASSET_KEY is the vault key of the asset to add. +#! - ASSET_VALUE is the value of the asset to add. #! #! Panics if: #! - the note index points to a non-existent output note. -#! - the ASSET is malformed (e.g., invalid faucet ID). +#! - the asset key or value are malformed (e.g., invalid faucet ID). #! - the max amount of fungible assets is exceeded. #! - the non-fungible asset already exists in the note. #! - the total number of ASSETs exceeds the maximum of 256. pub proc add_asset # check if the note exists, it must be within [0, num_of_notes] dup exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX - # => [note_idx, ASSET] + # => [note_idx, ASSET_KEY, ASSET_VALUE] # get a pointer to the memory address of the note at which the asset will be stored - dup movdn.5 exec.memory::get_output_note_ptr - # => [note_ptr, ASSET, note_idx] + exec.memory::get_output_note_ptr + # => [note_ptr, ASSET_KEY, ASSET_VALUE] - # get current num of assets - dup exec.memory::get_output_note_num_assets movdn.5 - # => [note_ptr, ASSET, num_of_assets, note_idx] + # duplicate note ptr + dup movdn.9 movdn.9 + # => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr] - # validate the ASSET - movdn.4 exec.asset::validate_asset - # => [ASSET, note_ptr, num_of_assets, note_idx] + # validate the asset + exec.asset::validate_asset + # => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr] # emit event to signal that a new asset is going to be added to the note. emit.NOTE_BEFORE_ADD_ASSET_EVENT - # => [ASSET, note_ptr, num_of_assets, note_idx] + # => [ASSET_KEY, ASSET_VALUE, note_ptr] - # Check if ASSET to add is fungible - exec.asset::is_fungible_asset - # => [is_fungible_asset?, ASSET, note_ptr, num_of_assets, note_idx] + # add the asset to the note + exec.add_asset_raw + # => [note_ptr] - if.true - # ASSET to add is fungible - exec.add_fungible_asset - # => [note_ptr, note_idx] - else - # ASSET to add is non-fungible - exec.add_non_fungible_asset - # => [note_ptr, note_idx] - end - # => [note_ptr, note_idx] + # emit event to signal that a new asset was added to the note. + emit.NOTE_AFTER_ADD_ASSET_EVENT + # => [note_ptr] # update the assets commitment dirty flag to signal that the current assets commitment is not # valid anymore push.1 swap exec.memory::set_output_note_dirty_flag - # => [note_idx] - - # emit event to signal that a new asset was added to the note. - emit.NOTE_AFTER_ADD_ASSET_EVENT - # => [note_idx] - - # drop the note index - drop # => [] end @@ -460,154 +446,125 @@ proc increment_num_output_notes # => [note_idx] end -#! Adds a fungible asset to a note. If the note already holds an asset issued by the same faucet id -#! the two quantities are summed up and the new quantity is stored at the old position in the note. -#! In the other case, the asset is stored at the next available position. -#! Returns the pointer to the note the asset was stored at. +#! Adds the asset to the note specified by the ptr. #! -#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] -#! Outputs: [note_ptr] +#! This procedure attempts to find an asset with the same key in the note's assets. +#! - If the asset is not found, the asset is appended at the end and the number of assets is +#! incremented. +#! - If the asset is found and the asset is +#! - fungible: the existing asset and the new asset are merged together. +#! - non-fungible: the procedure panics since non-fungible assets cannot be merged. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE, note_ptr] +#! Outputs: [] #! #! Where: -#! - ASSET is the fungible asset to be added to the note. +#! - ASSET_KEY is the vault key of the asset to add. +#! - ASSET_VALUE is the value of the asset to add. #! - note_ptr is the pointer to the note the asset will be added to. -#! - num_of_assets is the current number of assets. -#! - note_idx is the index of the note the asset will be added to. #! #! Panics if -#! - the summed amounts exceed the maximum amount of fungible assets. -proc add_fungible_asset - dup.4 exec.memory::get_output_note_asset_data_ptr - # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] +#! - asset is fungible and adding the two asset values would exceed FUNGIBLE_ASSET_MAX_AMOUNT. +#! - asset is non-fungible and the note already contains an asset with the same key. +proc add_asset_raw + dup.8 exec.memory::get_output_note_asset_data_ptr movdn.8 + # => [ASSET_KEY, ASSET_VALUE, asset_ptr, note_ptr] + + # get the number of assets in the output note + dup.9 exec.memory::get_output_note_num_assets + # => [num_assets, ASSET_KEY, ASSET_VALUE, asset_ptr, note_ptr] # compute the pointer at which we should stop iterating - dup dup.7 mul.4 add - # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + mul.ASSET_SIZE dup.9 add movdn.9 + # => [ASSET_KEY, ASSET_VALUE, asset_ptr, asset_end_ptr, note_ptr] - # reorganize and pad the stack, prepare for the loop - movdn.5 movdn.5 padw dup.9 - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - # compute the loop latch - dup dup.10 neq - # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] + # initialize loop variable is_existing_asset to false + push.0 movdn.8 + # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] + + # enter loop if asset_ptr != asset_end_ptr + dup.10 dup.10 neq + # => [has_assets, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] while.true - mem_loadw_be - # => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - dup.4 eq - # => [are_equal, 0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - if.true - # add the asset quantity, we don't overflow here, bc both ASSETs are valid. - movup.2 movup.6 add - # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - # check that we don't overflow bc we use lte - dup lte.FUNGIBLE_ASSET_MAX_AMOUNT - assert.err=ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED - # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - # prepare stack to store the "updated" ASSET'' with the new quantity - movdn.5 - # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # decrease num_of_assets by 1 to offset incrementing it later - movup.9 sub.1 movdn.9 - # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets - 1, note_idx] - - # end the loop we add 0's to the stack to have the correct number of elements - push.0.0 dup.9 push.0 - # => [0, asset_ptr, 0, 0, 0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets - 1, note_idx] - else - # => [0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] - - # drop ASSETs and increment the asset pointer - movup.2 drop push.0.0 movup.9 add.4 dup movdn.10 - # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, - # num_of_assets, note_idx] - - # check if we reached the end of the loop - dup dup.10 neq - end - end - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - # prepare stack for storing the ASSET - movdn.4 dropw - # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # Store the fungible asset, either the combined ASSET or the new ASSET - mem_storew_be dropw drop drop - # => [note_ptr, num_of_assets, note_idx] - - # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_output_note_num_assets - # => [note_ptr, note_idx] -end + padw dup.13 mem_loadw_be + # => [STORED_ASSET_KEY, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] -#! Adds a non-fungible asset to a note at the next available position. -#! Returns the pointer to the note the asset was stored at. -#! -#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] -#! Outputs: [note_ptr, note_idx] -#! -#! Where: -#! - ASSET is the non-fungible asset to be added to the note. -#! - note_ptr is the pointer to the note the asset will be added to. -#! - num_of_assets is the current number of assets. -#! - note_idx is the index of the note the asset will be added to. -#! -#! Panics if: -#! - the non-fungible asset already exists in the note. -proc add_non_fungible_asset - dup.4 exec.memory::get_output_note_asset_data_ptr - # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + exec.word::test_eq + # => [is_matching_key, STORED_ASSET_KEY, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] - # compute the pointer at which we should stop iterating - dup dup.7 mul.4 add - # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + # set is_existing_asset = is_matching_key + swap.13 drop dropw + # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] - # reorganize and pad the stack, prepare for the loop - movdn.5 movdn.5 padw dup.9 - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + # if is_existing_asset, increment asset_ptr by 0 (so the ptr points to the existing asset + # after the loop) + # if !is_existing_asset, increment asset_ptr by ASSET_SIZE + dup.8 not mul.ASSET_SIZE + # => [asset_size_or_0, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] - # compute the loop latch - dup dup.10 neq - # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] + # compute asset_ptr + asset_size_or_0 + movup.10 add movdn.9 + # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] - while.true - # load the asset and compare - mem_loadw_be exec.word::test_eq - assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS - # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # drop ASSET' and increment the asset pointer - dropw movup.5 add.4 dup movdn.6 padw movup.4 - # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, - # num_of_assets, note_idx] - - # check if we reached the end of the loop - dup dup.10 neq + # continue looping if (!is_existing_asset) && asset_ptr != asset_end_ptr + dup.10 dup.10 neq + # => [is_end_reached, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] + + dup.9 not and + # => [should_loop, ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] end - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] + + # after the loop: + # if is_existing_asset: asset_ptr points to the entry where the existing asset is stored + # if !is_existing_asset: asset_ptr points to the entry where the asset can be appended, + # i.e. asset_ptr = asset_end_ptr - # prepare stack for storing the ASSET - movdn.4 dropw - # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + # discard asset end ptr + movup.10 drop + # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, note_ptr] - # end of the loop reached, no error so we can store the non-fungible asset - mem_storew_be dropw drop drop - # => [note_ptr, num_of_assets, note_idx] + movup.8 + # => [is_existing_asset, ASSET_KEY, ASSET_VALUE, asset_ptr, note_ptr] + + if.true + # if the asset exists, do not increment num assets - # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_output_note_num_assets - # => [note_ptr, note_idx] + # abort if the asset is non-fungible since it cannot be merged + exec.asset::is_fungible_asset + assert.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS + # => [ASSET_KEY, ASSET_VALUE, asset_ptr, note_ptr] + + # if the asset is fungible, merge the asset values + # overwrite asset key on the stack with the stored asset value + # note that asset_ptr already stores ASSET_KEY so there is no need to overwrite it + dup.8 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [STORED_ASSET_VALUE, ASSET_VALUE, asset_ptr, note_ptr] + + # merge the two fungible assets + exec.fungible_asset::merge + # => [MERGED_ASSET_VALUE, asset_ptr, note_ptr] + + # store the merged asset value + movup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_storew_be dropw drop + # => [] + else + # if the asset does not exist, increment num assets and append the asset + + # increment number of assets + # this panics if the max allowed number of assets is exceeded + # this implicitly validates that asset_ptr is not out of bounds + movup.9 exec.memory::increment_output_note_num_assets + # => [ASSET_KEY, ASSET_VALUE, asset_ptr] + + # store ASSET_KEY + dup.8 mem_storew_be dropw + # => [ASSET_VALUE, asset_ptr] + + # store ASSET_VALUE + movup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_storew_be dropw + # => [] + end end diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index 837803c09c..d9113a518a 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -1,3 +1,5 @@ +use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET +use ::miden::protocol::asset::ASSET_SIZE use miden::protocol::native_account use miden::protocol::output_note use miden::protocol::active_note @@ -65,16 +67,16 @@ end #! #! Inputs: [] #! Outputs: [] -@locals(1024) +@locals(2048) pub proc add_assets_to_account # write assets to local memory starting at offset 0 - # we have allocated 4 * MAX_ASSETS_PER_NOTE number of locals so all assets should fit + # we have allocated ASSET_SIZE * MAX_ASSETS_PER_NOTE number of locals so all assets should fit # since the asset memory will be overwritten, we don't have to initialize the locals to zero locaddr.0 exec.active_note::get_assets # => [num_of_assets, ptr = 0] # compute the pointer at which we should stop iterating - mul.4 dup.1 add + mul.ASSET_SIZE dup.1 add # => [end_ptr, ptr] # pad the stack and move the pointer to the top @@ -92,13 +94,13 @@ pub proc add_assets_to_account dup movdn.5 # => [ptr, EMPTY_WORD, ptr, end_ptr] - # load the asset - mem_loadw_be - # => [ASSET, ptr, end_ptr] + # load the asset value + add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, ptr, end_ptr] # pad the stack before call padw swapw padw padw swapdw - # => [ASSET, pad(12), ptr, end_ptr] + # => [ASSET_VALUE, pad(12), ptr, end_ptr] # add asset to the account call.receive_asset @@ -109,8 +111,8 @@ pub proc add_assets_to_account # => [EMPTY_WORD, ptr, end_ptr] # increment the pointer and continue looping if ptr != end_ptr - movup.4 add.4 dup dup.6 neq - # => [should_loop, ptr+4, EMPTY_WORD, end_ptr] + movup.4 add.ASSET_SIZE dup dup.6 neq + # => [should_loop, ptr+ASSET_SIZE, EMPTY_WORD, end_ptr] end # => [ptr', EMPTY_WORD, end_ptr] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 9507e3393e..b35dd6bf57 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -19,6 +19,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; +use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_protocol::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::mock_account::MockAccountExt; @@ -204,10 +205,16 @@ async fn test_active_note_get_assets() -> anyhow::Result<()> { for asset in note.assets().iter() { code += &format!( r#" - # assert the asset is correct - dup padw movup.4 mem_loadw_be push.{asset} assert_eqw.err="asset mismatch" push.4 add + dup padw movup.4 mem_loadw_be push.{ASSET_KEY} + assert_eqw.err="asset key mismatch" + + dup padw movup.4 add.{ASSET_VALUE_OFFSET} mem_loadw_be push.{ASSET_VALUE} + assert_eqw.err="asset value mismatch" + + add.{ASSET_SIZE} "#, - asset = Word::from(asset) + ASSET_KEY = asset.vault_key().as_word(), + ASSET_VALUE = Word::from(asset), ); } code diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 318de9ec27..d9ddad2332 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -33,11 +33,14 @@ use miden_protocol::testing::account_id::{ }; use miden_protocol::testing::constants::NON_FUNGIBLE_ASSET_DATA_2; use miden_protocol::transaction::memory::{ + ASSET_SIZE, + ASSET_VALUE_OFFSET, NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, OUTPUT_NOTE_ATTACHMENT_OFFSET, OUTPUT_NOTE_METADATA_HEADER_OFFSET, + OUTPUT_NOTE_NUM_ASSETS_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, }; @@ -388,7 +391,7 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; let recipient = Word::from([0, 1, 2, 3u32]); let tag = NoteTag::with_account_target(faucet_id); - let asset = Word::from(FungibleAsset::new(faucet_id, 10)?); + let asset = FungibleAsset::new(faucet_id, 10)?; let code = format!( " @@ -410,8 +413,8 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { dup assertz.err=\"index of the created note should be zero\" # => [note_idx] - push.{asset} - # => [ASSET, note_idx] + push.{ASSET_VALUE} + # => [ASSET_VALUE, note_idx] call.output_note::add_asset # => [] @@ -423,15 +426,20 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { recipient = recipient, PUBLIC_NOTE = NoteType::Public as u8, tag = tag, - asset = asset, + ASSET_VALUE = asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), - asset, - "asset must be stored at the correct memory location", + asset.to_key_word(), + "asset key must be stored at the correct memory location", + ); + assert_eq!( + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), + asset.to_value_word(), + "asset value must be stored at the correct memory location", ); Ok(()) @@ -447,13 +455,12 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { let recipient = Word::from([0, 1, 2, 3u32]); let tag = NoteTag::with_account_target(faucet_2); - let asset = Word::from(FungibleAsset::new(faucet, 10)?); - let asset_2 = Word::from(FungibleAsset::new(faucet_2, 20)?); - let asset_3 = Word::from(FungibleAsset::new(faucet_2, 30)?); - let asset_2_and_3 = Word::from(FungibleAsset::new(faucet_2, 50)?); + let asset = FungibleAsset::new(faucet, 10)?; + let asset_2 = FungibleAsset::new(faucet_2, 20)?; + let asset_3 = FungibleAsset::new(faucet_2, 30)?; + let asset_2_plus_3 = FungibleAsset::new(faucet_2, 50)?; let non_fungible_asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); - let non_fungible_asset_encoded = Word::from(non_fungible_asset); let code = format!( " @@ -474,19 +481,19 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { # => [note_idx] dup push.{asset} - call.output_note::add_asset + exec.output_note::add_asset # => [note_idx] dup push.{asset_2} - call.output_note::add_asset + exec.output_note::add_asset # => [note_idx] dup push.{asset_3} - call.output_note::add_asset + exec.output_note::add_asset # => [note_idx] push.{nft} - call.output_note::add_asset + exec.output_note::add_asset # => [] # truncate the stack @@ -496,30 +503,69 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { recipient = recipient, PUBLIC_NOTE = NoteType::Public as u8, tag = tag, - asset = asset, - asset_2 = asset_2, - asset_3 = asset_3, - nft = non_fungible_asset_encoded, + asset = asset.to_value_word(), + asset_2 = asset_2.to_value_word(), + asset_3 = asset_3.to_value_word(), + nft = non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; + assert_eq!( + exec_output + .get_kernel_mem_element(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_NUM_ASSETS_OFFSET) + .as_int(), + 3, + "unexpected number of assets in output note", + ); + assert_eq!( exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), - asset, - "asset must be stored at the correct memory location", + asset.to_key_word(), + "asset key must be stored at the correct memory location", + ); + assert_eq!( + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + ASSET_VALUE_OFFSET + ), + asset.to_value_word(), + "asset value must be stored at the correct memory location", ); assert_eq!( - exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), - asset_2_and_3, - "asset_2 and asset_3 must be stored at the same correct memory location", + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + ASSET_SIZE + ), + asset_2_plus_3.to_key_word(), + "asset key must be stored at the correct memory location", + ); + assert_eq!( + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + + OUTPUT_NOTE_ASSETS_OFFSET + + ASSET_SIZE + + ASSET_VALUE_OFFSET + ), + asset_2_plus_3.to_value_word(), + "asset value must be stored at the correct memory location", ); assert_eq!( - exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 8), - non_fungible_asset_encoded, - "non_fungible_asset must be stored at the correct memory location", + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + ASSET_SIZE * 2 + ), + non_fungible_asset.to_key_word(), + "asset key must be stored at the correct memory location", + ); + assert_eq!( + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + + OUTPUT_NOTE_ASSETS_OFFSET + + ASSET_SIZE * 2 + + ASSET_VALUE_OFFSET + ), + non_fungible_asset.to_value_word(), + "asset value must be stored at the correct memory location", ); Ok(()) @@ -916,13 +962,13 @@ async fn test_get_assets() -> anyhow::Result<()> { push.{note_idx} push.{dest_ptr} # => [dest_ptr, note_index] - # write the assets to the memory + # write the assets to memory exec.output_note::get_assets # => [num_assets, dest_ptr, note_index] # assert the number of note assets push.{assets_number} - assert_eq.err="note {note_index} has incorrect assets number" + assert_eq.err="expected note {note_index} to have {assets_number} assets" # => [dest_ptr, note_index] "#, note_idx = note_index, @@ -936,18 +982,30 @@ async fn test_get_assets() -> anyhow::Result<()> { r#" # load the asset stored in memory padw dup.4 mem_loadw_be - # => [STORED_ASSET, dest_ptr, note_index] + # => [STORED_ASSET_KEY, dest_ptr, note_index] + + # assert the asset key matches + push.{NOTE_ASSET_KEY} + assert_eqw.err="expected asset key at asset index {asset_index} of the note\ + {note_index} to be {NOTE_ASSET_KEY}" + # => [dest_ptr, note_index] + + # load the asset stored in memory + padw dup.4 add.{ASSET_VALUE_OFFSET} mem_loadw_be + # => [STORED_ASSET_VALUE, dest_ptr, note_index] - # assert the asset - push.{NOTE_ASSET} - assert_eqw.err="asset {asset_index} of the note {note_index} is incorrect" + # assert the asset value matches + push.{NOTE_ASSET_VALUE} + assert_eqw.err="expected asset value at asset index {asset_index} of the note\ + {note_index} to be {NOTE_ASSET_VALUE}" # => [dest_ptr, note_index] # move the pointer - add.4 - # => [dest_ptr+4, note_index] + add.{ASSET_SIZE} + # => [dest_ptr+ASSET_SIZE, note_index] "#, - NOTE_ASSET = Word::from(*asset), + NOTE_ASSET_KEY = asset.to_key_word(), + NOTE_ASSET_VALUE = asset.to_value_word(), asset_index = asset_index, note_index = note_index, )); @@ -981,9 +1039,9 @@ async fn test_get_assets() -> anyhow::Result<()> { create_note_0 = create_output_note(&p2id_note_0_assets), check_note_0 = check_assets_code(0, 0, &p2id_note_0_assets), create_note_1 = create_output_note(&p2id_note_1_asset), - check_note_1 = check_assets_code(1, 4, &p2id_note_1_asset), + check_note_1 = check_assets_code(1, 8, &p2id_note_1_asset), create_note_2 = create_output_note(&p2id_note_2_assets), - check_note_2 = check_assets_code(2, 8, &p2id_note_2_assets), + check_note_2 = check_assets_code(2, 16, &p2id_note_2_assets), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 1732ff3580..3d051d6c6d 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -104,7 +104,7 @@ pub fn create_p2any_note( " # add first asset - padw dup.4 mem_loadw_be + padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be padw swapw padw padw swapdw call.wallet::receive_asset dropw movup.12 @@ -116,8 +116,8 @@ pub fn create_p2any_note( " # add next asset - add.4 dup movdn.13 - padw movup.4 mem_loadw_be + add.ASSET_SIZE dup movdn.13 + padw movup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be call.wallet::receive_asset dropw movup.12 # => [dest_ptr, pad(12)]", @@ -130,6 +130,8 @@ pub fn create_p2any_note( r#" use mock::account use miden::protocol::active_note + use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET + use ::miden::protocol::asset::ASSET_SIZE use miden::standards::wallets::basic->wallet begin diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index aa22167193..451c9d0d5f 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -377,9 +377,9 @@ impl TransactionEvent { TransactionEventId::NoteAfterCreated => None, TransactionEventId::NoteBeforeAddAsset => { - // Expected stack state: [event, ASSET, note_ptr, num_of_assets, note_idx] - let note_idx = process.get_stack_item(7).as_int() as usize; - let asset_value = process.get_stack_word_be(1); + // Expected stack state: [event, ASSET_KEY, ASSET_VALUE, note_ptr] + let asset_value = process.get_stack_word_be(5); + let note_ptr = process.get_stack_item(9); let asset = Asset::try_from(asset_value).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { @@ -387,6 +387,7 @@ impl TransactionEvent { source, } })?; + let note_idx = note_ptr_to_idx(note_ptr)? as usize; Some(TransactionEvent::NoteBeforeAddAsset { note_idx, asset }) }, From f7d8454bd77905fb885cf353763b69777f0e9a7f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 17:50:16 +0100 Subject: [PATCH 011/100] feat: refactor `prologue.masm` --- .../asm/kernels/transaction/lib/prologue.masm | 104 +++++++----------- .../src/kernel_tests/tx/test_input_note.rs | 33 ++++-- .../src/kernel_tests/tx/test_prologue.rs | 23 ++-- 3 files changed, 77 insertions(+), 83 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index a8992cb09b..136fb8d6d8 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -4,16 +4,15 @@ use miden::core::crypto::hashes::rpo256 use miden::core::word use $kernel::account -use $kernel::account_delta use $kernel::account_id use $kernel::asset_vault +use $kernel::asset::ASSET_SIZE +use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET use $kernel::constants::EMPTY_SMT_ROOT use $kernel::constants::MAX_ASSETS_PER_NOTE use $kernel::constants::MAX_INPUT_NOTES_PER_TX use $kernel::constants::MAX_NOTE_STORAGE_ITEMS use $kernel::constants::NOTE_TREE_DEPTH -use $kernel::constants::STORAGE_SLOT_TYPE_MAP -use $kernel::constants::STORAGE_SLOT_TYPE_VALUE use $kernel::memory # CONSTS @@ -628,83 +627,51 @@ end #! #! Inputs: #! Operand stack: [note_ptr] -#! Advice stack: [assets_count, ASSET_0, ..., ASSET_N] +#! Advice stack: [num_assets, ASSET_KEY_0, ASSET_VALUE_0, ..., ASSET_KEY_N, ASSET_VALUE_N] #! Outputs: #! Operand stack: [] #! Advice stack: [] #! #! Where: #! - note_ptr is the memory location for the input note. -#! - assets_count is the note's assets count. -#! - ASSET_0, ..., ASSET_N are the padded note's assets. +#! - num_assets is the number of note assets. +#! - ASSET_KEY_0, ASSET_VALUE_0, ..., ASSET_KEY_N, ASSET_VALUE_N are the note's assets. proc process_note_assets - # verify and save the assets count + # Validate num_assets and setup commitment computation. # --------------------------------------------------------------------------------------------- adv_push.1 - # => [assets_count, note_ptr] + # => [num_assets, note_ptr] - dup push.MAX_ASSETS_PER_NOTE lte + dup lte.MAX_ASSETS_PER_NOTE assert.err=ERR_PROLOGUE_NUMBER_OF_NOTE_ASSETS_EXCEEDS_LIMIT - # => [assets_count, note_ptr] + # => [num_assets, note_ptr] dup dup.2 exec.memory::set_input_note_num_assets - # => [assets_count, note_ptr] - - # round up the number of assets, to the its padded length - dup u32and.1 add - # => [rounded_num_assets, note_ptr] + # => [num_assets, note_ptr] - # read the note's assets - # --------------------------------------------------------------------------------------------- - - # Stack organization: - # - Top of the stack contains the hash state. The complete state is needed to extract the final - # hash. - # - Followed by the assets_ptr, with the target address used to pipe data from the advice - # provider. - # - Followed by a copy of the note_ptr for later use. - # - Followed by the loop variables, the current counter and rounded_num_assets, laid at this - # depth because dup.15 is an efficient operation. + dup.1 exec.memory::get_input_note_assets_ptr swap + # => [num_assets, assets_ptr, note_ptr] - push.0 movup.2 - # => [note_ptr, counter, rounded_num_assets] + mul.ASSET_SIZE dup.1 add swap + # => [assets_ptr, assets_end_ptr, note_ptr] - dup exec.memory::get_input_note_assets_ptr - # => [assets_ptr, note_ptr, counter, rounded_num_assets] + exec.rpo256::init_no_padding + # => [PERM, PERM, PERM, assets_ptr, assets_end_ptr, note_ptr] - padw padw padw - # => [PERM, PERM, PERM, assets_ptr, note_ptr, counter, rounded_num_assets] - - # loop condition: counter != rounded_num_assets - dup.15 dup.15 neq - # => [should_loop, PERM, PERM, PERM, assets_ptr, note_ptr, counter, rounded_num_assets] - - # loop and read assets from the advice provider - while.true - # read data and compute its digest. See `Advice stack` above for details. - adv_pipe exec.rpo256::permute - # => [PERM, PERM, PERM, assets_ptr+8, note_ptr, counter, rounded_num_assets] - - # update counter - swapw.3 movup.2 add.2 movdn.2 swapw.3 - # => [PERM, PERM, PERM, assets_ptr+8, note_ptr, counter+2, rounded_num_assets] - - # loop condition: counter != rounded_num_assets - dup.15 dup.15 neq - # => [should_loop, PERM, PERM, PERM, assets_ptr+8, note_ptr, counter+2, rounded_num_assets] - end - # => [PERM, PERM, PERM, assets_ptr+8n, note_ptr, counter+2n, rounded_num_assets] + # Compute assets commitment and validate. + # --------------------------------------------------------------------------------------------- + exec.mem::pipe_double_words_to_memory exec.rpo256::squeeze_digest - # => [ASSET_COMMITMENT_COMPUTED, assets_ptr+8n, note_ptr, counter+2n, rounded_num_assets] + # => [COMPUTED_ASSETS_COMMITMENT, assets_ptr, note_ptr] - swapw drop movdn.2 drop drop - # => [note_ptr, ASSET_COMMITMENT_COMPUTED] - - # VERIFY: computed ASSET_COMMITMENT matches the provided hash - exec.memory::get_input_note_assets_commitment + # assert COMPUTED_ASSETS_COMMITMENT matches the provided commitment + movup.6 exec.memory::get_input_note_assets_commitment assert_eqw.err=ERR_PROLOGUE_PROVIDED_INPUT_ASSETS_INFO_DOES_NOT_MATCH_ITS_COMMITMENT + # => [assets_ptr] + + drop # => [] end @@ -725,7 +692,7 @@ proc add_input_note_assets_to_vault dup.1 exec.memory::get_input_note_assets_ptr # => [assets_start_ptr, input_vault_root_ptr, note_ptr] - dup movup.3 exec.memory::get_input_note_num_assets mul.4 add swap + dup movup.3 exec.memory::get_input_note_num_assets mul.ASSET_SIZE add swap # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # add input note's assets to input vault @@ -739,20 +706,25 @@ proc add_input_note_assets_to_vault dup.2 # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] - padw dup.5 mem_loadw_be - # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] + # load asset value + padw dup.5 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] + + # load asset key + padw dup.9 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # the witnesses for the note assets should be added prior to transaction execution and so # there should be no need to fetch them lazily via an event. exec.asset_vault::add_asset dropw # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] - add.4 - # => [assets_start_ptr+4, assets_end_ptr, input_vault_root_ptr] + add.ASSET_SIZE + # => [assets_start_ptr+ASSET_SIZE, assets_end_ptr, input_vault_root_ptr] # loop condition: assets_start_ptr != assets_end_ptr dup.1 dup.1 neq - # => [should_loop, assets_start_ptr+4, assets_end_ptr, input_vault_root_ptr] + # => [should_loop, assets_start_ptr+ASSET_SIZE, assets_end_ptr, input_vault_root_ptr] end drop drop drop @@ -806,7 +778,7 @@ end #! NOTE_ARGS, #! NOTE_METADATA_HEADER, #! NOTE_ATTACHMENT, -#! assets_count, +#! num_assets, #! ASSET_0, ..., ASSET_N, #! is_authenticated, #! ( @@ -830,7 +802,7 @@ end #! - NOTE_METADATA_HEADER is the note's metadata header. #! - NOTE_ATTACHMENT is the note's attachment. #! - NOTE_ARGS are the user arguments passed to the note. -#! - assets_count is the note's assets count. +#! - num_assets is the number of note assets. #! - ASSET_0, ..., ASSET_N are the padded note's assets. #! - is_authenticated is the boolean indicating if the note contains an authentication proof. #! - optional values, required if `is_authenticated` is true: diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index aaf943ad8c..216dc54c17 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -2,6 +2,7 @@ use alloc::string::String; use miden_protocol::Word; use miden_protocol::note::Note; +use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_standards::code_builder::CodeBuilder; use super::{TestSetup, setup_test}; @@ -234,20 +235,32 @@ async fn test_get_assets() -> anyhow::Result<()> { for (asset_index, asset) in note.assets().iter().enumerate() { check_assets_code.push_str(&format!( r#" - # load the asset stored in memory + # load the asset key stored in memory padw dup.4 mem_loadw_be - # => [STORED_ASSET, dest_ptr, note_index] + # => [STORED_ASSET_KEY, dest_ptr, note_index] - # assert the asset - push.{NOTE_ASSET} - assert_eqw.err="asset {asset_index} of the note {note_index} is incorrect" + # assert the asset key matches + push.{NOTE_ASSET_KEY} + assert_eqw.err="expected asset key at asset index {asset_index} of the note\ + {note_index} to be {NOTE_ASSET_KEY}" + # => [dest_ptr, note_index] + + # load the asset value stored in memory + padw dup.4 add.{ASSET_VALUE_OFFSET} mem_loadw_be + # => [STORED_ASSET_VALUE, dest_ptr, note_index] + + # assert the asset value matches + push.{NOTE_ASSET_VALUE} + assert_eqw.err="expected asset value at asset index {asset_index} of the note\ + {note_index} to be {NOTE_ASSET_VALUE}" # => [dest_ptr, note_index] # move the pointer - add.4 - # => [dest_ptr+4, note_index] + add.{ASSET_SIZE} + # => [dest_ptr+ASSET_SIZE, note_index] "#, - NOTE_ASSET = Word::from(*asset), + NOTE_ASSET_KEY = asset.vault_key().as_word(), + NOTE_ASSET_VALUE = Word::from(*asset), asset_index = asset_index, note_index = note_index, )); @@ -272,8 +285,8 @@ async fn test_get_assets() -> anyhow::Result<()> { end ", check_note_0 = check_assets_code(0, 0, &p2id_note_0_assets), - check_note_1 = check_assets_code(1, 4, &p2id_note_1_asset), - check_note_2 = check_assets_code(2, 8, &p2id_note_2_assets), + check_note_1 = check_assets_code(1, 8, &p2id_note_1_asset), + check_note_2 = check_assets_code(2, 16, &p2id_note_2_assets), ); let tx_script = CodeBuilder::default().compile_tx_script(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 96c2fd217f..8675a3277f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -21,6 +21,7 @@ use miden_protocol::testing::account_id::{ }; use miden_protocol::transaction::memory::{ ACCT_DB_ROOT_PTR, + ASSET_VALUE_OFFSET, BLOCK_COMMITMENT_PTR, BLOCK_METADATA_PTR, BLOCK_NUMBER_IDX, @@ -516,14 +517,22 @@ fn input_notes_memory_assertions( ); for (asset, asset_idx) in note.assets().iter().cloned().zip(0_u32..) { - let word: Word = asset.into(); + let asset_key: Word = *asset.vault_key().as_word(); + let asset_value: Word = asset.into(); + + let asset_key_addr = INPUT_NOTE_ASSETS_OFFSET + asset_idx * WORD_SIZE as u32; + let asset_value_addr = asset_key_addr + ASSET_VALUE_OFFSET; + + assert_eq!( + exec_output.get_note_mem_word(note_idx, asset_key_addr), + asset_key, + "asset key should be stored at the correct offset" + ); + assert_eq!( - exec_output.get_note_mem_word( - note_idx, - INPUT_NOTE_ASSETS_OFFSET + asset_idx * WORD_SIZE as u32 - ), - word, - "assets should be stored at (INPUT_NOTES_DATA_OFFSET + note_index * 2048 + 32 + asset_idx * 4)" + exec_output.get_note_mem_word(note_idx, asset_value_addr), + asset_value, + "asset value should be stored at the correct offset" ); } } From 9be1b36bc2153a4e3520d9919832879fc587996d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 18:05:48 +0100 Subject: [PATCH 012/100] chore: increase `NOTE_MEM_SIZE` to 3072 --- .../asm/kernels/transaction/lib/constants.masm | 2 +- .../src/transaction/kernel/memory.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm b/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm index 869a7a818a..6d7992ac6b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm @@ -14,7 +14,7 @@ pub const MAX_ASSETS_PER_NOTE = 256 pub const MAX_INPUT_NOTES_PER_TX = 1024 # The size of the memory segment allocated to each note. -pub const NOTE_MEM_SIZE = 2048 +pub const NOTE_MEM_SIZE = 3072 # The depth of the Merkle tree used to commit to notes produced in a block. pub const NOTE_TREE_DEPTH = 16 diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index 19962abfe9..47b11b69c6 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -21,9 +21,9 @@ pub type StorageSlot = u8; // | Kernel data | 1_600 | 140 | 34 procedures in total, 4 elements each | // | Accounts data | 8_192 | 524_288 | 64 accounts max, 8192 elements each | // | Account delta | 532_480 | 263 | | -// | Input notes | 4_194_304 | 2_162_688 | nullifiers data segment + 1024 input notes | -// | | | | max, 2048 elements each | -// | Output notes | 16_777_216 | 2_097_152 | 1024 output notes max, 2048 elements each | +// | Input notes | 4_194_304 | 3_211_264 | nullifiers data segment (2^16 elements) | +// | | | | + 1024 input notes max, 3072 elements each | +// | Output notes | 16_777_216 | 3_145_728 | 1024 output notes max, 3072 elements each | // | Link Map Memory | 33_554_432 | 33_554_432 | Enough for 2_097_151 key-value pairs | // Relative layout of one account @@ -324,7 +324,7 @@ pub const NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR: MemoryAddress = // ================================================================================================ /// The size of the memory segment allocated to each note. -pub const NOTE_MEM_SIZE: MemoryAddress = 2048; +pub const NOTE_MEM_SIZE: MemoryAddress = 3072; #[allow(clippy::empty_line_after_outer_attr)] #[rustfmt::skip] @@ -338,11 +338,11 @@ pub const NOTE_MEM_SIZE: MemoryAddress = 2048; // │ NUM │ NOTE 0 │ NOTE 1 │ ... │ NOTE n │ PADDING │ NOTE 0 │ NOTE 1 │ ... │ NOTE n │ // │ NOTES │ NULLIFIER │ NULLIFIER │ │ NULLIFIER │ │ DATA │ DATA │ │ DATA │ // └─────────┴───────────┴───────────┴─────┴───────────┴─────────┴────────┴────────┴───────┴────────┘ -// 4_194_304 4_194_308 4_194_312 4_194_304+4(n+1) 4_259_840 +2048 +4096 +2048n +// 4_194_304 4_194_308 4_194_312 4_194_304+4(n+1) 4_259_840 +3072 +6144 +3072n // // Here `n` represents number of input notes. // -// Each nullifier occupies a single word. A data section for each note consists of exactly 2048 +// Each nullifier occupies a single word. A data section for each note consists of exactly 3072 // elements and is laid out like so: // // ┌──────┬────────┬────────┬─────────┬────────────┬───────────┬──────────┬────────────┬───────┬─────────┬────────┬───────┬─────┬───────┬─────────┬ @@ -399,7 +399,7 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 44; // ┌─────────────┬─────────────┬───────────────┬─────────────┐ // │ NOTE 0 DATA │ NOTE 1 DATA │ ... │ NOTE n DATA │ // └─────────────┴─────────────┴───────────────┴─────────────┘ -// 16_777_216 +2048 +4096 +2048n +// 16_777_216 +3072 +6144 +3072n // // The total number of output notes for a transaction is stored in the bookkeeping section of the // memory. Data section of each note is laid out like so: @@ -441,7 +441,7 @@ pub const OUTPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 24; #[cfg(any(feature = "testing", test))] pub const ASSET_SIZE: MemoryOffset = 8; -/// The offset of the asset vaule in an asset's memory representation. +/// The offset of the asset value in an asset's memory representation. #[cfg(any(feature = "testing", test))] pub const ASSET_VALUE_OFFSET: MemoryOffset = 4; From d7552fb11f5ceeb2e443b98e81ae3dca50ef2eef Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Feb 2026 18:18:02 +0100 Subject: [PATCH 013/100] chore: adapt `NoteAssets` commitment --- crates/miden-protocol/src/note/assets.rs | 91 ++++++------------- .../src/transaction/kernel/advice_inputs.rs | 4 +- 2 files changed, 30 insertions(+), 65 deletions(-) diff --git a/crates/miden-protocol/src/note/assets.rs b/crates/miden-protocol/src/note/assets.rs index 260274eeaa..1cb2e7c6bf 100644 --- a/crates/miden-protocol/src/note/assets.rs +++ b/crates/miden-protocol/src/note/assets.rs @@ -1,5 +1,7 @@ use alloc::vec::Vec; +use miden_crypto::SequentialCommit; + use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; use crate::errors::NoteError; use crate::utils::serde::{ @@ -9,10 +11,11 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word, ZERO}; +use crate::{Felt, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word}; // NOTE ASSETS // ================================================================================================ + /// An asset container for a note. /// /// A note can contain between 0 and 256 assets. No duplicates are allowed, but the order of assets @@ -24,7 +27,7 @@ use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word, ZERO}; #[derive(Debug, Default, Clone)] pub struct NoteAssets { assets: Vec, - hash: Word, + commitment: Word, } impl NoteAssets { @@ -60,8 +63,10 @@ impl NoteAssets { } } - let hash = compute_asset_commitment(&assets); - Ok(Self { assets, hash }) + let mut assets = Self { assets, commitment: Word::empty() }; + assets.commitment = assets.to_commitment(); + + Ok(assets) } // PUBLIC ACCESSORS @@ -69,7 +74,7 @@ impl NoteAssets { /// Returns a commitment to the note's assets. pub fn commitment(&self) -> Word { - self.hash + self.commitment } /// Returns the number of assets. @@ -88,27 +93,8 @@ impl NoteAssets { } /// Returns all assets represented as a vector of field elements. - /// - /// The vector is padded with ZEROs so that its length is a multiple of 8. This is useful - /// because hashing the returned elements results in the note asset commitment. - pub fn to_padded_assets(&self) -> Vec { - // if we have an odd number of assets with pad with a single word. - let padded_len = if self.assets.len().is_multiple_of(2) { - self.assets.len() * WORD_SIZE - } else { - (self.assets.len() + 1) * WORD_SIZE - }; - - // allocate a vector to hold the padded assets - let mut padded_assets = Vec::with_capacity(padded_len * WORD_SIZE); - - // populate the vector with the assets - padded_assets.extend(self.assets.iter().flat_map(|asset| Word::from(*asset))); - - // pad with an empty word if we have an odd number of assets - padded_assets.resize(padded_len, ZERO); - - padded_assets + pub fn to_elements(&self) -> Vec { + ::to_elements(self) } /// Returns an iterator over all [`FungibleAsset`]. @@ -163,7 +149,8 @@ impl NoteAssets { } } - self.hash = compute_asset_commitment(&self.assets); + // Recompute the commitment. + self.commitment = self.to_commitment(); Ok(()) } @@ -177,42 +164,20 @@ impl PartialEq for NoteAssets { impl Eq for NoteAssets {} -// HELPER FUNCTIONS -// ================================================================================================ +impl SequentialCommit for NoteAssets { + type Commitment = Word; -/// Returns a commitment to a note's assets. -/// -/// The commitment is computed as a sequential hash of all assets (each asset represented by 4 -/// field elements), padded to the next multiple of 2. If the asset list is empty, a default digest -/// is returned. -fn compute_asset_commitment(assets: &[Asset]) -> Word { - if assets.is_empty() { - return Word::empty(); - } - - // If we have an odd number of assets we pad the vector with 4 zero elements. This is to - // ensure the number of elements is a multiple of 8 - the size of the hasher rate. - let word_capacity = if assets.len().is_multiple_of(2) { - assets.len() - } else { - assets.len() + 1 - }; - let mut asset_elements = Vec::with_capacity(word_capacity * WORD_SIZE); + /// Returns all assets represented as a vector of field elements. + fn to_elements(&self) -> Vec { + let mut elements = Vec::with_capacity(self.assets.len() * 2 * WORD_SIZE); - for asset in assets.iter() { - // convert the asset into field elements and add them to the list elements - let asset_word: Word = (*asset).into(); - asset_elements.extend_from_slice(asset_word.as_elements()); - } + for asset in self.assets.iter() { + elements.extend(asset.to_key_word().as_elements()); + elements.extend(asset.to_value_word().as_elements()); + } - // If we have an odd number of assets we pad the vector with 4 zero elements. This is to - // ensure the number of elements is a multiple of 8 - the size of the hasher rate. This - // simplifies hashing inside of the virtual machine when ingesting assets from a note. - if assets.len() % 2 == 1 { - asset_elements.extend_from_slice(Word::empty().as_elements()); + elements } - - Hasher::hash_elements(&asset_elements) } // SERIALIZATION @@ -240,7 +205,7 @@ impl Deserializable for NoteAssets { #[cfg(test)] mod tests { - use super::{NoteAssets, compute_asset_commitment}; + use super::NoteAssets; use crate::Word; use crate::account::AccountId; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; @@ -260,18 +225,18 @@ mod tests { // create empty assets let mut assets = NoteAssets::default(); - assert_eq!(assets.hash, Word::empty()); + assert_eq!(assets.commitment, Word::empty()); // add asset1 assert!(assets.add_asset(asset1).is_ok()); assert_eq!(assets.assets, vec![asset1]); - assert_eq!(assets.hash, compute_asset_commitment(&[asset1])); + assert!(!assets.commitment.is_empty()); // add asset2 assert!(assets.add_asset(asset2).is_ok()); let expected_asset = Asset::Fungible(FungibleAsset::new(faucet_id, 150).unwrap()); assert_eq!(assets.assets, vec![expected_asset]); - assert_eq!(assets.hash, compute_asset_commitment(&[expected_asset])); + assert!(!assets.commitment.is_empty()); } #[test] fn iter_fungible_asset() { diff --git a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs index 65b445bdd6..06371edfd0 100644 --- a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs +++ b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs @@ -339,7 +339,7 @@ impl TransactionAdviceInputs { // recipient storage self.add_map_entry(recipient.storage().commitment(), recipient.storage().to_elements()); // assets commitments - self.add_map_entry(assets.commitment(), assets.to_padded_assets()); + self.add_map_entry(assets.commitment(), assets.to_elements()); // array attachments if let NoteAttachmentContent::Array(array_attachment) = note.metadata().attachment().content() @@ -360,7 +360,7 @@ impl TransactionAdviceInputs { note_data.extend(note.metadata().to_attachment_word()); note_data.push(recipient.storage().num_items().into()); note_data.push((assets.num_assets() as u32).into()); - note_data.extend(assets.to_padded_assets()); + note_data.extend(assets.to_elements()); // authentication vs unauthenticated match input_note { From 798df710c7b87f5d37ecb3b2bd311a3a82dd1b56 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 3 Feb 2026 15:11:19 +0100 Subject: [PATCH 014/100] feat: refactor `note.masm` --- .../asm/note_scripts/B2AGG.masm | 9 +-- .../asm/kernels/transaction/lib/note.masm | 57 +++++-------------- crates/miden-protocol/asm/protocol/note.masm | 14 ++--- .../asm/standards/faucets/mod.masm | 8 ++- .../asm/standards/notes/swap.masm | 9 +-- .../src/kernel_tests/tx/test_active_note.rs | 4 +- 6 files changed, 37 insertions(+), 64 deletions(-) diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 79075a2a09..00b30661f8 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -2,6 +2,7 @@ use miden::agglayer::bridge_out use miden::protocol::account_id use miden::protocol::active_account use miden::protocol::active_note +use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET use miden::standards::wallets::basic->basic_wallet # CONSTANTS @@ -65,7 +66,7 @@ begin push.B2AGG_NOTE_NUM_STORAGE_ITEMS assert_eq.err=ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop # => [pad(16)] - # Store note assets -> mem[0..4] + # Store note assets -> mem[0..8] push.0 exec.active_note::get_assets # => [num_assets, ptr, pad(16)] @@ -77,9 +78,9 @@ begin mem_loadw_be.12 swapw.2 mem_loadw_be.8 swapw # => [EMPTY_WORD, dest_network, dest_address(5), pad(6)] - # Load ASSET onto the stack - mem_loadw_be.0 - # => [ASSET, dest_network, dest_address(5), pad(6)] + # Load ASSET_VALUE onto the stack from 0 + ASSET_VALUE_MEMORY_OFFSET + mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # => [ASSET_VALUE, dest_network, dest_address(5), pad(6)] call.bridge_out::bridge_out # => [pad(16)] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm index 8acbc7ab8b..c456e303cd 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm @@ -1,5 +1,6 @@ use miden::core::crypto::hashes::rpo256 +use $kernel::asset::ASSET_SIZE use $kernel::constants::NOTE_MEM_SIZE use $kernel::memory @@ -102,52 +103,20 @@ pub proc compute_output_note_assets_commitment # => [note_data_ptr] # duplicate note pointer and fetch num_assets - dup dup exec.memory::get_output_note_num_assets - # => [num_assets, note_data_ptr, note_data_ptr] - - # calculate the number of pairs of assets (takes ceiling if we have an odd number) - add.1 - u32assert.err=ERR_NOTE_NUM_OF_ASSETS_EXCEED_LIMIT - u32div.2 - # => [num_asset_pairs, note_data_ptr, note_data_ptr] - - # initiate counter for assets - push.0 - # => [asset_counter, num_asset_pairs, note_data_ptr, note_data_ptr] - - # prepare address and stack for reading assets - movup.2 exec.memory::get_output_note_asset_data_ptr padw padw padw - # => [PAD, PAD, PAD, asset_data_ptr, asset_counter, num_asset_pairs, note_data_ptr] - - # check if we should loop - dup.14 dup.14 neq - # => [should_loop, PAD, PAD, PAD, asset_data_ptr, asset_counter, num_asset_pairs, note_data_ptr] - - # loop and read assets from memory - while.true - # read assets from memory. - # if this is the last permutation of the loop and we have an odd number of assets then we - # implicitly pad the last word of the hasher rate with zeros by reading from empty memory. - mem_stream exec.rpo256::permute - # => [PERM, PERM, PERM, asset_data_ptr, asset_counter, num_asset_pairs, note_data_ptr] - - # check if we should loop again - movup.13 add.1 dup movdn.14 dup.15 neq - # => [should_loop, PERM, PERM, PERM, asset_data_ptr, asset_counter, num_asset_pairs, - # note_data_ptr] - end - - # extract digest - exec.rpo256::squeeze_digest - # => [ASSETS_COMMITMENT, asset_data_ptr, asset_counter, num_asset_pairs, note_data_ptr] - - # drop accessory variables from stack - movup.4 drop - movup.4 drop - movup.4 drop + dup exec.memory::get_output_note_asset_data_ptr + # => [assets_ptr, note_data_ptr] + + dup.1 exec.memory::get_output_note_num_assets + # => [num_assets, assets_ptr, note_data_ptr] + + # compute the asset_end_ptr + mul.ASSET_SIZE dup.1 add swap + # => [assets_ptr, assets_end_ptr, note_data_ptr] + + exec.rpo256::hash_double_words # => [ASSETS_COMMITMENT, note_data_ptr] - # save the assets hash to memory + # save the assets commitment to memory dup.4 exec.memory::set_output_note_assets_commitment # => [ASSETS_COMMITMENT, note_data_ptr] diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 2f8cab9b46..57d5ec26e4 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -59,18 +59,18 @@ pub proc write_assets_to_memory # OS => [ASSETS_COMMITMENT, num_assets, dest_ptr] # AS => [[ASSETS_DATA]] - # calculate number of assets rounded up to an even number - dup.4 dup is_odd add - # OS => [even_num_assets, ASSETS_COMMITMENT, num_assets, dest_ptr] + dup.5 dup.5 + # OS => [num_assets, dest_ptr, ASSETS_COMMITMENT, num_assets, dest_ptr] # AS => [[ASSETS_DATA]] - # prepare the stack for the `pipe_preimage_to_memory` procedure - dup.6 swap - # OS => [even_num_assets, dest_ptr, ASSETS_COMMITMENT, num_assets, dest_ptr] + # each asset takes up two words, so num_words = 2 * num_assets + # this also guarantees we pass an even number to pipe_double_words_preimage_to_memory + mul.2 + # OS => [num_words, dest_ptr, ASSETS_COMMITMENT, num_assets, dest_ptr] # AS => [[ASSETS_DATA]] # write the data from the advice stack into memory - exec.mem::pipe_preimage_to_memory drop + exec.mem::pipe_double_words_preimage_to_memory drop # OS => [num_assets, dest_ptr] # AS => [] end diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 5451bb9655..7e49f03b80 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -4,6 +4,7 @@ use miden::protocol::faucet use miden::protocol::native_account use miden::protocol::output_note use ::miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT +use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET # CONSTANTS # ================================================================================================= @@ -182,15 +183,16 @@ pub proc burn assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS # => [dest_ptr, pad(16)] - mem_loadw_be - # => [ASSET, pad(16)] + # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET + mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # => [ASSET_VALUE, pad(16)] # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], pad(16)] # Burn the asset from the transaction vault # --------------------------------------------------------------------------------------------- dup.3 movdn.4 - # => [ASSET, amount, pad(16)] + # => [ASSET_VALUE, amount, pad(16)] # burn the asset # this ensures we only burn assets that were issued by this faucet (which implies they are diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 48f86cb97d..3c71b6e63f 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -1,6 +1,7 @@ use miden::protocol::active_note use miden::protocol::output_note use miden::standards::wallets::basic->wallet +use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET # CONSTANTS # ================================================================================================= @@ -117,11 +118,11 @@ pub proc main assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS # => [ptr, pad(12)] - # load the ASSET - mem_loadw_be - # => [ASSET, pad(12)] + # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET + mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # => [ASSET_VALUE, pad(12)] - # add the ASSET to the account + # add the asset to the account call.wallet::receive_asset # => [pad(16)] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index b35dd6bf57..8fc6d0698e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -213,8 +213,8 @@ async fn test_active_note_get_assets() -> anyhow::Result<()> { add.{ASSET_SIZE} "#, - ASSET_KEY = asset.vault_key().as_word(), - ASSET_VALUE = Word::from(asset), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), ); } code From d7c4094a3767430f11f746932c8023e133db026a Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 3 Feb 2026 15:14:42 +0100 Subject: [PATCH 015/100] chore: refactor `api.masm` --- .../asm/kernels/transaction/api.masm | 92 +++++++++++-------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 2f0bf4127b..9db5ef8fde 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -1,3 +1,5 @@ +use $kernel::asset +use $kernel::asset_vault use $kernel::account use $kernel::account_delta use $kernel::account_id @@ -536,15 +538,15 @@ end #! Adds the specified asset to the vault. #! -#! Inputs: [ASSET, pad(12)] -#! Outputs: [ASSET', pad(12)] +#! Inputs: [ASSET_VALUE, pad(12)] +#! Outputs: [ASSET_VALUE', pad(12)] #! #! Where: -#! - ASSET is the asset to add to the vault. -#! - ASSET' final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. +#! - ASSET_VALUE is the value of the asset to add to the vault. +#! - ASSET_VALUE' final asset in the account vault defined as follows: +#! - If ASSET_VALUE is a non-fungible asset, then ASSET_VALUE' is the same as ASSET_VALUE. +#! - If ASSET_VALUE is a fungible asset, then ASSET_VALUE' is the total fungible asset in the account vault +#! after ASSET_VALUE was added to it. #! #! Panics if: #! - the asset is not valid. @@ -557,24 +559,26 @@ end pub proc account_add_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # add the specified asset to the account vault, emitting the corresponding events + # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. + exec.asset_vault::build_asset_vault_key exec.account::add_asset_to_vault - # => [ASSET', pad(12)] + # => [ASSET_VALUE', pad(12)] end #! Removes the specified asset from the vault. #! -#! Inputs: [ASSET, pad(12)] -#! Outputs: [ASSET, pad(12)] +#! Inputs: [ASSET_VALUE, pad(12)] +#! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: -#! - ASSET is the asset to remove from the vault. +#! - ASSET_VALUE is the value of the asset to remove from the vault. #! #! Panics if: #! - the fungible asset is not found in the vault. @@ -586,50 +590,52 @@ end pub proc account_remove_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # remove the specified asset from the account vault, emitting the corresponding events + # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. + exec.asset_vault::build_asset_vault_key exec.account::remove_asset_from_vault - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end -#! Returns the ASSET associated with the provided asset vault key in the active account's vault. +#! Returns the asset associated with the provided asset vault key in the active account's vault. #! #! Inputs: [ASSET_KEY, pad(12)] -#! Outputs: [ASSET, pad(12)] +#! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. #! #! Invocation: dynexec pub proc account_get_asset # TODO(expand_assets): Validate ASSET_KEY once validation logic exists. exec.account::get_asset - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end -#! Returns the ASSET associated with the provided asset vault key in the active account's vault at +#! Returns the asset associated with the provided asset vault key in the active account's vault at #! the beginning of the transaction. #! #! Inputs: [ASSET_KEY, pad(12)] -#! Outputs: [ASSET, pad(12)] +#! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. #! #! Invocation: dynexec pub proc account_get_initial_asset # TODO(expand_assets): Validate ASSET_KEY once validation logic exists. exec.account::get_initial_asset - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end #! Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise. @@ -722,11 +728,11 @@ end #! Mint an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET, pad(12)] -#! Outputs: [ASSET, pad(12)] +#! Inputs: [ASSET_VALUE, pad(12)] +#! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: -#! - ASSET is the asset that was minted. +#! - ASSET_VALUE is the value of the asset that was minted. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -743,24 +749,26 @@ end pub proc faucet_mint_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # mint the asset + # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. + exec.asset_vault::build_asset_vault_key exec.faucet::mint - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end #! Burn an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET, pad(12)] -#! Outputs: [ASSET, pad(12)] +#! Inputs: [ASSET_VALUE, pad(12)] +#! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: -#! - ASSET is the asset that was burned. +#! - ASSET_VALUE is the value of the asset that was burned. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -778,15 +786,17 @@ end pub proc faucet_burn_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # burn the asset + # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. + exec.asset_vault::build_asset_vault_key exec.faucet::burn - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end # INPUT NOTE @@ -1065,14 +1075,14 @@ pub proc output_note_create # => [note_idx, pad(15)] end -#! Adds the ASSET to the note specified by the index. +#! Adds the asset to the note specified by the index. #! -#! Inputs: [note_idx, ASSET, pad(11)] +#! Inputs: [note_idx, ASSET_VALUE, pad(11)] #! Outputs: [pad(16)] #! #! Where: #! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. +#! - ASSET_VALUE can be a fungible or non-fungible asset. #! #! Panics if: #! - the procedure is called when the active account is not the native one. @@ -1081,8 +1091,10 @@ end pub proc output_note_add_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [note_idx, ASSET, pad(11)] + # => [note_idx, ASSET_VALUE, pad(11)] + # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. + movdn.4 exec.asset_vault::build_asset_vault_key movup.8 exec.output_note::add_asset # => [pad(16)] end From f83cd687134c8145ceb6dc66198b122c96c3867d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 4 Feb 2026 10:18:30 +0100 Subject: [PATCH 016/100] chore: regenerate kernel proc hashes --- crates/miden-protocol/src/errors/tx_kernel.rs | 15 ++++---- .../src/transaction/kernel/procedures.rs | 34 +++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index 44ccbe1b1c..fb1a041624 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -73,9 +73,6 @@ pub const ERR_EPILOGUE_NONCE_CANNOT_BE_0: MasmError = MasmError::from_static_str /// Error Message: "total number of assets in the account and all involved notes must stay the same" pub const ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME: MasmError = MasmError::from_static_str("total number of assets in the account and all involved notes must stay the same"); -/// Error Message: "the burn_non_fungible_asset procedure can only be called on a non-fungible faucet" -pub const ERR_FAUCET_BURN_NON_FUNGIBLE_ASSET_CAN_ONLY_BE_CALLED_ON_NON_FUNGIBLE_FAUCET: MasmError = MasmError::from_static_str("the burn_non_fungible_asset procedure can only be called on a non-fungible faucet"); - /// Error Message: "creation of a foreign context against the native account is forbidden" pub const ERR_FOREIGN_ACCOUNT_CONTEXT_AGAINST_NATIVE_ACCOUNT: MasmError = MasmError::from_static_str("creation of a foreign context against the native account is forbidden"); /// Error Message: "ID of the provided foreign account equals zero" @@ -93,6 +90,8 @@ pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO: MasmError = MasmEr pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID: MasmError = MasmError::from_static_str("malformed fungible asset: `ASSET[2]` and `ASSET[3]` must be a valid fungible faucet id"); /// Error Message: "malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount" pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS: MasmError = MasmError::from_static_str("malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount"); +/// Error Message: "fungible asset vault key's account ID must be of type fungible faucet" +pub const ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE: MasmError = MasmError::from_static_str("fungible asset vault key's account ID must be of type fungible faucet"); /// Error Message: "requested input note index should be less than the total number of input notes" pub const ERR_INPUT_NOTE_INDEX_OUT_OF_BOUNDS: MasmError = MasmError::from_static_str("requested input note index should be less than the total number of input notes"); @@ -123,8 +122,8 @@ pub const ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: MasmError = MasmError::from_sta pub const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN: MasmError = MasmError::from_static_str("the origin of the non-fungible asset is not this faucet"); /// Error Message: "malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID: MasmError = MasmError::from_static_str("malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id"); -/// Error Message: "malformed non-fungible asset: the most significant bit must be 0" -pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_MOST_SIGNIFICANT_BIT_MUST_BE_ZERO: MasmError = MasmError::from_static_str("malformed non-fungible asset: the most significant bit must be 0"); +/// Error Message: "non-fungible asset vault key's account ID must be of type non-fungible faucet" +pub const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE: MasmError = MasmError::from_static_str("non-fungible asset vault key's account ID must be of type non-fungible faucet"); /// Error Message: "failed to access note assets of active note because no note is currently being processed" pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note assets of active note because no note is currently being processed"); @@ -204,10 +203,12 @@ pub const ERR_TX_TRANSACTION_SCRIPT_IS_MISSING: MasmError = MasmError::from_stat /// Error Message: "failed to add fungible asset to the asset vault due to the initial value being invalid" pub const ERR_VAULT_ADD_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID: MasmError = MasmError::from_static_str("failed to add fungible asset to the asset vault due to the initial value being invalid"); +/// Error Message: "account ID in asset vault key must be either of type fungible or non-fungible faucet" +pub const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET: MasmError = MasmError::from_static_str("account ID in asset vault key must be either of type fungible or non-fungible faucet"); /// Error Message: "failed to remove the fungible asset from the vault since the amount of the asset in the vault is less than the amount to remove" pub const ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW: MasmError = MasmError::from_static_str("failed to remove the fungible asset from the vault since the amount of the asset in the vault is less than the amount to remove"); -/// Error Message: "adding the fungible asset to the vault would exceed the max amount of 9223372036854775807" -pub const ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED: MasmError = MasmError::from_static_str("adding the fungible asset to the vault would exceed the max amount of 9223372036854775807"); +/// Error Message: "adding the fungible asset to the vault would exceed the max amount" +pub const ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED: MasmError = MasmError::from_static_str("adding the fungible asset to the vault would exceed the max amount"); /// Error Message: "the non-fungible asset already exists in the asset vault" pub const ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: MasmError = MasmError::from_static_str("the non-fungible asset already exists in the asset vault"); /// Error Message: "failed to remove non-existent non-fungible asset from the vault" diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 9b9017c07c..47c7e49526 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,9 +40,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x91a5276ebe971b2c25ec59e3226553aa4168d4ac68c0bcbdbc211d7b437bf7bd"), + word!("0x273ecaebf62df3cec1cf335890f1a659007b88ddd0f446851577c7d6c34821bb"), // account_remove_asset - word!("0xc0d6a0f9d42cf16b8196663f63bd58bf19638e4abe7a7ab4d83c0d109bef9b76"), + word!("0xd89f7a379e0eafc331f6b8fbf31766c91bbf65470f3134c9b935e4f00113eabf"), // account_get_asset word!("0x21dc0ef7e3475f28fbcf26636d9b58c3f7e349da7c7a36e85c1b49e50437fa65"), // account_get_initial_asset @@ -58,33 +58,33 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xb0b63fdd01af0bcb4aacb2412e934cdc7691308647152d416c7ae4fc909da076"), // faucet_mint_asset - word!("0x0378e38a593a1bbaca17708f7169f5b6d563fc7bbd9fb43c05b2e8686c38e27f"), + word!("0xacf3c83416f314cafdae6d38e7371f47b9cbb932e6cdeee8031f00f9c8c240c2"), // faucet_burn_asset - word!("0xdfd6f18147c45a7f06984715b66fa7139ff5a27acf18060e58681b4c622c0472"), + word!("0x860d59a60324f6e7f69b864b7e5298490b3e04d9f5e21b4b8445da05e8df11b0"), // input_note_get_metadata - word!("0x447b342e38855a9402cde0ea52ecb5e4c1fe542b535a5364cb5caa8e94c82442"), + word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info - word!("0xe0817bed99fb61180e705b2c9e5ca8c8f0c62864953247a56acbc65b7d58c2d5"), + word!("0x3b42b367dd4ad0814b089d3dbbb561e51c4b32341a7261ba602f04f033644749"), // input_note_get_script_root - word!("0x527036257e58c3a84cf0aa170fb3f219a4553db17d269279355ad164a2b90ac5"), + word!("0xbc4cdcb7555c5ddb3e2d11fb93f35ca6791baf04bbe094da8f44d564761e2595"), // input_note_get_storage_info - word!("0xb7f45ec34f7708355551dcf1f82c9c40e2c19252f8d5c98dcf9ef1aa0a3eb878"), + word!("0xd66d5b761ff308b3cc82d896ed3f5d73fec784bc6b1c7a40faa2861ef39673c1"), // input_note_get_serial_number - word!("0x25815e02b7976d8e5c297dde60d372cc142c81f702f424ac0920190528c547ee"), + word!("0xd22e0f99c8c4241a7cc1a4a5b46715cb10ab7567a35e49330ad60859ded171d6"), // input_note_get_recipient - word!("0xd3c255177f9243bb1a523a87615bbe76dd5a3605fcae87eb9d3a626d4ecce33c"), + word!("0x798670cd085d02be944d9cb17857141e036cad719432eebfaf3fd91ac744e4fa"), // output_note_create - word!("0x2bc7c67228d296ef791ba872d2dfc467f31dcf1fbf8cb13561af24092173d8f3"), + word!("0x12407109c7b7b9ecfdc6a3910a21ec3ab919d872771efc286b809a681018b95d"), // output_note_get_metadata - word!("0x3db8427f20eb70603b72aa574a986506eb7216312004aeaf8b2a7e55d0049a48"), + word!("0x09c75fdaafc8b114aac1add62d170721ea94bd12992f699c37d9e0033da74d2b"), // output_note_get_assets_info - word!("0xbbf90ac2a7e16ee6d2336f7389d5b972bf0c1fa9938405945b85f948bf24fc4f"), + word!("0x796ab1c8cfe66dc60ec16e1d11dfa0a9a618ebd1e6c64bc7443d514c73e56cc8"), // output_note_get_recipient - word!("0x1ce137f0c5be72832970e6c818968a789f65b97db34515bfebb767705f28db67"), + word!("0x6aeec5901ae0afd538bdbb6f7c5a05da66e75fb9e2100c1ffe2a3fa5d9910b64"), // output_note_add_asset - word!("0xe2cc1a8068bebb976b7f3d2a4c7c3cf5e7d9846743109ad656bedd1b40c47586"), + word!("0x6b531e2fd8f2619de3ad1ca2cdf35bada6a7cdd0502a56a39ac74374f7bae801"), // output_note_set_attachment - word!("0xb950dadea783fb7d3903aeefd7ef7d253b085b20f6628597f85c8df21eee7df9"), + word!("0xc33e8568f74b1accf0ee7f5de52fea30fe524b9e0dad7958d7e506e9ba3e3bbe"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), // tx_get_input_notes_commitment @@ -92,7 +92,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // tx_get_num_output_notes word!("0x2511fca9c078cd96e526fd488d1362cbfd597eb3db8452aedb00beffee9782b4"), // tx_get_output_notes_commitment - word!("0x8b9b29c837b5d0834f550d7f32703b35e2ff014b523dd581a09a0b94a925fcec"), + word!("0xe30764b3bd8ea7e57473aa67fda0a6913503c0bfa026fa0e8224157495d38561"), // tx_get_block_commitment word!("0xe474b491a64d222397fcf83ee5db7b048061988e5e83ce99b91bae6fd75a3522"), // tx_get_block_number From 3ea65e5c3b38f25ee28862710dd92aa859157d0d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 4 Feb 2026 12:01:24 +0100 Subject: [PATCH 017/100] chore: add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c56ff94f..6377b34e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - [BREAKING] Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). - [BREAKING] Moved standard note code into individual note modules ([#2363](https://github.com/0xMiden/miden-base/pull/2363)). - [BREAKING] Added `miden::standards::note_tag` module for account target note tags ([#2366](https://github.com/0xMiden/miden-base/pull/2366)). +- [BREAKING] Refactored assets in the tx kernel from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396)). ## 0.13.3 (2026-01-27) From c1b163f7a5d3b1e9d160ef0b5fc630744ca130b5 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 10:28:36 +0100 Subject: [PATCH 018/100] fix: faucet::mint output docs --- crates/miden-protocol/asm/kernels/transaction/api.masm | 7 +++++-- .../miden-protocol/asm/kernels/transaction/lib/faucet.masm | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 9db5ef8fde..e683266d0f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -729,10 +729,13 @@ end #! Mint an asset from the faucet the transaction is being executed against. #! #! Inputs: [ASSET_VALUE, pad(12)] -#! Outputs: [ASSET_VALUE, pad(12)] +#! Outputs: [NEW_ASSET_VALUE, pad(12)] #! #! Where: #! - ASSET_VALUE is the value of the asset that was minted. +#! - NEW_ASSET_VALUE is: +#! - For fungible assets: the ASSET_VALUE merged with the existing vault asset value, if any. +#! - For non-fungible assets: identical to ASSET_VALUE. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -759,7 +762,7 @@ pub proc faucet_mint_asset # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. exec.asset_vault::build_asset_vault_key exec.faucet::mint - # => [ASSET_VALUE, pad(12)] + # => [NEW_ASSET_VALUE, pad(12)] end #! Burn an asset from the faucet the transaction is being executed against. diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index f514d7d359..6dcabb1479 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -152,9 +152,9 @@ end #! Where: #! - ASSET_KEY is the vault key of the asset to mint. #! - ASSET_VALUE is the value of the asset value to mint. -#! - For fungible assets: NEW_ASSET_VALUE is ASSET_VALUE merged with the existing vault asset -#! value, if any. -#! - For non-fungible assets: NEW_ASSET_VALUE is identical to ASSET_VALUE. +#! - NEW_ASSET_VALUE is: +#! - For fungible assets: the ASSET_VALUE merged with the existing vault asset value, if any. +#! - For non-fungible assets: identical to ASSET_VALUE. #! #! Panics if: #! - the transaction is not being executed against a faucet. From 23263d64eaff1032a9470d9fbbe521182f9f4dda Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 16:30:02 +0100 Subject: [PATCH 019/100] chore: update memory.rs input/output note memory layouts --- .../src/transaction/kernel/memory.rs | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index 47b11b69c6..959507bb4e 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -345,22 +345,28 @@ pub const NOTE_MEM_SIZE: MemoryAddress = 3072; // Each nullifier occupies a single word. A data section for each note consists of exactly 3072 // elements and is laid out like so: // -// ┌──────┬────────┬────────┬─────────┬────────────┬───────────┬──────────┬────────────┬───────┬─────────┬────────┬───────┬─────┬───────┬─────────┬ -// │ NOTE │ SERIAL │ SCRIPT │ STORAGE │ ASSETS | RECIPIENT │ METADATA │ ATTACHMENT │ NOTE │ STORAGE │ NUM │ ASSET │ ... │ ASSET │ PADDING │ -// │ ID │ NUM │ ROOT │ COMM │ COMMITMENT | │ HEADER │ │ ARGS │ LENGTH │ ASSETS │ 0 │ │ n │ │ -// ├──────┼────────┼────────┼─────────┼────────────┼───────────┼──────────┼────────────┼───────┼─────────┼────────┼───────┼─────┼───────┼─────────┤ -// 0 4 8 12 16 20 24 28 32 36 40 44 + 4n +// ┌──────┬────────┬────────┬─────────┬────────────┬───────────┬──────────┬────────────┬───────┬ +// │ NOTE │ SERIAL │ SCRIPT │ STORAGE │ ASSETS | RECIPIENT │ METADATA │ ATTACHMENT │ NOTE │ +// │ ID │ NUM │ ROOT │ COMM │ COMMITMENT | │ HEADER │ │ ARGS │ +// ├──────┼────────┼────────┼─────────┼────────────┼───────────┼──────────┼────────────┼───────┼ +// 0 4 8 12 16 20 24 28 32 +// +// ┬─────────┬────────┬───────┬─────────┬─────┬────────┬─────────┬─────────┐ +// │ STORAGE │ NUM │ ASSET │ ASSET │ ... │ ASSET │ ASSET │ PADDING │ +// │ LENGTH │ ASSETS │ KEY 0 │ VALUE 0 │ │ KEY n │ VALUE n │ │ +// ┼─────────┼────────┼───────┼─────────┼─────┼────────┼─────────┼─────────┘ +// 36 40 44 48 44 + 8n 48 + 8n // // - NUM_STORAGE_ITEMS is encoded as [num_storage_items, 0, 0, 0]. // - NUM_ASSETS is encoded as [num_assets, 0, 0, 0]. // - STORAGE_COMMITMENT is the key to look up note storage in the advice map. // - ASSETS_COMMITMENT is the key to look up note assets in the advice map. // -// Notice that note storage values are not loaded to the memory, only their length. In order to obtain +// Notice that note storage item are not loaded to the memory, only their length. In order to obtain // the storage values the advice map should be used: they are stored there as // `STORAGE_COMMITMENT -> STORAGE`. // -// As opposed to the asset values, storage values are never used in kernel memory, so their presence +// As opposed to the asset values, storage items are never used in kernel memory, so their presence // there is unnecessary. /// The memory address at which the input note section begins. @@ -404,22 +410,24 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 44; // The total number of output notes for a transaction is stored in the bookkeeping section of the // memory. Data section of each note is laid out like so: // -// ┌──────┬──────────┬────────────┬───────────┬────────────┬────────────────┬─────────┬─────┬─────────┬─────────┐ -// │ NOTE │ METADATA │ METADATA │ RECIPIENT │ ASSETS │ NUM ASSETS │ ASSET 0 │ ... │ ASSET n │ PADDING │ -// | ID | HEADER | ATTACHMENT | | COMMITMENT | AND DIRTY FLAG | | | | | -// ├──────┼──────────┼────────────┼───────────┼────────────┼────────────────┼─────────┼─────┼─────────┼─────────┤ -// 0 1 2 3 4 5 6 6 + n +// ┌──────┬──────────┬────────────┬───────────┬────────────┬────────────────┬ +// │ NOTE │ METADATA │ METADATA │ RECIPIENT │ ASSETS │ NUM ASSETS │ +// │ ID │ HEADER │ ATTACHMENT │ │ COMMITMENT │ AND DIRTY FLAG │ +// ├──────┼──────────┼────────────┼───────────┼────────────┼────────────────┼ +// 0 4 8 12 16 20 // -// The NUM_ASSETS_AND_DIRTY_FLAG word has the following layout: -// `[num_assets, assets_commitment_dirty_flag, 0, 0]`, where: -// - `num_assets` is the number of assets in this output note. -// - `assets_commitment_dirty_flag` is the binary flag which specifies whether the assets commitment -// stored in this note is outdated. It holds 1 if some changes were made to the note assets since -// the last re-computation, and 0 otherwise. +// ┬────────┬───────┬───────┬─────────┬─────┬────────┬─────────┬─────────┐ +// │ NUM │ DIRTY │ ASSET │ ASSET │ ... │ ASSET │ ASSET │ PADDING │ +// │ ASSETS │ FLAG │ KEY 0 │ VALUE 0 │ │ KEY n │ VALUE n │ │ +// ┼────────┼───────┼───────┼─────────┼─────┼────────┼─────────┼─────────┘ +// 20 21 24 28 24 + 8n 28 + 8n // -// Dirty flag is set to 0 after every recomputation of the assets commitment in the -// `kernel::note::compute_output_note_assets_commitment` procedure. It is set to 1 in the -// `kernel::output_note::add_asset` procedure after any change was made to the assets data. +// The DIRTY_FLAG is the binary flag which specifies whether the assets commitment stored in this +// note is outdated. It holds 1 if some changes were made to the note assets since the last +// re-computation, and 0 otherwise. +// It is set to 0 after every recomputation of the assets commitment in the +// `$kernel::note::compute_output_note_assets_commitment` procedure. It is set to 1 in the +// `$kernel::output_note::add_asset` procedure after any change was made to the assets data. /// The memory address at which the output notes section begins. pub const OUTPUT_NOTE_SECTION_OFFSET: MemoryOffset = 16_777_216; From d85482be14e46a8ffa1c9bfaaac0c150d8b169e8 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 16:35:05 +0100 Subject: [PATCH 020/100] fix: duplicate num assets in memory.rs table --- .../src/transaction/kernel/memory.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index 959507bb4e..2d82a1739b 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -410,17 +410,17 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 44; // The total number of output notes for a transaction is stored in the bookkeeping section of the // memory. Data section of each note is laid out like so: // -// ┌──────┬──────────┬────────────┬───────────┬────────────┬────────────────┬ -// │ NOTE │ METADATA │ METADATA │ RECIPIENT │ ASSETS │ NUM ASSETS │ -// │ ID │ HEADER │ ATTACHMENT │ │ COMMITMENT │ AND DIRTY FLAG │ -// ├──────┼──────────┼────────────┼───────────┼────────────┼────────────────┼ -// 0 4 8 12 16 20 +// ┌──────┬──────────┬────────────┬───────────┬────────────┬────────┬───────┬ +// │ NOTE │ METADATA │ METADATA │ RECIPIENT │ ASSETS │ NUM │ DIRTY │ +// │ ID │ HEADER │ ATTACHMENT │ │ COMMITMENT │ ASSETS │ FLAG │ +// ├──────┼──────────┼────────────┼───────────┼────────────┼────────┼───────┼ +// 0 4 8 12 16 20 21 // -// ┬────────┬───────┬───────┬─────────┬─────┬────────┬─────────┬─────────┐ -// │ NUM │ DIRTY │ ASSET │ ASSET │ ... │ ASSET │ ASSET │ PADDING │ -// │ ASSETS │ FLAG │ KEY 0 │ VALUE 0 │ │ KEY n │ VALUE n │ │ -// ┼────────┼───────┼───────┼─────────┼─────┼────────┼─────────┼─────────┘ -// 20 21 24 28 24 + 8n 28 + 8n +// ┬───────┬─────────┬─────┬────────┬─────────┬─────────┐ +// │ ASSET │ ASSET │ ... │ ASSET │ ASSET │ PADDING │ +// │ KEY 0 │ VALUE 0 │ │ KEY n │ VALUE n │ │ +// ┼───────┼─────────┼─────┼────────┼─────────┼─────────┘ +// 24 28 24 + 8n 28 + 8n // // The DIRTY_FLAG is the binary flag which specifies whether the assets commitment stored in this // note is outdated. It holds 1 if some changes were made to the note assets since the last From e6909da205fb0ebc797f110199b7f79b8796b4e8 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 4 Feb 2026 16:53:01 +0100 Subject: [PATCH 021/100] feat: move `build_asset_vault_key` to shared utils --- .../kernels/transaction/lib/asset_vault.masm | 80 ++----------------- .../asm/protocol/active_account.masm | 41 +--------- .../asm/shared_utils/util/asset.masm | 70 ++++++++++++++++ 3 files changed, 78 insertions(+), 113 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index 299709ffba..bb09153957 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -20,11 +20,13 @@ const ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to re const ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-existent non-fungible asset from the vault" -# CONSTANTS +# RE-EXPORTS # ================================================================================================= -# The bitmask that when applied will set the fungible bit to zero. -const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 +# Re-export the shared utility procedures +pub use $kernel::util::asset::build_non_fungible_asset_vault_key +pub use $kernel::util::asset::build_fungible_asset_vault_key +pub use $kernel::util::asset::build_asset_vault_key # ACCESSORS # ================================================================================================= @@ -431,75 +433,3 @@ pub proc remove_asset # => [ASSET_VALUE] end end - -# HELPER PROCEDURES -# ================================================================================================= - -#! Builds the vault key of a non fungible asset. The asset is NOT validated and therefore must -#! be a valid non-fungible asset. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY] -#! -#! Where: -#! - ASSET_VALUE is the non-fungible asset for which the vault key is built. -#! - ASSET_KEY is the vault key of the non-fungible asset. -pub proc build_non_fungible_asset_vault_key - # create the asset key from the non-fungible asset by swapping hash0 with the faucet id - # => [faucet_id_prefix, hash2, hash1, hash0] - swap.3 - # => [hash0, hash2, hash1, faucet_id_prefix] - - # disassemble hash0 into u32 limbs - u32split swap - # => [hash0_lo, hash0_hi, hash2, hash1, faucet_id_prefix] - - # set the fungible bit to 0 - u32and.INVERSE_FUNGIBLE_BITMASK_U32 - # => [hash0_lo', hash0_hi, hash2, hash1, faucet_id_prefix] - - # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part - swap mul.0x0100000000 add - # => [ASSET_KEY] -end - -#! TODO: Add Rust <-> MASM test. -#! -#! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must -#! be a valid fungible asset. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - ASSET_VALUE is the fungible asset for which the vault key is built. -#! - ASSET_KEY is the vault key of the fungible asset. -pub proc build_fungible_asset_vault_key - # => [faucet_id_prefix, faucet_id_suffix, 0, amount] - - push.0.0 - # => [0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] - - dup.3 dup.3 - # => [faucet_id_prefix, faucet_id_suffix, 0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] -end - -#! TODO(expand_assets): Temporary procedure for building an asset vault key. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -pub proc build_asset_vault_key - # check the first element, it will be: - # - zero for a fungible asset - # - non zero for a non-fungible asset - dup.2 eq.0 - # => [is_fungible_asset, ASSET_VALUE] - - if.true - exec.build_fungible_asset_vault_key - # => [ASSET_KEY, ASSET_VALUE] - else - dupw exec.build_non_fungible_asset_vault_key - # => [ASSET_KEY, ASSET_VALUE] - end -end diff --git a/crates/miden-protocol/asm/protocol/active_account.masm b/crates/miden-protocol/asm/protocol/active_account.masm index 334db1f139..34b0a3b283 100644 --- a/crates/miden-protocol/asm/protocol/active_account.masm +++ b/crates/miden-protocol/asm/protocol/active_account.masm @@ -1,5 +1,6 @@ use miden::protocol::kernel_proc_offsets -use ::miden::protocol::account_id +use miden::protocol::util::asset +use miden::protocol::account_id use ::miden::protocol::kernel_proc_offsets::ACCOUNT_GET_ASSET_OFFSET use ::miden::protocol::kernel_proc_offsets::ACCOUNT_GET_INITIAL_ASSET_OFFSET use miden::core::word @@ -559,7 +560,7 @@ pub proc has_non_fungible_asset assert.err=ERR_VAULT_HAS_NON_FUNGIBLE_ASSET_PROC_CAN_BE_CALLED_ONLY_WITH_NON_FUNGIBLE_ASSET # => [ASSET] - exec.build_non_fungible_asset_vault_key + exec.asset::build_non_fungible_asset_vault_key # => [ASSET_KEY] exec.get_asset @@ -656,39 +657,3 @@ pub proc has_procedure swapdw dropw dropw swapw dropw movdn.3 drop drop drop # => [is_procedure_available] end - -# TODO(expand_assets): -# Exact copy of $kernel::asset_vault::build_non_fungible_asset_vault_key -# This should only be temporarily needed and if not, deduplicate. - -# The bitmask that when applied will set the fungible bit to zero. -const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 - -#! -#! Builds the vault key of a non fungible asset. The asset is NOT validated and therefore must -#! be a valid non-fungible asset. -#! -#! Inputs: [ASSET] -#! Outputs: [ASSET_KEY] -#! -#! Where: -#! - ASSET is the non-fungible asset for which the vault key is built. -#! - ASSET_KEY is the vault key of the non-fungible asset. -proc build_non_fungible_asset_vault_key - # create the asset key from the non-fungible asset by swapping hash0 with the faucet id - # => [faucet_id_prefix, hash2, hash1, hash0] - swap.3 - # => [hash0, hash2, hash1 faucet_id_prefix] - - # disassemble hash0 into u32 limbs - u32split swap - # => [hash0_lo, hash0_hi, hash2, hash1 faucet_id_prefix] - - # set the fungible bit to 0 - u32and.INVERSE_FUNGIBLE_BITMASK_U32 - # => [hash0_lo', hash0_hi, hash2, hash1 faucet_id_prefix] - - # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part - swap mul.0x0100000000 add - # => [ASSET_KEY] -end diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 4e7b59bc9c..d4e8b814d3 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -12,6 +12,9 @@ pub const ASSET_SIZE = 8 # The offset of the asset value in an asset stored in memory. pub const ASSET_VALUE_MEMORY_OFFSET = 4 +# The bitmask that when applied will set the fungible bit to zero. +const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 + # PROCEDURES # ================================================================================================= @@ -29,3 +32,70 @@ pub proc get_balance_from_fungible_asset drop drop drop # => [balance] end + +#! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must +#! be a valid fungible asset. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - ASSET_VALUE is the fungible asset for which the vault key is built. +#! - ASSET_KEY is the vault key of the fungible asset. +pub proc build_fungible_asset_vault_key + # => [faucet_id_prefix, faucet_id_suffix, 0, amount] + + push.0.0 + # => [0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] + + dup.3 dup.3 + # => [faucet_id_prefix, faucet_id_suffix, 0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] +end + +#! Builds the vault key of a non fungible asset. The asset is NOT validated and therefore must +#! be a valid non-fungible asset. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_VALUE is the non-fungible asset for which the vault key is built. +#! - ASSET_KEY is the vault key of the non-fungible asset. +pub proc build_non_fungible_asset_vault_key + # create the asset key from the non-fungible asset by swapping hash0 with the faucet id + # => [faucet_id_prefix, hash2, hash1, hash0] + swap.3 + # => [hash0, hash2, hash1, faucet_id_prefix] + + # disassemble hash0 into u32 limbs + u32split swap + # => [hash0_lo, hash0_hi, hash2, hash1, faucet_id_prefix] + + # set the fungible bit to 0 + u32and.INVERSE_FUNGIBLE_BITMASK_U32 + # => [hash0_lo', hash0_hi, hash2, hash1, faucet_id_prefix] + + # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part + swap mul.0x0100000000 add + # => [ASSET_KEY] +end + +#! Builds an asset vault key from an asset value. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +pub proc build_asset_vault_key + # check the first element, it will be: + # - zero for a fungible asset + # - non zero for a non-fungible asset + dup.2 eq.0 + # => [is_fungible_asset, ASSET_VALUE] + + if.true + exec.build_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] + else + dupw exec.build_non_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] + end +end From 2f200d578a0902fd20f2ec4fd07a2833991bef55 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 10:47:52 +0100 Subject: [PATCH 022/100] feat: refactor `faucet::mint` --- .../asm/kernels/transaction/api.masm | 9 ++- crates/miden-protocol/asm/protocol/asset.masm | 21 ++++-- .../miden-protocol/asm/protocol/faucet.masm | 35 ++++++---- .../asm/standards/faucets/mod.masm | 3 +- .../src/testing/mock_account_code.rs | 6 +- .../src/kernel_tests/tx/test_faucet.rs | 69 ++++++++++++------- 6 files changed, 91 insertions(+), 52 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index e683266d0f..a0c3c82839 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -728,10 +728,11 @@ end #! Mint an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET_VALUE, pad(12)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] #! Outputs: [NEW_ASSET_VALUE, pad(12)] #! #! Where: +#! - ASSET_KEY is the vault key of the asset to mint. #! - ASSET_VALUE is the value of the asset that was minted. #! - NEW_ASSET_VALUE is: #! - For fungible assets: the ASSET_VALUE merged with the existing vault asset value, if any. @@ -752,15 +753,13 @@ end pub proc faucet_mint_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # mint the asset - # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. - exec.asset_vault::build_asset_vault_key exec.faucet::mint # => [NEW_ASSET_VALUE, pad(12)] end diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index c10018e56b..5a258a2455 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -16,22 +16,32 @@ const ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT="fungible asset build const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet" +# RE-EXPORTS +# ================================================================================================= + +# TODO(expand_assets): These shouldn't be needed long-term. +# Re-export the shared utility procedures +pub use ::miden::protocol::util::asset::build_non_fungible_asset_vault_key +pub use ::miden::protocol::util::asset::build_fungible_asset_vault_key +pub use ::miden::protocol::util::asset::build_asset_vault_key + # PROCEDURES # ================================================================================================= #! Builds a fungible asset for the specified fungible faucet and amount. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, amount] -#! Outputs: [ASSET] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset #! for. #! - amount is the amount of the asset to create. -#! - ASSET is the built fungible asset. +#! - ASSET_KEY is the vault key of the created fungible asset. +#! - ASSET_VALUE is the value of the created fungible asset. #! #! Invocation: exec -pub proc build_fungible_asset +pub proc create_fungible_asset # assert the faucet is a fungible faucet dup exec.account_id::is_fungible_faucet assert.err=ERR_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID # => [faucet_id_prefix, faucet_id_suffix, amount] @@ -43,7 +53,10 @@ pub proc build_fungible_asset # create the asset push.0 movdn.2 - # => [ASSET] + # => [ASSET_VALUE] + + exec.build_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] end #! Builds a non fungible asset for the specified non-fungible faucet and amount. diff --git a/crates/miden-protocol/asm/protocol/faucet.masm b/crates/miden-protocol/asm/protocol/faucet.masm index 95a9c537d1..59c1a1fb68 100644 --- a/crates/miden-protocol/asm/protocol/faucet.masm +++ b/crates/miden-protocol/asm/protocol/faucet.masm @@ -5,11 +5,12 @@ use miden::protocol::kernel_proc_offsets #! Creates a fungible asset for the faucet the transaction is being executed against. #! #! Inputs: [amount] -#! Outputs: [ASSET] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - amount is the amount of the asset to create. -#! - ASSET is the created fungible asset. +#! - ASSET_KEY is the vault key of the created fungible asset. +#! - ASSET_VALUE is the value of the created fungible asset. #! #! Panics if: #! - the active account is not a fungible faucet. @@ -20,9 +21,9 @@ pub proc create_fungible_asset exec.active_account::get_id # => [id_prefix, id_suffix, amount] - # build the fungible asset - exec.asset::build_fungible_asset - # => [ASSET] + # create the fungible asset + exec.asset::create_fungible_asset + # => [ASSET_KEY, ASSET_VALUE] end #! Creates a non-fungible asset for the faucet the transaction is being executed against. @@ -50,11 +51,15 @@ end #! Mint an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [NEW_ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was minted. +#! - ASSET_KEY is the vault key of the asset to mint. +#! - ASSET_VALUE is the value of the asset that was minted. +#! - NEW_ASSET_VALUE is: +#! - For fungible assets: the ASSET_VALUE merged with the existing vault asset value, if any. +#! - For non-fungible assets: identical to ASSET_VALUE. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -67,19 +72,19 @@ end #! #! Invocation: exec pub proc mint - exec.kernel_proc_offsets::faucet_mint_asset_offset - # => [offset, ASSET] - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] + padw padw swapdw movup.8 drop + # => [ASSET_KEY, ASSET_VALUE, pad(7)] + + exec.kernel_proc_offsets::faucet_mint_asset_offset + # => [offset, ASSET_KEY, ASSET_VALUE, pad(7)] syscall.exec_kernel_proc - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end #! Burn an asset from the faucet the transaction is being executed against. diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 7e49f03b80..faf4bf083e 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -3,6 +3,7 @@ use miden::protocol::active_note use miden::protocol::faucet use miden::protocol::native_account use miden::protocol::output_note +use miden::protocol::asset use ::miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET @@ -127,7 +128,7 @@ pub proc distribute # creating the asset exec.faucet::create_fungible_asset - # => [ASSET, tag, note_type, RECIPIENT] + # => [ASSET_KEY, ASSET_VALUE, tag, note_type, RECIPIENT] # mint the asset; this is needed to satisfy asset preservation logic. # this ensures that the asset's faucet ID matches the native account's ID. diff --git a/crates/miden-standards/src/testing/mock_account_code.rs b/crates/miden-standards/src/testing/mock_account_code.rs index cabcb23028..1d718c5e77 100644 --- a/crates/miden-standards/src/testing/mock_account_code.rs +++ b/crates/miden-standards/src/testing/mock_account_code.rs @@ -7,11 +7,11 @@ use crate::code_builder::CodeBuilder; const MOCK_FAUCET_CODE: &str = " use miden::protocol::faucet - #! Inputs: [ASSET, pad(12)] - #! Outputs: [ASSET, pad(12)] + #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] + #! Outputs: [NEW_ASSET_VALUE, pad(12)] pub proc mint exec.faucet::mint - # => [ASSET, pad(12)] + # => [NEW_ASSET_VALUE, pad(12)] end #! Inputs: [ASSET, pad(12)] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 77660cebf6..a81f5fc47b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -1,5 +1,6 @@ use alloc::sync::Arc; +use miden_protocol::Word; use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId, AccountType}; use miden_protocol::assembly::DefaultSourceManager; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; @@ -23,7 +24,6 @@ use miden_protocol::testing::constants::{ NON_FUNGIBLE_ASSET_DATA_2, }; use miden_protocol::testing::noop_auth_component::NoopAuthComponent; -use miden_protocol::{Felt, Word}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::mock_account::MockAccountExt; @@ -50,28 +50,32 @@ async fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { exec.prologue::prepare_transaction # mint asset - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.mock_faucet::mint # assert the correct asset is returned - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} assert_eqw.err="minted asset does not match expected asset" # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{ASSET_KEY} + push.{FUNGIBLE_ASSET_KEY} exec.asset_vault::get_asset - # => [ASSET] + # => [ASSET_VALUE] # extract balance from asset drop drop drop # => [balance] push.{FUNGIBLE_ASSET_AMOUNT} assert_eq.err="input vault should contain minted asset" + + # truncate the stack + dropw end "#, - FUNGIBLE_ASSET = Word::from(asset), - ASSET_KEY = asset.vault_key(), + FUNGIBLE_ASSET_KEY = asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = asset.to_value_word(), ); TransactionContextBuilder::with_fungible_faucet(faucet_id.into()) @@ -86,17 +90,20 @@ async fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { #[tokio::test] async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; + let asset = FungibleAsset::mock(50); let code = format!( " use mock::faucet begin - push.{asset} + push.{asset_key} + push.{asset_value} call.faucet::mint end ", - asset = Word::from(FungibleAsset::mock(50)) + asset_key = asset.vault_key(), + asset_value = Word::from(asset), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -116,6 +123,7 @@ async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> TransactionContextBuilder::with_fungible_faucet(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1) .build()?; + let asset = FungibleAsset::mock(5); let code = format!( " use $kernel::prologue @@ -123,11 +131,13 @@ async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> begin exec.prologue::prepare_transaction - push.{asset} + push.{asset_key} + push.{asset_value} call.faucet::mint end ", - asset = Word::from(FungibleAsset::mock(5)) + asset_key = asset.vault_key(), + asset_value = Word::from(asset), ); let exec_output = tx_context.execute_code(&code).await; @@ -149,15 +159,20 @@ async fn test_mint_fungible_asset_fails_when_amount_exceeds_max_representable_am push.0 push.{faucet_id_suffix} push.{faucet_id_prefix} - # => [faucet_id_prefix, faucet_id_suffix, 0, max_amount_plus_1] + # => [ASSET_VALUE] + + push.0.0 + push.{faucet_id_suffix} + push.{faucet_id_prefix} + # => [ASSET_KEY, ASSET_VALUE] call.faucet::mint - dropw + dropw dropw end ", faucet_id_prefix = FungibleAsset::mock_issuer().prefix().as_felt(), faucet_id_suffix = FungibleAsset::mock_issuer().suffix(), - max_amount_plus_1 = Felt::try_from(FungibleAsset::MAX_AMOUNT + 1).unwrap(), + max_amount_plus_1 = FungibleAsset::MAX_AMOUNT + 1, ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -198,25 +213,26 @@ async fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { begin # mint asset exec.prologue::prepare_transaction - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.mock_faucet::mint # assert the correct asset is returned - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} assert_eqw.err="minted asset does not match expected asset" # assert the input vault has been updated. exec.memory::get_input_vault_root_ptr - push.{ASSET_KEY} + push.{NON_FUNGIBLE_ASSET_KEY} exec.asset_vault::get_asset - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} assert_eqw.err="vault should contain asset" dropw end "#, - ASSET_KEY = non_fungible_asset.vault_key(), - non_fungible_asset = Word::from(non_fungible_asset), + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset.to_value_word(), ); tx_context.execute_code(&code).await?; @@ -239,11 +255,13 @@ async fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow:: begin exec.prologue::prepare_transaction - push.{non_fungible_asset} + push.{asset_value} + push.{asset_key} call.faucet::mint end ", - non_fungible_asset = Word::from(non_fungible_asset) + asset_key = non_fungible_asset.to_key_word(), + asset_value = Word::from(non_fungible_asset), ); let exec_output = tx_context.execute_code(&code).await; @@ -256,17 +274,20 @@ async fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow:: #[tokio::test] async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; + let asset = FungibleAsset::mock(50); let code = format!( " use mock::faucet begin - push.{asset} + push.{asset_key} + push.{asset_value} call.faucet::mint end ", - asset = Word::from(FungibleAsset::mock(50)) + asset_key = asset.vault_key(), + asset_value = Word::from(asset), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; From 02502eea4d7fb53ecf8eac578133e7b5dbeef4cc Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 11:07:55 +0100 Subject: [PATCH 023/100] feat: refactor `faucet::burn` --- .../asm/kernels/transaction/api.masm | 9 ++- .../miden-protocol/asm/protocol/faucet.masm | 21 +++---- .../asm/standards/faucets/mod.masm | 15 +++-- .../src/testing/mock_account_code.rs | 6 +- .../src/kernel_tests/tx/test_asset.rs | 22 +++---- .../src/kernel_tests/tx/test_faucet.rs | 59 ++++++++++++------- 6 files changed, 73 insertions(+), 59 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index a0c3c82839..94d38a25a3 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -766,10 +766,11 @@ end #! Burn an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET_VALUE, pad(12)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] #! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: +#! - ASSET_KEY is the vault key of the asset to burn. #! - ASSET_VALUE is the value of the asset that was burned. #! #! Panics if: @@ -788,15 +789,13 @@ end pub proc faucet_burn_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # burn the asset - # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. - exec.asset_vault::build_asset_vault_key exec.faucet::burn # => [ASSET_VALUE, pad(12)] end diff --git a/crates/miden-protocol/asm/protocol/faucet.masm b/crates/miden-protocol/asm/protocol/faucet.masm index 59c1a1fb68..74a05b2f39 100644 --- a/crates/miden-protocol/asm/protocol/faucet.masm +++ b/crates/miden-protocol/asm/protocol/faucet.masm @@ -89,11 +89,12 @@ end #! Burn an asset from the faucet the transaction is being executed against. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset that was burned. +#! - ASSET_KEY is the vault key of the asset to burn. +#! - ASSET_VALUE is the value of the asset that was burned. #! #! Panics if: #! - the transaction is not being executed against a faucet. @@ -107,17 +108,17 @@ end #! #! Invocation: exec pub proc burn - exec.kernel_proc_offsets::faucet_burn_asset_offset - # => [offset, ASSET] - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] + padw padw swapdw movup.8 drop + # => [ASSET_KEY, ASSET_VALUE, pad(7)] + + exec.kernel_proc_offsets::faucet_burn_asset_offset + # => [offset, ASSET_KEY, ASSET_VALUE, pad(7)] syscall.exec_kernel_proc - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index faf4bf083e..c16faea6de 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -184,16 +184,19 @@ pub proc burn assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS # => [dest_ptr, pad(16)] - # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET - mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET - # => [ASSET_VALUE, pad(16)] - # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], pad(16)] + # load asset value from dest_ptr + ASSET_VALUE_MEMORY_OFFSET + padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, dest_ptr, pad(16)] + + # load asset key from dest_ptr + padw movup.8 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE, pad(16)] # Burn the asset from the transaction vault # --------------------------------------------------------------------------------------------- - dup.3 movdn.4 - # => [ASSET_VALUE, amount, pad(16)] + dup.7 movdn.8 + # => [ASSET_KEY, ASSET_VALUE, amount, pad(16)] # burn the asset # this ensures we only burn assets that were issued by this faucet (which implies they are diff --git a/crates/miden-standards/src/testing/mock_account_code.rs b/crates/miden-standards/src/testing/mock_account_code.rs index 1d718c5e77..988aca3db8 100644 --- a/crates/miden-standards/src/testing/mock_account_code.rs +++ b/crates/miden-standards/src/testing/mock_account_code.rs @@ -14,11 +14,11 @@ const MOCK_FAUCET_CODE: &str = " # => [NEW_ASSET_VALUE, pad(12)] end - #! Inputs: [ASSET, pad(12)] - #! Outputs: [ASSET, pad(12)] + #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] + #! Outputs: [ASSET_VALUE, pad(12)] pub proc burn exec.faucet::burn - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 20d23e1457..a0ab846c18 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -1,8 +1,7 @@ -use miden_protocol::account::AccountId; -use miden_protocol::asset::NonFungibleAsset; +use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; -use miden_protocol::{Felt, Hasher, Word}; +use miden_protocol::{Hasher, Word}; use crate::TransactionContextBuilder; use crate::kernel_tests::tx::ExecutionOutputExt; @@ -12,6 +11,7 @@ async fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET) .build()?; + let expected_asset = FungibleAsset::new(tx_context.account().id(), FUNGIBLE_ASSET_AMOUNT)?; let code = format!( " @@ -24,25 +24,19 @@ async fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { # create fungible asset push.{FUNGIBLE_ASSET_AMOUNT} exec.faucet::create_fungible_asset + # => [ASSET_KEY, ASSET_VALUE] # truncate the stack - swapw dropw + exec.::miden::core::sys::truncate_stack end " ); let exec_output = &tx_context.execute_code(&code).await?; - let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - assert_eq!( - exec_output.get_stack_word_be(0), - Word::from([ - Felt::new(FUNGIBLE_ASSET_AMOUNT), - Felt::new(0), - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ]) - ); + assert_eq!(exec_output.get_stack_word_be(0), expected_asset.to_key_word()); + assert_eq!(exec_output.get_stack_word_be(4), expected_asset.to_value_word()); + Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index a81f5fc47b..900129b499 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -324,19 +324,20 @@ async fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { exec.prologue::prepare_transaction # burn asset - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.mock_faucet::burn # assert the correct asset is returned - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} assert_eqw.err="burnt asset does not match expected asset" # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{ASSET_KEY} + push.{FUNGIBLE_ASSET_KEY} exec.asset_vault::get_asset - # => [ASSET] + # => [ASSET_VALUE] # extract balance from asset drop drop drop @@ -344,10 +345,12 @@ async fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { push.{final_input_vault_asset_amount} assert_eq.err="vault balance does not match expected balance" + + exec.::miden::core::sys::truncate_stack end "#, - FUNGIBLE_ASSET = Word::from(asset), - ASSET_KEY = asset.vault_key(), + FUNGIBLE_ASSET_VALUE = asset.to_value_word(), + FUNGIBLE_ASSET_KEY = asset.to_key_word(), final_input_vault_asset_amount = CONSUMED_ASSET_1_AMOUNT - FUNGIBLE_ASSET_AMOUNT, ); @@ -360,17 +363,20 @@ async fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { #[tokio::test] async fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; + let asset = FungibleAsset::mock(50); let code = format!( " use mock::faucet begin - push.{asset} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.faucet::burn end ", - asset = Word::from(FungibleAsset::mock(50)) + FUNGIBLE_ASSET_VALUE = asset.to_value_word(), + FUNGIBLE_ASSET_KEY = asset.to_key_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -391,6 +397,7 @@ async fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> .build()?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(); + let fungible_asset = FungibleAsset::new(faucet_id, FUNGIBLE_ASSET_AMOUNT)?; let code = format!( " @@ -399,12 +406,13 @@ async fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.faucet::burn end ", - prefix = faucet_id.prefix().as_felt(), - suffix = faucet_id.suffix(), + FUNGIBLE_ASSET_VALUE = fungible_asset.to_value_word(), + FUNGIBLE_ASSET_KEY = fungible_asset.to_key_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -420,6 +428,7 @@ async fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result< .build()?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(); + let fungible_asset = FungibleAsset::new(faucet_id, CONSUMED_ASSET_1_AMOUNT + 1)?; let code = format!( " @@ -428,13 +437,13 @@ async fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result< begin exec.prologue::prepare_transaction - push.{saturating_amount} push.0 push.{suffix} push.{prefix} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.faucet::burn end ", - prefix = faucet_id.prefix().as_felt(), - suffix = faucet_id.suffix(), - saturating_amount = CONSUMED_ASSET_1_AMOUNT + 1 + FUNGIBLE_ASSET_VALUE = fungible_asset.to_value_word(), + FUNGIBLE_ASSET_KEY = fungible_asset.to_key_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -482,6 +491,7 @@ async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { # burn the non-fungible asset push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.mock_faucet::burn # assert the correct asset is returned @@ -522,11 +532,13 @@ async fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<( begin # burn asset exec.prologue::prepare_transaction - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.faucet::burn end ", - non_fungible_asset = Word::from(non_fungible_asset_burnt) + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset_burnt.to_value_word(), + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset_burnt.to_key_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -539,17 +551,20 @@ async fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<( #[tokio::test] async fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; + let asset = FungibleAsset::mock(50); let code = format!( " use mock::faucet begin - push.{asset} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.faucet::burn end ", - asset = Word::from(FungibleAsset::mock(50)) + ASSET_VALUE = asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -581,11 +596,13 @@ async fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow:: begin # burn asset exec.prologue::prepare_transaction - push.{non_fungible_asset} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.faucet::burn end ", - non_fungible_asset = Word::from(non_fungible_asset_burnt) + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset_burnt.to_value_word(), + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset_burnt.to_key_word(), ); let exec_output = tx_context.execute_code(&code).await; From 2a6cc50f5180cd32f98347e8b575c16415811e0f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 11:15:59 +0100 Subject: [PATCH 024/100] chore: refactor `create_non_fungible_asset` for uniformity --- crates/miden-protocol/asm/protocol/asset.masm | 12 ++++++++---- crates/miden-protocol/asm/protocol/faucet.masm | 9 +++++---- .../miden-testing/src/kernel_tests/tx/test_asset.rs | 8 +++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index 5a258a2455..00cf670482 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -62,16 +62,17 @@ end #! Builds a non fungible asset for the specified non-fungible faucet and amount. #! #! Inputs: [faucet_id_prefix, DATA_HASH] -#! Outputs: [ASSET] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset #! for. #! - DATA_HASH is the data hash of the non-fungible asset to build. -#! - ASSET is the built non-fungible asset. +#! - ASSET_KEY is the vault key of the created non-fungible asset. +#! - ASSET_VALUE is the value of the created non-fungible asset. #! #! Invocation: exec -pub proc build_non_fungible_asset +pub proc create_non_fungible_asset # assert the faucet is a non-fungible faucet dup exec.account_id::is_non_fungible_faucet assert.err=ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID @@ -80,5 +81,8 @@ pub proc build_non_fungible_asset # build the asset swap drop # => [faucet_id_prefix, hash2, hash1, hash0] - # => [ASSET] + # => [ASSET_KEY, ASSET_VALUE] + + dupw exec.build_non_fungible_asset_vault_key + # => [ASSET_KEY, ASSET_VALUE] end diff --git a/crates/miden-protocol/asm/protocol/faucet.masm b/crates/miden-protocol/asm/protocol/faucet.masm index 74a05b2f39..31b7c1906d 100644 --- a/crates/miden-protocol/asm/protocol/faucet.masm +++ b/crates/miden-protocol/asm/protocol/faucet.masm @@ -29,11 +29,12 @@ end #! Creates a non-fungible asset for the faucet the transaction is being executed against. #! #! Inputs: [DATA_HASH] -#! Outputs: [ASSET] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - DATA_HASH is the data hash of the non-fungible asset to create. -#! - ASSET is the created non-fungible asset. +#! - ASSET_KEY is the vault key of the created non-fungible asset. +#! - ASSET_VALUE is the value of the created non-fungible asset. #! #! Panics if: #! - the active account is not a non-fungible faucet. @@ -45,8 +46,8 @@ pub proc create_non_fungible_asset # => [faucet_id_prefix, DATA_HASH] # build the non-fungible asset - exec.asset::build_non_fungible_asset - # => [ASSET] + exec.asset::create_non_fungible_asset + # => [ASSET_KEY, ASSET_VALUE] end #! Mint an asset from the faucet the transaction is being executed against. diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index a0ab846c18..dfb2d64b27 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -1,7 +1,7 @@ +use miden_protocol::Hasher; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; -use miden_protocol::{Hasher, Word}; use crate::TransactionContextBuilder; use crate::kernel_tests::tx::ExecutionOutputExt; @@ -61,14 +61,16 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { exec.faucet::create_non_fungible_asset # truncate the stack - swapw dropw + exec.::miden::core::sys::truncate_stack end ", non_fungible_asset_data_hash = Hasher::hash(&NON_FUNGIBLE_ASSET_DATA), ); let exec_output = &tx_context.execute_code(&code).await?; - assert_eq!(exec_output.get_stack_word_be(0), Word::from(non_fungible_asset)); + + assert_eq!(exec_output.get_stack_word_be(0), non_fungible_asset.to_key_word()); + assert_eq!(exec_output.get_stack_word_be(4), non_fungible_asset.to_value_word()); Ok(()) } From 01a0591373cc125b17798e445e1753ae38c12ca3 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 11:48:49 +0100 Subject: [PATCH 025/100] feat: refactor `native_account::remove_asset` --- .../asm/kernels/transaction/api.masm | 9 +++-- .../asm/protocol/native_account.masm | 21 +++++------ .../asm/standards/wallets/basic.masm | 18 ++++++---- .../src/testing/mock_account_code.rs | 6 ++-- .../src/kernel_tests/tx/test_account_delta.rs | 20 ++++++----- .../src/kernel_tests/tx/test_asset_vault.rs | 36 ++++++++++++------- .../src/kernel_tests/tx/test_lazy_loading.rs | 12 ++++--- 7 files changed, 71 insertions(+), 51 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 94d38a25a3..82d390f16b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -574,10 +574,11 @@ end #! Removes the specified asset from the vault. #! -#! Inputs: [ASSET_VALUE, pad(12)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] #! Outputs: [ASSET_VALUE, pad(12)] #! #! Where: +#! - ASSET_KEY is the vault key of the asset to remove from the vault. #! - ASSET_VALUE is the value of the asset to remove from the vault. #! #! Panics if: @@ -590,15 +591,13 @@ end pub proc account_remove_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # remove the specified asset from the account vault, emitting the corresponding events - # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. - exec.asset_vault::build_asset_vault_key exec.account::remove_asset_from_vault # => [ASSET_VALUE, pad(12)] end diff --git a/crates/miden-protocol/asm/protocol/native_account.masm b/crates/miden-protocol/asm/protocol/native_account.masm index f6bdced4cf..6d78d882b7 100644 --- a/crates/miden-protocol/asm/protocol/native_account.masm +++ b/crates/miden-protocol/asm/protocol/native_account.masm @@ -224,11 +224,12 @@ end #! Remove the specified asset from the vault. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE] #! #! Where: -#! - ASSET is the asset to remove from the vault. +#! - ASSET_KEY is the vault key of the asset to remove from the vault. +#! - ASSET_VALUE is the value of the asset to remove from the vault. #! #! Panics if: #! - the fungible asset is not found in the vault. @@ -237,19 +238,19 @@ end #! #! Invocation: exec pub proc remove_asset - exec.kernel_proc_offsets::account_remove_asset_offset - # => [offset, ASSET] - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] + padw padw swapdw movup.8 drop + # => [ASSET_KEY, ASSET_VALUE, pad(7)] + + exec.kernel_proc_offsets::account_remove_asset_offset + # => [offset, ASSET_KEY, ASSET_VALUE, pad(7)] syscall.exec_kernel_proc - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end # CODE diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index d9113a518a..0be9785f5f 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -38,12 +38,13 @@ end #! the contents of the `PAD` elements shown below. It is the caller's responsibility to make sure #! these elements do not contain any meaningful data. #! -#! Inputs: [ASSET, note_idx, pad(11)] -#! Outputs: [ASSET, note_idx, pad(11)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] +#! Outputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] #! #! Where: #! - note_idx is the index of the output note. -#! - ASSET is the fungible or non-fungible asset of interest. +#! - ASSET_KEY is the vault key of the asset to move to the note. +#! - ASSET_VALUE is the value of the asset to move to the note. #! #! Panics if: #! - the fungible asset is not found in the vault. @@ -54,13 +55,16 @@ end pub proc move_asset_to_note # remove the asset from the account exec.native_account::remove_asset - # => [ASSET, note_idx, pad(11)] + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] - dupw dup.8 movdn.4 - # => [ASSET, note_idx, ASSET, note_idx, pad(11)] + # TODO(expand_assets): Pass key to add_asset eventually. + dropw dupw dup.8 movdn.4 + # => [ASSET_VALUE, note_idx, pad(11)] + # => [ASSET_VALUE, note_idx, ASSET_VALUE, note_idx, pad(11)] exec.output_note::add_asset - # => [ASSET, note_idx, pad(11)] + # TODO(expand_assets): Consider not returning anything from this procedure. + # => [ASSET_VALUE, note_idx, pad(11)] end #! Adds all assets from the active note to the native account's vault. diff --git a/crates/miden-standards/src/testing/mock_account_code.rs b/crates/miden-standards/src/testing/mock_account_code.rs index 988aca3db8..417aa0773a 100644 --- a/crates/miden-standards/src/testing/mock_account_code.rs +++ b/crates/miden-standards/src/testing/mock_account_code.rs @@ -112,11 +112,11 @@ const MOCK_ACCOUNT_CODE: &str = " # => [ASSET', pad(12)] end - #! Inputs: [ASSET, pad(12)] - #! Outputs: [ASSET, pad(12)] + #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] + #! Outputs: [ASSET_VALUE, pad(12)] pub proc remove_asset exec.native_account::remove_asset - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] end #! Inputs: [pad(16)] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index e7e49b7efb..363fbbedff 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -525,16 +525,18 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { # => [] # remove and re-add asset 3 - push.{asset3} + push.{ASSET3_VALUE} + push.{ASSET3_KEY} exec.remove_asset - # => [ASSET] + # => [ASSET_VALUE] exec.add_asset dropw # => [] end ", asset1 = Word::from(asset1), asset2 = Word::from(asset2), - asset3 = Word::from(asset3), + ASSET3_KEY = asset3.to_key_word(), + ASSET3_VALUE = asset3.to_value_word(), ))?; let executed_tx = mock_chain @@ -1153,16 +1155,16 @@ const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " # => [ASSET'] end - #! Inputs: [ASSET] - #! Outputs: [ASSET] + #! Inputs: [ASSET_KEY, ASSET_VALUE] + #! Outputs: [ASSET_VALUE] proc remove_asset - repeat.12 push.0 movdn.4 end - # => [ASSET, pad(12)] + padw padw swapdw + # => [ASSET_KEY, ASSET_VALUE, pad(8)] call.account::remove_asset - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] repeat.12 movup.4 drop end - # => [ASSET] + # => [ASSET_VALUE] end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 15ac66e1a9..7ee95f4ec5 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -351,14 +351,16 @@ async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Re begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::remove_asset # truncate the stack - swapw dropw + exec.::miden::core::sys::truncate_stack end ", - FUNGIBLE_ASSET = Word::from(remove_fungible_asset) + FUNGIBLE_ASSET_KEY = remove_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = remove_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -396,11 +398,13 @@ async fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::remove_asset end ", - FUNGIBLE_ASSET = Word::from(remove_fungible_asset) + FUNGIBLE_ASSET_KEY = remove_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = remove_fungible_asset.to_value_word(), ); let exec_result = tx_context.execute_code(&code).await; @@ -435,14 +439,16 @@ async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Resul begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::remove_asset # truncate the stack - swapw dropw + exec.::miden::core::sys::truncate_stack end ", - FUNGIBLE_ASSET = Word::from(remove_fungible_asset) + FUNGIBLE_ASSET_KEY = remove_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = remove_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -484,11 +490,13 @@ async fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::remove_asset end ", - FUNGIBLE_ASSET = Word::from(non_existent_non_fungible_asset) + FUNGIBLE_ASSET_KEY = non_existent_non_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = non_existent_non_fungible_asset.to_value_word(), ); let exec_result = tx_context.execute_code(&code).await; @@ -520,14 +528,16 @@ async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::remove_asset # truncate the stack - swapw dropw + exec.::miden::core::sys::truncate_stack end ", - FUNGIBLE_ASSET = Word::from(non_fungible_asset) + FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index cf0216fa6d..e6397d3e6a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -91,7 +91,8 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result use mock::util begin - push.{FUNGIBLE_ASSET1} + push.{FUNGIBLE_ASSET1_VALUE} + push.{FUNGIBLE_ASSET1_KEY} call.account::remove_asset # => [] @@ -99,7 +100,8 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result exec.util::create_default_note_with_asset # => [] - push.{FUNGIBLE_ASSET2} + push.{FUNGIBLE_ASSET2_VALUE} + push.{FUNGIBLE_ASSET2_KEY} call.account::remove_asset # => [ASSET] @@ -108,8 +110,10 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result # => [] end ", - FUNGIBLE_ASSET1 = Word::from(fungible_asset1), - FUNGIBLE_ASSET2 = Word::from(fungible_asset2) + FUNGIBLE_ASSET1_KEY = fungible_asset1.to_key_word(), + FUNGIBLE_ASSET1_VALUE = fungible_asset1.to_value_word(), + FUNGIBLE_ASSET2_KEY = fungible_asset2.to_key_word(), + FUNGIBLE_ASSET2_VALUE = fungible_asset2.to_value_word(), ); let builder = CodeBuilder::with_mock_libraries(); From 7256ad201bc0d8f764795a28fd052b058ed3bbbc Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 14:20:16 +0100 Subject: [PATCH 026/100] chore: move `mock::util` lib to miden-standards --- .../miden-standards/src/code_builder/mod.rs | 2 +- .../src/testing/mock_util_lib.rs | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) rename crates/{miden-protocol => miden-standards}/src/testing/mock_util_lib.rs (51%) diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index dfcb4e8e45..f6cf1ba4e3 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -444,7 +444,7 @@ impl CodeBuilder { pub fn with_mock_libraries_with_source_manager( source_manager: Arc, ) -> Self { - use miden_protocol::testing::mock_util_lib::mock_util_library; + use crate::testing::mock_util_lib::mock_util_library; // Start with the builder linking against the transaction kernel, protocol library and // standards library. diff --git a/crates/miden-protocol/src/testing/mock_util_lib.rs b/crates/miden-standards/src/testing/mock_util_lib.rs similarity index 51% rename from crates/miden-protocol/src/testing/mock_util_lib.rs rename to crates/miden-standards/src/testing/mock_util_lib.rs index f9d454f5ee..757b827687 100644 --- a/crates/miden-protocol/src/testing/mock_util_lib.rs +++ b/crates/miden-standards/src/testing/mock_util_lib.rs @@ -1,11 +1,13 @@ -use miden_assembly::diagnostics::NamedSource; +use miden_protocol::assembly::Library; +use miden_protocol::assembly::diagnostics::NamedSource; +use miden_protocol::transaction::TransactionKernel; +use miden_protocol::utils::sync::LazyLock; -use crate::assembly::Library; -use crate::transaction::TransactionKernel; -use crate::utils::sync::LazyLock; +use crate::StandardsLib; const MOCK_UTIL_LIBRARY_CODE: &str = " use miden::protocol::output_note + use miden::standards::wallets::basic->wallet #! Inputs: [] #! Outputs: [note_idx] @@ -31,10 +33,36 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " exec.output_note::add_asset # => [] end + + #! Inputs: [ASSET_KEY, ASSET_VALUE] + #! Outputs: [] + pub proc create_default_note_with_moved_asset + exec.create_default_note + # => [note_idx, ASSET_KEY, ASSET_VALUE] + + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, note_idx] + + exec.move_asset_to_note + # => [] + end + + #! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx] + #! Outputs: [] + pub proc move_asset_to_note + repeat.7 push.0 movdn.9 end + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] + + call.wallet::move_asset_to_note + + dropw dropw dropw dropw + end "; static MOCK_UTIL_LIBRARY: LazyLock = LazyLock::new(|| { TransactionKernel::assembler() + .with_dynamic_library(StandardsLib::default()) + .expect("dynamically linking standards library should work") .assemble_library([NamedSource::new("mock::util", MOCK_UTIL_LIBRARY_CODE)]) .expect("mock util library should be valid") }); @@ -42,6 +70,6 @@ static MOCK_UTIL_LIBRARY: LazyLock = LazyLock::new(|| { /// Returns the mock test [`Library`] under the `mock::util` namespace. /// /// This provides convenient wrappers for testing purposes. -pub fn mock_util_library() -> Library { +pub(crate) fn mock_util_library() -> Library { MOCK_UTIL_LIBRARY.clone() } From d3c7220b354a29a3842d4d38f2e10454c617aa1a Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 14:23:50 +0100 Subject: [PATCH 027/100] feat: refactor `move_asset_to_note` --- bin/bench-transaction/src/context_setups.rs | 9 ++- crates/miden-protocol/src/testing/mod.rs | 1 - crates/miden-protocol/src/testing/storage.rs | 15 ---- .../asm/standards/notes/swap.masm | 48 ++++++----- .../asm/standards/wallets/basic.masm | 10 +-- .../src/account/interface/component.rs | 19 +++-- crates/miden-standards/src/testing/mod.rs | 1 + .../src/kernel_tests/tx/test_account.rs | 31 +++---- .../src/kernel_tests/tx/test_account_delta.rs | 80 ++++++++----------- .../src/kernel_tests/tx/test_epilogue.rs | 6 +- .../src/kernel_tests/tx/test_lazy_loading.rs | 8 +- .../src/kernel_tests/tx/test_output_note.rs | 25 +++--- .../src/kernel_tests/tx/test_tx.rs | 59 ++++++-------- crates/miden-testing/src/utils.rs | 15 ++-- crates/miden-testing/tests/scripts/p2id.rs | 19 +++-- crates/miden-testing/tests/scripts/swap.rs | 6 +- 16 files changed, 169 insertions(+), 183 deletions(-) diff --git a/bin/bench-transaction/src/context_setups.rs b/bin/bench-transaction/src/context_setups.rs index e49b8f882d..223f8e2558 100644 --- a/bin/bench-transaction/src/context_setups.rs +++ b/bin/bench-transaction/src/context_setups.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use miden_protocol::Word; use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::note::NoteType; use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; @@ -37,9 +36,10 @@ pub fn tx_create_single_p2id_note() -> Result { # => [note_idx] # move the asset to the note - push.{asset} + dup + push.{ASSET_VALUE} + push.{ASSET_KEY} call.::miden::standards::wallets::basic::move_asset_to_note - dropw # => [note_idx] # truncate the stack @@ -49,7 +49,8 @@ pub fn tx_create_single_p2id_note() -> Result { RECIPIENT = output_note.recipient().digest(), note_type = NoteType::Public as u8, tag = output_note.metadata().tag(), - asset = Word::from(fungible_asset), + ASSET_KEY = fungible_asset.to_key_word(), + ASSET_VALUE = fungible_asset.to_value_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_note_creation_script)?; diff --git a/crates/miden-protocol/src/testing/mod.rs b/crates/miden-protocol/src/testing/mod.rs index 9b04908a64..ca107d73f3 100644 --- a/crates/miden-protocol/src/testing/mod.rs +++ b/crates/miden-protocol/src/testing/mod.rs @@ -6,7 +6,6 @@ pub mod asset; pub mod block; pub mod block_note_tree; pub mod constants; -pub mod mock_util_lib; pub mod noop_auth_component; pub mod note; pub mod partial_blockchain; diff --git a/crates/miden-protocol/src/testing/storage.rs b/crates/miden-protocol/src/testing/storage.rs index c4c0fc47cb..0349c00d5b 100644 --- a/crates/miden-protocol/src/testing/storage.rs +++ b/crates/miden-protocol/src/testing/storage.rs @@ -1,4 +1,3 @@ -use alloc::string::{String, ToString}; use alloc::vec::Vec; use miden_core::{Felt, Word}; @@ -12,7 +11,6 @@ use crate::account::{ StorageSlotDelta, StorageSlotName, }; -use crate::note::NoteAssets; use crate::utils::sync::LazyLock; // ACCOUNT STORAGE DELTA @@ -135,16 +133,3 @@ impl AccountStorage { StorageMap::with_entries(STORAGE_LEAVES_2).unwrap() } } - -// UTILITIES -// -------------------------------------------------------------------------------------------- - -/// Returns a list of strings, one for each note asset. -pub fn prepare_assets(note_assets: &NoteAssets) -> Vec { - let mut assets = Vec::new(); - for &asset in note_assets.iter() { - let asset_word = Word::from(asset); - assets.push(asset_word.to_string()); - } - assets -} diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 3c71b6e63f..946982731f 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -1,7 +1,8 @@ use miden::protocol::active_note +use miden::protocol::asset +use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET use miden::protocol::output_note use miden::standards::wallets::basic->wallet -use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET # CONSTANTS # ================================================================================================= @@ -13,7 +14,7 @@ const PAYBACK_NOTE_TAG_ADDRESS=1 const ATTACHMENT_KIND_ADDRESS=2 const ATTACHMENT_SCHEME_ADDRESS=3 const ATTACHMENT_ADDRESS=4 -const REQUESTED_ASSET_ADDRESS=8 +const REQUESTED_ASSET_VALUE_ADDRESS=8 const PAYBACK_RECIPIENT_ADDRESS=12 # ERRORS @@ -67,61 +68,58 @@ pub proc main drop # => [] - mem_loadw_be.REQUESTED_ASSET_ADDRESS - # => [REQUESTED_ASSET] - padw mem_loadw_be.PAYBACK_RECIPIENT_ADDRESS - # => [PAYBACK_NOTE_RECIPIENT, REQUESTED_ASSET] + # => [PAYBACK_NOTE_RECIPIENT] # load payback P2ID details mem_load.PAYBACK_NOTE_TYPE_ADDRESS mem_load.PAYBACK_NOTE_TAG_ADDRESS - # => [tag, note_type, PAYBACK_NOTE_RECIPIENT, REQUESTED_ASSET] + # => [tag, note_type, PAYBACK_NOTE_RECIPIENT] # create payback P2ID note exec.output_note::create - # => [note_idx, REQUESTED_ASSET] + # => [note_idx] - movdn.4 - # => [REQUESTED_ASSET, note_idx] + padw push.0.0.0 dup.7 + # => [note_idx, pad(7), note_idx] - # padding stack with 11 zeros - repeat.11 - push.0 - movdn.5 - end - # => [REQUESTED_ASSET, note_idx, pad(11)] + padw mem_loadw_be.REQUESTED_ASSET_VALUE_ADDRESS + # => [REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] + + exec.asset::build_asset_vault_key + # => [REQUESTED_ASSET_KEY, REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] # move asset to the note call.wallet::move_asset_to_note - # => [REQUESTED_ASSET, note_idx, pad(11)] + # => [pad(16), note_idx] dropw - # => [note_idx, pad(11)] - mem_loadw_be.ATTACHMENT_ADDRESS + # => [ATTACHMENT, pad(8), note_idx] + mem_load.ATTACHMENT_KIND_ADDRESS mem_load.ATTACHMENT_SCHEME_ADDRESS - movup.6 - # => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT] + movup.14 + # => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT, pad(8)] exec.output_note::set_attachment - # => [pad(12)] + # => [pad(8)] # --- move assets from the SWAP note into the account ------------------------- # store the number of note assets to memory starting at address 0 push.0 exec.active_note::get_assets - # => [num_assets, ptr, pad(12)] + # => [num_assets, ptr, pad(8)] # make sure the number of assets is 1 assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS - # => [ptr, pad(12)] + # => [ptr, pad(8)] # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET - # => [ASSET_VALUE, pad(12)] + # => [ASSET_VALUE, pad(5)] + # TODO(expand_assets): fix padding when adapting call to receive_asset. # add the asset to the account call.wallet::receive_asset # => [pad(16)] diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index 0be9785f5f..82c9e70d7a 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -39,7 +39,7 @@ end #! these elements do not contain any meaningful data. #! #! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] -#! Outputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] +#! Outputs: [pad(16)] #! #! Where: #! - note_idx is the index of the output note. @@ -55,16 +55,12 @@ end pub proc move_asset_to_note # remove the asset from the account exec.native_account::remove_asset - # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] + # => [ASSET_VALUE, note_idx, pad(7)] # TODO(expand_assets): Pass key to add_asset eventually. - dropw dupw dup.8 movdn.4 - # => [ASSET_VALUE, note_idx, pad(11)] - # => [ASSET_VALUE, note_idx, ASSET_VALUE, note_idx, pad(11)] exec.output_note::add_asset - # TODO(expand_assets): Consider not returning anything from this procedure. - # => [ASSET_VALUE, note_idx, pad(11)] + # => [pad(16)] end #! Adds all assets from the active note to the native account's vault. diff --git a/crates/miden-standards/src/account/interface/component.rs b/crates/miden-standards/src/account/interface/component.rs index 110e23d1b3..d4ae928dc7 100644 --- a/crates/miden-standards/src/account/interface/component.rs +++ b/crates/miden-standards/src/account/interface/component.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use miden_protocol::account::auth::PublicKeyCommitment; use miden_protocol::account::{AccountId, AccountProcedureRoot, AccountStorage, StorageSlotName}; use miden_protocol::note::PartialNote; -use miden_protocol::{Felt, FieldElement, Word}; +use miden_protocol::{Felt, FieldElement}; use crate::AuthScheme; use crate::account::auth::{ @@ -275,13 +275,20 @@ impl AccountComponentInterface { for asset in partial_note.assets().iter() { body.push_str(&format!( " - push.{asset} - # => [ASSET, note_idx, pad(16)] + # duplicate note index + dup + push.{ASSET_VALUE} + push.{ASSET_KEY} + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(16)] call.::miden::standards::wallets::basic::move_asset_to_note - dropw - # => [note_idx, pad(16)]\n + # 9 parameter elements + 16 pad elements = 25 total pads after the call + # => [note_idx, pad(25)] + swapdw dropw dropw swap drop + # => [note_idx, pad(16)] + \n ", - asset = Word::from(*asset) + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), )); } }, diff --git a/crates/miden-standards/src/testing/mod.rs b/crates/miden-standards/src/testing/mod.rs index f08811b562..5573d84a1e 100644 --- a/crates/miden-standards/src/testing/mod.rs +++ b/crates/miden-standards/src/testing/mod.rs @@ -5,3 +5,4 @@ pub mod mock_account; pub mod mock_account_code; pub mod note; +pub mod mock_util_lib; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index e95017890c..017c4bdfe4 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1214,28 +1214,14 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { use miden::standards::wallets::basic->wallet use mock::util - # Inputs: [ASSET, note_idx] - # Outputs: [ASSET, note_idx] - proc move_asset_to_note - # pad the stack before call - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [ASSET, note_idx, pad(11)] - - call.wallet::move_asset_to_note - # => [ASSET, note_idx, pad(11)] - - # remove excess PADs from the stack - swapdw dropw dropw swapw movdn.7 drop drop drop - # => [ASSET, note_idx] - end - begin # create random note and move the asset into it exec.util::create_default_note # => [note_idx] - push.{REMOVED_ASSET} - exec.move_asset_to_note dropw drop + push.{REMOVED_ASSET_VALUE} + push.{REMOVED_ASSET_KEY} + exec.util::move_asset_to_note # => [] # push faucet ID prefix and suffix @@ -1260,7 +1246,8 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { assert_eq.err="initial balance is incorrect" end "#, - REMOVED_ASSET = Word::from(fungible_asset_for_note_existing), + REMOVED_ASSET_KEY = fungible_asset_for_note_existing.to_key_word(), + REMOVED_ASSET_VALUE = fungible_asset_for_note_existing.to_value_word(), suffix = faucet_existing_asset.suffix(), prefix = faucet_existing_asset.prefix().as_felt(), final_balance = @@ -1323,8 +1310,9 @@ async fn test_get_init_asset() -> anyhow::Result<()> { exec.util::create_default_note # => [note_idx] - push.{REMOVED_ASSET} - call.wallet::move_asset_to_note dropw drop + push.{REMOVED_ASSET_VALUE} + push.{REMOVED_ASSET_KEY} + exec.util::move_asset_to_note # => [] # get the current asset @@ -1344,7 +1332,8 @@ async fn test_get_init_asset() -> anyhow::Result<()> { end "#, ASSET_KEY = fungible_asset_for_note_existing.vault_key(), - REMOVED_ASSET = Word::from(fungible_asset_for_note_existing), + REMOVED_ASSET_VALUE = fungible_asset_for_note_existing.to_value_word(), + REMOVED_ASSET_KEY = fungible_asset_for_note_existing.to_key_word(), INITIAL_ASSET = Word::from(fungible_asset_for_account), FINAL_ASSET = Word::from(final_asset), ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 363fbbedff..201cacfdcc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -427,20 +427,31 @@ async fn fungible_asset_delta() -> anyhow::Result<()> { let tx_script = parse_tx_script(format!( " begin - push.{asset0} exec.create_note_with_asset + push.{ASSET0_VALUE} push.{ASSET0_KEY} + exec.util::create_default_note_with_moved_asset # => [] - push.{asset1} exec.create_note_with_asset + + push.{ASSET1_VALUE} push.{ASSET1_KEY} + exec.util::create_default_note_with_moved_asset # => [] - push.{asset2} exec.create_note_with_asset + + push.{ASSET2_VALUE} push.{ASSET2_KEY} + exec.util::create_default_note_with_moved_asset # => [] - push.{asset3} exec.create_note_with_asset + + push.{ASSET3_VALUE} push.{ASSET3_KEY} + exec.util::create_default_note_with_moved_asset # => [] end ", - asset0 = Word::from(removed_asset0), - asset1 = Word::from(removed_asset1), - asset2 = Word::from(removed_asset2), - asset3 = Word::from(removed_asset3), + ASSET0_KEY = removed_asset0.to_key_word(), + ASSET0_VALUE = removed_asset0.to_value_word(), + ASSET1_KEY = removed_asset1.to_key_word(), + ASSET1_VALUE = removed_asset1.to_value_word(), + ASSET2_KEY = removed_asset2.to_key_word(), + ASSET2_VALUE = removed_asset2.to_value_word(), + ASSET3_KEY = removed_asset3.to_key_word(), + ASSET3_VALUE = removed_asset3.to_value_word(), ))?; let executed_tx = mock_chain @@ -519,9 +530,12 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { let tx_script = parse_tx_script(format!( " begin - push.{asset1} exec.create_note_with_asset + push.{ASSET1_VALUE} push.{ASSET1_KEY} + exec.util::create_default_note_with_moved_asset # => [] - push.{asset2} exec.create_note_with_asset + + push.{ASSET2_VALUE} push.{ASSET2_KEY} + exec.util::create_default_note_with_moved_asset # => [] # remove and re-add asset 3 @@ -533,8 +547,10 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { # => [] end ", - asset1 = Word::from(asset1), - asset2 = Word::from(asset2), + ASSET1_KEY = asset1.to_key_word(), + ASSET1_VALUE = asset1.to_value_word(), + ASSET2_KEY = asset2.to_key_word(), + ASSET2_VALUE = asset2.to_value_word(), ASSET3_KEY = asset3.to_key_word(), ASSET3_VALUE = asset3.to_value_word(), ))?; @@ -624,16 +640,19 @@ async fn asset_and_storage_delta() -> anyhow::Result<()> { # => [note_idx, pad(15)] # move an asset to the created note to partially deplete fungible asset balance - swapw dropw push.{REMOVED_ASSET} + swapw dropw + push.{REMOVED_ASSET_VALUE} + push.{REMOVED_ASSET_KEY} call.::miden::standards::wallets::basic::move_asset_to_note - # => [ASSET, note_idx, pad(11)] + # => [pad(16)] # clear the stack dropw dropw dropw dropw ", NOTETYPE = note_types[i] as u8, tag = tags[i], - REMOVED_ASSET = Word::from(removed_assets[i]) + REMOVED_ASSET_KEY = removed_assets[i].to_key_word(), + REMOVED_ASSET_VALUE = removed_assets[i].to_value_word(), )); } @@ -1085,6 +1104,7 @@ fn parse_tx_script(code: impl AsRef) -> anyhow::Result { const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " use mock::account + use mock::util use miden::protocol::output_note #! Inputs: [slot_id_prefix, slot_id_suffix, VALUE] @@ -1112,36 +1132,6 @@ const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " # => [] end - #! Inputs: [ASSET] - #! Outputs: [] - proc create_note_with_asset - push.0.1.2.3 # recipient - push.2 # note_type private - push.0xC0000000 # tag - # => [tag, note_type, RECIPIENT, ASSET] - - exec.output_note::create - # => [note_idx, ASSET] - - movdn.4 - # => [ASSET, note_idx] - - exec.move_asset_to_note - # => [] - end - - #! Inputs: [ASSET, note_idx] - #! Outputs: [] - proc move_asset_to_note - repeat.11 push.0 movdn.5 end - # => [ASSET, note_idx, pad(11)] - - call.account::move_asset_to_note - - # return values are unused - dropw dropw dropw dropw - end - #! Inputs: [ASSET] #! Outputs: [ASSET'] proc add_asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 958727913e..d2ff3f0515 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -187,14 +187,16 @@ async fn test_compute_output_note_id() -> anyhow::Result<()> { exec.output_note::create # => [note_idx] - push.{asset} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.::miden::standards::wallets::basic::move_asset_to_note # => [] ", recipient = note.recipient().digest(), note_type = Felt::from(note.metadata().note_type()), tag = Felt::from(note.metadata().tag()), - asset = Word::from(asset) + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), )); } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index e6397d3e6a..cbf5add8aa 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -94,7 +94,9 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result push.{FUNGIBLE_ASSET1_VALUE} push.{FUNGIBLE_ASSET1_KEY} call.account::remove_asset - # => [] + # drop the excess word from the call + swapw dropw + # => [ASSET_VALUE] # move asset to note to adhere to asset preservation rules exec.util::create_default_note_with_asset @@ -103,7 +105,9 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result push.{FUNGIBLE_ASSET2_VALUE} push.{FUNGIBLE_ASSET2_KEY} call.account::remove_asset - # => [ASSET] + # drop the excess word from the call + swapw dropw + # => [ASSET_VALUE] # move asset to note to adhere to asset preservation rules exec.util::create_default_note_with_asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index d9ddad2332..3e65c7fae4 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -788,9 +788,10 @@ async fn test_get_asset_info() -> anyhow::Result<()> { # => [note_idx] # move the asset 0 to the note - push.{asset_0} + dup + push.{ASSET_0_VALUE} + push.{ASSET_0_KEY} call.::miden::standards::wallets::basic::move_asset_to_note - dropw # => [note_idx] # get the assets hash and assets number of the note having only asset_0 @@ -817,9 +818,10 @@ async fn test_get_asset_info() -> anyhow::Result<()> { # => [note_idx] # add asset_1 to the note - push.{asset_1} + dup + push.{ASSET_1_VALUE} + push.{ASSET_1_KEY} call.::miden::standards::wallets::basic::move_asset_to_note - dropw # => [note_idx] # get the assets hash and assets number of the note having asset_0 and asset_1 @@ -844,12 +846,14 @@ async fn test_get_asset_info() -> anyhow::Result<()> { RECIPIENT = output_note_1.recipient().digest(), note_type = NoteType::Public as u8, tag = output_note_1.metadata().tag(), - asset_0 = Word::from(fungible_asset_0), + ASSET_0_VALUE = fungible_asset_0.to_value_word(), + ASSET_0_KEY = fungible_asset_0.to_key_word(), // first data request COMPUTED_ASSETS_COMMITMENT_0 = output_note_0.assets().commitment(), assets_number_0 = output_note_0.assets().num_assets(), // second data request - asset_1 = Word::from(fungible_asset_1), + ASSET_1_VALUE = fungible_asset_1.to_value_word(), + ASSET_1_KEY = fungible_asset_1.to_key_word(), COMPUTED_ASSETS_COMMITMENT_1 = output_note_1.assets().commitment(), assets_number_1 = output_note_1.assets().num_assets(), ); @@ -1282,12 +1286,15 @@ fn create_output_note(note: &Note) -> String { create_note_code.push_str(&format!( " # move the asset to the note - push.{asset} + dup + push.{ASSET_VALUE} + push.{ASSET_KEY} + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] call.::miden::standards::wallets::basic::move_asset_to_note - dropw # => [note_idx] ", - asset = Word::from(*asset) + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word() )); } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 126e493bd7..164fb3ffb6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -245,24 +245,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { "\ use miden::standards::wallets::basic->wallet use miden::protocol::output_note - - #! Wrapper around move_asset_to_note for use with exec. - #! - #! Inputs: [ASSET, note_idx] - #! Outputs: [note_idx] - proc move_asset_to_note - # pad the stack before call - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [ASSET, note_idx, pad(11)] - - call.wallet::move_asset_to_note - dropw - # => [note_idx, pad(11)] - - # remove excess PADs from the stack - repeat.11 swap drop end - # => [note_idx] - end + use mock::util ## TRANSACTION SCRIPT ## ======================================================================================== @@ -276,15 +259,17 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { exec.output_note::create # => [note_idx = 0] - push.{REMOVED_ASSET_1} # asset_1 - # => [ASSET, note_idx] + dup + push.{REMOVED_ASSET_VALUE_1} + push.{REMOVED_ASSET_KEY_1} + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] - exec.move_asset_to_note + exec.util::move_asset_to_note # => [note_idx] - push.{REMOVED_ASSET_2} # asset_2 - exec.move_asset_to_note - drop + push.{REMOVED_ASSET_VALUE_2} + push.{REMOVED_ASSET_KEY_2} + exec.util::move_asset_to_note # => [] # send non-fungible asset @@ -294,12 +279,16 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { exec.output_note::create # => [note_idx = 1] - push.{REMOVED_ASSET_3} # asset_3 - exec.move_asset_to_note + dup + push.{REMOVED_ASSET_VALUE_3} + push.{REMOVED_ASSET_KEY_3} + exec.util::move_asset_to_note # => [note_idx] - push.{REMOVED_ASSET_4} # asset_4 - exec.move_asset_to_note + dup + push.{REMOVED_ASSET_VALUE_4} + push.{REMOVED_ASSET_KEY_4} + exec.util::move_asset_to_note # => [note_idx] push.{ATTACHMENT2} @@ -322,10 +311,14 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { # => [] end ", - REMOVED_ASSET_1 = Word::from(removed_asset_1), - REMOVED_ASSET_2 = Word::from(removed_asset_2), - REMOVED_ASSET_3 = Word::from(removed_asset_3), - REMOVED_ASSET_4 = Word::from(removed_asset_4), + REMOVED_ASSET_KEY_1 = removed_asset_1.to_key_word(), + REMOVED_ASSET_VALUE_1 = removed_asset_1.to_value_word(), + REMOVED_ASSET_KEY_2 = removed_asset_2.to_key_word(), + REMOVED_ASSET_VALUE_2 = removed_asset_2.to_value_word(), + REMOVED_ASSET_KEY_3 = removed_asset_3.to_key_word(), + REMOVED_ASSET_VALUE_3 = removed_asset_3.to_value_word(), + REMOVED_ASSET_KEY_4 = removed_asset_4.to_key_word(), + REMOVED_ASSET_VALUE_4 = removed_asset_4.to_value_word(), RECIPIENT2 = expected_output_note_2.recipient().digest(), RECIPIENT3 = expected_output_note_3.recipient().digest(), NOTETYPE1 = note_type1 as u8, @@ -337,7 +330,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { ATTACHMENT3 = attachment3.content().to_word(), ); - let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; + let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(tx_script_src)?; // expected delta // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 3d051d6c6d..9a8129be9f 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -6,7 +6,6 @@ use miden_protocol::account::AccountId; use miden_protocol::asset::Asset; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{Note, NoteType}; -use miden_protocol::testing::storage::prepare_assets; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::note::NoteBuilder; use rand::SeedableRng; @@ -252,11 +251,17 @@ fn note_script_that_creates_notes<'note>( attachment_kind = note.metadata().attachment().content().attachment_kind().as_u8(), )); - let assets_str = prepare_assets(note.assets()); - for asset in assets_str { + for asset in note.assets().iter() { out.push_str(&format!( - " push.{asset} - call.::miden::standards::wallets::basic::move_asset_to_note\n", + " dup + push.{ASSET_VALUE} + push.{ASSET_KEY} + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] + call.::miden::standards::wallets::basic::move_asset_to_note + # => [note_idx] + ", + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), )); } } diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index 5fd7944b54..70483a39cb 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -202,10 +202,13 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { let mock_chain = builder.build()?; + let asset_1 = FungibleAsset::mock(10); + let asset_2 = FungibleAsset::mock(5); + let output_note_1 = P2idNote::create( account.id(), ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into()?, - vec![FungibleAsset::mock(10)], + vec![asset_1], NoteType::Public, NoteAttachment::default(), &mut RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])), @@ -214,7 +217,7 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { let output_note_2 = P2idNote::create( account.id(), ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into()?, - vec![FungibleAsset::mock(5)], + vec![asset_2], NoteType::Public, NoteAttachment::default(), &mut RpoRandomCoin::new(Word::from([4, 3, 2, 1u32])), @@ -229,7 +232,8 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { push.{tag_1} exec.output_note::create - push.{asset_1} + push.{ASSET_VALUE_1} + push.{ASSET_KEY_1} call.::miden::standards::wallets::basic::move_asset_to_note dropw dropw dropw dropw @@ -238,7 +242,8 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { push.{tag_2} exec.output_note::create - push.{asset_2} + push.{ASSET_VALUE_2} + push.{ASSET_KEY_2} call.::miden::standards::wallets::basic::move_asset_to_note dropw dropw dropw dropw end @@ -246,11 +251,13 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { recipient_1 = output_note_1.recipient().digest(), note_type_1 = NoteType::Public as u8, tag_1 = Felt::from(output_note_1.metadata().tag()), - asset_1 = Word::from(FungibleAsset::mock(10)), + ASSET_KEY_1 = asset_1.to_key_word(), + ASSET_VALUE_1 = asset_1.to_value_word(), recipient_2 = output_note_2.recipient().digest(), note_type_2 = NoteType::Public as u8, tag_2 = Felt::from(output_note_2.metadata().tag()), - asset_2 = Word::from(FungibleAsset::mock(5)), + ASSET_KEY_2 = asset_2.to_key_word(), + ASSET_VALUE_2 = asset_2.to_value_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index 463cfff6d6..4d8a65d4d8 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -40,7 +40,8 @@ pub async fn prove_send_swap_note() -> anyhow::Result<()> { push.{tag} exec.output_note::create - push.{asset} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.::miden::standards::wallets::basic::move_asset_to_note dropw dropw dropw dropw end @@ -48,7 +49,8 @@ pub async fn prove_send_swap_note() -> anyhow::Result<()> { recipient = swap_note.recipient().digest(), note_type = NoteType::Public as u8, tag = Felt::from(swap_note.metadata().tag()), - asset = Word::from(offered_asset), + ASSET_KEY = offered_asset.to_key_word(), + ASSET_VALUE = offered_asset.to_value_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; From 82df521bea50381c0611ba7776f2acd5406628e3 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 14:31:06 +0100 Subject: [PATCH 028/100] feat: add asset key to SWAP storage --- .../asm/standards/notes/swap.masm | 12 +++++++----- crates/miden-standards/src/note/swap.rs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 946982731f..ae92c5e154 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -7,15 +7,16 @@ use miden::standards::wallets::basic->wallet # CONSTANTS # ================================================================================================= -const SWAP_NOTE_NUM_STORAGE_ITEMS=16 +const SWAP_NOTE_NUM_STORAGE_ITEMS=20 const PAYBACK_NOTE_TYPE_ADDRESS=0 const PAYBACK_NOTE_TAG_ADDRESS=1 const ATTACHMENT_KIND_ADDRESS=2 const ATTACHMENT_SCHEME_ADDRESS=3 const ATTACHMENT_ADDRESS=4 -const REQUESTED_ASSET_VALUE_ADDRESS=8 -const PAYBACK_RECIPIENT_ADDRESS=12 +const REQUESTED_ASSET_KEY_ADDRESS=8 +const REQUESTED_ASSET_VALUE_ADDRESS=12 +const PAYBACK_RECIPIENT_ADDRESS=16 # ERRORS # ================================================================================================= @@ -40,7 +41,8 @@ const ERR_SWAP_WRONG_NUMBER_OF_ASSETS="SWAP script requires exactly 1 note asset #! - attachment_kind #! - attachment_scheme #! - ATTACHMENT -#! - REQUESTED_ASSET +#! - REQUESTED_ASSET_KEY +#! - REQUESTED_ASSET_VALUE #! - PAYBACK_RECIPIENT #! #! Panics if: @@ -86,7 +88,7 @@ pub proc main padw mem_loadw_be.REQUESTED_ASSET_VALUE_ADDRESS # => [REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] - exec.asset::build_asset_vault_key + padw mem_loadw_be.REQUESTED_ASSET_KEY_ADDRESS # => [REQUESTED_ASSET_KEY, REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] # move asset to the note diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index e22ff92573..57250f8b6a 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -48,7 +48,7 @@ impl SwapNote { // -------------------------------------------------------------------------------------------- /// Expected number of storage items of the SWAP note. - pub const NUM_STORAGE_ITEMS: usize = 16; + pub const NUM_STORAGE_ITEMS: usize = 20; // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -94,24 +94,24 @@ impl SwapNote { let payback_serial_num = rng.draw_word(); let payback_recipient = P2idNote::build_recipient(sender, payback_serial_num)?; - let requested_asset_word: Word = requested_asset.into(); let payback_tag = NoteTag::with_account_target(sender); let attachment_scheme = Felt::from(payback_note_attachment.attachment_scheme().as_u32()); let attachment_kind = Felt::from(payback_note_attachment.attachment_kind().as_u8()); let attachment = payback_note_attachment.content().to_word(); - let mut inputs = Vec::with_capacity(16); - inputs.extend_from_slice(&[ + let mut storage = Vec::with_capacity(SwapNote::NUM_STORAGE_ITEMS); + storage.extend_from_slice(&[ payback_note_type.into(), payback_tag.into(), attachment_scheme, attachment_kind, ]); - inputs.extend_from_slice(attachment.as_elements()); - inputs.extend_from_slice(requested_asset_word.as_elements()); - inputs.extend_from_slice(payback_recipient.digest().as_elements()); - let inputs = NoteStorage::new(inputs)?; + storage.extend_from_slice(attachment.as_elements()); + storage.extend_from_slice(requested_asset.to_key_word().as_elements()); + storage.extend_from_slice(requested_asset.to_value_word().as_elements()); + storage.extend_from_slice(payback_recipient.digest().as_elements()); + let inputs = NoteStorage::new(storage)?; // build the tag for the SWAP use case let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset); From 0b650bf161bb05aa3281a1d4cac66d5c8fb6abec Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 14:51:50 +0100 Subject: [PATCH 029/100] feat: refactor `native_account::add_asset` --- .../asm/kernels/transaction/api.masm | 9 +++--- .../asm/protocol/native_account.masm | 28 +++++++++-------- .../asm/standards/wallets/basic.masm | 5 +++- .../src/testing/mock_account_code.rs | 6 ++-- crates/miden-standards/src/testing/mod.rs | 2 +- .../src/kernel_tests/tx/test_account.rs | 9 ++++-- .../src/kernel_tests/tx/test_account_delta.rs | 13 ++++---- .../src/kernel_tests/tx/test_asset_vault.rs | 30 ++++++++++++------- .../src/kernel_tests/tx/test_lazy_loading.rs | 16 ++++++---- crates/miden-testing/src/utils.rs | 4 +-- 10 files changed, 73 insertions(+), 49 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 82d390f16b..1daf6a1786 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -538,10 +538,11 @@ end #! Adds the specified asset to the vault. #! -#! Inputs: [ASSET_VALUE, pad(12)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] #! Outputs: [ASSET_VALUE', pad(12)] #! #! Where: +#! - ASSET_KEY is the vault key of the asset that is added to the vault. #! - ASSET_VALUE is the value of the asset to add to the vault. #! - ASSET_VALUE' final asset in the account vault defined as follows: #! - If ASSET_VALUE is a non-fungible asset, then ASSET_VALUE' is the same as ASSET_VALUE. @@ -559,15 +560,13 @@ end pub proc account_add_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET_VALUE, pad(12)] + # => [ASSET_KEY, ASSET_VALUE, pad(8)] # add the specified asset to the account vault, emitting the corresponding events - # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. - exec.asset_vault::build_asset_vault_key exec.account::add_asset_to_vault # => [ASSET_VALUE', pad(12)] end diff --git a/crates/miden-protocol/asm/protocol/native_account.masm b/crates/miden-protocol/asm/protocol/native_account.masm index 6d78d882b7..c306994c77 100644 --- a/crates/miden-protocol/asm/protocol/native_account.masm +++ b/crates/miden-protocol/asm/protocol/native_account.masm @@ -191,14 +191,16 @@ end #! Add the specified asset to the vault. #! -#! Inputs: [ASSET] -#! Outputs: [ASSET'] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_VALUE'] #! #! Where: -#! - ASSET' is a final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. +#! - ASSET_KEY is the vault key of the asset that is added to the vault. +#! - ASSET_VALUE is the value of the asset to add to the vault. +#! - ASSET_VALUE' final asset in the account vault defined as follows: +#! - If ASSET_VALUE is a non-fungible asset, then ASSET_VALUE' is the same as ASSET_VALUE. +#! - If ASSET_VALUE is a fungible asset, then ASSET_VALUE' is the total fungible asset in the account vault +#! after ASSET_VALUE was added to it. #! #! Panics if: #! - the asset is not valid. @@ -207,19 +209,19 @@ end #! #! Invocation: exec pub proc add_asset - exec.kernel_proc_offsets::account_add_asset_offset - # => [offset, ASSET] - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] + padw padw swapdw movup.8 drop + # => [ASSET_KEY, ASSET_VALUE, pad(7)] + + exec.kernel_proc_offsets::account_add_asset_offset + # => [offset, ASSET_KEY, ASSET_VALUE, pad(7)] syscall.exec_kernel_proc - # => [ASSET', pad(12)] + # => [ASSET_VALUE', pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET'] + # => [ASSET_VALUE'] end #! Remove the specified asset from the vault. diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index 82c9e70d7a..a9732b3cb4 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -23,8 +23,11 @@ const PUBLIC_NOTE=1 #! #! Invocation: call pub proc receive_asset + # TODO(expand_assets): Remove temp code. + exec.::miden::protocol::asset::build_asset_vault_key + exec.native_account::add_asset - # => [ASSET', pad(12)] + # => [ASSET_VALUE', pad(12)] # drop the final asset dropw diff --git a/crates/miden-standards/src/testing/mock_account_code.rs b/crates/miden-standards/src/testing/mock_account_code.rs index 417aa0773a..8146673c82 100644 --- a/crates/miden-standards/src/testing/mock_account_code.rs +++ b/crates/miden-standards/src/testing/mock_account_code.rs @@ -105,11 +105,11 @@ const MOCK_ACCOUNT_CODE: &str = " # => [STORAGE_COMMITMENT, pad(12)] end - #! Inputs: [ASSET, pad(12)] - #! Outputs: [ASSET', pad(12)] + #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] + #! Outputs: [ASSET_VALUE', pad(12)] pub proc add_asset exec.native_account::add_asset - # => [ASSET', pad(12)] + # => [ASSET_VALUE', pad(12)] end #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] diff --git a/crates/miden-standards/src/testing/mod.rs b/crates/miden-standards/src/testing/mod.rs index 5573d84a1e..01cf73f63c 100644 --- a/crates/miden-standards/src/testing/mod.rs +++ b/crates/miden-standards/src/testing/mod.rs @@ -4,5 +4,5 @@ pub mod account_interface; pub mod mock_account; pub mod mock_account_code; -pub mod note; pub mod mock_util_lib; +pub mod note; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 017c4bdfe4..e41fe3eb8d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1003,8 +1003,10 @@ async fn test_get_vault_root() -> anyhow::Result<()> { exec.prologue::prepare_transaction # add an asset to the account - push.{fungible_asset} - call.mock_account::add_asset dropw + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} + call.mock_account::add_asset + dropw dropw # => [] # get the current vault root @@ -1013,7 +1015,8 @@ async fn test_get_vault_root() -> anyhow::Result<()> { assert_eqw.err="vault root mismatch" end "#, - fungible_asset = Word::from(&fungible_asset), + FUNGIBLE_ASSET_VALUE = fungible_asset.to_value_word(), + FUNGIBLE_ASSET_KEY = fungible_asset.to_key_word(), expected_vault_root = &account.vault().root(), ); tx_context.execute_code(&code).await?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 201cacfdcc..62b6482689 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -543,6 +543,9 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { push.{ASSET3_KEY} exec.remove_asset # => [ASSET_VALUE] + + push.{ASSET3_KEY} + # => [ASSET_KEY, ASSET_VALUE] exec.add_asset dropw # => [] end @@ -1132,14 +1135,14 @@ const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " # => [] end - #! Inputs: [ASSET] - #! Outputs: [ASSET'] + #! Inputs: [ASSET_KEY, ASSET_VALUE] + #! Outputs: [ASSET_VALUE'] proc add_asset - repeat.12 push.0 movdn.4 end - # => [ASSET, pad(12)] + repeat.8 push.0 movdn.8 end + # => [ASSET_KEY, ASSET_VALUE, pad(8)] call.account::add_asset - # => [ASSET', pad(12)] + # => [ASSET_VALUE', pad(12)] repeat.12 movup.4 drop end # => [ASSET'] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 7ee95f4ec5..862b5a250a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -194,14 +194,16 @@ async fn test_add_fungible_asset_success() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::add_asset # truncate the stack - swapw dropw + swapdw dropw dropw end ", - FUNGIBLE_ASSET = Word::from(add_fungible_asset) + FUNGIBLE_ASSET_KEY = add_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = add_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -241,11 +243,14 @@ async fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{FUNGIBLE_ASSET_VALUE} + push.{FUNGIBLE_ASSET_KEY} call.account::add_asset + dropw dropw end ", - FUNGIBLE_ASSET = Word::from(add_fungible_asset) + FUNGIBLE_ASSET_KEY = add_fungible_asset.to_key_word(), + FUNGIBLE_ASSET_VALUE = add_fungible_asset.to_value_word(), ); let exec_result = tx_context.execute_code(&code).await; @@ -272,14 +277,16 @@ async fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.account::add_asset # truncate the stack - swapw dropw + swapdw dropw dropw end ", - FUNGIBLE_ASSET = Word::from(add_non_fungible_asset) + NON_FUNGIBLE_ASSET_KEY = add_non_fungible_asset.to_key_word(), + NON_FUNGIBLE_ASSET_VALUE = add_non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -314,11 +321,14 @@ async fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{NON_FUNGIBLE_ASSET} + push.{NON_FUNGIBLE_ASSET_VALUE} + push.{NON_FUNGIBLE_ASSET_KEY} call.account::add_asset + dropw dropw end ", - NON_FUNGIBLE_ASSET = Word::from(non_fungible_asset) + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), + NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset.to_value_word(), ); let exec_result = tx_context.execute_code(&code).await; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index cbf5add8aa..95dc5d9f85 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -43,15 +43,19 @@ async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<( use mock::account begin - push.{FUNGIBLE_ASSET1} - call.account::add_asset dropw + push.{FUNGIBLE_ASSET_VALUE1} + push.{FUNGIBLE_ASSET_KEY1} + call.account::add_asset dropw dropw - push.{FUNGIBLE_ASSET2} - call.account::add_asset dropw + push.{FUNGIBLE_ASSET_VALUE2} + push.{FUNGIBLE_ASSET_KEY2} + call.account::add_asset dropw dropw end ", - FUNGIBLE_ASSET1 = Word::from(fungible_asset1), - FUNGIBLE_ASSET2 = Word::from(fungible_asset2) + FUNGIBLE_ASSET_KEY1 = fungible_asset1.to_key_word(), + FUNGIBLE_ASSET_VALUE1 = fungible_asset1.to_value_word(), + FUNGIBLE_ASSET_KEY2 = fungible_asset2.to_key_word(), + FUNGIBLE_ASSET_VALUE2 = fungible_asset2.to_value_word() ); let builder = CodeBuilder::with_mock_libraries(); diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 9a8129be9f..2a5e76c1b3 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -260,8 +260,8 @@ fn note_script_that_creates_notes<'note>( call.::miden::standards::wallets::basic::move_asset_to_note # => [note_idx] ", - ASSET_KEY = asset.to_key_word(), - ASSET_VALUE = asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), )); } } From 5ff13bded719e9790f17203add7cba79da777162 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 5 Feb 2026 16:54:47 +0100 Subject: [PATCH 030/100] chore: refactor `receive_asset` --- .../asm/standards/notes/swap.masm | 21 +++++--- .../asm/standards/wallets/basic.masm | 54 ++++++++++--------- crates/miden-testing/src/utils.rs | 43 ++++++++------- 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index ae92c5e154..ff3868a564 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -17,6 +17,8 @@ const ATTACHMENT_ADDRESS=4 const REQUESTED_ASSET_KEY_ADDRESS=8 const REQUESTED_ASSET_VALUE_ADDRESS=12 const PAYBACK_RECIPIENT_ADDRESS=16 +const ASSET_KEY_ADDRESS=20 +const ASSET_VALUE_ADDRESS=ASSET_KEY_ADDRESS + ASSET_VALUE_MEMORY_OFFSET # ERRORS # ================================================================================================= @@ -109,19 +111,26 @@ pub proc main # --- move assets from the SWAP note into the account ------------------------- - # store the number of note assets to memory starting at address 0 - push.0 exec.active_note::get_assets + # store the number of note assets to memory starting at address ASSET_KEY_ADDRESS + push.ASSET_KEY_ADDRESS exec.active_note::get_assets # => [num_assets, ptr, pad(8)] # make sure the number of assets is 1 assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS # => [ptr, pad(8)] - # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET - mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET - # => [ASSET_VALUE, pad(5)] + # consider the ptr as padding + push.0 push.0 push.0 + # => [pad(12)] + + # load asset value + mem_loadw_be.ASSET_VALUE_ADDRESS + # => [ASSET_VALUE, pad(8)] + + # load asset key + padw mem_loadw_be.ASSET_KEY_ADDRESS + # => [ASSET_KEY, ASSET_VALUE, pad(8)] - # TODO(expand_assets): fix padding when adapting call to receive_asset. # add the asset to the account call.wallet::receive_asset # => [pad(16)] diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index a9732b3cb4..f9b46caa32 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -10,11 +10,12 @@ const PUBLIC_NOTE=1 #! Adds the provided asset to the active account. #! -#! Inputs: [ASSET, pad(12)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] #! Outputs: [pad(16)] #! #! Where: -#! - ASSET is the asset to be received, can be fungible or non-fungible +#! - ASSET_KEY is the vault key of the received asset. +#! - ASSET_VALUE is the value of the received asset. #! #! Panics if: #! - the same non-fungible asset already exists in the account. @@ -23,9 +24,6 @@ const PUBLIC_NOTE=1 #! #! Invocation: call pub proc receive_asset - # TODO(expand_assets): Remove temp code. - exec.::miden::protocol::asset::build_asset_vault_key - exec.native_account::add_asset # => [ASSET_VALUE', pad(12)] @@ -58,7 +56,7 @@ end pub proc move_asset_to_note # remove the asset from the account exec.native_account::remove_asset - # => [ASSET_VALUE, note_idx, pad(7)] + # => [ASSET_VALUE, note_idx, pad(11)] # TODO(expand_assets): Pass key to add_asset eventually. @@ -83,43 +81,51 @@ pub proc add_assets_to_account # => [end_ptr, ptr] # pad the stack and move the pointer to the top - padw movup.5 - # => [ptr, EMPTY_WORD, end_ptr] + padw padw movup.9 + # => [ptr, pad(8), end_ptr] # loop if the amount of assets is non-zero - dup dup.6 neq - # => [should_loop, ptr, EMPTY_WORD, end_ptr] + dup dup.10 neq + # => [should_loop, ptr, pad(8), end_ptr] while.true - # => [ptr, EMPTY_WORD, end_ptr] + # => [ptr, pad(8), end_ptr] # save the pointer so that we can use it later - dup movdn.5 - # => [ptr, EMPTY_WORD, ptr, end_ptr] + dup movdn.9 + # => [ptr, pad(8), ptr, end_ptr] # load the asset value - add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - # => [ASSET_VALUE, ptr, end_ptr] + add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be swapw + # => [EMPTY_WORD, ASSET_VALUE, ptr, end_ptr] + + # load the asset key + dup.8 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE, ptr, end_ptr] # pad the stack before call - padw swapw padw padw swapdw - # => [ASSET_VALUE, pad(12), ptr, end_ptr] + padw padw swapdw + # => [ASSET_KEY, ASSET_VALUE, pad(8), ptr, end_ptr] # add asset to the account call.receive_asset # => [pad(16), ptr, end_ptr] # clean the stack after call - dropw dropw dropw - # => [EMPTY_WORD, ptr, end_ptr] + dropw dropw + # => [pad(8), ptr, end_ptr] + + # increment the pointer + movup.8 add.ASSET_SIZE dup + # => [ptr+ASSET_SIZE, ptr+ASSET_SIZE, pad(8), end_ptr] - # increment the pointer and continue looping if ptr != end_ptr - movup.4 add.ASSET_SIZE dup dup.6 neq - # => [should_loop, ptr+ASSET_SIZE, EMPTY_WORD, end_ptr] + # continue looping if ptr != end_ptr + dup.10 neq + # => [should_loop, ptr+ASSET_SIZE, pad(8), end_ptr] end - # => [ptr', EMPTY_WORD, end_ptr] + # => [ptr', pad(8), end_ptr] # clear the stack - drop dropw drop + drop dropw dropw drop # => [] end diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 2a5e76c1b3..069423ddcb 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -96,32 +96,31 @@ pub fn create_p2any_note( let serial_number = rng.draw_word(); let assets: Vec<_> = assets.into_iter().collect(); let mut code_body = String::new(); - for i in 0..assets.len() { - if i == 0 { - // first asset (dest_ptr is already on stack) - code_body.push_str( - " - # add first asset + for asset_idx in 0..assets.len() { + code_body.push_str(&format!( + " + # => [dest_ptr] + + # current_asset_ptr = dest_ptr + ASSET_SIZE * asset_idx + dup push.ASSET_SIZE mul.{asset_idx} + # => [current_asset_ptr, dest_ptr] padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - padw swapw padw padw swapdw - call.wallet::receive_asset - dropw movup.12 - # => [dest_ptr, pad(12)] - ", - ); - } else { - code_body.push_str( - " - # add next asset + # => [ASSET_VALUE, current_asset_ptr, dest_ptr] + + padw movup.8 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE, current_asset_ptr, dest_ptr] + + padw padw swapdw + # => [ASSET_KEY, ASSET_VALUE, pad(12), dest_ptr] - add.ASSET_SIZE dup movdn.13 - padw movup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be call.wallet::receive_asset - dropw movup.12 - # => [dest_ptr, pad(12)]", - ); - } + # => [pad(16), dest_ptr] + + dropw dropw dropw dropw + # => [dest_ptr] + ", + )); } code_body.push_str("dropw dropw dropw dropw"); From b8bab15332afec993de6b81a896a6f43be11bbb4 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 10:08:39 +0100 Subject: [PATCH 031/100] feat: refactor `output_note::add_asset` --- .../miden-agglayer/asm/bridge/bridge_out.masm | 83 ++++++---- .../asm/note_scripts/B2AGG.masm | 6 + .../asm/kernels/transaction/api.masm | 12 +- .../asm/protocol/output_note.masm | 18 +- .../asm/standards/faucets/mod.masm | 30 ++-- .../asm/standards/wallets/basic.masm | 8 +- .../src/testing/mock_util_lib.rs | 8 +- .../src/kernel_tests/tx/test_epilogue.rs | 19 ++- .../src/kernel_tests/tx/test_lazy_loading.rs | 16 +- .../src/kernel_tests/tx/test_output_note.rs | 155 ++++++++---------- 10 files changed, 191 insertions(+), 164 deletions(-) diff --git a/crates/miden-agglayer/asm/bridge/bridge_out.masm b/crates/miden-agglayer/asm/bridge/bridge_out.masm index e78e2983e9..0cd26d0a05 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_out.masm @@ -14,17 +14,22 @@ const LOCAL_EXIT_TREE_SLOT=word("miden::agglayer::let") const PUBLIC_NOTE=1 const BURN_NOTE_NUM_STORAGE_ITEMS=0 -const BURN_ASSET_MEM_PTR=24 +const BURN_ASSET_KEY_MEM_PTR=32 +const BURN_ASSET_VALUE_MEM_PTR=36 + +const CREATE_BURN_NOTE_ASSET_KEY_LOC=0 +const CREATE_BURN_NOTE_ASSET_VALUE_LOC=4 +const CREATE_BURN_NOTE_TAG_LOC=8 #! Computes the SERIAL_NUM of the outputted BURN note. #! #! The serial number is computed as hash(B2AGG_SERIAL_NUM, ASSET). #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_KEY] #! Outputs: [SERIAL_NUM] #! #! Where: -#! - ASSET is the asset for which to compute the burn note serial number. +#! - ASSET_KEY is the vault key from which to compute the burn note serial number. #! - SERIAL_NUM is the computed serial number for the BURN note. #! #! Invocation: exec @@ -41,26 +46,32 @@ end #! This procedure creates an output note that represents a burn operation for the given asset. #! The note is configured with the appropriate recipient, tag, and execution hint. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET is the asset to be burned. +#! - ASSET_KEY is the vault key of the asset to be burnt. +#! - ASSET_VALUE is the value of the asset to be burnt. #! #! Invocation: exec -@locals(8) +@locals(9) proc create_burn_note - loc_storew_be.0 dupw - # => [ASSET, ASSET] + loc_storew_be.CREATE_BURN_NOTE_ASSET_KEY_LOC + swapw + # => [ASSET_VALUE, ASSET_KEY] + + loc_storew_be.CREATE_BURN_NOTE_ASSET_VALUE_LOC + dropw dupw + # => [ASSET_KEY, ASSET_KEY] movup.2 drop movup.2 drop - # => [faucet_id_prefix, faucet_id_suffix, ASSET] + # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] exec.note_tag::create_account_target - # => [network_faucet_tag, ASSET] + # => [network_faucet_tag, ASSET_KEY] - loc_store.5 - # => [ASSET] + loc_store.CREATE_BURN_NOTE_TAG_LOC + # => [ASSET_KEY] exec.compute_burn_note_serial_num # => [SERIAL_NUM] @@ -75,14 +86,17 @@ proc create_burn_note # => [RECIPIENT] push.PUBLIC_NOTE - loc_load.5 + loc_load.CREATE_BURN_NOTE_TAG_LOC # => [tag, note_type, RECIPIENT] call.output_note::create # => [note_idx] - movdn.4 loc_loadw_be.0 - # => [ASSET, note_idx] + movdn.8 loc_loadw_be.CREATE_BURN_NOTE_ASSET_VALUE_LOC + # => [ASSET_VALUE, pad(4), note_idx] + + swapw loc_loadw_be.CREATE_BURN_NOTE_ASSET_KEY_LOC + # => [ASSET_KEY, ASSET_VALUE, note_idx] exec.output_note::add_asset # => [] @@ -97,18 +111,22 @@ end #! - Storing the updated MMR root in account storage #! - Creating a BURN note with the bridged out asset #! -#! Inputs: [ASSET, dest_network, dest_address(5)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5)] #! Outputs: [] #! #! Where: -#! - ASSET is the asset to be bridged out. +#! - ASSET_KEY is the vault key of the asset to be bridged out. +#! - ASSET_VALUE is the value of the asset to be bridged out. #! - dest_network is the u32 destination network/chain ID. #! - dest_address(5) are 5 u32 values representing a 20-byte Ethereum address. #! #! Invocation: call pub proc bridge_out - mem_storew_be.BURN_ASSET_MEM_PTR - # => [ASSET, dest_network, dest_address(5)] + mem_storew_be.BURN_ASSET_KEY_MEM_PTR + swapw + mem_storew_be.BURN_ASSET_VALUE_MEM_PTR + swapw + # => [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5)] # @dev TODO: Look up asset faucet id in asset registry # -> return scaling factor @@ -120,27 +138,32 @@ pub proc bridge_out # in preparation for keccak256 hashing # keccak256 inputs: - # => [ASSET, dest_network, dest_address(5)] + # => [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5)] # TODO we should convert Miden->Ethereum asset values, incl. amount conversion etc. # TODO: make building bridge message a separate procedure # TODO: match Agglayer addLeafBridge logic # TODO: convert Miden asset amount to Ethereum amount - # Store ASSET as u32 limbs in memory starting at address 0 + + # Store ASSET_KEY as u32 limbs in memory starting at address 0 push.0 movdn.4 exec.word::store_word_u32s_le - # => [dest_network, dest_address(5)] + # => [ASSET_VALUE, dest_network, dest_address(5)] - # Store [dest_network, dest_address[0..3]] as u32 limbs in memory starting at address 8 + # Store ASSET_VALUE as u32 limbs in memory starting at address 8 push.8 movdn.4 exec.word::store_word_u32s_le - # => [dest_address(2), 0, 0] + # => [dest_network, dest_address(5)] - # Store [dest_address[3..5], 0, 0] as u32 limbs in memory starting at address 16 + # Store [dest_network, dest_address[0..3]] as u32 limbs in memory starting at address 16 push.16 movdn.4 exec.word::store_word_u32s_le + # => [dest_address(2), 0, 0] + + # Store [dest_address[3..5], 0, 0] as u32 limbs in memory starting at address 24 + push.24 movdn.4 exec.word::store_word_u32s_le # => [] # 1 u32 = 4 bytes - # 10 u32 values = 40 bytes - push.40 push.0 + # 14 u32 values = 56 bytes + push.56 push.0 # => [ptr, len_bytes] exec.keccak256::hash_bytes @@ -151,8 +174,10 @@ pub proc bridge_out # => [] # creating BURN output note for ASSET - mem_loadw_be.BURN_ASSET_MEM_PTR - # => [ASSET] + mem_loadw_be.BURN_ASSET_VALUE_MEM_PTR + swapw + mem_loadw_be.BURN_ASSET_KEY_MEM_PTR + # => [ASSET_KEY, ASSET_VALUE] exec.create_burn_note # => [] diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 72701dbbbd..6b5c838381 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -94,7 +94,13 @@ begin mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET # => [ASSET_VALUE, dest_network, dest_address(5), pad(6)] + padw mem_loadw_be.0 + # => [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5), pad(6)] + call.bridge_out::bridge_out + # => [pad(20)] + + dropw # => [pad(16)] end # => [pad(16)] diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 1daf6a1786..ea9c77423b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -1076,12 +1076,13 @@ end #! Adds the asset to the note specified by the index. #! -#! Inputs: [note_idx, ASSET_VALUE, pad(11)] +#! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] #! Outputs: [pad(16)] #! #! Where: #! - note_idx is the index of the note to which the asset is added. -#! - ASSET_VALUE can be a fungible or non-fungible asset. +#! - ASSET_KEY is the vault key of the asset to add. +#! - ASSET_VALUE is the value of the asset to add. #! #! Panics if: #! - the procedure is called when the active account is not the native one. @@ -1090,10 +1091,11 @@ end pub proc output_note_add_asset # check that this procedure was executed against the native account exec.memory::assert_native_account - # => [note_idx, ASSET_VALUE, pad(11)] + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] + + movup.8 + # => [note_idx, ASSET_KEY, ASSET_VALUE, pad(7)] - # TODO(expand_assets): Derive the asset key from the asset for temporary compatibility. - movdn.4 exec.asset_vault::build_asset_vault_key movup.8 exec.output_note::add_asset # => [pad(16)] end diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 97f0180a26..38e42d409d 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -121,24 +121,24 @@ pub proc get_assets # => [num_assets, dest_ptr, note_index] end -#! Adds the ASSET to the note specified by the index. +#! Adds the asset to the note specified by the index. #! -#! Inputs: [ASSET, note_idx] +#! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx] #! Outputs: [] #! #! Where: #! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. +#! - ASSET_KEY is the vault key of the asset to add. +#! - ASSET_VALUE is the value of the asset to add. #! #! Invocation: exec pub proc add_asset - movup.4 exec.kernel_proc_offsets::output_note_add_asset_offset - # => [offset, note_idx, ASSET] + exec.kernel_proc_offsets::output_note_add_asset_offset + # => [offset, ASSET_KEY, ASSET_VALUE, note_idx] - # pad the stack before the syscall to prevent accidental modification of the deeper stack - # elements - push.0.0 movdn.7 movdn.7 padw padw swapdw - # => [offset, note_idx, ASSET, pad(10)] + # pad the stack + repeat.6 push.0 movdn.10 end + # => [offset, ASSET_KEY, ASSET_VALUE, note_idx, pad(6)] syscall.exec_kernel_proc # => [pad(16)] diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index c16faea6de..d9e24bbccd 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -123,33 +123,35 @@ pub proc distribute push.METADATA_SLOT[0..2] exec.native_account::set_item dropw # => [amount, tag, note_type, RECIPIENT] + # Create a new note. + # --------------------------------------------------------------------------------------------- + + movdn.6 exec.output_note::create + # => [note_idx, amount] + + dup movup.2 + # => [amount, note_idx, note_idx] + # Mint the asset. # --------------------------------------------------------------------------------------------- # creating the asset exec.faucet::create_fungible_asset - # => [ASSET_KEY, ASSET_VALUE, tag, note_type, RECIPIENT] + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] + + dupw.1 dupw.1 + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, ASSET_VALUE, note_idx, note_idx] # mint the asset; this is needed to satisfy asset preservation logic. # this ensures that the asset's faucet ID matches the native account's ID. # this is ensured because create_fungible_asset creates the asset with the native account's ID exec.faucet::mint - # => [ASSET, tag, note_type, RECIPIENT] - - movdn.9 movdn.9 movdn.9 movdn.9 - # => [tag, note_type, RECIPIENT, ASSET] + dropw + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] - # Create a new note with the asset. + # Add the asset to the note. # --------------------------------------------------------------------------------------------- - # create a note - exec.output_note::create - # => [note_idx, ASSET] - - # load the ASSET and add it to the note - dup movdn.5 movdn.5 - # => [ASSET, note_idx, note_idx] - exec.output_note::add_asset # => [note_idx] end diff --git a/crates/miden-standards/asm/standards/wallets/basic.masm b/crates/miden-standards/asm/standards/wallets/basic.masm index f9b46caa32..626ad34f6f 100644 --- a/crates/miden-standards/asm/standards/wallets/basic.masm +++ b/crates/miden-standards/asm/standards/wallets/basic.masm @@ -54,11 +54,13 @@ end #! #! Invocation: call pub proc move_asset_to_note + dupw.1 dupw.1 + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] + # remove the asset from the account exec.native_account::remove_asset - # => [ASSET_VALUE, note_idx, pad(11)] - - # TODO(expand_assets): Pass key to add_asset eventually. + dropw + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] exec.output_note::add_asset # => [pad(16)] diff --git a/crates/miden-standards/src/testing/mock_util_lib.rs b/crates/miden-standards/src/testing/mock_util_lib.rs index 757b827687..292513aa2f 100644 --- a/crates/miden-standards/src/testing/mock_util_lib.rs +++ b/crates/miden-standards/src/testing/mock_util_lib.rs @@ -21,14 +21,14 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " # => [note_idx] end - #! Inputs: [ASSET] + #! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] pub proc create_default_note_with_asset exec.create_default_note - # => [note_idx, ASSET] + # => [note_idx, ASSET_KEY, ASSET_VALUE] - movdn.4 - # => [ASSET, note_idx] + movdn.8 + # => [ASSET_KEY, ASSET_VALUE, note_idx] exec.output_note::add_asset # => [] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index d2ff3f0515..648b1c68cc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -74,7 +74,8 @@ async fn test_transaction_epilogue() -> anyhow::Result<()> { exec.output_note::create # => [note_idx] - push.{asset} + push.{ASSET_VALUE} + push.{ASSET_KEY} exec.output_note::add_asset # => [] @@ -87,7 +88,8 @@ async fn test_transaction_epilogue() -> anyhow::Result<()> { recipient = output_note_1.recipient().digest(), note_type = Felt::from(output_note_1.metadata().note_type()), tag = Felt::from(output_note_1.metadata().tag()), - asset = Word::from(asset) + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), ); let exec_output = tx_context.execute_code(&code).await?; @@ -259,12 +261,14 @@ async fn epilogue_fails_when_num_output_assets_exceed_num_input_assets() -> anyh begin # create a note with the output asset - push.{OUTPUT_ASSET} + push.{OUTPUT_ASSET_VALUE} + push.{OUTPUT_ASSET_KEY} exec.util::create_default_note_with_asset # => [] end ", - OUTPUT_ASSET = Word::from(output_asset), + OUTPUT_ASSET_KEY = output_asset.to_key_word(), + OUTPUT_ASSET_VALUE = output_asset.to_value_word(), ); let builder = CodeBuilder::with_mock_libraries(); @@ -312,12 +316,15 @@ async fn epilogue_fails_when_num_input_assets_exceed_num_output_assets() -> anyh begin # create a note with the output asset - push.{OUTPUT_ASSET} + push.{OUTPUT_ASSET_VALUE} + push.{OUTPUT_ASSET_KEY} exec.util::create_default_note_with_asset # => [] end ", - OUTPUT_ASSET = Word::from(input_asset), + // TODO(expand_assets): This seems wrong. + OUTPUT_ASSET_KEY = input_asset.to_key_word(), + OUTPUT_ASSET_VALUE = input_asset.to_value_word(), ); let builder = CodeBuilder::with_mock_libraries(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index 95dc5d9f85..f49f8aaf4a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -98,22 +98,26 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result push.{FUNGIBLE_ASSET1_VALUE} push.{FUNGIBLE_ASSET1_KEY} call.account::remove_asset - # drop the excess word from the call - swapw dropw - # => [ASSET_VALUE] + # drop the excess words from the call + dropw dropw + # => [] # move asset to note to adhere to asset preservation rules + push.{FUNGIBLE_ASSET1_VALUE} + push.{FUNGIBLE_ASSET1_KEY} exec.util::create_default_note_with_asset # => [] push.{FUNGIBLE_ASSET2_VALUE} push.{FUNGIBLE_ASSET2_KEY} call.account::remove_asset - # drop the excess word from the call - swapw dropw - # => [ASSET_VALUE] + # drop the excess words from the call + dropw dropw + # => [] # move asset to note to adhere to asset preservation rules + push.{FUNGIBLE_ASSET2_VALUE} + push.{FUNGIBLE_ASSET2_KEY} exec.util::create_default_note_with_asset # => [] end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 3e65c7fae4..b39973d859 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1,7 +1,5 @@ use alloc::string::String; -use alloc::vec::Vec; -use anyhow::Context; use miden_protocol::account::{Account, AccountId}; use miden_protocol::asset::{Asset, FungibleAsset, NonFungibleAsset}; use miden_protocol::crypto::rand::RpoRandomCoin; @@ -11,7 +9,6 @@ use miden_protocol::errors::tx_kernel::{ }; use miden_protocol::note::{ Note, - NoteAssets, NoteAttachment, NoteAttachmentScheme, NoteMetadata, @@ -203,75 +200,40 @@ async fn test_create_note_too_many_notes() -> anyhow::Result<()> { #[tokio::test] async fn test_get_output_notes_commitment() -> anyhow::Result<()> { - let tx_context = { - let account = - Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - - let output_note_1 = - create_public_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); - - let input_note_1 = create_public_p2any_note( - ACCOUNT_ID_PRIVATE_SENDER.try_into()?, - [FungibleAsset::mock(100)], - ); - - let input_note_2 = create_public_p2any_note( - ACCOUNT_ID_PRIVATE_SENDER.try_into()?, - [FungibleAsset::mock(200)], - ); + let mut rng = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])); + let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - TransactionContextBuilder::new(account) - .extend_input_notes(vec![input_note_1, input_note_2]) - .extend_expected_output_notes(vec![OutputNote::Full(output_note_1)]) - .build()? - }; + let asset_1 = FungibleAsset::mock(100); + let asset_2 = FungibleAsset::mock(200); - // extract input note data - let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); - let input_asset_1 = **input_note_1 - .assets() - .iter() - .take(1) - .collect::>() - .first() - .context("getting first expected input asset")?; - let input_note_2 = tx_context.tx_inputs().input_notes().get_note(1).note(); - let input_asset_2 = **input_note_2 - .assets() - .iter() - .take(1) - .collect::>() - .first() - .context("getting second expected input asset")?; - - // Choose random accounts as the target for the note tag. - let network_account = AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET)?; - let local_account = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET)?; + let input_note_1 = create_public_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [asset_1]); + let input_note_2 = create_public_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [asset_2]); // create output note 1 - let output_serial_no_1 = Word::from([8u32; 4]); - let output_tag_1 = NoteTag::with_account_target(network_account); - let assets = NoteAssets::new(vec![input_asset_1])?; - let metadata = NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public) - .with_tag(output_tag_1); - let inputs = NoteStorage::new(vec![])?; - let recipient = NoteRecipient::new(output_serial_no_1, input_note_1.script().clone(), inputs); - let output_note_1 = Note::new(assets, metadata, recipient); + let output_note_1 = NoteBuilder::new(account.id(), &mut rng) + .tag(NoteTag::with_account_target(account.id()).as_u32()) + .note_type(NoteType::Public) + .add_assets([asset_1]) + .build()?; // create output note 2 - let output_serial_no_2 = Word::from([11u32; 4]); - let output_tag_2 = NoteTag::with_account_target(local_account); - let assets = NoteAssets::new(vec![input_asset_2])?; - let attachment = NoteAttachment::new_array( - NoteAttachmentScheme::new(5), - [42, 43, 44, 45, 46u32].map(Felt::from).to_vec(), - )?; - let metadata = NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public) - .with_tag(output_tag_2) - .with_attachment(attachment); - let inputs = NoteStorage::new(vec![])?; - let recipient = NoteRecipient::new(output_serial_no_2, input_note_2.script().clone(), inputs); - let output_note_2 = Note::new(assets, metadata, recipient); + let output_note_2 = NoteBuilder::new(account.id(), &mut rng) + .tag(NoteTag::with_custom_account_target(account.id(), 2)?.as_u32()) + .note_type(NoteType::Public) + .add_assets([asset_2]) + .attachment(NoteAttachment::new_array( + NoteAttachmentScheme::new(5), + [42, 43, 44, 45, 46u32].map(Felt::from).to_vec(), + )?) + .build()?; + + let tx_context = TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note_1.clone(), input_note_2.clone()]) + .extend_expected_output_notes(vec![ + OutputNote::Full(output_note_1.clone()), + OutputNote::Full(output_note_2.clone()), + ]) + .build()?; // compute expected output notes commitment let expected_output_notes_commitment = OutputNotes::new(vec![ @@ -300,7 +262,8 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { exec.output_note::create # => [note_idx] - push.{asset_1} + push.{ASSET_1_VALUE} + push.{ASSET_1_KEY} exec.output_note::add_asset # => [] @@ -311,7 +274,9 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { exec.output_note::create # => [note_idx] - dup push.{asset_2} + dup + push.{ASSET_2_VALUE} + push.{ASSET_2_KEY} exec.output_note::add_asset # => [note_idx] @@ -334,14 +299,12 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { PUBLIC_NOTE = NoteType::Public as u8, recipient_1 = output_note_1.recipient().digest(), tag_1 = output_note_1.metadata().tag(), - asset_1 = Word::from( - **output_note_1.assets().iter().take(1).collect::>().first().unwrap() - ), + ASSET_1_KEY = asset_1.to_key_word(), + ASSET_1_VALUE = asset_1.to_value_word(), recipient_2 = output_note_2.recipient().digest(), tag_2 = output_note_2.metadata().tag(), - asset_2 = Word::from( - **output_note_2.assets().iter().take(1).collect::>().first().unwrap() - ), + ASSET_2_KEY = asset_2.to_key_word(), + ASSET_2_VALUE = asset_2.to_value_word(), ATTACHMENT2 = output_note_2.metadata().to_attachment_word(), attachment_scheme2 = output_note_2.metadata().attachment().attachment_scheme().as_u32(), ); @@ -414,7 +377,8 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { # => [note_idx] push.{ASSET_VALUE} - # => [ASSET_VALUE, note_idx] + push.{ASSET_KEY} + # => [ASSET_KEY, ASSET_VALUE, note_idx] call.output_note::add_asset # => [] @@ -426,6 +390,7 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { recipient = recipient, PUBLIC_NOTE = NoteType::Public as u8, tag = tag, + ASSET_KEY = asset.to_key_word(), ASSET_VALUE = asset.to_value_word(), ); @@ -480,19 +445,26 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { dup assertz.err=\"index of the created note should be zero\" # => [note_idx] - dup push.{asset} + dup + push.{ASSET_VALUE} + push.{ASSET_KEY} exec.output_note::add_asset # => [note_idx] - dup push.{asset_2} + dup + push.{ASSET2_VALUE} + push.{ASSET2_KEY} exec.output_note::add_asset # => [note_idx] - dup push.{asset_3} + dup + push.{ASSET3_VALUE} + push.{ASSET3_KEY} exec.output_note::add_asset # => [note_idx] - push.{nft} + push.{ASSET4_VALUE} + push.{ASSET4_KEY} exec.output_note::add_asset # => [] @@ -503,10 +475,14 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { recipient = recipient, PUBLIC_NOTE = NoteType::Public as u8, tag = tag, - asset = asset.to_value_word(), - asset_2 = asset_2.to_value_word(), - asset_3 = asset_3.to_value_word(), - nft = non_fungible_asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), + ASSET2_KEY = asset_2.to_key_word(), + ASSET2_VALUE = asset_2.to_value_word(), + ASSET3_KEY = asset_3.to_key_word(), + ASSET3_VALUE = asset_3.to_value_word(), + ASSET4_KEY = non_fungible_asset.to_key_word(), + ASSET4_VALUE = non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -578,7 +554,6 @@ async fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { let recipient = Word::from([0, 1, 2, 3u32]); let tag = NoteTag::new(999 << 16 | 777); let non_fungible_asset = NonFungibleAsset::mock(&[1, 2, 3]); - let encoded = Word::from(non_fungible_asset); let code = format!( " @@ -595,13 +570,16 @@ async fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { exec.output_note::create # => [note_idx] - dup push.{nft} - # => [NFT, note_idx, note_idx] + dup + push.{ASSET_VALUE} + push.{ASSET_KEY} + # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx] exec.output_note::add_asset # => [note_idx] - push.{nft} + push.{ASSET_VALUE} + push.{ASSET_KEY} exec.output_note::add_asset # => [] end @@ -609,7 +587,8 @@ async fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { recipient = recipient, PUBLIC_NOTE = NoteType::Public as u8, tag = tag, - nft = encoded, + ASSET_KEY = non_fungible_asset.to_key_word(), + ASSET_VALUE = non_fungible_asset.to_value_word(), ); let exec_output = tx_context.execute_code(&code).await; From 6a8dfd93ecb89f9a45849910076e88de7af1dc50 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 11:40:39 +0100 Subject: [PATCH 032/100] chore: deduplicate epilogue asset preservation test --- .../src/kernel_tests/tx/test_epilogue.rs | 76 ++++--------------- 1 file changed, 14 insertions(+), 62 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 648b1c68cc..6054a0cc96 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -235,13 +235,21 @@ async fn test_compute_output_note_id() -> anyhow::Result<()> { Ok(()) } -/// Tests that a transaction fails due to the asset preservation rules when the input note has an -/// asset with amount 100 and the output note has the same asset with amount 200. +/// Tests that a transaction fails when assets aren't preserved, i.e. +/// - when the input note has asset amount 100 and the output note has asset amount 200. +/// - when the input note has asset amount 200 and the output note has asset amount 100. +#[rstest::rstest] +#[case::outputs_exceed_inputs(100, 200)] +#[case::inputs_exceed_outputs(200, 100)] #[tokio::test] -async fn epilogue_fails_when_num_output_assets_exceed_num_input_assets() -> anyhow::Result<()> { - // Create an input asset with amount 100 and an output asset with amount 200. - let input_asset = FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, 100)?; - let output_asset = input_asset.add(input_asset)?; +async fn epilogue_fails_when_assets_arent_preserved( + #[case] input_amount: u64, + #[case] output_amount: u64, +) -> anyhow::Result<()> { + let input_asset = + FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, input_amount)?; + let output_asset = + FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, output_amount)?; let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -290,62 +298,6 @@ async fn epilogue_fails_when_num_output_assets_exceed_num_input_assets() -> anyh Ok(()) } -/// Tests that a transaction fails due to the asset preservation rules when the input note has an -/// asset with amount 200 and the output note has the same asset with amount 100. -#[tokio::test] -async fn epilogue_fails_when_num_input_assets_exceed_num_output_assets() -> anyhow::Result<()> { - // Create an input asset with amount 200 and an output asset with amount 100. - let output_asset = FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, 100)?; - let input_asset = output_asset.add(output_asset)?; - - let mut builder = MockChain::builder(); - let account = builder.add_existing_mock_account(Auth::IncrNonce)?; - // Add an input note that (automatically) adds its assets to the transaction's input vault, but - // _does not_ add the asset to the account. This is just to keep the test conceptually simple - - // there is no account involved. - let input_note = NoteBuilder::new(account.id(), *builder.rng_mut()) - .add_assets([Asset::from(output_asset)]) - .build()?; - builder.add_output_note(OutputNote::Full(input_note.clone())); - let mock_chain = builder.build()?; - - let code = format!( - " - use mock::account - use mock::util - - begin - # create a note with the output asset - push.{OUTPUT_ASSET_VALUE} - push.{OUTPUT_ASSET_KEY} - exec.util::create_default_note_with_asset - # => [] - end - ", - // TODO(expand_assets): This seems wrong. - OUTPUT_ASSET_KEY = input_asset.to_key_word(), - OUTPUT_ASSET_VALUE = input_asset.to_value_word(), - ); - - let builder = CodeBuilder::with_mock_libraries(); - let source_manager = builder.source_manager(); - let tx_script = builder.compile_tx_script(code)?; - - let tx_context = mock_chain - .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[input_note])? - .tx_script(tx_script) - .with_source_manager(source_manager) - .build()?; - - let exec_output = tx_context.execute().await; - assert_transaction_executor_error!( - exec_output, - ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME - ); - - Ok(()) -} - #[tokio::test] async fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; From 21b0b02ecd7d0539dcd6283f5f2e9569bff588e9 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 11:43:37 +0100 Subject: [PATCH 033/100] chore: remove re-export of vault key builder procedures --- crates/miden-protocol/asm/protocol/asset.masm | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index 00cf670482..3d4f8b229b 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -16,15 +16,6 @@ const ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT="fungible asset build const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet" -# RE-EXPORTS -# ================================================================================================= - -# TODO(expand_assets): These shouldn't be needed long-term. -# Re-export the shared utility procedures -pub use ::miden::protocol::util::asset::build_non_fungible_asset_vault_key -pub use ::miden::protocol::util::asset::build_fungible_asset_vault_key -pub use ::miden::protocol::util::asset::build_asset_vault_key - # PROCEDURES # ================================================================================================= @@ -55,7 +46,7 @@ pub proc create_fungible_asset push.0 movdn.2 # => [ASSET_VALUE] - exec.build_fungible_asset_vault_key + exec.::miden::protocol::util::asset::build_fungible_asset_vault_key # => [ASSET_KEY, ASSET_VALUE] end @@ -83,6 +74,6 @@ pub proc create_non_fungible_asset # => [faucet_id_prefix, hash2, hash1, hash0] # => [ASSET_KEY, ASSET_VALUE] - dupw exec.build_non_fungible_asset_vault_key + dupw exec.::miden::protocol::util::asset::build_non_fungible_asset_vault_key # => [ASSET_KEY, ASSET_VALUE] end From fc0e85000cda3bfe0da21099504f44a1339ad1be Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 11:50:46 +0100 Subject: [PATCH 034/100] chore: regenerate kernel procedure hashes --- .../src/transaction/kernel/procedures.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 47c7e49526..a345540573 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,9 +40,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x273ecaebf62df3cec1cf335890f1a659007b88ddd0f446851577c7d6c34821bb"), + word!("0x50985ece56c97b478f5f6da3c82f4ff4e0e972f37aab7670b040182993b78535"), // account_remove_asset - word!("0xd89f7a379e0eafc331f6b8fbf31766c91bbf65470f3134c9b935e4f00113eabf"), + word!("0x069672b069a35b40e36911e0e029aef59a42a10209b6a70892badb3e828ca8a9"), // account_get_asset word!("0x21dc0ef7e3475f28fbcf26636d9b58c3f7e349da7c7a36e85c1b49e50437fa65"), // account_get_initial_asset @@ -58,9 +58,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xb0b63fdd01af0bcb4aacb2412e934cdc7691308647152d416c7ae4fc909da076"), // faucet_mint_asset - word!("0xacf3c83416f314cafdae6d38e7371f47b9cbb932e6cdeee8031f00f9c8c240c2"), + word!("0x5015be55c6d4b7c23627bc5c07e3dd113bbf2839077eb1e2afbeaf56c090e05d"), // faucet_burn_asset - word!("0x860d59a60324f6e7f69b864b7e5298490b3e04d9f5e21b4b8445da05e8df11b0"), + word!("0xcce90fd532495cd3205cb1657d20ef6b1857265ad11cd4641f33c44b887ac690"), // input_note_get_metadata word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info @@ -82,7 +82,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // output_note_get_recipient word!("0x6aeec5901ae0afd538bdbb6f7c5a05da66e75fb9e2100c1ffe2a3fa5d9910b64"), // output_note_add_asset - word!("0x6b531e2fd8f2619de3ad1ca2cdf35bada6a7cdd0502a56a39ac74374f7bae801"), + word!("0x6e1a699df870c682549c840bf030111000f495d3babb2b3a6f8cace062d90995"), // output_note_set_attachment word!("0xc33e8568f74b1accf0ee7f5de52fea30fe524b9e0dad7958d7e506e9ba3e3bbe"), // tx_get_num_input_notes From 77f4984bfe850a78d62586bd21d472f839ff1ec5 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 11:50:54 +0100 Subject: [PATCH 035/100] chore: add changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b677e29e..9a069f1691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ - [BREAKING] Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). - [BREAKING] Moved standard note code into individual note modules ([#2363](https://github.com/0xMiden/miden-base/pull/2363)). - [BREAKING] Added `miden::standards::note_tag` module for account target note tags ([#2366](https://github.com/0xMiden/miden-base/pull/2366)). -- [BREAKING] Refactored assets in the tx kernel from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396)). +- [BREAKING] Refactored assets in the tx kernel and `miden::protocol` from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396), [#2410](https://github.com/0xMiden/miden-base/pull/2410)). ## 0.13.3 (2026-01-27) From 8530a468f71aa98424ec1630729567906e0d268b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 13:16:20 +0100 Subject: [PATCH 036/100] fix: doc link to mock util lib --- crates/miden-standards/src/code_builder/mod.rs | 2 +- crates/miden-standards/src/testing/mock_util_lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index f6cf1ba4e3..101a6d6210 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -424,7 +424,7 @@ impl CodeBuilder { /// /// [account_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_library /// [faucet_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_library - /// [util_lib]: miden_protocol::testing::mock_util_lib::mock_util_library + /// [util_lib]: crate::testing::mock_util_lib::mock_util_library #[cfg(any(feature = "testing", test))] pub fn with_mock_libraries() -> Self { Self::with_mock_libraries_with_source_manager(Arc::new(DefaultSourceManager::default())) diff --git a/crates/miden-standards/src/testing/mock_util_lib.rs b/crates/miden-standards/src/testing/mock_util_lib.rs index 292513aa2f..211feed5d9 100644 --- a/crates/miden-standards/src/testing/mock_util_lib.rs +++ b/crates/miden-standards/src/testing/mock_util_lib.rs @@ -70,6 +70,6 @@ static MOCK_UTIL_LIBRARY: LazyLock = LazyLock::new(|| { /// Returns the mock test [`Library`] under the `mock::util` namespace. /// /// This provides convenient wrappers for testing purposes. -pub(crate) fn mock_util_library() -> Library { +pub fn mock_util_library() -> Library { MOCK_UTIL_LIBRARY.clone() } From b360b7d7bdf24365cf2f62de5b259d3b6ceb6950 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 14:17:54 +0100 Subject: [PATCH 037/100] chore: improve send_note_body impl and test --- .../asm/standards/notes/swap.masm | 2 +- .../src/account/interface/component.rs | 16 ++++--- .../miden-testing/tests/scripts/send_note.rs | 47 +++++++++++++++---- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index ff3868a564..d817028c50 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -1,6 +1,6 @@ use miden::protocol::active_note use miden::protocol::asset -use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET +use miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET use miden::protocol::output_note use miden::standards::wallets::basic->wallet diff --git a/crates/miden-standards/src/account/interface/component.rs b/crates/miden-standards/src/account/interface/component.rs index d4ae928dc7..185121d1d7 100644 --- a/crates/miden-standards/src/account/interface/component.rs +++ b/crates/miden-standards/src/account/interface/component.rs @@ -276,16 +276,18 @@ impl AccountComponentInterface { body.push_str(&format!( " # duplicate note index - dup + padw push.0 push.0 push.0 dup.7 + # => [note_idx, pad(7), note_idx, pad(16)] + push.{ASSET_VALUE} push.{ASSET_KEY} - # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(16)] + # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7), note_idx, pad(16)] + call.::miden::standards::wallets::basic::move_asset_to_note - # 9 parameter elements + 16 pad elements = 25 total pads after the call - # => [note_idx, pad(25)] - swapdw dropw dropw swap drop - # => [note_idx, pad(16)] - \n + # => [pad(16), note_idx, pad(16)] + + dropw dropw dropw dropw + # => [note_idx, pad(16)]\n ", ASSET_KEY = asset.to_key_word(), ASSET_VALUE = asset.to_value_word(), diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index 35ef74b57f..bde9080e2f 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -1,7 +1,7 @@ use core::slice; use std::collections::BTreeMap; -use miden_protocol::asset::{Asset, FungibleAsset}; +use miden_protocol::asset::{Asset, FungibleAsset, NonFungibleAsset}; use miden_protocol::crypto::rand::{FeltRng, RpoRandomCoin}; use miden_protocol::note::{ Note, @@ -19,19 +19,37 @@ use miden_protocol::transaction::OutputNote; use miden_protocol::{Felt, Word}; use miden_standards::account::interface::{AccountInterface, AccountInterfaceExt}; use miden_standards::code_builder::CodeBuilder; +use miden_testing::utils::create_p2any_note; use miden_testing::{Auth, MockChain}; /// Tests the execution of the generated send_note transaction script in case the sending account /// has the [`BasicWallet`][wallet] interface. /// +/// This tests consumes a SPAWN note first so that the note_idx in the send_note script is not zero +/// to make sure the note_idx is correctly kept on the stack. +/// +/// The test also sends two assets to make sure the generated script deals correctly with multiple +/// assets. +/// /// [wallet]: miden_standards::account::interface::AccountComponentInterface::BasicWallet #[tokio::test] async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { - let sent_asset = FungibleAsset::mock(10); + let total_asset = FungibleAsset::mock(100); + let sent_asset0 = NonFungibleAsset::mock(&[4, 5, 6]); + + let sent_asset1 = FungibleAsset::mock(10); + let sent_asset2 = FungibleAsset::mock(40); let mut builder = MockChain::builder(); let sender_basic_wallet_account = - builder.add_existing_wallet_with_assets(Auth::BasicAuth, [FungibleAsset::mock(100)])?; + builder.add_existing_wallet_with_assets(Auth::BasicAuth, [sent_asset0, total_asset])?; + let p2any_note = create_p2any_note( + sender_basic_wallet_account.id(), + NoteType::Private, + [sent_asset2], + &mut RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])), + ); + let spawn_note = builder.add_spawn_note([&p2any_note])?; let mock_chain = builder.build()?; let sender_account_interface = AccountInterface::from_account(&sender_basic_wallet_account); @@ -42,7 +60,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let metadata = NoteMetadata::new(sender_basic_wallet_account.id(), NoteType::Public) .with_tag(tag) .with_attachment(attachment.clone()); - let assets = NoteAssets::new(vec![sent_asset]).unwrap(); + let assets = NoteAssets::new(vec![sent_asset0, sent_asset1]).unwrap(); let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); @@ -55,7 +73,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { .build_send_notes_script(slice::from_ref(&partial_note), Some(expiration_delta))?; let executed_transaction = mock_chain - .build_tx_context(sender_basic_wallet_account.id(), &[], &[]) + .build_tx_context(sender_basic_wallet_account.id(), &[spawn_note.id()], &[]) .expect("failed to build tx context") .tx_script(send_note_transaction_script) .extend_expected_output_notes(vec![OutputNote::Full(note.clone())]) @@ -70,13 +88,22 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { .removed_assets() .map(|asset| (asset.vault_key(), asset)) .collect(); - assert_eq!(removed_assets.len(), 1, "one asset should have been removed"); + assert_eq!(removed_assets.len(), 2, "two assets should have been removed"); assert_eq!( - removed_assets.remove(&sent_asset.vault_key()).unwrap(), - sent_asset, - "sent asset should be in removed assets" + removed_assets.remove(&sent_asset0.vault_key()).unwrap(), + sent_asset0, + "sent asset0 should be in removed assets" ); - assert_eq!(executed_transaction.output_notes().get_note(0), &OutputNote::Full(note)); + assert_eq!( + removed_assets.remove(&sent_asset1.vault_key()).unwrap(), + sent_asset1.unwrap_fungible().add(sent_asset2.unwrap_fungible())?.into(), + "sent asset1 + sent_asset2 should be in removed assets" + ); + assert_eq!( + executed_transaction.output_notes().get_note(0), + &OutputNote::Partial(p2any_note.into()) + ); + assert_eq!(executed_transaction.output_notes().get_note(1), &OutputNote::Full(note)); Ok(()) } From b1af2caa1b804976c52dd1b1a6890e007fdf4f40 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 16:15:45 +0100 Subject: [PATCH 038/100] fix: replace leftover `ASSET`s with `ASSET_VALUE` --- .../miden-agglayer/asm/bridge/bridge_out.masm | 6 +-- .../asm/kernels/transaction/api.masm | 3 +- .../kernels/transaction/lib/asset_vault.masm | 2 +- .../kernels/transaction/lib/output_note.masm | 6 +-- .../asm/protocol/active_account.masm | 38 ++++++++++--------- .../asm/shared_utils/util/asset.masm | 4 +- .../asm/standards/notes/swap.masm | 2 +- crates/miden-testing/tests/scripts/faucet.rs | 4 +- 8 files changed, 34 insertions(+), 31 deletions(-) diff --git a/crates/miden-agglayer/asm/bridge/bridge_out.masm b/crates/miden-agglayer/asm/bridge/bridge_out.masm index 0cd26d0a05..9997d1d0e9 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_out.masm @@ -23,7 +23,7 @@ const CREATE_BURN_NOTE_TAG_LOC=8 #! Computes the SERIAL_NUM of the outputted BURN note. #! -#! The serial number is computed as hash(B2AGG_SERIAL_NUM, ASSET). +#! The serial number is computed as hash(B2AGG_SERIAL_NUM, ASSET_KEY). #! #! Inputs: [ASSET_KEY] #! Outputs: [SERIAL_NUM] @@ -35,7 +35,7 @@ const CREATE_BURN_NOTE_TAG_LOC=8 #! Invocation: exec proc compute_burn_note_serial_num exec.active_note::get_serial_number - # => [B2AGG_SERIAL_NUM, ASSET] + # => [B2AGG_SERIAL_NUM, ASSET_KEY] exec.rpo256::merge # => [SERIAL_NUM] @@ -173,7 +173,7 @@ pub proc bridge_out exec.local_exit_tree::add_asset_message # => [] - # creating BURN output note for ASSET + # creating BURN output note for the asset mem_loadw_be.BURN_ASSET_VALUE_MEM_PTR swapw mem_loadw_be.BURN_ASSET_KEY_MEM_PTR diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index ea9c77423b..4ddc0a77cf 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -608,7 +608,8 @@ end #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't +#! present. #! #! Invocation: dynexec pub proc account_get_asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index bb09153957..e9e5bd6aeb 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -53,7 +53,7 @@ pub proc get_asset # => [ASSET_VALUE] end -#! Returns the _peeked_ ASSET associated with the provided asset vault key. +#! Returns the _peeked_ asset associated with the provided asset vault key. #! #! WARNING: Peeked means the asset is loaded from the advice provider, which is susceptible to #! manipulation from a malicious host. Therefore this should only be used when the inclusion of the diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index f74738de5e..7c9a09d928 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -57,9 +57,9 @@ const NOTE_BEFORE_CREATED_EVENT=event("miden::protocol::note::before_created") # Event emitted after a new note is created. const NOTE_AFTER_CREATED_EVENT=event("miden::protocol::note::after_created") -# Event emitted before an ASSET is added to a note +# Event emitted before an asset is added to a note const NOTE_BEFORE_ADD_ASSET_EVENT=event("miden::protocol::note::before_add_asset") -# Event emitted after an ASSET is added to a note +# Event emitted after an asset is added to a note const NOTE_AFTER_ADD_ASSET_EVENT=event("miden::protocol::note::after_add_asset") # Event emitted before an ATTACHMENT is added to a note @@ -178,7 +178,7 @@ pub proc get_assets_info # => [ASSETS_COMMITMENT, num_assets] end -#! Adds the ASSET to the note specified by the index. +#! Adds the asset to the note specified by the index. #! #! Inputs: [note_idx, ASSET_KEY, ASSET_VALUE] #! Outputs: [] diff --git a/crates/miden-protocol/asm/protocol/active_account.masm b/crates/miden-protocol/asm/protocol/active_account.masm index 34b0a3b283..45f2bd3859 100644 --- a/crates/miden-protocol/asm/protocol/active_account.masm +++ b/crates/miden-protocol/asm/protocol/active_account.masm @@ -419,14 +419,15 @@ end # VAULT # ------------------------------------------------------------------------------------------------- -#! Returns the asset associated with the provided faucet_id in the active account's vault. +#! Returns the asset associated with the provided asset vault key in the active account's vault. #! #! Inputs: [ASSET_KEY] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset from the vault, which can be the EMPTY_WORD if it isn't present. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't +#! present. #! #! Invocation: exec pub proc get_asset @@ -439,22 +440,23 @@ pub proc get_asset # => [offset, ASSET_KEY, pad(11)] syscall.exec_kernel_proc - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end -#! Returns the asset issued by the provided faucet_id in the active account's vault at the -#! beginning of the transaction. +#! Returns the asset associated with the provided asset vault key in the active account's vault at +#! the beginning of the transaction. #! #! Inputs: [ASSET_KEY] -#! Outputs: [ASSET] +#! Outputs: [ASSET_VALUE] #! #! Where: #! - ASSET_KEY is the asset vault key of the asset to fetch. -#! - ASSET is the asset at the beginning of the transaction. +#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't +#! present. #! #! Invocation: exec pub proc get_initial_asset @@ -467,11 +469,11 @@ pub proc get_initial_asset # => [offset, ASSET_KEY, pad(11)] syscall.exec_kernel_proc - # => [ASSET, pad(12)] + # => [ASSET_VALUE, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ASSET] + # => [ASSET_VALUE] end #! Returns the balance of the fungible asset associated with the provided faucet_id in the active @@ -499,7 +501,7 @@ pub proc get_balance # => [ASSET_KEY] exec.get_asset - # => [ASSET] + # => [ASSET_VALUE] # extract the asset's balance exec.::miden::protocol::util::asset::get_balance_from_fungible_asset @@ -531,7 +533,7 @@ pub proc get_initial_balance # => [ASSET_KEY] exec.get_initial_asset - # => [ASSET] + # => [ASSET_VALUE] # extract the asset's balance exec.::miden::protocol::util::asset::get_balance_from_fungible_asset @@ -541,15 +543,15 @@ end #! Returns a boolean indicating whether the non-fungible asset is present in the active account's #! vault. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_VALUE] #! Outputs: [has_asset] #! #! Where: -#! - ASSET is the non-fungible asset of interest +#! - ASSET_VALUE is the non-fungible asset of interest #! - has_asset is a boolean indicating whether the account vault has the asset of interest #! #! Panics if: -#! - the ASSET is a fungible asset. +#! - the ASSET_VALUE is a fungible asset. #! #! Invocation: exec pub proc has_non_fungible_asset @@ -558,13 +560,13 @@ pub proc has_non_fungible_asset # TODO(expand_assets): This procedure may go away. If not, check more reliably. dup.2 neq.0 assert.err=ERR_VAULT_HAS_NON_FUNGIBLE_ASSET_PROC_CAN_BE_CALLED_ONLY_WITH_NON_FUNGIBLE_ASSET - # => [ASSET] + # => [ASSET_VALUE] exec.asset::build_non_fungible_asset_vault_key # => [ASSET_KEY] exec.get_asset - # => [ASSET] + # => [ASSET_VALUE] # compare with EMPTY_WORD to assess if the asset exists in the vault exec.word::eqz not diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index d4e8b814d3..c72ab37cde 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -22,11 +22,11 @@ const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 #! #! Note: Assumes that the given asset is fungible and does NOT validate it. #! -#! Inputs: [ASSET] +#! Inputs: [ASSET_VALUE] #! Outputs: [balance] #! #! Where: -#! - ASSET is the fungible asset from which to extract the balance. +#! - ASSET_VALUE is the fungible asset from which to extract the balance. #! - balance is the amount of the fungible asset. pub proc get_balance_from_fungible_asset drop drop drop diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index d817028c50..5818574866 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -28,7 +28,7 @@ const ERR_SWAP_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="SWAP script expects exactly 1 const ERR_SWAP_WRONG_NUMBER_OF_ASSETS="SWAP script requires exactly 1 note asset" #! Swap script: adds an asset from the note into consumers account and -#! creates a note consumable by note issuer containing requested ASSET. +#! creates a note consumable by note issuer containing requested asset. #! #! Requires that the account exposes: #! - miden::standards::wallets::basic::receive_asset procedure. diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 05975d6c79..a461af0353 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -248,7 +248,7 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R # => [] call.::miden::standards::faucets::basic_fungible::burn - # => [ASSET] + # => [ASSET_VALUE] # truncate the stack dropw @@ -312,7 +312,7 @@ async fn faucet_burn_fungible_asset_fails_amount_exceeds_token_supply() -> anyho # => [] call.::miden::standards::faucets::basic_fungible::burn - # => [ASSET] + # => [ASSET_VALUE] # truncate the stack dropw From 23daea6bae9d30eb3150433f602609c6473db61b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 16:46:33 +0100 Subject: [PATCH 039/100] chore: update protocol library docs --- docs/src/protocol_library.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index cdc64aef74..6d4d0c46c6 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -44,9 +44,11 @@ Active account procedures can be used to read from storage, fetch or compute com | `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

**Inputs:** `[slot_id_prefix, slot_id_suffix]`
**Outputs:** `[VALUE]` | Account | | `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[slot_id_prefix, slot_id_suffix, KEY]`
**Outputs:** `[VALUE]` | Account | | `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

**Inputs:** `[slot_id_prefix, slot_id_suffix, KEY]`
**Outputs:** `[VALUE]` | Account | +| `get_asset` | Returns the asset associated with the provided asset vault key in the active account's vault.

**Inputs:** `[ASSET_KEY]`
**Outputs:** `[ASSET_VALUE]` | Any | +| `get_initial_asset` | Returns the asset associated with the provided asset vault key in the active account's vault at the beginning of the transaction.

**Inputs:** `[ASSET_KEY]`
**Outputs:** `[ASSET_VALUE]` | Any | | `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | | `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | -| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the active account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | +| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the active account's vault.

**Inputs:** `[ASSET_VALUE]`
**Outputs:** `[has_asset]` | Any | | `get_initial_vault_root` | Returns the vault root of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | | `get_vault_root` | Returns the vault root of the active account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | | `get_num_procedures` | Returns the number of procedures in the active account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | @@ -64,8 +66,8 @@ Native account procedures can be used to write to storage, add or remove assets | `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[DELTA_COMMITMENT]` | Auth | | `set_item` | Sets an item in the native account storage.

**Inputs:** `[slot_id_prefix, slot_id_suffix, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | | `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given native account storage slot.

**Inputs:** `[slot_id_prefix, slot_id_suffix, KEY, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | -| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET']` | Native & Account | -| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account | +| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET_KEY, ASSET_VALUE]`
**Outputs:** `[ASSET_VALUE']` | Native & Account | +| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET_KEY, ASSET_VALUE]`
**Outputs:** `[ASSET_VALUE]` | Native & Account | | `was_procedure_called` | Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | ## Active Note Procedures (`miden::protocol::active_note`) @@ -106,7 +108,7 @@ Output note procedures can be used to fetch data on output notes created by the | `create` | Creates a new output note and returns its index.

**Inputs:** `[tag, note_type, RECIPIENT]`
**Outputs:** `[note_idx]` | Native & Account | | `get_assets_info` | Returns the information about assets in the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | | `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | -| `add_asset` | Adds the `ASSET` to the output note specified by the index.

**Inputs:** `[ASSET, note_idx]`
**Outputs:** `[]` | Native | +| `add_asset` | Adds the asset to the output note specified by the index.

**Inputs:** `[ASSET_KEY, ASSET_VALUE, note_idx]`
**Outputs:** `[]` | Native | | `set_attachment` | Sets the attachment of the note specified by the index.

If attachment_kind == Array, there must be an advice map entry for ATTACHMENT.

**Inputs:**
`Operand Stack: [note_idx, attachment_scheme, attachment_kind, ATTACHMENT]`
`Advice map: { ATTACHMENT?: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | | `set_array_attachment` | Sets the attachment of the note specified by the note index to the provided ATTACHMENT which commits to an array of felts.

**Inputs:**
`Operand Stack: [note_idx, attachment_scheme, ATTACHMENT]`
`Advice map: { ATTACHMENT: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | | `set_word_attachment` | Sets the attachment of the note specified by the note index to the provided word.

**Inputs:** `[note_idx, attachment_scheme, ATTACHMENT]`
**Outputs:** `[]` | @@ -148,10 +150,10 @@ Faucet procedures allow reading and writing to faucet accounts to mint and burn | Procedure | Description | Context | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[amount]`
**Outputs:** `[ASSET]` | Faucet | -| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[DATA_HASH]`
**Outputs:** `[ASSET]` | Faucet | -| `mint` | Mint an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | -| `burn` | Burn an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | +| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[amount]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Faucet | +| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[DATA_HASH]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Faucet | +| `mint` | Mint an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET_KEY, ASSET_VALUE]`
**Outputs:** `[NEW_ASSET_VALUE]` | Native & Account & Faucet | +| `burn` | Burn an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET_KEY, ASSET_VALUE]`
**Outputs:** `[ASSET_VALUE]` | Native & Account & Faucet | ## Asset Procedures (`miden::protocol::asset`) @@ -159,5 +161,5 @@ Asset procedures provide utilities for creating fungible and non-fungible assets | Procedure | Description | Context | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `build_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, amount]`
**Outputs:** `[ASSET]` | Any | -| `build_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, DATA_HASH]`
**Outputs:** `[ASSET]` | Any | +| `create_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, amount]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Any | +| `create_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, DATA_HASH]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Any | From 1a9963fbf8d13437a15c122f7155f932978562c5 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 6 Feb 2026 16:47:32 +0100 Subject: [PATCH 040/100] fix: rename leftover `ASSET` to `ASSET_VALUE` --- crates/miden-testing/src/kernel_tests/tx/test_account.rs | 2 +- crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index e41fe3eb8d..8920e2a49a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1320,7 +1320,7 @@ async fn test_get_init_asset() -> anyhow::Result<()> { # get the current asset push.{ASSET_KEY} exec.active_account::get_asset - # => [ASSET] + # => [ASSET_VALUE] push.{FINAL_ASSET} assert_eqw.err="final asset is incorrect" diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 62b6482689..f3c716b849 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -1145,7 +1145,7 @@ const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " # => [ASSET_VALUE', pad(12)] repeat.12 movup.4 drop end - # => [ASSET'] + # => [ASSET_VALUE'] end #! Inputs: [ASSET_KEY, ASSET_VALUE] From 7ca82dd60a22c83f5632cbcf9620dc0be0fa1539 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 14:52:44 +0100 Subject: [PATCH 041/100] chore: remove unused error --- crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm | 1 - .../asm/kernels/transaction/lib/output_note.masm | 3 --- 2 files changed, 4 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index 6dcabb1479..5fce2731be 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -3,7 +3,6 @@ use $kernel::account_id use $kernel::asset use $kernel::asset_vault use $kernel::memory -use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT # FUNGIBLE ASSETS # ================================================================================================== diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index 7c9a09d928..a785c43669 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -43,8 +43,6 @@ const ERR_OUTPUT_NOTE_ATTACHMENT_KIND_NONE_MUST_BE_EMPTY_WORD="attachment kind N const ERR_NOTE_INVALID_INDEX="failed to find note at the given index; index must be within [0, num_of_notes]" -const ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" - const ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="non-fungible asset that already exists in the note cannot be added again" const ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 most significant bits of the felt must be zero" @@ -478,7 +476,6 @@ proc add_asset_raw mul.ASSET_SIZE dup.9 add movdn.9 # => [ASSET_KEY, ASSET_VALUE, asset_ptr, asset_end_ptr, note_ptr] - # initialize loop variable is_existing_asset to false push.0 movdn.8 # => [ASSET_KEY, ASSET_VALUE, is_existing_asset, asset_ptr, asset_end_ptr, note_ptr] From e11fb335a8f52383898efdf8bda231434fb617aa Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 14:56:39 +0100 Subject: [PATCH 042/100] chore: regenerate tx kernel errors --- crates/miden-protocol/src/errors/tx_kernel.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index fb1a041624..3de5bc90db 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -137,8 +137,6 @@ pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCES pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note serial number of active note because no note is currently being processed"); /// Error Message: "failed to access note storage of active note because no note is currently being processed" pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_STORAGE_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note storage of active note because no note is currently being processed"); -/// Error Message: "adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" -pub const ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED: MasmError = MasmError::from_static_str("adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807"); /// Error Message: "failed to find note at the given index; index must be within [0, num_of_notes]" pub const ERR_NOTE_INVALID_INDEX: MasmError = MasmError::from_static_str("failed to find note at the given index; index must be within [0, num_of_notes]"); /// Error Message: "invalid note type" From fd47b2e99bb61973f42867f66720f8ec467982cb Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 10:08:23 +0100 Subject: [PATCH 043/100] chore: improve note assets commitment computation --- crates/miden-protocol/src/note/assets.rs | 32 ++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/miden-protocol/src/note/assets.rs b/crates/miden-protocol/src/note/assets.rs index 1cb2e7c6bf..ed2f25ce95 100644 --- a/crates/miden-protocol/src/note/assets.rs +++ b/crates/miden-protocol/src/note/assets.rs @@ -11,7 +11,7 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::{Felt, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word}; +use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word}; // NOTE ASSETS // ================================================================================================ @@ -63,10 +63,9 @@ impl NoteAssets { } } - let mut assets = Self { assets, commitment: Word::empty() }; - assets.commitment = assets.to_commitment(); + let commitment = to_commitment(&assets); - Ok(assets) + Ok(Self { assets, commitment }) } // PUBLIC ACCESSORS @@ -169,15 +168,28 @@ impl SequentialCommit for NoteAssets { /// Returns all assets represented as a vector of field elements. fn to_elements(&self) -> Vec { - let mut elements = Vec::with_capacity(self.assets.len() * 2 * WORD_SIZE); + to_elements(&self.assets) + } - for asset in self.assets.iter() { - elements.extend(asset.to_key_word().as_elements()); - elements.extend(asset.to_value_word().as_elements()); - } + /// Computes the commitment to the assets. + fn to_commitment(&self) -> Self::Commitment { + to_commitment(&self.assets) + } +} - elements +fn to_elements(assets: &[Asset]) -> Vec { + let mut elements = Vec::with_capacity(assets.len() * 2 * WORD_SIZE); + + for asset in assets.iter() { + elements.extend(asset.to_key_word().as_elements()); + elements.extend(asset.to_value_word().as_elements()); } + + elements +} + +fn to_commitment(assets: &[Asset]) -> Word { + Hasher::hash_elements(&to_elements(assets)) } // SERIALIZATION From 51c91f24fc4d461d1a5fb3249cce8e3a9c417a31 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 10:16:53 +0100 Subject: [PATCH 044/100] fix: input notes memory assertions --- .../src/kernel_tests/tx/test_prologue.rs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 8675a3277f..a95afb918b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -13,14 +13,16 @@ use miden_protocol::account::{ StorageSlot, StorageSlotName, }; -use miden_protocol::asset::FungibleAsset; +use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; use miden_protocol::errors::tx_kernel::ERR_ACCOUNT_SEED_AND_COMMITMENT_DIGEST_MISMATCH; +use miden_protocol::note::NoteId; use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; use miden_protocol::transaction::memory::{ ACCT_DB_ROOT_PTR, + ASSET_SIZE, ASSET_VALUE_OFFSET, BLOCK_COMMITMENT_PTR, BLOCK_METADATA_PTR, @@ -103,7 +105,7 @@ async fn test_transaction_prologue() -> anyhow::Result<()> { ); let input_note_2 = create_public_p2any_note( ACCOUNT_ID_SENDER.try_into().unwrap(), - [FungibleAsset::mock(100)], + [FungibleAsset::mock(100), NonFungibleAsset::mock(&[1, 2, 3])], ); let input_note_3 = create_public_p2any_note( ACCOUNT_ID_SENDER.try_into().unwrap(), @@ -130,16 +132,15 @@ async fn test_transaction_prologue() -> anyhow::Result<()> { let tx_script = CodeBuilder::default().compile_tx_script(mock_tx_script_code).unwrap(); - let note_args = [Word::from([91u32; 4]), Word::from([92u32; 4])]; - + // Input note 2 does not have any note args. let note_args_map = BTreeMap::from([ - (tx_context.input_notes().get_note(0).note().id(), note_args[0]), - (tx_context.input_notes().get_note(1).note().id(), note_args[1]), + (tx_context.input_notes().get_note(0).note().id(), Word::from([91u32; 4])), + (tx_context.input_notes().get_note(1).note().id(), Word::from([92u32; 4])), ]); let tx_args = TransactionArgs::new(tx_context.tx_args().advice_inputs().clone().map) .with_tx_script(tx_script) - .with_note_args(note_args_map); + .with_note_args(note_args_map.clone()); tx_context.set_tx_args(tx_args); let exec_output = &tx_context.execute_code(code).await?; @@ -149,7 +150,7 @@ async fn test_transaction_prologue() -> anyhow::Result<()> { partial_blockchain_memory_assertions(exec_output, &tx_context); kernel_data_memory_assertions(exec_output); account_data_memory_assertions(exec_output, &tx_context); - input_notes_memory_assertions(exec_output, &tx_context, ¬e_args); + input_notes_memory_assertions(exec_output, &tx_context, ¬e_args_map); Ok(()) } @@ -437,7 +438,7 @@ fn account_data_memory_assertions(exec_output: &ExecutionOutput, inputs: &Transa fn input_notes_memory_assertions( exec_output: &ExecutionOutput, inputs: &TransactionContext, - note_args: &[Word], + note_args: &BTreeMap, ) { assert_eq!( exec_output.get_kernel_mem_word(INPUT_NOTE_SECTION_PTR), @@ -506,7 +507,7 @@ fn input_notes_memory_assertions( assert_eq!( exec_output.get_note_mem_word(note_idx, INPUT_NOTE_ARGS_OFFSET), - note_args[note_idx as usize], + note_args.get(&input_note.id()).copied().unwrap_or_default(), "note args should be stored at the correct offset" ); @@ -520,7 +521,7 @@ fn input_notes_memory_assertions( let asset_key: Word = *asset.vault_key().as_word(); let asset_value: Word = asset.into(); - let asset_key_addr = INPUT_NOTE_ASSETS_OFFSET + asset_idx * WORD_SIZE as u32; + let asset_key_addr = INPUT_NOTE_ASSETS_OFFSET + asset_idx * ASSET_SIZE; let asset_value_addr = asset_key_addr + ASSET_VALUE_OFFSET; assert_eq!( From 625a3c8945d483fc6131acd8c518b00649c03f6b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 10:22:25 +0100 Subject: [PATCH 045/100] chore: add renamed procedures to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c823be55..c19d75b4b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ - [BREAKING] Moved standard note code into individual note modules ([#2363](https://github.com/0xMiden/miden-base/pull/2363)). - [BREAKING] Added `miden::standards::note_tag` module for account target note tags ([#2366](https://github.com/0xMiden/miden-base/pull/2366)). - [BREAKING] Refactored assets in the tx kernel and `miden::protocol` from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396), [#2410](https://github.com/0xMiden/miden-base/pull/2410)). +- [BREAKING] Rename `miden::protocol::asset::build_fungible_asset` to `miden::protocol::asset::create_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). +- [BREAKING] Rename `miden::protocol::asset::build_non_fungible_asset` to `miden::protocol::asset::create_non_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). ## 0.13.3 (2026-01-27) From 04a5fef6ceafd62b8e5eaf6737e0a142d281aaa2 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 10:23:22 +0100 Subject: [PATCH 046/100] fix: incorrect stack and doc comment --- crates/miden-protocol/asm/protocol/asset.masm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index 3d4f8b229b..aa9fea8b35 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -50,7 +50,7 @@ pub proc create_fungible_asset # => [ASSET_KEY, ASSET_VALUE] end -#! Builds a non fungible asset for the specified non-fungible faucet and amount. +#! Builds a non fungible asset for the specified non-fungible faucet. #! #! Inputs: [faucet_id_prefix, DATA_HASH] #! Outputs: [ASSET_KEY, ASSET_VALUE] @@ -72,7 +72,7 @@ pub proc create_non_fungible_asset # build the asset swap drop # => [faucet_id_prefix, hash2, hash1, hash0] - # => [ASSET_KEY, ASSET_VALUE] + # => [ASSET_VALUE] dupw exec.::miden::protocol::util::asset::build_non_fungible_asset_vault_key # => [ASSET_KEY, ASSET_VALUE] From 955d92bc9d068f70869ef77e6c0c99fdfe70bdaa Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 15:39:29 +0100 Subject: [PATCH 047/100] fix: p2id::new test --- crates/miden-testing/tests/scripts/p2id.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index ee74cdcaa8..389aeeba2f 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -323,7 +323,8 @@ async fn test_p2id_new_constructor() -> anyhow::Result<()> { # => [note_idx] # Add an asset to the created note - push.{asset} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.::miden::standards::wallets::basic::move_asset_to_note # Clean up stack @@ -335,7 +336,8 @@ async fn test_p2id_new_constructor() -> anyhow::Result<()> { tag = Felt::from(tag), note_type = NoteType::Public as u8, serial_num = serial_num, - asset = Word::from(FungibleAsset::mock(50)), + ASSET_KEY = FungibleAsset::mock(50).to_key_word(), + ASSET_VALUE = FungibleAsset::mock(50).to_value_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(&tx_script_src)?; From eb30d26e3d0aacbb004b9e15ae71f4ddf01ce9fd Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 13:15:05 +0100 Subject: [PATCH 048/100] feat: validate new asset layouts --- .../asm/kernels/transaction/lib/asset.masm | 239 ++---------------- .../kernels/transaction/lib/asset_vault.masm | 5 +- .../asm/kernels/transaction/lib/faucet.masm | 10 +- .../transaction/lib/fungible_asset.masm | 113 +++++++++ .../transaction/lib/non_fungible_asset.masm | 65 +++++ 5 files changed, 209 insertions(+), 223 deletions(-) create mode 100644 crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 39ad9cf0e4..f08a23ea15 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -1,24 +1,10 @@ use $kernel::account_id +use $kernel::fungible_asset +use $kernel::non_fungible_asset # ERRORS # ================================================================================================= -const ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE = "fungible asset vault key's account ID must be of type fungible faucet" - -const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO="malformed fungible asset: `ASSET[1]` must be 0" - -const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed fungible asset: `ASSET[2]` and `ASSET[3]` must be a valid fungible faucet id" - -const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS="malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount" - -const ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the fungible asset is not this faucet" - -const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE = "non-fungible asset vault key's account ID must be of type non-fungible faucet" - -const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" - -const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungible asset is not this faucet" - const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET="account ID in asset vault key must be either of type fungible or non-fungible faucet" # CONSTANT ACCESSORS @@ -31,75 +17,6 @@ pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET # PROCEDURES # ================================================================================================= -#! Validates that a fungible asset is well formed. -#! -#! Inputs: [ASSET_KEY, ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - ASSET_KEY is the vault key of the asset to validate. -#! - ASSET_VALUE is the value of the asset to validate. -#! -#! Panics if: -#! - the asset key or value are not well formed. -pub proc validate_fungible_asset - exec.validate_fungible_asset_key - # => [ASSET_KEY, ASSET_VALUE] - - swapw exec.validate_fungible_asset_value swapw - # => [ASSET_KEY, ASSET_VALUE] -end - -#! Validates that a fungible asset value is well formed. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_VALUE] -#! -#! Where: -#! - ASSET_VALUE is the value of the asset to validate. -#! -#! Panics if: -#! - the asset value is not well formed. -proc validate_fungible_asset_value - # assert that ASSET_VALUE[1] == ZERO - dup.2 eq.0 assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO - # => [ASSET_VALUE] - - # assert that the tuple (ASSET_VALUE[3], ASSET_VALUE[2]) forms a valid account ID - dup.1 dup.1 exec.account_id::validate - # => [ASSET_VALUE] - - # assert that the prefix (ASSET_VALUE[3]) of the account ID is of type fungible faucet - dup exec.account_id::is_fungible_faucet - assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID - # => [ASSET_VALUE] - - # assert that the max amount (ASSET_VALUE[0]) of a fungible asset is not exceeded - dup.3 lte.FUNGIBLE_ASSET_MAX_AMOUNT - assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS - # => [ASSET_VALUE] -end - -#! Validates that a fungible asset's vault key is well formed. -#! -#! Inputs: [ASSET_KEY] -#! Outputs: [ASSET_KEY] -#! -#! Where: -#! - ASSET_KEY is the vault key of the asset to validate. -#! -#! Panics if: -#! - the asset key's account ID is not valid. -#! - the asset key's faucet ID is not a fungible one. -pub proc validate_fungible_asset_key - exec.validate_asset_key - # => [ASSET_KEY] - - exec.is_fungible_asset - assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE - # => [ASSET_KEY] -end - #! Returns a boolean indicating whether the asset is fungible. #! #! Inputs: [ASSET_KEY] @@ -115,81 +32,7 @@ pub proc is_fungible_asset # => [is_fungible_asset, ASSET_KEY] end -#! Validates that a non fungible asset is well formed. -#! -#! Inputs: [ASSET_KEY, ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - ASSET_KEY is the vault key of the asset to validate. -#! - ASSET_VALUE is the value of the asset to validate. -#! -#! Panics if: -#! - the asset key or value are not well formed. -pub proc validate_non_fungible_asset - exec.validate_non_fungible_asset_key - swapw - # => [ASSET_VALUE, ASSET_KEY] - - exec.validate_non_fungible_asset_value - swapw - # => [ASSET_KEY, ASSET_VALUE] -end - -#! Validates that a non fungible asset is well formed. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_VALUE] -#! -#! Where: -#! - ASSET_VALUE is the value of the asset to validate. -#! -#! Panics if: -#! - the asset key or value are not well formed. -proc validate_non_fungible_asset_value - # assert that ASSET_VALUE[3] is a valid account ID prefix - # hack: because we only have the prefix we add a 0 as the suffix which is always valid - push.0 dup.1 exec.account_id::validate - # => [ASSET_VALUE] - - # assert that the account ID prefix ASSET_VALUE[3] is of type non fungible faucet - dup exec.account_id::is_non_fungible_faucet - assert.err=ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID - # => [ASSET_VALUE] -end - -#! Validates that a non-fungible asset's vault key is well formed. -#! -#! Inputs: [ASSET_KEY] -#! Outputs: [ASSET_KEY] -#! -#! Where: -#! - ASSET_KEY is the vault key of the asset to validate. -#! -#! Panics if: -#! - the asset key's account ID is not valid. -#! - the asset key's faucet ID is not a non-fungible one. -proc validate_non_fungible_asset_key - # => [ASSET_KEY] - # => [hash0', hash2, hash1, faucet_id_prefix] - - # hack: because we only have the prefix we add a 0 as the suffix which is always valid - push.0 dup.4 - # => [faucet_id_prefix, 0, ASSET_KEY] - - exec.account_id::validate - # => [ASSET_KEY] - - exec.is_non_fungible_asset - assert.err=ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE - - # => [ASSET_KEY] -end - -#! Validates that a fungible asset's vault key is well formed. -#! -#! TODO(expand_assets): -#! WARNING: For now, this only works for keys from fungible assets. +#! Validates that an asset's vault key is well formed. #! #! Inputs: [ASSET_KEY] #! Outputs: [ASSET_KEY] @@ -200,7 +43,7 @@ end #! Panics if: #! - the asset key's account ID is not valid. #! - the asset key's account ID is neither of type fungible nor of type non-fungible. -proc validate_asset_key +pub proc validate_asset_key # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] dup.1 dup.1 @@ -230,13 +73,9 @@ end #! - ASSET_KEY is the vault key of the asset to check. #! - is_non_fungible_asset is a boolean indicating whether the asset is non-fungible. pub proc is_non_fungible_asset - # TODO(expand_assets): Eventually do: # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - # dup exec.account_id::is_non_fungible_faucet - # => [is_non_fungible_asset, ASSET_KEY] - # => [ASSET_KEY] - dup.3 exec.account_id::is_non_fungible_faucet + dup exec.account_id::is_non_fungible_faucet # => [is_non_fungible_asset, ASSET_KEY] end @@ -258,67 +97,18 @@ pub proc validate_asset # if the asset is fungible, validate the fungible asset if.true - exec.validate_fungible_asset + exec.fungible_asset::validate # => [ASSET_KEY, ASSET_VALUE] else # if the asset is non fungible, validate the non fungible asset - exec.validate_non_fungible_asset + exec.non_fungible_asset::validate # => [ASSET_KEY, ASSET_VALUE] end # => [ASSET_KEY, ASSET_VALUE] end -#! Validates that a fungible asset is associated with the provided faucet_id. -#! -#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY, ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - faucet_id_prefix is the prefix of the faucet's account ID. -#! - ASSET_KEY is the vault key of the asset to validate. -#! - ASSET_VALUE is the value of the asset to validate. -pub proc validate_fungible_asset_origin - movdn.9 movdn.9 - # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] - - # assert the fungible asset key and value are valid - exec.validate_fungible_asset - # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] - - # assert the origin of the asset is the faucet_id provided via the stack - exec.get_vault_key_faucet_id - # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] - - movup.11 movup.11 - # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE] - - exec.account_id::is_equal assert.err=ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN - # => [ASSET_KEY, ASSET_VALUE] -end - -#! Validates that a non-fungible asset is associated with the provided faucet_id. -#! -#! Inputs: [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - faucet_id_prefix is the prefix of the faucet's account ID. -#! - ASSET_KEY is the vault key of the asset to validate. -#! - ASSET_VALUE is the value of the asset to validate. -pub proc validate_non_fungible_asset_origin - # assert the origin of the asset is the faucet_id prefix provided via the stack - dup.4 assert_eq.err=ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN - # => [ASSET_KEY, ASSET_VALUE] - - # assert the non-fungible asset is valid - exec.validate_non_fungible_asset - # => [ASSET_KEY, ASSET_VALUE] -end - #! Returns the faucet ID from an asset vault key. #! -#! TODO(expand_assets): -#! WARNING: For now, this only works for keys from fungible assets. #! WARNING: The faucet ID is not validated. #! #! Inputs: [ASSET_KEY] @@ -333,3 +123,18 @@ proc get_vault_key_faucet_id dup.1 dup.1 # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] end + +#! Returns the asset ID from an asset vault key. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [asset_id_prefix, asset_id_suffix, ASSET_KEY] +#! +#! Where: +#! - asset_id is the asset ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the asset ID. +proc get_vault_key_asset_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.3 dup.3 + # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] +end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index e9e5bd6aeb..5df21d9838 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -5,6 +5,7 @@ use $kernel::account_id use $kernel::asset use $kernel::fungible_asset use $kernel::memory +use $kernel::non_fungible_asset use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT # ERRORS @@ -262,14 +263,14 @@ pub proc add_asset # add the asset to the asset vault if.true # validate the fungible asset - exec.asset::validate_fungible_asset + exec.fungible_asset::validate # => [ASSET_KEY, ASSET_VALUE, vault_root_ptr] exec.add_fungible_asset # => [ASSET_VALUE'] else # validate the non-fungible asset - exec.asset::validate_non_fungible_asset + exec.non_fungible_asset::validate # => [ASSET_KEY, ASSET_VALUE, vault_root_ptr] exec.add_non_fungible_asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index 5fce2731be..4c777d0e77 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -2,6 +2,8 @@ use $kernel::account use $kernel::account_id use $kernel::asset use $kernel::asset_vault +use $kernel::fungible_asset +use $kernel::non_fungible_asset use $kernel::memory # FUNGIBLE ASSETS @@ -26,7 +28,7 @@ pub proc mint_fungible_asset # assert that the asset was issued by the faucet the transaction is being executed against and # that the asset is valid exec.account::get_id - exec.asset::validate_fungible_asset_origin + exec.fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr @@ -57,7 +59,7 @@ proc burn_fungible_asset # assert that the asset is associated with the faucet the transaction is being executed against # and that the asset is valid exec.account::get_id - exec.asset::validate_fungible_asset_origin + exec.fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr @@ -95,7 +97,7 @@ proc mint_non_fungible_asset swap drop # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] - exec.asset::validate_non_fungible_asset_origin + exec.non_fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] exec.memory::get_input_vault_root_ptr @@ -128,7 +130,7 @@ proc burn_non_fungible_asset swap drop # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] - exec.asset::validate_non_fungible_asset_origin + exec.non_fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] # remove the non-fungible asset from the input vault for asset preservation diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index 240470cc77..48a757555a 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -1,12 +1,24 @@ # Contains procedures for the built-in fungible asset. +use $kernel::account_id use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT +use $kernel::asset # ERRORS # ================================================================================================= const ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding the fungible asset to the vault would exceed the max amount" +const ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE = "fungible asset vault key's account ID must be of type fungible faucet" + +const ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the fungible asset is not this faucet" + +const ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT="fungible asset amount exceeds the maximum allowed amount" + +const ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO="fungible asset value elements 1, 2 and 3 must be zeros" + +const ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO="fungible asset key asset ID prefix and suffix must be zero" + # PROCEDURES # ================================================================================================= @@ -61,3 +73,104 @@ end pub proc get_amount exec.::$kernel::util::asset::get_balance_from_fungible_asset end + +#! Validates that a fungible asset is well formed. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. +#! +#! Panics if: +#! - the asset key or value are not well formed. +pub proc validate + exec.validate_key + # => [ASSET_KEY, ASSET_VALUE] + + dupw.1 exec.validate_value + # => [ASSET_KEY, ASSET_VALUE] +end + +#! Validates that a fungible asset's vault key is well formed. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's faucet ID is not a fungible one. +pub proc validate_key + exec.asset::get_vault_key_faucet_id + exec.account_id::validate + # => [ASSET_KEY] + + exec.asset::is_fungible_asset + assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE + # => [ASSET_KEY] + + exec.asset::get_vault_key_asset_id + # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] + + eq.0 assert.err=ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO + eq.0 assert.err=ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO + # => [ASSET_KEY] +end + +#! Validates that a fungible asset value is well formed. +#! +#! Inputs: [ASSET_VALUE] +#! Outputs: [] +#! +#! Where: +#! - ASSET_VALUE is the asset to validate. +#! +#! Panics if: +#! - the asset value is not well formed. +proc validate_value + # assuming the asset is valid, its layout is: + # => [0, 0, 0, amount] + + # assert the first three elements are zeros + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + # => [amount] + + # assert amount <= FUNGIBLE_ASSET_MAX_AMOUNT + lte.FUNGIBLE_ASSET_MAX_AMOUNT + assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT + # => [] +end + +#! Validates that a fungible asset is associated with the provided faucet_id. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - faucet_id_prefix is the prefix of the faucet's account ID. +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. +pub proc validate_origin + movdn.9 movdn.9 + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] + + # assert the fungible asset key and value are valid + exec.validate + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] + + # assert the origin of the asset is the faucet_id provided via the stack + exec.asset::get_vault_key_faucet_id + # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] + + movup.11 movup.11 + # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE] + + exec.account_id::is_equal assert.err=ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN + # => [ASSET_KEY, ASSET_VALUE] +end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm new file mode 100644 index 0000000000..58c8841ae0 --- /dev/null +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -0,0 +1,65 @@ +use $kernel::account_id +use $kernel::asset + +# ERRORS +# ================================================================================================= + +const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE = "non-fungible asset vault key's account ID must be of type non-fungible faucet" + +const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" + +const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungible asset is not this faucet" + +# PROCEDURES +# ================================================================================================= + +#! Validates that a non fungible asset is well formed. +#! +#! The value is not validated since any value is valid. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's faucet ID is not a non-fungible one. +pub proc validate + exec.asset::validate_asset_key + # => [ASSET_KEY] + + exec.asset::is_non_fungible_asset + assert.err=ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE + # => [ASSET_KEY] +end + +#! Validates that a non-fungible asset is associated with the provided faucet_id. +#! +#! The value is not validated since any value is valid. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - faucet_id_prefix is the prefix of the faucet's account ID. +#! - ASSET_KEY is the vault key of the asset to validate. +pub proc validate_origin + movdn.5 movdn.5 + # => [ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + + # assert the non-fungible asset key is valid + exec.validate + # => [ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + + # assert the origin of the asset is the faucet_id provided via the stack + exec.asset::get_vault_key_faucet_id + # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + + movup.7 movup.7 + # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY] + + exec.account_id::is_equal assert.err=ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN + # => [ASSET_KEY] +end From eea32911a3749ad90925455d7c108b9c92b67a47 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 13:39:37 +0100 Subject: [PATCH 049/100] chore: update asset procedure calls in faucet --- .../miden-protocol/asm/kernels/transaction/lib/faucet.masm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index 4c777d0e77..3c5c09d1da 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -94,9 +94,6 @@ proc mint_non_fungible_asset # assert that the asset is associated with the faucet the transaction is being executed against # and that the asset is valid exec.account::get_id - swap drop - # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] - exec.non_fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] @@ -127,9 +124,6 @@ proc burn_non_fungible_asset # assert that the asset was issued by the faucet the transaction is being executed against and # that the asset is valid exec.account::get_id - swap drop - # => [faucet_id_prefix, ASSET_KEY, ASSET_VALUE] - exec.non_fungible_asset::validate_origin # => [ASSET_KEY, ASSET_VALUE] From 710c4ac56d813981545c2ea2396426e14aa56240 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 14:44:48 +0100 Subject: [PATCH 050/100] feat: add create_fungible_asset_unchecked --- .../asm/kernels/transaction/lib/epilogue.masm | 14 +++++------- .../transaction/lib/fungible_asset.masm | 5 +++++ crates/miden-protocol/asm/protocol/asset.masm | 14 +++++------- .../asm/shared_utils/util/asset.masm | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 8fc298af00..af06ee3c39 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -4,6 +4,7 @@ use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET use $kernel::asset_vault use $kernel::constants::NOTE_MEM_SIZE +use $kernel::fungible_asset use $kernel::memory use $kernel::note @@ -281,7 +282,7 @@ proc compute_fee # => [verification_cost] end -#! Builds the fee asset with the provided fee amount and the native asset ID of the transaction's +#! Creates the fee asset with the provided fee amount and the native asset ID of the transaction's #! reference block as the faucet ID. #! #! Inputs: [fee_amount] @@ -292,15 +293,12 @@ end #! - FEE_ASSET_KEY is the asset vault key of the fee asset. #! - FEE_ASSET_VALUE is the fungible asset with amount set to fee_amount and the faucet ID set to #! the native asset. -proc build_native_fee_asset +proc create_native_fee_asset exec.memory::get_native_asset_id # => [native_asset_id_prefix, native_asset_id_suffix, fee_amount] - push.0 movdn.2 - # => [native_asset_id_prefix, native_asset_id_suffix, 0, fee_amount] - # => [FEE_ASSET_VALUE] - - exec.asset_vault::build_fungible_asset_vault_key + # SAFETY: native asset ID should be fungible and amount should not be exceeded + exec.fungible_asset::create_unchecked # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] end @@ -326,7 +324,7 @@ proc compute_and_remove_fee # => [fee_amount] # build the native asset from the fee amount - exec.build_native_fee_asset + exec.create_native_fee_asset # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT_EVENT diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index 48a757555a..f6fbd8a8da 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -4,6 +4,11 @@ use $kernel::account_id use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT use $kernel::asset +# RE-EXPORTS +# ================================================================================================= + +pub use $kernel::util::asset::create_fungible_asset_unchecked->create_unchecked + # ERRORS # ================================================================================================= diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index aa9fea8b35..c08c228fe5 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -1,4 +1,5 @@ use miden::protocol::account_id +use miden::protocol::util::asset # RE-EXPORTS # ================================================================================================= @@ -10,16 +11,16 @@ pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET # ERRORS # ================================================================================================= -const ERR_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the fungible asset because the provided faucet id is not from a fungible faucet" - const ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT="fungible asset build operation called with amount that exceeds the maximum allowed asset amount" +const ERR_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the fungible asset because the provided faucet id is not from a fungible faucet" + const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet" # PROCEDURES # ================================================================================================= -#! Builds a fungible asset for the specified fungible faucet and amount. +#! Creates a fungible asset for the specified fungible faucet and amount. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, amount] #! Outputs: [ASSET_KEY, ASSET_VALUE] @@ -42,11 +43,8 @@ pub proc create_fungible_asset assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT # => [faucet_id_prefix, faucet_id_suffix, amount] - # create the asset - push.0 movdn.2 - # => [ASSET_VALUE] - - exec.::miden::protocol::util::asset::build_fungible_asset_vault_key + # SAFETY: faucet ID and amount are validated + exec.asset::create_fungible_asset_unchecked # => [ASSET_KEY, ASSET_VALUE] end diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index c72ab37cde..30eb047ce7 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -33,6 +33,28 @@ pub proc get_balance_from_fungible_asset # => [balance] end +#! Creates a fungible asset for the specified fungible faucet and amount. +#! +#! WARNING: Does not validate its inputs. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix, amount] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset +#! for. +#! - amount is the amount of the asset to create. +#! - ASSET_KEY is the vault key of the created fungible asset. +#! - ASSET_VALUE is the value of the created fungible asset. +#! +#! Invocation: exec +pub proc create_fungible_asset_unchecked + # create the key and value + repeat.5 push.0 movdn.2 end + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], [0, 0, 0, amount]] + # => [ASSET_KEY, ASSET_VALUE] +end + #! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must #! be a valid fungible asset. #! From c0d5af8b5f26f347fb934283f708b52ee3ac06b1 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 9 Feb 2026 15:07:26 +0100 Subject: [PATCH 051/100] chore: change has_non_fungible_asset to take asset key --- .../asm/protocol/active_account.masm | 19 +++++++------------ .../src/kernel_tests/tx/test_asset_vault.rs | 4 ++-- .../src/kernel_tests/tx/test_fpi.rs | 4 ++-- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/active_account.masm b/crates/miden-protocol/asm/protocol/active_account.masm index 759363727b..4fb492c1ac 100644 --- a/crates/miden-protocol/asm/protocol/active_account.masm +++ b/crates/miden-protocol/asm/protocol/active_account.masm @@ -555,29 +555,24 @@ pub proc get_initial_balance # => [balance] end -#! Returns a boolean indicating whether the non-fungible asset is present in the active account's -#! vault. +#! Returns a boolean indicating whether the active account stores an asset with the provided +#! non-fungible asset vault key in its vault. #! -#! Inputs: [ASSET_VALUE] +#! Inputs: [ASSET_KEY] #! Outputs: [has_asset] #! #! Where: -#! - ASSET_VALUE is the non-fungible asset of interest -#! - has_asset is a boolean indicating whether the account vault has the asset of interest +#! - ASSET_KEY is the key of the fungible asset to check. +#! - has_asset is a boolean indicating whether the account vault has the asset. #! #! Panics if: #! - the ASSET_VALUE is a fungible asset. #! #! Invocation: exec pub proc has_non_fungible_asset - # check if the asset is a non-fungible asset - # hack: assert second element is zero to ensure it is a non-fungible asset - # TODO(expand_assets): This procedure may go away. If not, check more reliably. - dup.2 neq.0 + # assert that the faucet id is a non-fungible faucet + dup exec.account_id::is_non_fungible_faucet assert.err=ERR_VAULT_HAS_NON_FUNGIBLE_ASSET_PROC_CAN_BE_CALLED_ONLY_WITH_NON_FUNGIBLE_ASSET - # => [ASSET_VALUE] - - exec.asset::build_non_fungible_asset_vault_key # => [ASSET_KEY] exec.get_asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 862b5a250a..1f77b7cab9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -156,14 +156,14 @@ async fn test_has_non_fungible_asset() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{non_fungible_asset_key} + push.{NON_FUNGIBLE_ASSET_KEY} exec.active_account::has_non_fungible_asset # truncate the stack swap drop end ", - non_fungible_asset_key = Word::from(non_fungible_asset) + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), ); let exec_output = tx_context.execute_code(&code).await?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index abfdf1c8ae..f16a018230 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -696,7 +696,7 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu # => [balance] # check presence of non fungible asset - push.{non_fungible_asset_word} + push.{NON_FUNGIBLE_ASSET_KEY} exec.active_account::has_non_fungible_asset # => [has_asset, balance] @@ -711,7 +711,7 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu ", fungible_faucet_id_prefix = fungible_faucet_id.prefix().as_felt(), fungible_faucet_id_suffix = fungible_faucet_id.suffix(), - non_fungible_asset_word = Word::from(non_fungible_asset), + NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), ); let source_manager = Arc::new(DefaultSourceManager::default()); From 6349f9fcf369a9c1faba34291dc115605aab7c27 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 10 Feb 2026 15:01:15 +0100 Subject: [PATCH 052/100] feat: include full faucet ID limbs in asset delta --- .../transaction/lib/account_delta.masm | 174 +++++++++++------- .../asm/kernels/transaction/lib/memory.masm | 26 +-- .../miden-protocol/src/account/delta/mod.rs | 36 ++-- .../miden-protocol/src/account/delta/vault.rs | 66 ++++--- .../miden-protocol/src/asset/nonfungible.rs | 6 + 5 files changed, 172 insertions(+), 136 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm index 8eaab8ab1f..10b302a4d6 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm @@ -5,6 +5,8 @@ use $kernel::constants::STORAGE_SLOT_TYPE_VALUE use $kernel::fungible_asset use $kernel::link_map use $kernel::memory +use $kernel::memory::ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR +use $kernel::memory::ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR use $kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT use miden::core::crypto::hashes::rpo256 use miden::core::word @@ -326,7 +328,7 @@ end #! Outputs: [RATE, RATE, PERM] @locals(2) proc update_fungible_asset_delta - exec.memory::get_account_delta_fungible_asset_ptr + push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR # => [account_delta_fungible_asset_ptr, RATE, RATE, PERM] exec.link_map::iter @@ -352,32 +354,27 @@ proc update_fungible_asset_delta # compute the absolute value of delta amount with a flag indicating whether it's positive exec.delta_amount_absolute - # => [[is_delta_amount_positive, delta_amount_abs, 0, 0, 0], ...] + # => [is_delta_amount_positive, [delta_amount_abs, 0, 0, 0], [faucet_id_prefix, faucet_id_suffix, 0, 0], ...] - # rename is_delta_amount_positive to was_added - swap.3 drop - # => [[delta_amount_abs, 0, was_added, 0], ...] + # define the was_added value as equivalent to is_delta_amount_positive + # this value is 1 if the amount was added and 0 if the amount was removed + swap.7 drop + # => [[delta_amount_abs, 0, 0, 0], [faucet_id_prefix, faucet_id_suffix, was_added, 0], ...] dup neq.0 - # => [is_delta_amount_non_zero, [delta_amount_abs, 0, was_added, 0], [faucet_id_prefix, faucet_id_suffix, 0, 0], ...] + # => [is_delta_amount_non_zero, [delta_amount_abs, 0, 0, 0], [faucet_id_prefix, faucet_id_suffix, was_added, 0], ...] # if delta amount is non-zero, update the hasher if.true - swap.7 - # => [[0, 0, was_added, 0], [faucet_id_prefix, faucet_id_suffix, 0, delta_amount_abs], ...] - - drop push.DOMAIN_ASSET - # => [[domain, 0, was_added, 0], [faucet_id_prefix, faucet_id_suffix, 0, delta_amount_abs], ...] + push.DOMAIN_ASSET swap.8 drop + # => [[delta_amount_abs, 0, 0, 0], [faucet_id_prefix, faucet_id_suffix, was_added, domain], ...] swap.3 - # => [[0, 0, was_added, domain], [faucet_id_prefix, faucet_id_suffix, 0, delta_amount_abs], ...] - - swapw - # => [[faucet_id_prefix, faucet_id_suffix, 0, delta_amount_abs], [0, 0, was_added, domain], RATE, RATE, PERM] + # => [[0, 0, 0, delta_amount_abs], [faucet_id_prefix, faucet_id_suffix, was_added, domain], RATE, RATE, PERM] # drop previous RATE elements swapdw dropw dropw - # => [[faucet_id_prefix, faucet_id_suffix, 0, delta_amount_abs], [0, 0, was_added, domain], PERM] + # => [[0, 0, 0, delta_amount_abs], [faucet_id_prefix, faucet_id_suffix, was_added, domain], PERM] exec.rpo256::permute # => [RATE, RATE, PERM] @@ -407,7 +404,7 @@ end #! Outputs: [RATE, RATE, PERM] @locals(2) proc update_non_fungible_asset_delta - exec.memory::get_account_delta_non_fungible_asset_ptr + push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR # => [account_delta_non_fungible_asset_ptr, RATE, RATE, PERM] exec.link_map::iter @@ -415,47 +412,52 @@ proc update_non_fungible_asset_delta # enter loop if the link map is not empty while.true - exec.link_map::next_key_value - # => [KEY, VALUE0, has_next, iter, ...] + exec.link_map::next_key_double_value + # => [KEY, VALUE0, VALUE1, has_next, iter, ...] # store has_next - movup.8 loc_store.0 - # => [KEY, VALUE0, iter, ...] + movup.12 loc_store.0 + # => [KEY, VALUE0, VALUE1, iter, ...] # store iter - movup.8 loc_store.1 - # => [KEY, VALUE0, ...] + movup.12 loc_store.1 + # => [KEY, VALUE0, VALUE1, ...] # this stack state is equivalent to: - # => [ASSET_VALUE, [was_added, 0, 0, 0], ...] + # => [ASSET_KEY, [was_added, 0, 0, 0], ASSET_VALUE, ...] dup.4 neq.0 - # => [was_added_or_removed, ASSET_VALUE, [was_added, 0, 0, 0], ...] + # => [was_added_or_removed, ASSET_KEY, [was_added, 0, 0, 0], ASSET_VALUE, ...] # if the asset was added or removed (i.e. if was_added != 0), update the hasher if.true - movup.4 - # => [was_added, ASSET_VALUE, [0, 0, 0], ...] + swapw + # => [[was_added, 0, 0, 0], ASSET_KEY, ASSET_VALUE, ...] # convert was_added to a boolean # was_added is 1 if the asset was added and 0 - 1 if it was removed eq.1 - # => [was_added, ASSET_VALUE, [0, 0, 0], ...] + # => [[was_added, 0, 0, 0], [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix], ASSET_VALUE, ...] - movdn.6 - # => [ASSET_VALUE, [0, 0, was_added, 0], ...] + # replace asset_id_prefix with was_added and drop the remaining word + swap.6 dropw + # => [[faucet_id_prefix, faucet_id_suffix, was_added, asset_id_suffix], ASSET_VALUE, ...] - push.DOMAIN_ASSET swap.8 drop - # => [ASSET_VALUE, [0, 0, was_added, domain], RATE, RATE, PERM] + # replace asset_id_suffix with domain + push.DOMAIN_ASSET swap.4 drop + # => [[faucet_id_prefix, faucet_id_suffix, was_added, domain], ASSET_VALUE, ...] + + swapw + # => [ASSET_VALUE, [faucet_id_prefix, faucet_id_suffix, was_added, domain], RATE, RATE, PERM] # drop previous RATE elements swapdw dropw dropw - # => [ASSET_VALUE, [0, 0, was_added, domain], PERM] + # => [ASSET_VALUE, [faucet_id_prefix, faucet_id_suffix, was_added, domain], PERM] exec.rpo256::permute # => [RATE, RATE, PERM] else - # discard the two key and value words loaded from the map - dropw dropw + # discard the key, value0 and value1 words loaded from the map + dropw dropw dropw # => [RATE, RATE, PERM] end # => [RATE, RATE, PERM] @@ -520,10 +522,6 @@ pub proc add_asset exec.add_fungible_asset # => [] else - # asset key is unused for now - dropw - # => [ASSET_VALUE] - exec.add_non_fungible_asset # => [] end @@ -556,10 +554,6 @@ pub proc remove_asset exec.remove_fungible_asset # => [] else - # asset key is unused for now - dropw - # => [ASSET_VALUE] - exec.remove_non_fungible_asset # => [] end @@ -574,7 +568,7 @@ end #! - ASSET_KEY is the asset key of the fungible asset. #! - amount is the amount by which the fungible asset's amount increases. pub proc add_fungible_asset - dupw exec.memory::get_account_delta_fungible_asset_ptr + dupw push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR # => [fungible_delta_map_ptr, ASSET_KEY, ASSET_KEY, amount] # retrieve the current delta amount @@ -595,7 +589,7 @@ pub proc add_fungible_asset swapw padw movdnw.2 # => [ASSET_KEY, delta_amount, 0, 0, 0, EMPTY_WORD] - exec.memory::get_account_delta_fungible_asset_ptr + push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR # => [fungible_delta_map_ptr, ASSET_KEY, delta_amount, 0, 0, 0, EMPTY_WORD] exec.link_map::set drop @@ -611,7 +605,7 @@ end #! - ASSET_KEY is the asset key of the fungible asset. #! - amount is the amount by which the fungible asset's amount decreases. pub proc remove_fungible_asset - dupw exec.memory::get_account_delta_fungible_asset_ptr + dupw push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR # => [fungible_delta_map_ptr, ASSET_KEY, ASSET_KEY, amount] # retrieve the current delta amount @@ -632,7 +626,7 @@ pub proc remove_fungible_asset swapw padw movdnw.2 # => [ASSET_KEY, delta_amount, 0, 0, 0, EMPTY_WORD] - exec.memory::get_account_delta_fungible_asset_ptr + push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR # => [fungible_delta_map_ptr, ASSET_KEY, delta_amount, 0, 0, 0, EMPTY_WORD] exec.link_map::set drop @@ -655,29 +649,48 @@ end #! 0 -> no change to the asset #! +1 -> asset was added #! -#! Inputs: [ASSET_VALUE] +#! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET_VALUE is the non-fungible asset to be added. +#! - ASSET_KEY is the vault key of the non-fungible asset to be added. +#! - ASSET_VALUE is the value of the non-fungible asset to be added. pub proc add_non_fungible_asset - dupw exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET_VALUE, ASSET_VALUE] + dupw push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR + # => [non_fungible_delta_map_ptr, ASSET_KEY, ASSET_KEY, ASSET_VALUE] # retrieve the current delta - # contains_key can be ignored because the default value is an empty word and the - # was_added value is therefore 0 + # contains_key can be ignored because the asset vault ensures each asset key is only added to + # the delta once + # if no entry exists, the default value is an empty word and so the was_added value is 0 exec.link_map::get drop - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] + # => [was_added, 0, 0, 0, PREV_ASSET_VALUE, ASSET_KEY, ASSET_VALUE] + + dupw.3 movupw.2 + # => [PREV_ASSET_VALUE, ASSET_VALUE, was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + + # the asset vault guarantees that this procedure is only called when the asset was not yet + # _added_ to the vault, so it can either be absent or it could have been removed + # absent means PREV_ASSET_VALUE is the EMPTY_WORD + # removal means PREV_ASSET_VALUE is equal to ASSET_VALUE + # sanity check that this assumption is true + exec.word::testz movdn.8 + # => [PREV_ASSET_VALUE, ASSET_VALUE, is_empty_word, was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + + exec.word::eq or + assert.err="add: prev_asset_value must be empty or equal to asset_value for non-fungible assets" + # => [was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + + # add 1 to cancel out a previous removal (was_added = 0) or mark the asset as added (was_added = 1) add.1 - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] + # => [was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] - movupw.2 - # => [ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] + swapw + # => [ASSET_KEY, was_added, 0, 0, 0, ASSET_VALUE] - exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] + push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR + # => [non_fungible_delta_map_ptr, ASSET_KEY, was_added, 0, 0, 0, ASSET_VALUE] exec.link_map::set drop # => [] @@ -689,29 +702,48 @@ end #! #! See add_non_fungible_asset for documentation. #! -#! Inputs: [ASSET_VALUE] +#! Inputs: [ASSET_KEY, ASSET_VALUE] #! Outputs: [] #! #! Where: -#! - ASSET_VALUE is the value of the asset that is removed. +#! - ASSET_KEY is the vault key of the non-fungible asset to be removed. +#! - ASSET_VALUE is the value of the non-fungible asset to be removed. pub proc remove_non_fungible_asset - dupw exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET_VALUE, ASSET_VALUE] + dupw push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR + # => [non_fungible_delta_map_ptr, ASSET_KEY, ASSET_KEY, ASSET_VALUE] # retrieve the current delta - # contains_key can be ignored because the default value is an empty word and the - # was_added value is therefore 0 + # contains_key can be ignored because the asset vault ensures each asset key is only removed + # from the delta once + # if no entry exists, the default value is an empty word and so the was_added value is 0 exec.link_map::get drop - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] + # => [was_added, 0, 0, 0, PREV_ASSET_VALUE, ASSET_KEY, ASSET_VALUE] + + dupw.3 movupw.2 + # => [PREV_ASSET_VALUE, ASSET_VALUE, was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + + # the asset vault guarantees that this procedure is only called when the asset was not yet + # _removed_ from the vault, so it can either be present or it could have been removed + # absent means PREV_ASSET_VALUE is the EMPTY_WORD + # addition means PREV_ASSET_VALUE is equal to ASSET_VALUE + # sanity check that this assumption is true + + exec.word::testz movdn.8 + # => [PREV_ASSET_VALUE, ASSET_VALUE, is_empty_word, was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + + exec.word::eq or + assert.err="remove: prev_asset_value must be empty or equal to asset_value for non-fungible assets" + # => [was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] + # sub 1 to cancel out a previous addition (was_added = 1) or mark the asset as removed (was_added = -1) sub.1 - # => [was_added, 0, 0, 0, EMPTY_WORD, ASSET_VALUE] + # => [was_added, 0, 0, 0, ASSET_KEY, ASSET_VALUE] - movupw.2 - # => [ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] + swapw + # => [ASSET_KEY, was_added, 0, 0, 0, ASSET_VALUE] - exec.memory::get_account_delta_non_fungible_asset_ptr - # => [non_fungible_delta_map_ptr, ASSET_VALUE, was_added, 0, 0, 0, EMPTY_WORD] + push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR + # => [non_fungible_delta_map_ptr, ASSET_KEY, was_added, 0, 0, 0, ASSET_VALUE] exec.link_map::set drop # => [] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm index 69d420ae22..7d0af99d6a 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm @@ -191,10 +191,10 @@ const ACCT_ACTIVE_STORAGE_SLOTS_SECTION_OFFSET=2340 # ------------------------------------------------------------------------------------------------- # The link map pointer at which the delta of the fungible asset vault is stored. -const ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR=532480 +pub const ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR=532480 # The link map pointer at which the delta of the non-fungible asset vault is stored. -const ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR=ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR+4 +pub const ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR=ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR+4 # The section of link map pointers where storage map deltas are stored. # This section is offset by `slot index` to get the link map ptr for the storage map @@ -1428,28 +1428,6 @@ end ### ACCOUNT DELTA ################################################# -#! Returns the link map pointer to the fungible asset vault delta. -#! -#! Inputs: [] -#! Outputs: [account_delta_fungible_asset_ptr] -#! -#! Where: -#! - account_delta_fungible_asset_ptr is the link map pointer to the fungible asset vault delta. -pub proc get_account_delta_fungible_asset_ptr - push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR -end - -#! Returns the link map pointer to the non-fungible asset vault delta. -#! -#! Inputs: [] -#! Outputs: [account_delta_non_fungible_asset_ptr] -#! -#! Where: -#! - account_delta_non_fungible_asset_ptr is the link map pointer to the non-fungible asset vault delta. -pub proc get_account_delta_non_fungible_asset_ptr - push.ACCOUNT_DELTA_NON_FUNGIBLE_ASSET_PTR -end - #! Returns the link map pointer to the storage map delta of the storage map in the given slot index. #! #! Inputs: [slot_idx] diff --git a/crates/miden-protocol/src/account/delta/mod.rs b/crates/miden-protocol/src/account/delta/mod.rs index 1b8d13d1f5..91da8fafcf 100644 --- a/crates/miden-protocol/src/account/delta/mod.rs +++ b/crates/miden-protocol/src/account/delta/mod.rs @@ -190,22 +190,23 @@ impl AccountDelta { /// [`LexicographicWord`](crate::LexicographicWord). The WORD layout is in memory-order. /// /// - Append `[[nonce_delta, 0, account_id_suffix, account_id_prefix], EMPTY_WORD]`, where - /// account_id_{prefix,suffix} are the prefix and suffix felts of the native account id and - /// nonce_delta is the value by which the nonce was incremented. + /// `account_id_{prefix,suffix}` are the prefix and suffix felts of the native account id and + /// `nonce_delta` is the value by which the nonce was incremented. /// - Fungible Asset Delta /// - For each **updated** fungible asset, sorted by its vault key, whose amount delta is /// **non-zero**: - /// - Append `[domain = 1, was_added, 0, 0]`. - /// - Append `[amount, 0, faucet_id_suffix, faucet_id_prefix]` where amount is the delta by - /// which the fungible asset's amount has changed and was_added is a boolean flag - /// indicating whether the amount was added (1) or subtracted (0). + /// - Append `[domain = 1, was_added, faucet_id_suffix, faucet_id_prefix]`. + /// - Append `[amount_delta, 0, 0, 0]` where `amount_delta` is the delta by which the + /// fungible asset's amount has changed and `was_added` is a boolean flag indicating + /// whether the amount was added (1) or subtracted (0). /// - Non-Fungible Asset Delta /// - For each **updated** non-fungible asset, sorted by its vault key: - /// - Append `[domain = 1, was_added, 0, 0]` where was_added is a boolean flag indicating - /// whether the asset was added (1) or removed (0). Note that the domain is the same for - /// assets since `faucet_id_prefix` is at the same position in the layout for both assets, - /// and, by design, it is never the same for fungible and non-fungible assets. - /// - Append `[hash0, hash1, hash2, faucet_id_prefix]`, i.e. the non-fungible asset. + /// - Append `[domain = 1, was_added, faucet_id_suffix, faucet_id_prefix]` where `was_added` + /// is a boolean flag indicating whether the asset was added (1) or removed (0). Note that + /// the domain is the same for assets since `faucet_id_suffix` and `faucet_id_prefix` are + /// at the same position in the layout for both assets, and, by design, they are never the + /// same for fungible and non-fungible assets. + /// - Append `[hash0, hash1, hash2, hash3]`, i.e. the non-fungible asset. /// - Storage Slots are sorted by slot ID and are iterated in this order. For each slot **whose /// value has changed**, depending on the slot type: /// - Value Slot @@ -269,7 +270,7 @@ impl AccountDelta { /// [ /// ID_AND_NONCE, EMPTY_WORD, /// [/* no fungible asset delta */], - /// [[domain = 1, was_added = 0, 0, 0], NON_FUNGIBLE_ASSET], + /// [[domain = 1, was_added = 0, faucet_id_suffix, faucet_id_prefix], NON_FUNGIBLE_ASSET], /// [/* no storage delta */] /// ] /// ``` @@ -279,14 +280,15 @@ impl AccountDelta { /// ID_AND_NONCE, EMPTY_WORD, /// [/* no fungible asset delta */], /// [/* no non-fungible asset delta */], - /// [[domain = 2, 0, slot_id_suffix = 0, slot_id_prefix = 0], NEW_VALUE] + /// [[domain = 2, 0, slot_id_suffix = faucet_id_suffix, slot_id_prefix = faucet_id_prefix], NEW_VALUE] /// ] /// ``` /// - /// `NEW_VALUE` is user-controllable so it can be crafted to match `NON_FUNGIBLE_ASSET`. The - /// domain separator is then the only value that differentiates these two deltas. This shows the - /// importance of placing the domain separators in the same index within each word's layout - /// which makes it easy to see that this value cannot be crafted to be the same. + /// `NEW_VALUE` is user-controllable so it can be crafted to match `NON_FUNGIBLE_ASSET`. Users + /// would have to choose a slot ID that is equal to the faucet ID which is possible since slot + /// IDs can be chosen at account creation time. The domain separator is then the only value that + /// differentiates these two deltas. This shows the importance of placing the domain separators + /// in the same index within each word's layout to ensure users cannot craft an ambiguous delta. /// /// ### Number of Changed Entries /// diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index 959b813764..dd88ae252a 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -12,8 +12,8 @@ use super::{ Serializable, }; use crate::account::{AccountId, AccountType}; -use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; -use crate::{Felt, LexicographicWord, ONE, Word, ZERO}; +use crate::asset::{Asset, AssetVaultKey, FungibleAsset, NonFungibleAsset}; +use crate::{Felt, LexicographicWord, ONE, ZERO}; // ACCOUNT VAULT DELTA // ================================================================================================ @@ -327,12 +327,17 @@ impl FungibleAssetDelta { "fungible asset iterator should never yield amount deltas of 0" ); - let asset = FungibleAsset::new(*faucet_id, amount_delta.unsigned_abs()) - .expect("absolute amount delta should be less than i64::MAX"); let was_added = if *amount_delta > 0 { ONE } else { ZERO }; - - elements.extend_from_slice(&[DOMAIN_ASSET, was_added, ZERO, ZERO]); - elements.extend_from_slice(Word::from(asset).as_elements()); + let amount_delta = Felt::try_from(amount_delta.unsigned_abs()) + .expect("amount delta should be less than i64::MAX"); + + elements.extend_from_slice(&[ + DOMAIN_ASSET, + was_added, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]); + elements.extend_from_slice(&[amount_delta, ZERO, ZERO, ZERO]); } } } @@ -376,13 +381,13 @@ impl Deserializable for FungibleAssetDelta { /// in-kernel account delta which uses a link map. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct NonFungibleAssetDelta( - BTreeMap, NonFungibleDeltaAction>, + BTreeMap, (NonFungibleAsset, NonFungibleDeltaAction)>, ); impl NonFungibleAssetDelta { /// Creates a new non-fungible asset delta. pub const fn new( - map: BTreeMap, NonFungibleDeltaAction>, + map: BTreeMap, (NonFungibleAsset, NonFungibleDeltaAction)>, ) -> Self { Self(map) } @@ -415,7 +420,9 @@ impl NonFungibleAssetDelta { /// Returns an iterator over the (key, value) pairs of the map. pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|(key, value)| (key.inner(), value)) + self.0 + .iter() + .map(|(_key, (non_fungible_asset, delta_action))| (non_fungible_asset, delta_action)) } /// Merges another delta into this one, overwriting any existing values. @@ -426,8 +433,8 @@ impl NonFungibleAssetDelta { /// Returns an error if duplicate non-fungible assets are added or removed. pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { // Merge non-fungible assets. Each non-fungible asset can cancel others out. - for (&key, &action) in other.0.iter() { - self.apply_action(key.into_inner(), action)?; + for (&asset, &action) in other.iter() { + self.apply_action(asset, action)?; } Ok(()) @@ -446,13 +453,13 @@ impl NonFungibleAssetDelta { asset: NonFungibleAsset, action: NonFungibleDeltaAction, ) -> Result<(), AccountDeltaError> { - match self.0.entry(LexicographicWord::new(asset)) { + match self.0.entry(LexicographicWord::new(asset.vault_key())) { Entry::Vacant(entry) => { - entry.insert(action); + entry.insert((asset, action)); }, Entry::Occupied(entry) => { - let previous = *entry.get(); - if previous == action { + let (_prev_asset, previous_action) = *entry.get(); + if previous_action == action { // Asset cannot be added nor removed twice. return Err(AccountDeltaError::DuplicateNonFungibleVaultUpdate(asset)); } @@ -471,8 +478,8 @@ impl NonFungibleAssetDelta { ) -> impl Iterator + '_ { self.0 .iter() - .filter(move |&(_, cur_action)| cur_action == &action) - .map(|(key, _)| key.into_inner()) + .filter(move |&(_, (_asset, cur_action))| cur_action == &action) + .map(|(_key, (asset, _action))| *asset) } /// Appends the non-fungible asset vault delta to the given `elements` from which the delta @@ -484,8 +491,13 @@ impl NonFungibleAssetDelta { NonFungibleDeltaAction::Add => ONE, }; - elements.extend_from_slice(&[DOMAIN_ASSET, was_added, ZERO, ZERO]); - elements.extend_from_slice(Word::from(*asset).as_elements()); + elements.extend_from_slice(&[ + DOMAIN_ASSET, + was_added, + asset.faucet_id().suffix(), + asset.faucet_id().prefix().as_felt(), + ]); + elements.extend_from_slice(asset.to_value_word().as_elements()); } } } @@ -519,14 +531,20 @@ impl Deserializable for NonFungibleAssetDelta { let num_added = source.read_usize()?; for _ in 0..num_added { - let added_asset = source.read()?; - map.insert(LexicographicWord::new(added_asset), NonFungibleDeltaAction::Add); + let added_asset: NonFungibleAsset = source.read()?; + map.insert( + LexicographicWord::new(added_asset.vault_key()), + (added_asset, NonFungibleDeltaAction::Add), + ); } let num_removed = source.read_usize()?; for _ in 0..num_removed { - let removed_asset = source.read()?; - map.insert(LexicographicWord::new(removed_asset), NonFungibleDeltaAction::Remove); + let removed_asset: NonFungibleAsset = source.read()?; + map.insert( + LexicographicWord::new(removed_asset.vault_key()), + (removed_asset, NonFungibleDeltaAction::Remove), + ); } Ok(Self::new(map)) diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index 394c43c276..593e3eed03 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -5,6 +5,7 @@ use core::fmt; use super::vault::AssetVaultKey; use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; +use crate::account::AccountId; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{FieldElement, WORD_SIZE}; @@ -125,6 +126,11 @@ impl NonFungibleAsset { AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS_BE]) } + /// Return ID of the faucet which issued this asset. + pub fn faucet_id(&self) -> AccountId { + todo!() + } + /// Returns the asset's key encoded to a [`Word`]. pub fn to_key_word(&self) -> Word { *self.vault_key().as_word() From fc4777611ec794be422c714baaa7060cb32d8728 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 10 Feb 2026 15:55:42 +0100 Subject: [PATCH 053/100] chore: remove into `Word` conversions for assets --- .../miden-protocol/src/account/delta/vault.rs | 4 +- crates/miden-protocol/src/asset/fungible.rs | 15 +--- crates/miden-protocol/src/asset/mod.rs | 72 ++++++++----------- .../miden-protocol/src/asset/nonfungible.rs | 8 +-- .../src/asset/vault/asset_witness.rs | 6 +- crates/miden-protocol/src/asset/vault/mod.rs | 10 +-- .../miden-protocol/src/asset/vault/partial.rs | 2 +- crates/miden-protocol/src/errors/tx_kernel.rs | 12 ++-- .../src/transaction/kernel/mod.rs | 2 +- .../src/transaction/kernel/procedures.rs | 12 ++-- .../src/kernel_tests/tx/test_account.rs | 6 +- .../src/kernel_tests/tx/test_asset_vault.rs | 14 ++-- .../src/kernel_tests/tx/test_epilogue.rs | 11 ++- .../src/kernel_tests/tx/test_faucet.rs | 16 ++--- .../src/kernel_tests/tx/test_input_note.rs | 2 +- .../src/kernel_tests/tx/test_prologue.rs | 4 +- 16 files changed, 83 insertions(+), 113 deletions(-) diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index dd88ae252a..e950025360 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -141,7 +141,7 @@ impl AccountVaultDelta { Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) }) .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Add).map(|key| { - Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.into()) }) + Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.to_value_word()) }) })) } @@ -156,7 +156,7 @@ impl AccountVaultDelta { Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) }) .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Remove).map(|key| { - Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.into()) }) + Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.to_value_word()) }) })) } } diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index bc093e0114..e04ec7e0a7 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -3,7 +3,7 @@ use alloc::string::ToString; use core::fmt; use super::vault::AssetVaultKey; -use super::{AccountType, Asset, AssetError, Felt, Word, ZERO, is_not_a_non_fungible_asset}; +use super::{AccountType, Asset, AssetError, Word, ZERO}; use crate::account::{AccountId, AccountIdPrefix}; use crate::utils::serde::{ ByteReader, @@ -96,7 +96,7 @@ impl FungibleAsset { /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { - Word::from(*self) + todo!() } // OPERATIONS @@ -175,17 +175,6 @@ impl FungibleAsset { } } -impl From for Word { - fn from(asset: FungibleAsset) -> Self { - let mut result = Word::empty(); - result[0] = Felt::new(asset.amount); - result[2] = asset.faucet_id.suffix(); - result[3] = asset.faucet_id.prefix().as_felt(); - debug_assert!(is_not_a_non_fungible_asset(result)); - result - } -} - impl From for Asset { fn from(asset: FungibleAsset) -> Self { Asset::Fungible(asset) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 0fcd6b1620..2222b3a4d3 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -152,7 +152,10 @@ impl Asset { /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { - Word::from(*self) + match self { + Asset::Fungible(fungible_asset) => fungible_asset.to_value_word(), + Asset::NonFungible(non_fungible_asset) => non_fungible_asset.to_value_word(), + } } /// Returns the inner [`FungibleAsset`]. @@ -180,21 +183,6 @@ impl Asset { } } -impl From for Word { - fn from(asset: Asset) -> Self { - match asset { - Asset::Fungible(asset) => asset.into(), - Asset::NonFungible(asset) => asset.into(), - } - } -} - -impl From<&Asset> for Word { - fn from(value: &Asset) -> Self { - (*value).into() - } -} - impl TryFrom<&Word> for Asset { type Error = AssetError; @@ -289,7 +277,6 @@ fn is_not_a_non_fungible_asset(asset: Word) -> bool { #[cfg(test)] mod tests { - use miden_crypto::Word; use miden_crypto::utils::{Deserializable, Serializable}; use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; @@ -334,31 +321,32 @@ mod tests { } } - #[test] - fn test_new_unchecked() { - for fungible_account_id in [ - ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, - ] { - let account_id = AccountId::try_from(fungible_account_id).unwrap(); - let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into(); - assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset))); - } - - for non_fungible_account_id in [ - ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, - ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, - ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, - ] { - let account_id = AccountId::try_from(non_fungible_account_id).unwrap(); - let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap(); - let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into(); - assert_eq!(non_fungible_asset, Asset::new_unchecked(Word::from(non_fungible_asset))); - } - } + // TODO(expand_assets): Replace with from_key_value test. + // #[test] + // fn test_new_unchecked() { + // for fungible_account_id in [ + // ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, + // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, + // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, + // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, + // ] { + // let account_id = AccountId::try_from(fungible_account_id).unwrap(); + // let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into(); + // assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset))); + // } + + // for non_fungible_account_id in [ + // ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, + // ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, + // ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, + // ] { + // let account_id = AccountId::try_from(non_fungible_account_id).unwrap(); + // let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, + // 3]).unwrap(); let non_fungible_asset: Asset = + // NonFungibleAsset::new(&details).unwrap().into(); assert_eq!(non_fungible_asset, + // Asset::new_unchecked(Word::from(non_fungible_asset))); } + // } /// This test asserts that account ID's prefix is serialized in the first felt of assets. /// Asset deserialization relies on that fact and if this changes the serialization must diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index 593e3eed03..964cf44ebf 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -138,7 +138,7 @@ impl NonFungibleAsset { /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { - Word::from(*self) + todo!() } // HELPER FUNCTIONS @@ -162,12 +162,6 @@ impl NonFungibleAsset { } } -impl From for Word { - fn from(asset: NonFungibleAsset) -> Self { - asset.0 - } -} - impl From for Asset { fn from(asset: NonFungibleAsset) -> Self { Asset::NonFungible(asset) diff --git a/crates/miden-protocol/src/asset/vault/asset_witness.rs b/crates/miden-protocol/src/asset/vault/asset_witness.rs index 503b468d41..0fea86edbf 100644 --- a/crates/miden-protocol/src/asset/vault/asset_witness.rs +++ b/crates/miden-protocol/src/asset/vault/asset_witness.rs @@ -144,8 +144,10 @@ mod tests { let fungible_asset = FungibleAsset::mock(500); let non_fungible_asset = NonFungibleAsset::mock(&[1]); - let smt = - Smt::with_entries([(fungible_asset.vault_key().into(), non_fungible_asset.into())])?; + let smt = Smt::with_entries([( + fungible_asset.vault_key().into(), + non_fungible_asset.to_value_word(), + )])?; let proof = smt.open(&fungible_asset.vault_key().into()); let err = AssetWitness::new(proof).unwrap_err(); diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index b74c9abfc4..6cacc11504 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -60,8 +60,9 @@ impl AssetVault { /// Returns a new [AssetVault] initialized with the provided assets. pub fn new(assets: &[Asset]) -> Result { Ok(Self { + // TODO(expand_assets): Replace with hashed key. asset_tree: Smt::with_entries( - assets.iter().map(|asset| (asset.vault_key().into(), (*asset).into())), + assets.iter().map(|asset| (*asset.vault_key().as_word(), asset.to_value_word())), ) .map_err(AssetVaultError::DuplicateAsset)?, }) @@ -225,7 +226,7 @@ impl AssetVault { }, }; self.asset_tree - .insert(new.vault_key().into(), new.into()) + .insert(new.vault_key().into(), new.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the new asset @@ -242,9 +243,10 @@ impl AssetVault { asset: NonFungibleAsset, ) -> Result { // add non-fungible asset to the vault + // TODO(expand_assets): Replace with hashed key. let old = self .asset_tree - .insert(asset.vault_key().into(), asset.into()) + .insert(asset.vault_key().into(), asset.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // if the asset already exists, return an error @@ -301,7 +303,7 @@ impl AssetVault { // if the amount of the asset is zero, remove the asset from the vault. let value = match new.amount() { 0 => Smt::EMPTY_VALUE, - _ => new.into(), + _ => new.to_value_word(), }; self.asset_tree .insert(new.vault_key().into(), value) diff --git a/crates/miden-protocol/src/asset/vault/partial.rs b/crates/miden-protocol/src/asset/vault/partial.rs index 1427a8902c..4491cfaef5 100644 --- a/crates/miden-protocol/src/asset/vault/partial.rs +++ b/crates/miden-protocol/src/asset/vault/partial.rs @@ -216,7 +216,7 @@ mod tests { fn partial_vault_ensures_asset_vault_key_matches() -> anyhow::Result<()> { let asset = FungibleAsset::mock(500); let invalid_vault_key = Word::from([0, 1, 2, 3u32]); - let smt = Smt::with_entries([(invalid_vault_key, asset.into())])?; + let smt = Smt::with_entries([(invalid_vault_key, asset.to_value_word())])?; let proof = smt.open(&invalid_vault_key); let partial_smt = PartialSmt::from_proofs([proof.clone()])?; diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index 3de5bc90db..29606310bd 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -82,16 +82,16 @@ pub const ERR_FOREIGN_ACCOUNT_INVALID_COMMITMENT: MasmError = MasmError::from_st /// Error Message: "maximum allowed number of foreign account to be loaded (64) was exceeded" pub const ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED: MasmError = MasmError::from_static_str("maximum allowed number of foreign account to be loaded (64) was exceeded"); +/// Error Message: "fungible asset amount exceeds the maximum allowed amount" +pub const ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT: MasmError = MasmError::from_static_str("fungible asset amount exceeds the maximum allowed amount"); /// Error Message: "the origin of the fungible asset is not this faucet" pub const ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN: MasmError = MasmError::from_static_str("the origin of the fungible asset is not this faucet"); -/// Error Message: "malformed fungible asset: `ASSET[1]` must be 0" -pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO: MasmError = MasmError::from_static_str("malformed fungible asset: `ASSET[1]` must be 0"); -/// Error Message: "malformed fungible asset: `ASSET[2]` and `ASSET[3]` must be a valid fungible faucet id" -pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID: MasmError = MasmError::from_static_str("malformed fungible asset: `ASSET[2]` and `ASSET[3]` must be a valid fungible faucet id"); -/// Error Message: "malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount" -pub const ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS: MasmError = MasmError::from_static_str("malformed fungible asset: `ASSET[0]` exceeds the maximum allowed amount"); /// Error Message: "fungible asset vault key's account ID must be of type fungible faucet" pub const ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE: MasmError = MasmError::from_static_str("fungible asset vault key's account ID must be of type fungible faucet"); +/// Error Message: "fungible asset key asset ID prefix and suffix must be zero" +pub const ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO: MasmError = MasmError::from_static_str("fungible asset key asset ID prefix and suffix must be zero"); +/// Error Message: "fungible asset value elements 1, 2 and 3 must be zeros" +pub const ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO: MasmError = MasmError::from_static_str("fungible asset value elements 1, 2 and 3 must be zeros"); /// Error Message: "requested input note index should be less than the total number of input notes" pub const ERR_INPUT_NOTE_INDEX_OUT_OF_BOUNDS: MasmError = MasmError::from_static_str("requested input note index should be less than the total number of input notes"); diff --git a/crates/miden-protocol/src/transaction/kernel/mod.rs b/crates/miden-protocol/src/transaction/kernel/mod.rs index 334376165a..2aeb630bbb 100644 --- a/crates/miden-protocol/src/transaction/kernel/mod.rs +++ b/crates/miden-protocol/src/transaction/kernel/mod.rs @@ -221,7 +221,7 @@ impl TransactionKernel { Hasher::merge(&[final_account_commitment, account_delta_commitment]); let mut outputs: Vec = Vec::with_capacity(9); outputs.push(Felt::from(expiration_block_num)); - outputs.extend(Word::from(fee)); + outputs.extend(fee.to_value_word()); outputs.extend(account_update_commitment); outputs.extend(output_notes_commitment); outputs.reverse(); diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index a345540573..1889202948 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,15 +40,15 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x50985ece56c97b478f5f6da3c82f4ff4e0e972f37aab7670b040182993b78535"), + word!("0xc52f196ec16ad73718286dd7cef276371c7cca1dcbeef8de40c2292ad2b4ca69"), // account_remove_asset - word!("0x069672b069a35b40e36911e0e029aef59a42a10209b6a70892badb3e828ca8a9"), + word!("0x48e72eb921fdc829be72b0cff2849e1ae2671d59b1c86bf6e389c43472ebc7bc"), // account_get_asset word!("0x21dc0ef7e3475f28fbcf26636d9b58c3f7e349da7c7a36e85c1b49e50437fa65"), // account_get_initial_asset word!("0x8e1fabe016fa23523489a8aec0a0cc3804610b75c8b4a1774e46eb6a9d205cb5"), // account_compute_delta_commitment - word!("0x09767ee5e29aeca91a57f3af3871bbfb3037681e193444b3f7af878894c1aaa3"), + word!("0x8ea3f06c69939fa791c60e2fa06f19fd070aa20e20e65ba4c8f7931dcad6c86a"), // account_get_num_procedures word!("0x53b5ec38b7841948762c258010e6e07ad93963bcaac2d83813f8edb6710dc720"), // account_get_procedure_root @@ -58,9 +58,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xb0b63fdd01af0bcb4aacb2412e934cdc7691308647152d416c7ae4fc909da076"), // faucet_mint_asset - word!("0x5015be55c6d4b7c23627bc5c07e3dd113bbf2839077eb1e2afbeaf56c090e05d"), + word!("0xcb1e09ed949517f3d4fa78f294c9132cdba6a835a9095de4fd4ac092592fdac6"), // faucet_burn_asset - word!("0xcce90fd532495cd3205cb1657d20ef6b1857265ad11cd4641f33c44b887ac690"), + word!("0x8a07632e2cabc04096efcd2c60a91d6a75c03e302f14f907b7a511dc78fd1060"), // input_note_get_metadata word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info @@ -82,7 +82,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // output_note_get_recipient word!("0x6aeec5901ae0afd538bdbb6f7c5a05da66e75fb9e2100c1ffe2a3fa5d9910b64"), // output_note_add_asset - word!("0x6e1a699df870c682549c840bf030111000f495d3babb2b3a6f8cace062d90995"), + word!("0x69696c78741f0827b20620c242db91436b03ef55bd9b7cc44c506791098bfc45"), // output_note_set_attachment word!("0xc33e8568f74b1accf0ee7f5de52fea30fe524b9e0dad7958d7e506e9ba3e3bbe"), // tx_get_num_input_notes diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 538b06d1e9..5d6ec2ebbc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1331,15 +1331,15 @@ async fn test_get_init_asset() -> anyhow::Result<()> { push.{ASSET_KEY} exec.active_account::get_initial_asset # => [INITIAL_ASSET] - push.{INITIAL_ASSET} + push.{INITIAL_ASSET_VALUE} assert_eqw.err="initial asset is incorrect" end "#, ASSET_KEY = fungible_asset_for_note_existing.vault_key(), REMOVED_ASSET_VALUE = fungible_asset_for_note_existing.to_value_word(), REMOVED_ASSET_KEY = fungible_asset_for_note_existing.to_key_word(), - INITIAL_ASSET = Word::from(fungible_asset_for_account), - FINAL_ASSET = Word::from(final_asset), + INITIAL_ASSET_VALUE = fungible_asset_for_account.to_value_word(), + FINAL_ASSET = final_asset.to_value_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(remove_existing_source)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 1f77b7cab9..8daf04f4fd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -90,7 +90,7 @@ async fn peek_asset_returns_correct_asset() -> anyhow::Result<()> { # => [ASSET_KEY, account_vault_root_ptr] exec.asset_vault::peek_asset - # => [PEEKED_ASSET] + # => [PEEKED_ASSET_VALUE] # truncate the stack swapw dropw @@ -103,7 +103,7 @@ async fn peek_asset_returns_correct_asset() -> anyhow::Result<()> { assert_eq!( exec_output.get_stack_word_be(0), - Word::from(tx_context.account().vault().get(asset_key).unwrap()) + tx_context.account().vault().get(asset_key).unwrap().to_value_word() ); Ok(()) @@ -210,7 +210,7 @@ async fn test_add_fungible_asset_success() -> anyhow::Result<()> { assert_eq!( exec_output.get_stack_word_be(0), - Word::from(account_vault.add_asset(add_fungible_asset).unwrap()) + account_vault.add_asset(add_fungible_asset).unwrap().to_value_word() ); assert_eq!( @@ -293,7 +293,7 @@ async fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { assert_eq!( exec_output.get_stack_word_be(0), - Word::from(account_vault.add_asset(add_non_fungible_asset)?) + account_vault.add_asset(add_non_fungible_asset)?.to_value_word() ); assert_eq!( @@ -377,7 +377,7 @@ async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Re assert_eq!( exec_output.get_stack_word_be(0), - Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) + account_vault.remove_asset(remove_fungible_asset).unwrap().to_value_word() ); assert_eq!( @@ -465,7 +465,7 @@ async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Resul assert_eq!( exec_output.get_stack_word_be(0), - Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) + account_vault.remove_asset(remove_fungible_asset).unwrap().to_value_word() ); assert_eq!( @@ -554,7 +554,7 @@ async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { assert_eq!( exec_output.get_stack_word_be(0), - Word::from(account_vault.remove_asset(non_fungible_asset).unwrap()) + account_vault.remove_asset(non_fungible_asset).unwrap().to_value_word() ); assert_eq!( diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 6054a0cc96..ed9ee01a99 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -122,13 +122,12 @@ async fn test_transaction_epilogue() -> anyhow::Result<()> { expected_stack.extend(output_notes.commitment().as_elements().iter().rev()); expected_stack.extend(account_update_commitment.as_elements().iter().rev()); expected_stack.extend( - Word::from( - FungibleAsset::new( - tx_context.tx_inputs().block_header().fee_parameters().native_asset_id(), - 0, - ) - .unwrap(), + FungibleAsset::new( + tx_context.tx_inputs().block_header().fee_parameters().native_asset_id(), + 0, ) + .unwrap() + .to_value_word() .iter() .rev(), ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 805627f364..e7c44b27b0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -1,12 +1,11 @@ use alloc::sync::Arc; -use miden_protocol::Word; use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId, AccountType}; use miden_protocol::assembly::DefaultSourceManager; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; use miden_protocol::errors::tx_kernel::{ + ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN, - ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND, @@ -103,7 +102,7 @@ async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> end ", asset_key = asset.vault_key(), - asset_value = Word::from(asset), + asset_value = asset.to_value_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -137,7 +136,7 @@ async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> end ", asset_key = asset.vault_key(), - asset_value = Word::from(asset), + asset_value = asset.to_value_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -183,10 +182,7 @@ async fn test_mint_fungible_asset_fails_when_amount_exceeds_max_representable_am .execute() .await; - assert_transaction_executor_error!( - result, - ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ZERO_MUST_BE_WITHIN_LIMITS - ); + assert_transaction_executor_error!(result, ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT); Ok(()) } @@ -261,7 +257,7 @@ async fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow:: end ", asset_key = non_fungible_asset.to_key_word(), - asset_value = Word::from(non_fungible_asset), + asset_value = non_fungible_asset.to_value_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -287,7 +283,7 @@ async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result end ", asset_key = asset.vault_key(), - asset_value = Word::from(asset), + asset_value = asset.to_value_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 216dc54c17..7a4da2d5ff 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -260,7 +260,7 @@ async fn test_get_assets() -> anyhow::Result<()> { # => [dest_ptr+ASSET_SIZE, note_index] "#, NOTE_ASSET_KEY = asset.vault_key().as_word(), - NOTE_ASSET_VALUE = Word::from(*asset), + NOTE_ASSET_VALUE = asset.to_value_word(), asset_index = asset_index, note_index = note_index, )); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index a95afb918b..a9fa1b1d72 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -518,8 +518,8 @@ fn input_notes_memory_assertions( ); for (asset, asset_idx) in note.assets().iter().cloned().zip(0_u32..) { - let asset_key: Word = *asset.vault_key().as_word(); - let asset_value: Word = asset.into(); + let asset_key = asset.to_key_word(); + let asset_value = asset.to_value_word(); let asset_key_addr = INPUT_NOTE_ASSETS_OFFSET + asset_idx * ASSET_SIZE; let asset_value_addr = asset_key_addr + ASSET_VALUE_OFFSET; From 4b5fd9303cf668dcf605168b3de1d96999b83cf6 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 10 Feb 2026 17:49:28 +0100 Subject: [PATCH 054/100] feat: Implement strongly typed asset vault key --- .../src/asset/vault/asset_id.rs | 26 +++ .../src/asset/vault/asset_witness.rs | 2 +- crates/miden-protocol/src/asset/vault/mod.rs | 45 ++-- .../src/asset/vault/vault_key.rs | 198 +++++++++++------- 4 files changed, 173 insertions(+), 98 deletions(-) create mode 100644 crates/miden-protocol/src/asset/vault/asset_id.rs diff --git a/crates/miden-protocol/src/asset/vault/asset_id.rs b/crates/miden-protocol/src/asset/vault/asset_id.rs new file mode 100644 index 0000000000..517b5ec340 --- /dev/null +++ b/crates/miden-protocol/src/asset/vault/asset_id.rs @@ -0,0 +1,26 @@ +use crate::Felt; + +/// The [`AssetId`] in an [`AssetVaultKey`](crate::asset::AssetVaultKey) distinguishes different +/// assets issued by the same faucet. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AssetId { + suffix: Felt, + prefix: Felt, +} + +impl AssetId { + /// Constructs an asset ID from its parts. + pub fn new(suffix: Felt, prefix: Felt) -> Self { + Self { suffix, prefix } + } + + /// Returns the suffix of the asset ID. + pub fn suffix(&self) -> Felt { + self.suffix + } + + /// Returns the prefix of the asset ID. + pub fn prefix(&self) -> Felt { + self.prefix + } +} diff --git a/crates/miden-protocol/src/asset/vault/asset_witness.rs b/crates/miden-protocol/src/asset/vault/asset_witness.rs index 0fea86edbf..efb31be17f 100644 --- a/crates/miden-protocol/src/asset/vault/asset_witness.rs +++ b/crates/miden-protocol/src/asset/vault/asset_witness.rs @@ -53,7 +53,7 @@ impl AssetWitness { /// Returns `true` if this [`AssetWitness`] authenticates the provided [`AssetVaultKey`], i.e. /// if its leaf index matches, `false` otherwise. pub fn authenticates_asset_vault_key(&self, vault_key: AssetVaultKey) -> bool { - self.0.leaf().index() == vault_key.to_leaf_index() + self.0.leaf().index() == vault_key.as_hashed_key().to_leaf_index() } /// Searches for an [`Asset`] in the witness with the given `vault_key`. diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index 6cacc11504..69af1a59a4 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -28,6 +28,9 @@ pub use asset_witness::AssetWitness; mod vault_key; pub use vault_key::AssetVaultKey; +mod asset_id; +pub use asset_id::AssetId; + // ASSET VAULT // ================================================================================================ @@ -60,9 +63,10 @@ impl AssetVault { /// Returns a new [AssetVault] initialized with the provided assets. pub fn new(assets: &[Asset]) -> Result { Ok(Self { - // TODO(expand_assets): Replace with hashed key. asset_tree: Smt::with_entries( - assets.iter().map(|asset| (*asset.vault_key().as_word(), asset.to_value_word())), + assets.iter().map(|asset| { + (asset.vault_key().as_hashed_key().as_word(), asset.to_value_word()) + }), ) .map_err(AssetVaultError::DuplicateAsset)?, }) @@ -79,7 +83,7 @@ impl AssetVault { /// Returns the asset corresponding to the provided asset vault key, or `None` if the asset /// doesn't exist. pub fn get(&self, asset_vault_key: AssetVaultKey) -> Option { - let word = self.asset_tree.get_value(asset_vault_key.as_word()); + let word = self.asset_tree.get_value(&asset_vault_key.as_hashed_key().as_word()); if word.is_empty() { None @@ -91,7 +95,7 @@ impl AssetVault { /// Returns true if the specified non-fungible asset is stored in this vault. pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result { // check if the asset is stored in the vault - match self.asset_tree.get_value(&asset.vault_key().into()) { + match self.asset_tree.get_value(&asset.vault_key().as_hashed_key().as_word()) { asset if asset == Smt::EMPTY_VALUE => Ok(false), _ => Ok(true), } @@ -109,7 +113,7 @@ impl AssetVault { // if the tree value is [0, 0, 0, 0], the asset is not stored in the vault match self.asset_tree.get_value( - &AssetVaultKey::from_account_id(faucet_id) + &AssetVaultKey::new_fungible(faucet_id) .expect("faucet ID should be of type fungible") .into(), ) { @@ -133,7 +137,7 @@ impl AssetVault { /// /// The `vault_key` can be obtained with [`Asset::vault_key`]. pub fn open(&self, vault_key: AssetVaultKey) -> AssetWitness { - let smt_proof = self.asset_tree.open(&vault_key.into()); + let smt_proof = self.asset_tree.open(&vault_key.as_hashed_key().as_word()); // SAFETY: The asset vault should only contain valid assets. AssetWitness::new_unchecked(smt_proof) } @@ -218,15 +222,16 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch current asset value from the tree and add the new asset to it. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) { - current if current == Smt::EMPTY_VALUE => asset, - current => { - let current = FungibleAsset::new_unchecked(current); - current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? - }, - }; + let new: FungibleAsset = + match self.asset_tree.get_value(&asset.vault_key().as_hashed_key().as_word()) { + current if current == Smt::EMPTY_VALUE => asset, + current => { + let current = FungibleAsset::new_unchecked(current); + current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? + }, + }; self.asset_tree - .insert(new.vault_key().into(), new.to_value_word()) + .insert(new.vault_key().as_hashed_key().as_word(), new.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the new asset @@ -243,10 +248,9 @@ impl AssetVault { asset: NonFungibleAsset, ) -> Result { // add non-fungible asset to the vault - // TODO(expand_assets): Replace with hashed key. let old = self .asset_tree - .insert(asset.vault_key().into(), asset.to_value_word()) + .insert(asset.vault_key().as_hashed_key().as_word(), asset.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // if the asset already exists, return an error @@ -290,7 +294,10 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch the asset from the vault. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) { + let new: FungibleAsset = match self + .asset_tree + .get_value(&asset.vault_key().as_hashed_key().as_word()) + { current if current == Smt::EMPTY_VALUE => { return Err(AssetVaultError::FungibleAssetNotFound(asset)); }, @@ -306,7 +313,7 @@ impl AssetVault { _ => new.to_value_word(), }; self.asset_tree - .insert(new.vault_key().into(), value) + .insert(new.vault_key().as_hashed_key().as_word(), value) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the asset that was removed. @@ -326,7 +333,7 @@ impl AssetVault { // remove the asset from the vault. let old = self .asset_tree - .insert(asset.vault_key().into(), Smt::EMPTY_VALUE) + .insert(asset.vault_key().as_hashed_key().as_word(), Smt::EMPTY_VALUE) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return an error if the asset did not exist in the vault. diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 4e5bfd0811..d2b81daa40 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -1,107 +1,119 @@ -use core::fmt; +use alloc::boxed::Box; +use alloc::string::String; +use miden_core::LexicographicWord; use miden_crypto::merkle::smt::LeafIndex; use miden_processor::SMT_DEPTH; +use miden_protocol_macros::WordWrapper; -use crate::Word; -use crate::account::AccountType::FungibleFaucet; -use crate::account::{AccountId, AccountIdPrefix}; +use crate::account::AccountId; +use crate::account::AccountType::{self}; +use crate::asset::vault::AssetId; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; use crate::errors::AssetError; +use crate::{Felt, FieldElement, Hasher, Word}; -/// The key of an [`Asset`] in the asset vault. +/// The unique identifier of an [`Asset`] in the [`AssetVault`](crate::asset::AssetVault). /// -/// The layout of an asset key is: -/// - Fungible asset key: `[0, 0, faucet_id_suffix, faucet_id_prefix]`. -/// - Non-fungible asset key: `[faucet_id_prefix, hash1, hash2, hash0']`, where `hash0'` is -/// equivalent to `hash0` with the fungible bit set to `0`. See [`NonFungibleAsset::vault_key`] -/// for more details. +/// Note that the asset vault key is not used directly as the key in an asset vault. See +/// the derived [`AssetVaultKeyHash`] for details. /// -/// For details on the layout of an asset, see the documentation of [`Asset`]. +/// Its [`Word`] layout is: +/// ```text +/// [ +/// asset_id_suffix (64 bits), +/// asset_id_prefix (64 bits), +/// faucet_id_suffix (56 bits), +/// faucet_id_prefix (64 bits) +/// ] +/// ``` /// -/// ## Guarantees -/// -/// This type guarantees that it contains a valid fungible or non-fungible asset key: -/// - For fungible assets -/// - The felt at index 3 has the fungible bit set to 1 and it is a valid account ID prefix. -/// - The felt at index 2 is a valid account ID suffix. -/// - For non-fungible assets -/// - The felt at index 3 has the fungible bit set to 0. -/// - The felt at index 0 is a valid account ID prefix. -/// -/// The fungible bit is the bit in the [`AccountId`] that encodes whether the ID is a faucet. -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -pub struct AssetVaultKey(Word); +/// See the [`Asset`] documentation for the differences between fungible and non-fungible assets. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct AssetVaultKey { + /// The asset ID of the vault key. + asset_id: AssetId, + + /// The ID of the faucet that issued the asset. + faucet_id: AccountId, + + /// The cached hash of the vault key's word representation for use in the asset vault. + key_hash: AssetVaultKeyHash, +} impl AssetVaultKey { - /// Creates a new [`AssetVaultKey`] from the given [`Word`] **without performing validation**. - /// - /// ## Warning + /// Creates an [`AssetVaultKey`] from its parts. + pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Self { + let word = vault_key_to_word(asset_id, faucet_id); + let key_hash = Hasher::hash_elements(word.as_elements()); + let key_hash = AssetVaultKeyHash::from_raw(key_hash); + + Self { asset_id, faucet_id, key_hash } + } + + /// Returns the word representation of the vault key. /// - /// This function **does not check** whether the provided `Word` represents a valid - /// fungible or non-fungible asset key. - pub fn new_unchecked(value: Word) -> Self { - Self(value) + /// See the type-level documentation for details. + pub fn to_word(self) -> Word { + vault_key_to_word(self.asset_id, self.faucet_id) } - /// Returns an [`AccountIdPrefix`] from the asset key. - pub fn faucet_id_prefix(&self) -> AccountIdPrefix { - if self.is_fungible() { - AccountIdPrefix::new_unchecked(self.0[3]) - } else { - AccountIdPrefix::new_unchecked(self.0[0]) - } + /// Returns the [`AssetVaultKeyHash`] of the vault key for use in the asset vault. + pub fn as_hashed_key(self) -> AssetVaultKeyHash { + self.key_hash } - /// Returns the [`AccountId`] from the asset key if it is a fungible asset, `None` otherwise. - pub fn faucet_id(&self) -> Option { - if self.is_fungible() { - Some(AccountId::new_unchecked([self.0[3], self.0[2]])) - } else { - None - } + /// Returns the [`AssetId`] of the vault key that distinguishes different assets issued by the + /// same faucet. + pub fn asset_id(&self) -> AssetId { + self.asset_id } - /// Returns the leaf index of a vault key. - pub fn to_leaf_index(&self) -> LeafIndex { - LeafIndex::::from(self.0) + /// Returns the [`AccountId`] of the faucet that issued the asset. + pub fn faucet_id(&self) -> AccountId { + self.faucet_id } /// Constructs a fungible asset's key from a faucet ID. /// /// Returns `None` if the provided ID is not of type /// [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet) - pub fn from_account_id(faucet_id: AccountId) -> Option { - match faucet_id.account_type() { - FungibleFaucet => { - let mut key = Word::empty(); - key[2] = faucet_id.suffix(); - key[3] = faucet_id.prefix().as_felt(); - Some(AssetVaultKey::new_unchecked(key)) - }, - _ => None, + pub fn new_fungible(faucet_id: AccountId) -> Option { + if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) { + let asset_id = AssetId::new(Felt::ZERO, Felt::ZERO); + Some(Self::new(asset_id, faucet_id)) + } else { + None } } - /// Returns a reference to the inner [Word] of this key. - pub fn as_word(&self) -> &Word { - &self.0 - } - /// Returns `true` if the asset key is for a fungible asset, `false` otherwise. fn is_fungible(&self) -> bool { - self.0[0].as_int() == 0 && self.0[1].as_int() == 0 + matches!(self.faucet_id.account_type(), AccountType::FungibleFaucet) + } +} + +// CONVERSIONS +// ================================================================================================ + +impl From for Word { + fn from(vault_key: AssetVaultKey) -> Self { + vault_key.to_word() } } -impl fmt::Display for AssetVaultKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) +impl Ord for AssetVaultKey { + /// Implements comparison based on [`LexicographicWord`]. + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + LexicographicWord::new(self.to_word()).cmp(&LexicographicWord::new(other.to_word())) } } -// CONVERSIONS -// ================================================================================================ +impl PartialOrd for AssetVaultKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} impl TryFrom for AssetVaultKey { type Error = AssetError; @@ -111,18 +123,18 @@ impl TryFrom for AssetVaultKey { /// # Errors /// /// Returns an error if: - /// - TODO(expand_assets) + /// - the faucet ID in the key is invalid. fn try_from(key: Word) -> Result { - // TODO(expand_assets): Implement validation once the new structure of the asset vault - // key is defined. + let asset_id_suffix = key[0]; + let asset_id_prefix = key[1]; + let faucet_id_suffix = key[2]; + let faucet_id_prefix = key[3]; - Ok(Self::new_unchecked(key)) - } -} + let asset_id = AssetId::new(asset_id_suffix, asset_id_prefix); + let faucet_id = AccountId::try_from([faucet_id_prefix, faucet_id_suffix]) + .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?; -impl From for Word { - fn from(vault_key: AssetVaultKey) -> Self { - vault_key.0 + Ok(Self::new(asset_id, faucet_id)) } } @@ -144,6 +156,36 @@ impl From for AssetVaultKey { } } +fn vault_key_to_word(asset_id: AssetId, faucet_id: AccountId) -> Word { + Word::new([ + asset_id.suffix(), + asset_id.prefix(), + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) +} + +// ASSET VAULT KEY HASH +// ================================================================================================ + +/// The key of an asset in the [`AssetVault`](crate::asset::AssetVault). +/// +/// The hash combines the asset ID and faucet ID into a single hashed value to ensure that assets +/// from the _same_ faucet with _different_ IDs map to different leaves, while assets sharing both +/// IDs produce identical keys. +/// +/// This ensures that non-fungible assets issued by the same faucet are stored in different leaves, +/// while fungible assets issued by the same faucet are stored in the same leaf. +#[derive(Debug, Clone, Copy, WordWrapper, PartialEq, Eq, PartialOrd, Ord)] +pub struct AssetVaultKeyHash(Word); + +impl AssetVaultKeyHash { + /// Returns the leaf index of a vault key. + pub fn to_leaf_index(&self) -> LeafIndex { + LeafIndex::::from(self.0) + } +} + // TESTS // ================================================================================================ @@ -169,7 +211,7 @@ mod tests { ); let key = - AssetVaultKey::from_account_id(id).expect("Expected AssetVaultKey for FungibleFaucet"); + AssetVaultKey::new_fungible(id).expect("Expected AssetVaultKey for FungibleFaucet"); // faucet_id_prefix() should match AccountId prefix assert_eq!(key.faucet_id_prefix(), id.prefix()); From 3fc8b421da748b5066f62e61693ddb79351e41d6 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 10 Feb 2026 17:52:31 +0100 Subject: [PATCH 055/100] chore: temporarily remove asset vault key hash --- .../src/asset/vault/asset_witness.rs | 2 +- crates/miden-protocol/src/asset/vault/mod.rs | 20 ++++----- .../src/asset/vault/vault_key.rs | 44 +++---------------- 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/crates/miden-protocol/src/asset/vault/asset_witness.rs b/crates/miden-protocol/src/asset/vault/asset_witness.rs index efb31be17f..0fea86edbf 100644 --- a/crates/miden-protocol/src/asset/vault/asset_witness.rs +++ b/crates/miden-protocol/src/asset/vault/asset_witness.rs @@ -53,7 +53,7 @@ impl AssetWitness { /// Returns `true` if this [`AssetWitness`] authenticates the provided [`AssetVaultKey`], i.e. /// if its leaf index matches, `false` otherwise. pub fn authenticates_asset_vault_key(&self, vault_key: AssetVaultKey) -> bool { - self.0.leaf().index() == vault_key.as_hashed_key().to_leaf_index() + self.0.leaf().index() == vault_key.to_leaf_index() } /// Searches for an [`Asset`] in the witness with the given `vault_key`. diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index 69af1a59a4..5f9f2a8963 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -65,7 +65,7 @@ impl AssetVault { Ok(Self { asset_tree: Smt::with_entries( assets.iter().map(|asset| { - (asset.vault_key().as_hashed_key().as_word(), asset.to_value_word()) + (asset.vault_key().to_word(), asset.to_value_word()) }), ) .map_err(AssetVaultError::DuplicateAsset)?, @@ -83,7 +83,7 @@ impl AssetVault { /// Returns the asset corresponding to the provided asset vault key, or `None` if the asset /// doesn't exist. pub fn get(&self, asset_vault_key: AssetVaultKey) -> Option { - let word = self.asset_tree.get_value(&asset_vault_key.as_hashed_key().as_word()); + let word = self.asset_tree.get_value(&asset_vault_key.to_word()); if word.is_empty() { None @@ -95,7 +95,7 @@ impl AssetVault { /// Returns true if the specified non-fungible asset is stored in this vault. pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result { // check if the asset is stored in the vault - match self.asset_tree.get_value(&asset.vault_key().as_hashed_key().as_word()) { + match self.asset_tree.get_value(&asset.vault_key().to_word()) { asset if asset == Smt::EMPTY_VALUE => Ok(false), _ => Ok(true), } @@ -137,7 +137,7 @@ impl AssetVault { /// /// The `vault_key` can be obtained with [`Asset::vault_key`]. pub fn open(&self, vault_key: AssetVaultKey) -> AssetWitness { - let smt_proof = self.asset_tree.open(&vault_key.as_hashed_key().as_word()); + let smt_proof = self.asset_tree.open(&vault_key.to_word()); // SAFETY: The asset vault should only contain valid assets. AssetWitness::new_unchecked(smt_proof) } @@ -223,7 +223,7 @@ impl AssetVault { ) -> Result { // fetch current asset value from the tree and add the new asset to it. let new: FungibleAsset = - match self.asset_tree.get_value(&asset.vault_key().as_hashed_key().as_word()) { + match self.asset_tree.get_value(&asset.vault_key().to_word()) { current if current == Smt::EMPTY_VALUE => asset, current => { let current = FungibleAsset::new_unchecked(current); @@ -231,7 +231,7 @@ impl AssetVault { }, }; self.asset_tree - .insert(new.vault_key().as_hashed_key().as_word(), new.to_value_word()) + .insert(new.vault_key().to_word(), new.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the new asset @@ -250,7 +250,7 @@ impl AssetVault { // add non-fungible asset to the vault let old = self .asset_tree - .insert(asset.vault_key().as_hashed_key().as_word(), asset.to_value_word()) + .insert(asset.vault_key().to_word(), asset.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // if the asset already exists, return an error @@ -296,7 +296,7 @@ impl AssetVault { // fetch the asset from the vault. let new: FungibleAsset = match self .asset_tree - .get_value(&asset.vault_key().as_hashed_key().as_word()) + .get_value(&asset.vault_key().to_word()) { current if current == Smt::EMPTY_VALUE => { return Err(AssetVaultError::FungibleAssetNotFound(asset)); @@ -313,7 +313,7 @@ impl AssetVault { _ => new.to_value_word(), }; self.asset_tree - .insert(new.vault_key().as_hashed_key().as_word(), value) + .insert(new.vault_key().to_word(), value) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the asset that was removed. @@ -333,7 +333,7 @@ impl AssetVault { // remove the asset from the vault. let old = self .asset_tree - .insert(asset.vault_key().as_hashed_key().as_word(), Smt::EMPTY_VALUE) + .insert(asset.vault_key().to_word(), Smt::EMPTY_VALUE) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return an error if the asset did not exist in the vault. diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index d2b81daa40..6cb48d1c09 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -1,17 +1,15 @@ use alloc::boxed::Box; -use alloc::string::String; use miden_core::LexicographicWord; use miden_crypto::merkle::smt::LeafIndex; use miden_processor::SMT_DEPTH; -use miden_protocol_macros::WordWrapper; use crate::account::AccountId; use crate::account::AccountType::{self}; use crate::asset::vault::AssetId; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; use crate::errors::AssetError; -use crate::{Felt, FieldElement, Hasher, Word}; +use crate::{Felt, FieldElement, Word}; /// The unique identifier of an [`Asset`] in the [`AssetVault`](crate::asset::AssetVault). /// @@ -36,19 +34,12 @@ pub struct AssetVaultKey { /// The ID of the faucet that issued the asset. faucet_id: AccountId, - - /// The cached hash of the vault key's word representation for use in the asset vault. - key_hash: AssetVaultKeyHash, } impl AssetVaultKey { /// Creates an [`AssetVaultKey`] from its parts. pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Self { - let word = vault_key_to_word(asset_id, faucet_id); - let key_hash = Hasher::hash_elements(word.as_elements()); - let key_hash = AssetVaultKeyHash::from_raw(key_hash); - - Self { asset_id, faucet_id, key_hash } + Self { asset_id, faucet_id } } /// Returns the word representation of the vault key. @@ -58,11 +49,6 @@ impl AssetVaultKey { vault_key_to_word(self.asset_id, self.faucet_id) } - /// Returns the [`AssetVaultKeyHash`] of the vault key for use in the asset vault. - pub fn as_hashed_key(self) -> AssetVaultKeyHash { - self.key_hash - } - /// Returns the [`AssetId`] of the vault key that distinguishes different assets issued by the /// same faucet. pub fn asset_id(&self) -> AssetId { @@ -91,6 +77,11 @@ impl AssetVaultKey { fn is_fungible(&self) -> bool { matches!(self.faucet_id.account_type(), AccountType::FungibleFaucet) } + + /// Returns the leaf index of a vault key. + pub fn to_leaf_index(&self) -> LeafIndex { + LeafIndex::::from(self.to_word()) + } } // CONVERSIONS @@ -165,27 +156,6 @@ fn vault_key_to_word(asset_id: AssetId, faucet_id: AccountId) -> Word { ]) } -// ASSET VAULT KEY HASH -// ================================================================================================ - -/// The key of an asset in the [`AssetVault`](crate::asset::AssetVault). -/// -/// The hash combines the asset ID and faucet ID into a single hashed value to ensure that assets -/// from the _same_ faucet with _different_ IDs map to different leaves, while assets sharing both -/// IDs produce identical keys. -/// -/// This ensures that non-fungible assets issued by the same faucet are stored in different leaves, -/// while fungible assets issued by the same faucet are stored in the same leaf. -#[derive(Debug, Clone, Copy, WordWrapper, PartialEq, Eq, PartialOrd, Ord)] -pub struct AssetVaultKeyHash(Word); - -impl AssetVaultKeyHash { - /// Returns the leaf index of a vault key. - pub fn to_leaf_index(&self) -> LeafIndex { - LeafIndex::::from(self.0) - } -} - // TESTS // ================================================================================================ From 53d5b1d2743ed74f738eff6ca698bfafd9413a1b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 10:53:38 +0100 Subject: [PATCH 056/100] chore: remove asset from word conversion --- crates/miden-protocol/src/asset/fungible.rs | 71 ++++-------- crates/miden-protocol/src/asset/mod.rs | 63 ++++++----- .../miden-protocol/src/asset/nonfungible.rs | 101 +++++------------- .../src/asset/vault/asset_witness.rs | 20 ++-- crates/miden-protocol/src/asset/vault/mod.rs | 33 +++--- .../miden-protocol/src/asset/vault/partial.rs | 25 ++--- crates/miden-protocol/src/errors/mod.rs | 18 ++-- crates/miden-protocol/src/testing/asset.rs | 2 +- .../src/transaction/inputs/mod.rs | 4 +- .../src/transaction/kernel/mod.rs | 6 +- 10 files changed, 136 insertions(+), 207 deletions(-) diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index e04ec7e0a7..d0bf48f2c9 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -1,9 +1,8 @@ -use alloc::boxed::Box; use alloc::string::ToString; use core::fmt; use super::vault::AssetVaultKey; -use super::{AccountType, Asset, AssetError, Word, ZERO}; +use super::{AccountType, Asset, AssetError, Word}; use crate::account::{AccountId, AccountIdPrefix}; use crate::utils::serde::{ ByteReader, @@ -12,6 +11,7 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; +use crate::{Felt, FieldElement}; // FUNGIBLE ASSET // ================================================================================================ @@ -46,10 +46,26 @@ impl FungibleAsset { /// # Errors /// Returns an error if: /// - The faucet_id is not a valid fungible faucet ID. - /// - The provided amount is greater than 2^63 - 1. + /// - The provided amount is greater than [`FungibleAsset::MAX_AMOUNT`]. pub const fn new(faucet_id: AccountId, amount: u64) -> Result { - let asset = Self { faucet_id, amount }; - asset.validate() + if !matches!(faucet_id.account_type(), AccountType::FungibleFaucet) { + return Err(AssetError::FungibleFaucetIdTypeMismatch(faucet_id)); + } + + if amount > Self::MAX_AMOUNT { + return Err(AssetError::FungibleAssetAmountTooBig(amount)); + } + + Ok(Self { faucet_id, amount }) + } + + /// TODO + pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { + if key.asset_id().prefix() != Felt::ZERO || key.asset_id().suffix() != Felt::ZERO { + return Err(AssetError::FungibleAssetIdMustBeZero(key.asset_id())); + } + + Self::new(key.faucet_id(), value[0].as_int()) } /// Creates a new [FungibleAsset] without checking its validity. @@ -68,11 +84,6 @@ impl FungibleAsset { self.faucet_id } - /// Return ID prefix of the faucet which issued this asset. - pub fn faucet_id_prefix(&self) -> AccountIdPrefix { - self.faucet_id.prefix() - } - /// Returns the amount of this asset. pub fn amount(&self) -> u64 { self.amount @@ -85,13 +96,12 @@ impl FungibleAsset { /// Returns the key which is used to store this asset in the account vault. pub fn vault_key(&self) -> AssetVaultKey { - AssetVaultKey::from_account_id(self.faucet_id) - .expect("faucet ID should be of type fungible") + AssetVaultKey::new_fungible(self.faucet_id).expect("faucet ID should be of type fungible") } /// Returns the asset's key encoded to a [`Word`]. pub fn to_key_word(&self) -> Word { - *self.vault_key().as_word() + self.vault_key().to_word() } /// Returns the asset's value encoded to a [`Word`]. @@ -152,27 +162,6 @@ impl FungibleAsset { Ok(FungibleAsset { faucet_id: self.faucet_id, amount }) } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - - /// Validates this fungible asset. - /// # Errors - /// Returns an error if: - /// - The faucet_id is not a valid fungible faucet ID. - /// - The provided amount is greater than 2^63 - 1. - const fn validate(self) -> Result { - let account_type = self.faucet_id.account_type(); - if !matches!(account_type, AccountType::FungibleFaucet) { - return Err(AssetError::FungibleFaucetIdTypeMismatch(self.faucet_id)); - } - - if self.amount > Self::MAX_AMOUNT { - return Err(AssetError::FungibleAssetAmountTooBig(self.amount)); - } - - Ok(self) - } } impl From for Asset { @@ -181,20 +170,6 @@ impl From for Asset { } } -impl TryFrom for FungibleAsset { - type Error = AssetError; - - fn try_from(value: Word) -> Result { - if value[1] != ZERO { - return Err(AssetError::FungibleAssetExpectedZero(value)); - } - let faucet_id = AccountId::try_from([value[3], value[2]]) - .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?; - let amount = value[0].as_int(); - Self::new(faucet_id, amount) - } -} - impl fmt::Display for FungibleAsset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 2222b3a4d3..17074dc878 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -7,11 +7,10 @@ use super::utils::serde::{ DeserializationError, Serializable, }; -use super::{Felt, Hasher, Word, ZERO}; -use crate::account::AccountIdPrefix; +use super::{Felt, Hasher, Word}; +use crate::account::{AccountId, AccountIdPrefix}; mod fungible; -use alloc::boxed::Box; pub use fungible::FungibleAsset; @@ -93,6 +92,23 @@ pub enum Asset { } impl Asset { + /// TODO(expand_assets) + pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { + if matches!(key.faucet_id().account_type(), AccountType::FungibleFaucet) { + FungibleAsset::from_key_value(key, value).map(Asset::Fungible) + } else { + NonFungibleAsset::from_key_value(key, value).map(Asset::NonFungible) + } + } + + /// TODO(expand_assets) + /// + /// Prefer [`Self::from_key_value`] for more type safety. + pub fn from_words(key: Word, value: Word) -> Result { + let vault_key = AssetVaultKey::try_from(key)?; + Self::from_key_value(vault_key, value) + } + /// Creates a new [Asset] without checking its validity. pub(crate) fn new_unchecked(value: Word) -> Asset { if is_not_a_non_fungible_asset(value) { @@ -126,14 +142,11 @@ impl Asset { matches!(self, Self::NonFungible(_)) } - /// Returns the prefix of the faucet ID which issued this asset. - /// - /// To get the full [`AccountId`](crate::account::AccountId) of a fungible asset the asset - /// must be matched on. - pub fn faucet_id_prefix(&self) -> AccountIdPrefix { + /// Returns the ID of the faucet that issued this asset. + pub fn faucet_id(&self) -> AccountId { match self { - Self::Fungible(asset) => asset.faucet_id_prefix(), - Self::NonFungible(asset) => asset.faucet_id_prefix(), + Self::Fungible(asset) => asset.faucet_id(), + Self::NonFungible(asset) => asset.faucet_id(), } } @@ -147,7 +160,7 @@ impl Asset { /// Returns the asset's key encoded to a [`Word`]. pub fn to_key_word(&self) -> Word { - *self.vault_key().as_word() + self.vault_key().to_word() } /// Returns the asset's value encoded to a [`Word`]. @@ -183,29 +196,15 @@ impl Asset { } } -impl TryFrom<&Word> for Asset { - type Error = AssetError; - - fn try_from(value: &Word) -> Result { - (*value).try_into() +impl Ord for Asset { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.vault_key().cmp(&other.vault_key()) } } -impl TryFrom for Asset { - type Error = AssetError; - - fn try_from(value: Word) -> Result { - // Return an error if element 3 is not a valid account ID prefix, which cannot be checked by - // is_not_a_non_fungible_asset. - // Keep in mind serialized assets do _not_ carry the suffix required to reconstruct the full - // account identifier. - let prefix = AccountIdPrefix::try_from(value[3]) - .map_err(|err| AssetError::InvalidFaucetAccountId(Box::from(err)))?; - match prefix.account_type() { - AccountType::FungibleFaucet => FungibleAsset::try_from(value).map(Asset::from), - AccountType::NonFungibleFaucet => NonFungibleAsset::try_from(value).map(Asset::from), - _ => Err(AssetError::InvalidFaucetAccountIdPrefix(prefix)), - } +impl PartialOrd for Asset { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } @@ -356,7 +355,7 @@ mod tests { for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] { let serialized_asset = asset.to_bytes(); let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap(); - assert_eq!(prefix, asset.faucet_id_prefix()); + assert_eq!(prefix, asset.faucet_id()); } } } diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index 964cf44ebf..cdec409173 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; @@ -6,6 +5,7 @@ use core::fmt; use super::vault::AssetVaultKey; use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; use crate::account::AccountId; +use crate::asset::vault::AssetId; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{FieldElement, WORD_SIZE}; @@ -28,18 +28,9 @@ const FAUCET_ID_POS_BE: usize = 3; /// [`NonFungibleAsset`] itself does not contain the actual asset data. The container for this data /// is [`NonFungibleAssetDetails`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct NonFungibleAsset(Word); - -impl PartialOrd for NonFungibleAsset { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for NonFungibleAsset { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.cmp(&other.0) - } +pub struct NonFungibleAsset { + faucet_id: AccountId, + value: Word, } impl NonFungibleAsset { @@ -71,14 +62,21 @@ impl NonFungibleAsset { /// /// # Errors /// Returns an error if the provided faucet ID is not for a non-fungible asset faucet. - pub fn from_parts(faucet_id: AccountIdPrefix, mut data_hash: Word) -> Result { + pub fn from_parts(faucet_id: AccountId, value: Word) -> Result { if !matches!(faucet_id.account_type(), AccountType::NonFungibleFaucet) { return Err(AssetError::NonFungibleFaucetIdTypeMismatch(faucet_id)); } - data_hash[FAUCET_ID_POS_BE] = Felt::from(faucet_id); + Ok(Self { faucet_id, value }) + } + + /// TODO + pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { + if key.asset_id().suffix() != value[0] || key.asset_id().prefix() != value[1] { + return Err(AssetError::NonFungibleAssetIdMustMatchValue); + } - Ok(Self(data_hash)) + Self::from_parts(key.faucet_id(), value) } /// Creates a new [NonFungibleAsset] without checking its validity. @@ -86,8 +84,8 @@ impl NonFungibleAsset { /// # Safety /// This function requires that the provided value is a valid word encoding of a /// [NonFungibleAsset]. - pub unsafe fn new_unchecked(value: Word) -> NonFungibleAsset { - NonFungibleAsset(value) + pub unsafe fn new_unchecked(_value: Word) -> NonFungibleAsset { + unimplemented!() } // ACCESSORS @@ -109,57 +107,27 @@ impl NonFungibleAsset { /// asset and a fungible asset, as the former's vault key always has the fungible bit set to `0` /// and the latter's vault key always has the bit set to `1`. pub fn vault_key(&self) -> AssetVaultKey { - let mut vault_key = self.0; - - // Swap prefix of faucet ID with hash0. - vault_key.swap(0, FAUCET_ID_POS_BE); + let asset_id_suffix = self.value[0]; + let asset_id_prefix = self.value[1]; + let asset_id = AssetId::new(asset_id_suffix, asset_id_prefix); - // Set the fungible bit to zero. - vault_key[3] = - AccountIdPrefix::clear_fungible_bit(self.faucet_id_prefix().version(), vault_key[3]); - - AssetVaultKey::new_unchecked(vault_key) + AssetVaultKey::new(asset_id, self.faucet_id) } - /// Return ID prefix of the faucet which issued this asset. - pub fn faucet_id_prefix(&self) -> AccountIdPrefix { - AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS_BE]) - } - - /// Return ID of the faucet which issued this asset. + /// Returns the ID of the faucet which issued this asset. pub fn faucet_id(&self) -> AccountId { - todo!() + self.faucet_id } /// Returns the asset's key encoded to a [`Word`]. pub fn to_key_word(&self) -> Word { - *self.vault_key().as_word() + self.vault_key().to_word() } /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { todo!() } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - - /// Validates this non-fungible asset. - /// # Errors - /// Returns an error if: - /// - The faucet_id is not a valid non-fungible faucet ID. - /// - The most significant bit of the asset is not ZERO. - fn validate(&self) -> Result<(), AssetError> { - let faucet_id = AccountIdPrefix::try_from(self.0[FAUCET_ID_POS_BE]) - .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?; - - let account_type = faucet_id.account_type(); - if !matches!(account_type, AccountType::NonFungibleFaucet) { - return Err(AssetError::NonFungibleFaucetIdTypeMismatch(faucet_id)); - } - - Ok(()) - } } impl From for Asset { @@ -168,16 +136,7 @@ impl From for Asset { } } -impl TryFrom for NonFungibleAsset { - type Error = AssetError; - - fn try_from(value: Word) -> Result { - let asset = Self(value); - asset.validate()?; - Ok(asset) - } -} - +// TODO(expand_assets): Remove. impl fmt::Display for NonFungibleAsset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") @@ -191,10 +150,8 @@ impl Serializable for NonFungibleAsset { fn write_into(&self, target: &mut W) { // All assets should serialize their faucet ID at the first position to allow them to be // easily distinguishable during deserialization. - target.write(self.faucet_id_prefix()); - target.write(self.0[2]); - target.write(self.0[1]); - target.write(self.0[0]); + target.write(self.faucet_id()); + target.write(self.value); } fn get_size_hint(&self) -> usize { @@ -240,7 +197,7 @@ impl NonFungibleAsset { /// Unlike [NonFungibleAsset] struct, this struct contains full details of a non-fungible asset. #[derive(Debug, Clone, PartialEq, Eq)] pub struct NonFungibleAssetDetails { - faucet_id: AccountIdPrefix, + faucet_id: AccountId, asset_data: Vec, } @@ -249,7 +206,7 @@ impl NonFungibleAssetDetails { /// /// # Errors /// Returns an error if the provided faucet ID is not for a non-fungible asset faucet. - pub fn new(faucet_id: AccountIdPrefix, asset_data: Vec) -> Result { + pub fn new(faucet_id: AccountId, asset_data: Vec) -> Result { if !matches!(faucet_id.account_type(), AccountType::NonFungibleFaucet) { return Err(AssetError::NonFungibleFaucetIdTypeMismatch(faucet_id)); } @@ -258,7 +215,7 @@ impl NonFungibleAssetDetails { } /// Returns ID of the faucet which issued this asset. - pub fn faucet_id(&self) -> AccountIdPrefix { + pub fn faucet_id(&self) -> AccountId { self.faucet_id } diff --git a/crates/miden-protocol/src/asset/vault/asset_witness.rs b/crates/miden-protocol/src/asset/vault/asset_witness.rs index 0fea86edbf..c3333d4dbd 100644 --- a/crates/miden-protocol/src/asset/vault/asset_witness.rs +++ b/crates/miden-protocol/src/asset/vault/asset_witness.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use alloc::string::ToString; use miden_crypto::merkle::InnerNodeInfo; @@ -23,17 +24,12 @@ impl AssetWitness { /// # Errors /// /// Returns an error if: - /// - any of the entries in the SMT leaf is not a valid asset. - /// - any of the entries' vault keys does not match the expected vault key of the asset. + /// - any of the key value pairs in the SMT leaf do not form a valid asset. pub fn new(smt_proof: SmtProof) -> Result { - for (vault_key, asset) in smt_proof.leaf().entries() { - let asset = Asset::try_from(asset)?; - if *vault_key != asset.vault_key().into() { - return Err(AssetError::AssetVaultKeyMismatch { - actual: *vault_key, - expected: asset.vault_key().into(), - }); - } + for (vault_key, asset_value) in smt_proof.leaf().entries() { + // This ensures that vault key and value are consistent. + Asset::from_words(*vault_key, *asset_value) + .map_err(|err| AssetError::AssetWitnessInvalid(Box::new(err)))?; } Ok(Self(smt_proof)) @@ -72,8 +68,8 @@ impl AssetWitness { SmtLeaf::Multiple(kv_pairs) => kv_pairs, }; - entries.iter().map(|(_key, value)| { - Asset::try_from(value).expect("asset witness should track valid assets") + entries.iter().map(|(key, value)| { + Asset::from_words(*key, *value).expect("asset witness should track valid assets") }) } diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index 5f9f2a8963..3bdaee1958 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -64,9 +64,7 @@ impl AssetVault { pub fn new(assets: &[Asset]) -> Result { Ok(Self { asset_tree: Smt::with_entries( - assets.iter().map(|asset| { - (asset.vault_key().to_word(), asset.to_value_word()) - }), + assets.iter().map(|asset| (asset.vault_key().to_word(), asset.to_value_word())), ) .map_err(AssetVaultError::DuplicateAsset)?, }) @@ -83,12 +81,15 @@ impl AssetVault { /// Returns the asset corresponding to the provided asset vault key, or `None` if the asset /// doesn't exist. pub fn get(&self, asset_vault_key: AssetVaultKey) -> Option { - let word = self.asset_tree.get_value(&asset_vault_key.to_word()); + let asset_value = self.asset_tree.get_value(&asset_vault_key.to_word()); - if word.is_empty() { + if asset_value.is_empty() { None } else { - Some(Asset::try_from(word).expect("asset vault should only store valid assets")) + Some( + Asset::from_key_value(asset_vault_key, asset_value) + .expect("asset vault should only store valid assets"), + ) } } @@ -222,14 +223,13 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch current asset value from the tree and add the new asset to it. - let new: FungibleAsset = - match self.asset_tree.get_value(&asset.vault_key().to_word()) { - current if current == Smt::EMPTY_VALUE => asset, - current => { - let current = FungibleAsset::new_unchecked(current); - current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? - }, - }; + let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().to_word()) { + current if current == Smt::EMPTY_VALUE => asset, + current => { + let current = FungibleAsset::new_unchecked(current); + current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? + }, + }; self.asset_tree .insert(new.vault_key().to_word(), new.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; @@ -294,10 +294,7 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch the asset from the vault. - let new: FungibleAsset = match self - .asset_tree - .get_value(&asset.vault_key().to_word()) - { + let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().to_word()) { current if current == Smt::EMPTY_VALUE => { return Err(AssetVaultError::FungibleAssetNotFound(asset)); }, diff --git a/crates/miden-protocol/src/asset/vault/partial.rs b/crates/miden-protocol/src/asset/vault/partial.rs index 4491cfaef5..d99b386501 100644 --- a/crates/miden-protocol/src/asset/vault/partial.rs +++ b/crates/miden-protocol/src/asset/vault/partial.rs @@ -98,13 +98,16 @@ impl PartialVault { /// Returns an error if: /// - the key is not tracked by this partial SMT. pub fn get(&self, vault_key: AssetVaultKey) -> Result, MerkleError> { - self.partial_smt.get_value(&vault_key.into()).map(|word| { - if word.is_empty() { + self.partial_smt.get_value(&vault_key.into()).map(|asset_value| { + if asset_value.is_empty() { None } else { // SAFETY: If this returned a non-empty word, then it should be a valid asset, // because the vault should only track valid ones. - Some(Asset::try_from(word).expect("partial vault should only track valid assets")) + Some( + Asset::from_key_value(vault_key, asset_value) + .expect("partial vault should only track valid assets"), + ) } }) } @@ -136,17 +139,11 @@ impl PartialVault { fn validate_entries<'a>( entries: impl IntoIterator, ) -> Result<(), PartialAssetVaultError> { - for (vault_key, asset) in entries { - let asset = Asset::try_from(asset).map_err(|source| { - PartialAssetVaultError::InvalidAssetInSmt { entry: *asset, source } + for (vault_key, asset_value) in entries { + // This ensures that vault key and value are consistent. + Asset::from_words(*vault_key, *asset_value).map_err(|source| { + PartialAssetVaultError::InvalidAssetInSmt { entry: *asset_value, source } })?; - - if *vault_key != asset.vault_key().into() { - return Err(PartialAssetVaultError::AssetVaultKeyMismatch { - expected: asset.vault_key(), - actual: *vault_key, - }); - } } Ok(()) @@ -221,7 +218,7 @@ mod tests { let partial_smt = PartialSmt::from_proofs([proof.clone()])?; let err = PartialVault::try_from(partial_smt).unwrap_err(); - assert_matches!(err, PartialAssetVaultError::AssetVaultKeyMismatch { expected, actual } => { + assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, source } => { assert_eq!(actual, invalid_vault_key); assert_eq!(expected, asset.vault_key()); }); diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 3bd79883cf..52905958dc 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -31,7 +31,7 @@ use crate::account::{ StorageSlotName, }; use crate::address::AddressType; -use crate::asset::AssetVaultKey; +use crate::asset::{AssetId, AssetVaultKey}; use crate::batch::BatchId; use crate::block::BlockNumber; use crate::note::{NoteAssets, NoteAttachmentArray, NoteTag, NoteType, Nullifier}; @@ -458,14 +458,22 @@ pub enum AssetError { expected_ty = AccountType::FungibleFaucet )] FungibleFaucetIdTypeMismatch(AccountId), + #[error( + "asset ID prefix and suffix in a non-fungible asset's vault key must match indices 0 and 1 in the value" + )] + NonFungibleAssetIdMustMatchValue, + #[error( + "asset ID prefix and suffix in a fungible asset's vault key must be zero but was {0:?}" + )] + FungibleAssetIdMustBeZero(AssetId), #[error( "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets", id_type = .0.account_type(), expected_ty = AccountType::NonFungibleFaucet )] - NonFungibleFaucetIdTypeMismatch(AccountIdPrefix), - #[error("asset vault key {actual} does not match expected asset vault key {expected}")] - AssetVaultKeyMismatch { actual: Word, expected: Word }, + NonFungibleFaucetIdTypeMismatch(AccountId), + #[error("smt proof in asset witness contains invalid key or value")] + AssetWitnessInvalid(#[source] Box), } // TOKEN SYMBOL ERROR @@ -513,8 +521,6 @@ pub enum AssetVaultError { pub enum PartialAssetVaultError { #[error("provided SMT entry {entry} is not a valid asset")] InvalidAssetInSmt { entry: Word, source: AssetError }, - #[error("expected asset vault key to be {expected} but it was {actual}")] - AssetVaultKeyMismatch { expected: AssetVaultKey, actual: Word }, #[error("failed to add asset proof")] FailedToAddProof(#[source] MerkleError), #[error("asset is not tracked in the partial vault")] diff --git a/crates/miden-protocol/src/testing/asset.rs b/crates/miden-protocol/src/testing/asset.rs index b1b12223bd..8f96e759ee 100644 --- a/crates/miden-protocol/src/testing/asset.rs +++ b/crates/miden-protocol/src/testing/asset.rs @@ -91,7 +91,7 @@ impl NonFungibleAsset { /// Returns a mocked non-fungible asset, issued by [ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET]. pub fn mock(asset_data: &[u8]) -> Asset { let non_fungible_asset_details = NonFungibleAssetDetails::new( - AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap().prefix(), + AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(), asset_data.to_vec(), ) .unwrap(); diff --git a/crates/miden-protocol/src/transaction/inputs/mod.rs b/crates/miden-protocol/src/transaction/inputs/mod.rs index 1e879e660f..07afa359fa 100644 --- a/crates/miden-protocol/src/transaction/inputs/mod.rs +++ b/crates/miden-protocol/src/transaction/inputs/mod.rs @@ -348,8 +348,8 @@ impl TransactionInputs { let asset = smt_leaf .entries() .iter() - .find(|(key, _value)| key == asset_key.as_word()) - .map(|(_key, value)| Asset::try_from(value)) + .find(|(key, _value)| key == &asset_key.to_word()) + .map(|(_key, value)| Asset::from_key_value(asset_key, *value)) .transpose()?; Ok(asset) diff --git a/crates/miden-protocol/src/transaction/kernel/mod.rs b/crates/miden-protocol/src/transaction/kernel/mod.rs index 2aeb630bbb..22a7eeb3b3 100644 --- a/crates/miden-protocol/src/transaction/kernel/mod.rs +++ b/crates/miden-protocol/src/transaction/kernel/mod.rs @@ -9,7 +9,7 @@ use crate::account::{AccountHeader, AccountId}; use crate::assembly::Library; use crate::assembly::debuginfo::SourceManagerSync; use crate::assembly::{Assembler, DefaultSourceManager, KernelLibrary}; -use crate::asset::FungibleAsset; +use crate::asset::{AssetVaultKey, FungibleAsset}; use crate::block::BlockNumber; use crate::crypto::SequentialCommit; use crate::errors::TransactionOutputError; @@ -294,7 +294,9 @@ impl TransactionKernel { )); } - let fee = FungibleAsset::try_from(fee) + // TODO(expand_assets): fix when changing tx fee output + let vault_key = AssetVaultKey::try_from(Word::empty()).expect("TODO"); + let fee = FungibleAsset::from_key_value(vault_key, fee) .map_err(TransactionOutputError::FeeAssetNotFungibleAsset)?; Ok((output_notes_commitment, account_update_commitment, fee, expiration_block_num)) From 706e8017286f4eca7033744d7b0fd1c128c095e5 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 11:00:14 +0100 Subject: [PATCH 057/100] feat: update `Asset` docs --- crates/miden-protocol/src/asset/mod.rs | 72 +++++++++++++------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 17074dc878..e91a5b5502 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -22,69 +22,67 @@ mod token_symbol; pub use token_symbol::TokenSymbol; mod vault; -pub use vault::{AssetVault, AssetVaultKey, AssetWitness, PartialVault}; +pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; // ASSET // ================================================================================================ /// A fungible or a non-fungible asset. /// -/// All assets are encoded using a single word (4 elements) such that it is easy to determine the -/// type of an asset both inside and outside Miden VM. Specifically: +/// All assets are encoded as the vault key of the asset and its value, each represented as one word +/// (4 elements). This makes it is easy to determine the type of an asset both inside and outside +/// Miden VM. Specifically: /// -/// Element 1 of the asset will be: -/// - ZERO for a fungible asset. -/// - non-ZERO for a non-fungible asset. +/// The vault key of an asset contains the [`AccountId`](crate::account::AccountId) of the faucet +/// that issues the asset. It can be used to distinguish assets based on the encoded +/// [`AccountId::account_type`](crate::account::AccountId::account_type). In the vault keys of +/// assets, the account type bits at index 4 and 5 determine whether the asset is fungible or +/// non-fungible. /// -/// Element 3 of both asset types is an [`AccountIdPrefix`] or equivalently, the prefix of an -/// [`AccountId`](crate::account::AccountId), which can be used to distinguish assets -/// based on [`AccountIdPrefix::account_type`]. -/// -/// For element 3 of the vault keys of assets, the bit at index 5 (referred to as the -/// "fungible bit" will be): -/// - `1` for a fungible asset. -/// - `0` for a non-fungible asset. -/// -/// The above properties guarantee that there can never be a collision between a fungible and a +/// This property guarantees that there can never be a collision between a fungible and a /// non-fungible asset. /// /// The methodology for constructing fungible and non-fungible assets is described below. /// /// # Fungible assets /// -/// - A fungible asset's data layout is: `[amount, 0, faucet_id_suffix, faucet_id_prefix]`. +/// - A fungible asset's value layout is: `[amount, 0, 0, 0]`. /// - A fungible asset's vault key layout is: `[0, 0, faucet_id_suffix, faucet_id_prefix]`. /// -/// The most significant elements of a fungible asset are set to the prefix (`faucet_id_prefix`) and -/// suffix (`faucet_id_suffix`) of the ID of the faucet which issues the asset. This guarantees the -/// properties described above (the fungible bit is `1`). -/// -/// The least significant element is set to the amount of the asset. This amount cannot be greater -/// than [`FungibleAsset::MAX_AMOUNT`] and thus fits into a felt. +/// The most significant elements of a fungible asset's key are set to the prefix +/// (`faucet_id_prefix`) and suffix (`faucet_id_suffix`) of the ID of the faucet which issues the +/// asset. The asset ID limbs are set to zero, which means two instances of the same fungible asset +/// have the same asset key and will be merged together when stored in the same account's vault. /// -/// Elements 1 and 2 are set to ZERO. +/// The least significant element of the value is set to the amount of the asset and the remaining +/// felts are zero. This amount cannot be greater than [`FungibleAsset::MAX_AMOUNT`] and thus fits +/// into a felt. /// /// It is impossible to find a collision between two fungible assets issued by different faucets as -/// the faucet_id is included in the description of the asset and this is guaranteed to be different +/// the faucet ID is included in the description of the asset and this is guaranteed to be different /// for each faucet as per the faucet creation logic. /// /// # Non-fungible assets /// -/// - A non-fungible asset's data layout is: `[hash0, hash1, hash2, faucet_id_prefix]`. -/// - A non-fungible asset's vault key layout is: `[faucet_id_prefix, hash1, hash2, hash0']`, where -/// `hash0'` is equivalent to `hash0` with the fungible bit set to `0`. See -/// [`NonFungibleAsset::vault_key`] for more details. +/// - A non-fungible asset's data layout is: `[hash0, hash1, hash2, hash3]`. +/// - A non-fungible asset's vault key layout is: `[hash0, hash1, faucet_id_suffix, +/// faucet_id_prefix]`. /// -/// The 4 elements of non-fungible assets are computed as follows: -/// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field -/// elements: `[hash0, hash1, hash2, hash3]`. -/// - `hash3` is then replaced with the prefix of the faucet ID (`faucet_id_prefix`) which issues -/// the asset: `[hash0, hash1, hash2, faucet_id_prefix]`. +/// The most significant elements of a fungible asset's key are set to the prefix +/// (`faucet_id_prefix`) and suffix (`faucet_id_suffix`) of the ID of the faucet which issues the +/// asset. The asset ID limbs are set to hashes from the asset's value, which means two instances of +/// the same non-fungible asset will never have the same asset key and thus will never collide when +/// stored in the same account's vault. +/// +/// The 4 elements of non-fungible assets are computed by hashing the asset data. This compresses an +/// asset of an arbitrary length to 4 field elements: `[hash0, hash1, hash2, hash3]`. /// /// It is impossible to find a collision between two non-fungible assets issued by different faucets -/// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed -/// to be different as per the faucet creation logic. Collision resistance for non-fungible assets -/// issued by the same faucet is ~2^95. +/// as the faucet ID is included in the description of the non-fungible asset and this is guaranteed +/// to be different as per the faucet creation logic. +/// +/// Collision resistance for non-fungible assets issued by the same faucet is ~2^64, due to the +/// 128-bit asset ID that is unique per non-fungible asset. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Asset { Fungible(FungibleAsset), From 0c5bb288ba3b76bef1bba1d54800994f18980a09 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 11:03:55 +0100 Subject: [PATCH 058/100] chore: remove unused (non-)fungible asset builders --- crates/miden-protocol/src/testing/asset.rs | 84 +--------------------- 1 file changed, 1 insertion(+), 83 deletions(-) diff --git a/crates/miden-protocol/src/testing/asset.rs b/crates/miden-protocol/src/testing/asset.rs index 8f96e759ee..a89be85545 100644 --- a/crates/miden-protocol/src/testing/asset.rs +++ b/crates/miden-protocol/src/testing/asset.rs @@ -1,92 +1,10 @@ -use rand::Rng; -use rand::distr::StandardUniform; - -use crate::account::{AccountId, AccountIdPrefix, AccountType}; +use crate::account::AccountId; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; -use crate::errors::AssetError; use crate::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, }; -/// Builder for an `NonFungibleAssetDetails`, the builder can be configured and used multiplied -/// times. -#[derive(Debug, Clone)] -pub struct NonFungibleAssetDetailsBuilder { - faucet_id: AccountIdPrefix, - rng: T, -} - -/// Builder for an `FungibleAsset`, the builder can be configured and used multiplied times. -#[derive(Debug, Clone)] -pub struct FungibleAssetBuilder { - faucet_id: AccountId, - amount: u64, -} - -impl NonFungibleAssetDetailsBuilder { - pub fn new(faucet_id: AccountIdPrefix, rng: T) -> Result { - if !matches!(faucet_id.account_type(), AccountType::NonFungibleFaucet) { - return Err(AssetError::NonFungibleFaucetIdTypeMismatch(faucet_id)); - } - - Ok(Self { faucet_id, rng }) - } - - pub fn build(&mut self) -> Result { - let data = (&mut self.rng).sample_iter(StandardUniform).take(5).collect(); - NonFungibleAssetDetails::new(self.faucet_id, data) - } -} - -/// Builder for an `NonFungibleAsset`, the builder can be configured and used multiplied times. -#[derive(Debug, Clone)] -pub struct NonFungibleAssetBuilder { - details_builder: NonFungibleAssetDetailsBuilder, -} - -impl NonFungibleAssetBuilder { - pub fn new(faucet_id: AccountIdPrefix, rng: T) -> Result { - let details_builder = NonFungibleAssetDetailsBuilder::new(faucet_id, rng)?; - Ok(Self { details_builder }) - } - - pub fn build(&mut self) -> Result { - let details = self.details_builder.build()?; - NonFungibleAsset::new(&details) - } -} - -impl FungibleAssetBuilder { - pub const DEFAULT_AMOUNT: u64 = 10; - - pub fn new(faucet_id: AccountId) -> Result { - let account_type = faucet_id.account_type(); - if !matches!(account_type, AccountType::FungibleFaucet) { - return Err(AssetError::FungibleFaucetIdTypeMismatch(faucet_id)); - } - - Ok(Self { faucet_id, amount: Self::DEFAULT_AMOUNT }) - } - - pub fn amount(&mut self, amount: u64) -> Result<&mut Self, AssetError> { - if amount > FungibleAsset::MAX_AMOUNT { - return Err(AssetError::FungibleAssetAmountTooBig(amount)); - } - - self.amount = amount; - Ok(self) - } - - pub fn with_amount(&self, amount: u64) -> Result { - FungibleAsset::new(self.faucet_id, amount) - } - - pub fn build(&self) -> Result { - FungibleAsset::new(self.faucet_id, self.amount) - } -} - impl NonFungibleAsset { /// Returns a mocked non-fungible asset, issued by [ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET]. pub fn mock(asset_data: &[u8]) -> Asset { From 883cdb344657b7cb1c38f8266f01d0fc07264367 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 11:49:43 +0100 Subject: [PATCH 059/100] feat: refactor asset serialization and tests --- .../src/account/account_id/id_prefix.rs | 16 +-- .../src/account/account_id/v0/mod.rs | 3 - .../miden-protocol/src/account/delta/mod.rs | 3 +- .../miden-protocol/src/account/delta/vault.rs | 28 ++-- crates/miden-protocol/src/asset/fungible.rs | 128 +++++++++++++----- crates/miden-protocol/src/asset/mod.rs | 127 +++++++---------- .../miden-protocol/src/asset/nonfungible.rs | 128 ++++++++++++------ .../src/asset/vault/asset_id.rs | 12 ++ .../src/asset/vault/asset_witness.rs | 14 +- crates/miden-protocol/src/asset/vault/mod.rs | 91 +++++++------ .../miden-protocol/src/asset/vault/partial.rs | 7 +- .../src/asset/vault/vault_key.rs | 73 ++++------ crates/miden-protocol/src/errors/mod.rs | 15 +- crates/miden-protocol/src/note/assets.rs | 2 +- 14 files changed, 349 insertions(+), 298 deletions(-) diff --git a/crates/miden-protocol/src/account/account_id/id_prefix.rs b/crates/miden-protocol/src/account/account_id/id_prefix.rs index 46207bcc85..cc3c1d8441 100644 --- a/crates/miden-protocol/src/account/account_id/id_prefix.rs +++ b/crates/miden-protocol/src/account/account_id/id_prefix.rs @@ -4,7 +4,7 @@ use core::fmt; use super::v0; use crate::Felt; use crate::account::account_id::AccountIdPrefixV0; -use crate::account::{AccountIdV0, AccountIdVersion, AccountStorageMode, AccountType}; +use crate::account::{AccountIdVersion, AccountStorageMode, AccountType}; use crate::errors::AccountIdError; use crate::utils::serde::{ ByteReader, @@ -153,20 +153,6 @@ impl AccountIdPrefix { AccountIdPrefix::V0(id_prefix) => id_prefix.to_hex(), } } - - /// Returns `felt` with the fungible bit set to zero. The version must be passed as the location - /// of the fungible bit may depend on the underlying account ID version. - pub(crate) fn clear_fungible_bit(version: AccountIdVersion, felt: Felt) -> Felt { - match version { - AccountIdVersion::Version0 => { - // Set the fungible bit to zero by taking the bitwise `and` of the felt with the - // inverted is_faucet mask. - let clear_fungible_bit_mask = !AccountIdV0::IS_FAUCET_MASK; - Felt::try_from(felt.as_int() & clear_fungible_bit_mask) - .expect("felt should still be valid as we cleared a bit and did not set any") - }, - } - } } // CONVERSIONS FROM ACCOUNT ID PREFIX diff --git a/crates/miden-protocol/src/account/account_id/v0/mod.rs b/crates/miden-protocol/src/account/account_id/v0/mod.rs index 34ad3ebbb9..b5ff428ac6 100644 --- a/crates/miden-protocol/src/account/account_id/v0/mod.rs +++ b/crates/miden-protocol/src/account/account_id/v0/mod.rs @@ -61,9 +61,6 @@ impl AccountIdV0 { pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT; pub(crate) const STORAGE_MODE_SHIFT: u64 = 6; - /// The bit at index 5 of the prefix encodes whether the account is a faucet. - pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT; - // CONSTRUCTORS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-protocol/src/account/delta/mod.rs b/crates/miden-protocol/src/account/delta/mod.rs index 91da8fafcf..90832696a5 100644 --- a/crates/miden-protocol/src/account/delta/mod.rs +++ b/crates/miden-protocol/src/account/delta/mod.rs @@ -697,8 +697,7 @@ mod tests { AccountIdBuilder::new() .account_type(AccountType::NonFungibleFaucet) .storage_mode(AccountStorageMode::Public) - .build_with_rng(&mut rand::rng()) - .prefix(), + .build_with_rng(&mut rand::rng()), vec![6], ) .unwrap(), diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index e950025360..9004e196b2 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -132,7 +132,7 @@ impl AccountVaultDelta { /// Returns an iterator over the added assets in this delta. pub fn added_assets(&self) -> impl Iterator + '_ { - use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; + use crate::asset::{Asset, FungibleAsset}; self.fungible .0 .iter() @@ -140,14 +140,16 @@ impl AccountVaultDelta { .map(|(&faucet_id, &diff)| { Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) }) - .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Add).map(|key| { - Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.to_value_word()) }) - })) + .chain( + self.non_fungible + .filter_by_action(NonFungibleDeltaAction::Add) + .map(Asset::NonFungible), + ) } /// Returns an iterator over the removed assets in this delta. pub fn removed_assets(&self) -> impl Iterator + '_ { - use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; + use crate::asset::{Asset, FungibleAsset}; self.fungible .0 .iter() @@ -155,9 +157,11 @@ impl AccountVaultDelta { .map(|(&faucet_id, &diff)| { Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) }) - .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Remove).map(|key| { - Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.to_value_word()) }) - })) + .chain( + self.non_fungible + .filter_by_action(NonFungibleDeltaAction::Remove) + .map(Asset::NonFungible), + ) } } @@ -563,7 +567,7 @@ pub enum NonFungibleDeltaAction { #[cfg(test)] mod tests { use super::{AccountVaultDelta, Deserializable, Serializable}; - use crate::account::{AccountId, AccountIdPrefix}; + use crate::account::AccountId; use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; use crate::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, @@ -645,11 +649,11 @@ mod tests { /// Creates an [AccountVaultDelta] with an optional [NonFungibleAsset] delta. This delta /// will be added if `Some(true)`, removed for `Some(false)` and missing for `None`. fn create_delta_with_non_fungible( - account_id_prefix: AccountIdPrefix, + account_id: AccountId, added: Option, ) -> AccountVaultDelta { let asset: Asset = NonFungibleAsset::new( - &NonFungibleAssetDetails::new(account_id_prefix, vec![1, 2, 3]).unwrap(), + &NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(), ) .unwrap() .into(); @@ -661,7 +665,7 @@ mod tests { } } - let account_id = NonFungibleAsset::mock_issuer().prefix(); + let account_id = NonFungibleAsset::mock_issuer(); let mut delta_x = create_delta_with_non_fungible(account_id, x); let delta_y = create_delta_with_non_fungible(account_id, y); diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index d0bf48f2c9..6c93173321 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -3,7 +3,7 @@ use core::fmt; use super::vault::AssetVaultKey; use super::{AccountType, Asset, AssetError, Word}; -use crate::account::{AccountId, AccountIdPrefix}; +use crate::account::AccountId; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -36,18 +36,20 @@ impl FungibleAsset { /// The serialized size of a [`FungibleAsset`] in bytes. /// - /// Currently an account ID (15 bytes) plus an amount (u64). + /// An account ID (15 bytes) plus an amount (u64). pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + core::mem::size_of::(); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- + /// Returns a fungible asset instantiated with the provided faucet ID and amount. /// /// # Errors + /// /// Returns an error if: - /// - The faucet_id is not a valid fungible faucet ID. + /// - The faucet ID is not a valid fungible faucet ID. /// - The provided amount is greater than [`FungibleAsset::MAX_AMOUNT`]. - pub const fn new(faucet_id: AccountId, amount: u64) -> Result { + pub fn new(faucet_id: AccountId, amount: u64) -> Result { if !matches!(faucet_id.account_type(), AccountType::FungibleFaucet) { return Err(AssetError::FungibleFaucetIdTypeMismatch(faucet_id)); } @@ -59,21 +61,40 @@ impl FungibleAsset { Ok(Self { faucet_id, amount }) } - /// TODO + /// Creates a fungible asset from the provided key and value. + /// + /// # Errors + /// + /// Returns an error if: + /// - The provided key does not contain a valid faucet ID. + /// - The provided key's asset ID limbs are not zero. + /// - The faucet ID is not a fungible faucet ID. + /// - The provided value's amount is greater than [`FungibleAsset::MAX_AMOUNT`] or its three + /// most significant elements are not zero. pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { if key.asset_id().prefix() != Felt::ZERO || key.asset_id().suffix() != Felt::ZERO { return Err(AssetError::FungibleAssetIdMustBeZero(key.asset_id())); } + if value[1] != Felt::ZERO || value[2] != Felt::ZERO || value[3] != Felt::ZERO { + return Err(AssetError::FungibleAssetValueMostSignificantElementsMustBeZero(value)); + } + Self::new(key.faucet_id(), value[0].as_int()) } - /// Creates a new [FungibleAsset] without checking its validity. - pub(crate) fn new_unchecked(value: Word) -> FungibleAsset { - FungibleAsset { - faucet_id: AccountId::new_unchecked([value[3], value[2]]), - amount: value[0].as_int(), - } + /// Creates a fungible asset from the provided key and value. + /// + /// Prefer [`Self::from_key_value`] for more type safety. + /// + /// # Errors + /// + /// Returns an error if: + /// - The provided key does not contain a valid faucet ID. + /// - [`Self::from_key_value`] fails. + pub fn from_key_value_words(key: Word, value: Word) -> Result { + let vault_key = AssetVaultKey::try_from(key)?; + Self::from_key_value(vault_key, value) } // PUBLIC ACCESSORS @@ -106,7 +127,13 @@ impl FungibleAsset { /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { - todo!() + Word::new([ + Felt::try_from(self.amount) + .expect("fungible asset should only allow amounts that fit into a felt"), + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + ]) } // OPERATIONS @@ -172,6 +199,7 @@ impl From for Asset { impl fmt::Display for FungibleAsset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Replace with hex representation? write!(f, "{self:?}") } } @@ -194,30 +222,18 @@ impl Serializable for FungibleAsset { impl Deserializable for FungibleAsset { fn read_from(source: &mut R) -> Result { - let faucet_id_prefix: AccountIdPrefix = source.read()?; - FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) + let faucet_id: AccountId = source.read()?; + FungibleAsset::deserialize_with_faucet_id(faucet_id, source) } } impl FungibleAsset { - /// Deserializes a [`FungibleAsset`] from an [`AccountIdPrefix`] and the remaining data from the - /// given `source`. - pub(super) fn deserialize_with_faucet_id_prefix( - faucet_id_prefix: AccountIdPrefix, + /// Deserializes a [`FungibleAsset`] from an [`AccountId`] and the remaining data from the given + /// `source`. + pub(super) fn deserialize_with_faucet_id( + faucet_id: AccountId, source: &mut R, ) -> Result { - // The 8 bytes of the prefix have already been read, so we only need to read the remaining 7 - // bytes of the account ID's 15 total bytes. - let suffix_bytes: [u8; 7] = source.read()?; - // Convert prefix back to bytes so we can call the TryFrom<[u8; 15]> impl. - let prefix_bytes: [u8; 8] = faucet_id_prefix.into(); - let mut id_bytes: [u8; 15] = [0; 15]; - id_bytes[..8].copy_from_slice(&prefix_bytes); - id_bytes[8..].copy_from_slice(&suffix_bytes); - - let faucet_id = AccountId::try_from(id_bytes) - .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; - let amount: u64 = source.read()?; FungibleAsset::new(faucet_id, amount) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) @@ -229,8 +245,11 @@ impl FungibleAsset { #[cfg(test)] mod tests { + use assert_matches::assert_matches; + use super::*; use crate::account::AccountId; + use crate::asset::AssetId; use crate::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, @@ -241,7 +260,34 @@ mod tests { }; #[test] - fn test_fungible_asset_serde() { + fn fungible_asset_from_key_value_fails_on_invalid_asset_id() -> anyhow::Result<()> { + let invalid_key = AssetVaultKey::new( + AssetId::new(1u32.into(), 2u32.into()), + ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into()?, + ); + + let err = + FungibleAsset::from_key_value(invalid_key, FungibleAsset::mock(5).to_value_word()) + .unwrap_err(); + assert_matches!(err, AssetError::FungibleAssetIdMustBeZero(_)); + + Ok(()) + } + + #[test] + fn fungible_asset_from_key_value_fails_on_invalid_value() -> anyhow::Result<()> { + let asset = FungibleAsset::mock(42); + let mut invalid_value = asset.to_value_word(); + invalid_value[2] = Felt::from(5u32); + + let err = FungibleAsset::from_key_value(asset.vault_key(), invalid_value).unwrap_err(); + assert_matches!(err, AssetError::FungibleAssetValueMostSignificantElementsMustBeZero(_)); + + Ok(()) + } + + #[test] + fn test_fungible_asset_serde() -> anyhow::Result<()> { for fungible_account_id in [ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -255,6 +301,15 @@ mod tests { fungible_asset, FungibleAsset::read_from_bytes(&fungible_asset.to_bytes()).unwrap() ); + assert_eq!(fungible_asset.to_bytes().len(), fungible_asset.get_size_hint()); + + assert_eq!( + fungible_asset, + FungibleAsset::from_key_value_words( + fungible_asset.to_key_word(), + fungible_asset.to_value_word() + )? + ) } let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(); @@ -270,5 +325,16 @@ mod tests { asset_bytes[0..15].copy_from_slice(&non_fungible_faucet_id.to_bytes()); let err = FungibleAsset::read_from_bytes(&asset_bytes).unwrap_err(); assert!(matches!(err, DeserializationError::InvalidValue(_))); + + Ok(()) + } + + #[test] + fn test_vault_key_for_fungible_asset() { + let asset = FungibleAsset::mock(34); + + assert_eq!(asset.vault_key().faucet_id(), FungibleAsset::mock_issuer()); + assert_eq!(asset.vault_key().asset_id().prefix().as_int(), 0); + assert_eq!(asset.vault_key().asset_id().suffix().as_int(), 0); } } diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index e91a5b5502..3cc4053113 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -7,8 +7,8 @@ use super::utils::serde::{ DeserializationError, Serializable, }; -use super::{Felt, Hasher, Word}; -use crate::account::{AccountId, AccountIdPrefix}; +use super::{Felt, Word}; +use crate::account::AccountId; mod fungible; @@ -90,7 +90,12 @@ pub enum Asset { } impl Asset { - /// TODO(expand_assets) + /// Creates an asset from the provided key and value. + /// + /// # Errors + /// + /// Returns an error if: + /// - [`FungibleAsset::from_key_value`] or [`NonFungibleAsset::from_key_value`] fails. pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { if matches!(key.faucet_id().account_type(), AccountType::FungibleFaucet) { FungibleAsset::from_key_value(key, value).map(Asset::Fungible) @@ -99,23 +104,20 @@ impl Asset { } } - /// TODO(expand_assets) + /// Creates an asset from the provided key and value. /// /// Prefer [`Self::from_key_value`] for more type safety. - pub fn from_words(key: Word, value: Word) -> Result { + /// + /// # Errors + /// + /// Returns an error if: + /// - The provided key does not contain a valid faucet ID. + /// - [`Self::from_key_value`] fails. + pub fn from_key_value_words(key: Word, value: Word) -> Result { let vault_key = AssetVaultKey::try_from(key)?; Self::from_key_value(vault_key, value) } - /// Creates a new [Asset] without checking its validity. - pub(crate) fn new_unchecked(value: Word) -> Asset { - if is_not_a_non_fungible_asset(value) { - Asset::Fungible(FungibleAsset::new_unchecked(value)) - } else { - Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(value) }) - } - } - /// Returns true if this asset is the same as the specified asset. /// /// Two assets are defined to be the same if: @@ -131,12 +133,12 @@ impl Asset { } /// Returns true if this asset is a fungible asset. - pub const fn is_fungible(&self) -> bool { + pub fn is_fungible(&self) -> bool { matches!(self, Self::Fungible(_)) } /// Returns true if this asset is a non fungible asset. - pub const fn is_non_fungible(&self) -> bool { + pub fn is_non_fungible(&self) -> bool { matches!(self, Self::NonFungible(_)) } @@ -227,47 +229,24 @@ impl Serializable for Asset { impl Deserializable for Asset { fn read_from(source: &mut R) -> Result { - // Both asset types have their faucet ID prefix as the first element, so we can use it to - // inspect what type of asset it is. - let faucet_id_prefix: AccountIdPrefix = source.read()?; + // Both asset types have their faucet ID as the first element, so we can use it to inspect + // what type of asset it is. + let faucet_id: AccountId = source.read()?; - match faucet_id_prefix.account_type() { + match faucet_id.account_type() { AccountType::FungibleFaucet => { - FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) - .map(Asset::from) + FungibleAsset::deserialize_with_faucet_id(faucet_id, source).map(Asset::from) }, AccountType::NonFungibleFaucet => { - NonFungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) - .map(Asset::from) + NonFungibleAsset::deserialize_with_faucet_id(faucet_id, source).map(Asset::from) }, other_type => Err(DeserializationError::InvalidValue(format!( - "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}" + "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type}" ))), } } } -// HELPER FUNCTIONS -// ================================================================================================ - -/// Returns `true` if asset in [Word] is not a non-fungible asset. -/// -/// Note: this does not mean that the word is a fungible asset as the word may contain a value -/// which is not a valid asset. -fn is_not_a_non_fungible_asset(asset: Word) -> bool { - match AccountIdPrefix::try_from(asset[3]) { - Ok(prefix) => { - matches!(prefix.account_type(), AccountType::FungibleFaucet) - }, - Err(_err) => { - #[cfg(debug_assertions)] - panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {_err}"); - #[cfg(not(debug_assertions))] - false - }, - } -} - // TESTS // ================================================================================================ @@ -277,7 +256,7 @@ mod tests { use miden_crypto::utils::{Deserializable, Serializable}; use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; - use crate::account::{AccountId, AccountIdPrefix}; + use crate::account::AccountId; use crate::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, @@ -289,8 +268,9 @@ mod tests { ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, }; + /// Tests the serialization roundtrip for assets for assets <-> bytes and assets <-> words. #[test] - fn test_asset_serde() { + fn test_asset_serde() -> anyhow::Result<()> { for fungible_account_id in [ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -301,6 +281,13 @@ mod tests { let account_id = AccountId::try_from(fungible_account_id).unwrap(); let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into(); assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap()); + assert_eq!( + fungible_asset, + Asset::from_key_value_words( + fungible_asset.to_key_word(), + fungible_asset.to_value_word() + )?, + ); } for non_fungible_account_id in [ @@ -309,50 +296,32 @@ mod tests { ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, ] { let account_id = AccountId::try_from(non_fungible_account_id).unwrap(); - let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap(); + let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(); let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into(); assert_eq!( non_fungible_asset, Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap() ); + assert_eq!( + non_fungible_asset, + Asset::from_key_value_words( + non_fungible_asset.to_key_word(), + non_fungible_asset.to_value_word() + )? + ); } + + Ok(()) } - // TODO(expand_assets): Replace with from_key_value test. - // #[test] - // fn test_new_unchecked() { - // for fungible_account_id in [ - // ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, - // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, - // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, - // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, - // ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, - // ] { - // let account_id = AccountId::try_from(fungible_account_id).unwrap(); - // let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into(); - // assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset))); - // } - - // for non_fungible_account_id in [ - // ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, - // ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, - // ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, - // ] { - // let account_id = AccountId::try_from(non_fungible_account_id).unwrap(); - // let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, - // 3]).unwrap(); let non_fungible_asset: Asset = - // NonFungibleAsset::new(&details).unwrap().into(); assert_eq!(non_fungible_asset, - // Asset::new_unchecked(Word::from(non_fungible_asset))); } - // } - - /// This test asserts that account ID's prefix is serialized in the first felt of assets. + /// This test asserts that account ID's is serialized in the first felt of assets. /// Asset deserialization relies on that fact and if this changes the serialization must /// be updated. #[test] - fn test_account_id_prefix_is_in_first_serialized_felt() { + fn test_account_id_prefix_is_serialized_first() { for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] { let serialized_asset = asset.to_bytes(); - let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap(); + let prefix = AccountId::read_from_bytes(&serialized_asset).unwrap(); assert_eq!(prefix, asset.faucet_id()); } } diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index cdec409173..450f1a2188 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -3,14 +3,11 @@ use alloc::vec::Vec; use core::fmt; use super::vault::AssetVaultKey; -use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; +use super::{AccountType, Asset, AssetError, Word}; +use crate::Hasher; use crate::account::AccountId; use crate::asset::vault::AssetId; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use crate::{FieldElement, WORD_SIZE}; - -/// Position of the faucet_id inside the [`NonFungibleAsset`] word having fields in BigEndian. -const FAUCET_ID_POS_BE: usize = 3; // NON-FUNGIBLE ASSET // ================================================================================================ @@ -39,8 +36,8 @@ impl NonFungibleAsset { /// The serialized size of a [`NonFungibleAsset`] in bytes. /// - /// Currently represented as a word. - pub const SERIALIZED_SIZE: usize = Felt::ELEMENT_BYTES * WORD_SIZE; + /// An account ID (15 bytes) plus a word (32 bytes). + pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + Word::SERIALIZED_SIZE; // CONSTRUCTORS // -------------------------------------------------------------------------------------------- @@ -70,22 +67,38 @@ impl NonFungibleAsset { Ok(Self { faucet_id, value }) } - /// TODO + /// Creates a non-fungible asset from the provided key and value. + /// + /// # Errors + /// + /// Returns an error if: + /// - The provided key does not contain a valid faucet ID. + /// - The provided key's asset ID limbs are not equal to the provided value's first and second + /// element. + /// - The faucet ID is not a non-fungible faucet ID. pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { if key.asset_id().suffix() != value[0] || key.asset_id().prefix() != value[1] { - return Err(AssetError::NonFungibleAssetIdMustMatchValue); + return Err(AssetError::NonFungibleAssetIdMustMatchValue { + asset_id: key.asset_id(), + value, + }); } Self::from_parts(key.faucet_id(), value) } - /// Creates a new [NonFungibleAsset] without checking its validity. + /// Creates a non-fungible asset from the provided key and value. + /// + /// Prefer [`Self::from_key_value`] for more type safety. /// - /// # Safety - /// This function requires that the provided value is a valid word encoding of a - /// [NonFungibleAsset]. - pub unsafe fn new_unchecked(_value: Word) -> NonFungibleAsset { - unimplemented!() + /// # Errors + /// + /// Returns an error if: + /// - The provided key does not contain a valid faucet ID. + /// - [`Self::from_key_value`] fails. + pub fn from_key_value_words(key: Word, value: Word) -> Result { + let vault_key = AssetVaultKey::try_from(key)?; + Self::from_key_value(vault_key, value) } // ACCESSORS @@ -126,23 +139,23 @@ impl NonFungibleAsset { /// Returns the asset's value encoded to a [`Word`]. pub fn to_value_word(&self) -> Word { - todo!() + self.value } } -impl From for Asset { - fn from(asset: NonFungibleAsset) -> Self { - Asset::NonFungible(asset) - } -} - -// TODO(expand_assets): Remove. impl fmt::Display for NonFungibleAsset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Replace with hex representation? write!(f, "{self:?}") } } +impl From for Asset { + fn from(asset: NonFungibleAsset) -> Self { + Asset::NonFungible(asset) + } +} + // SERIALIZATION // ================================================================================================ @@ -161,31 +174,24 @@ impl Serializable for NonFungibleAsset { impl Deserializable for NonFungibleAsset { fn read_from(source: &mut R) -> Result { - let faucet_id_prefix: AccountIdPrefix = source.read()?; + let faucet_id: AccountId = source.read()?; - Self::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) + Self::deserialize_with_faucet_id(faucet_id, source) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } impl NonFungibleAsset { - /// Deserializes a [`NonFungibleAsset`] from an [`AccountIdPrefix`] and the remaining data from - /// the given `source`. - pub(super) fn deserialize_with_faucet_id_prefix( - faucet_id_prefix: AccountIdPrefix, + /// Deserializes a [`NonFungibleAsset`] from an [`AccountId`] and the remaining data from the + /// given `source`. + pub(super) fn deserialize_with_faucet_id( + faucet_id: AccountId, source: &mut R, ) -> Result { - let hash_2: Felt = source.read()?; - let hash_1: Felt = source.read()?; - let hash_0: Felt = source.read()?; - - // The last felt in the data_hash will be replaced by the faucet id, so we can set it to - // zero here. - NonFungibleAsset::from_parts( - faucet_id_prefix, - Word::from([hash_0, hash_1, hash_2, Felt::ZERO]), - ) - .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + let value: Word = source.read()?; + + NonFungibleAsset::from_parts(faucet_id, value) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -242,32 +248,66 @@ mod tests { }; #[test] - fn test_non_fungible_asset_serde() { + fn fungible_asset_from_key_value_fails_on_invalid_asset_id() -> anyhow::Result<()> { + let invalid_key = AssetVaultKey::new( + AssetId::new(1u32.into(), 2u32.into()), + ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET.try_into()?, + ); + let err = + NonFungibleAsset::from_key_value(invalid_key, Word::from([4, 5, 6, 7u32])).unwrap_err(); + + assert_matches!(err, AssetError::NonFungibleAssetIdMustMatchValue { .. }); + + Ok(()) + } + + #[test] + fn test_non_fungible_asset_serde() -> anyhow::Result<()> { for non_fungible_account_id in [ ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, ] { let account_id = AccountId::try_from(non_fungible_account_id).unwrap(); - let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap(); + let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(); let non_fungible_asset = NonFungibleAsset::new(&details).unwrap(); assert_eq!( non_fungible_asset, NonFungibleAsset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap() ); + assert_eq!(non_fungible_asset.to_bytes().len(), non_fungible_asset.get_size_hint()); + + assert_eq!( + non_fungible_asset, + NonFungibleAsset::from_key_value_words( + non_fungible_asset.to_key_word(), + non_fungible_asset.to_value_word() + )? + ) } let account = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(); - let details = NonFungibleAssetDetails::new(account.prefix(), vec![4, 5, 6, 7]).unwrap(); + let details = NonFungibleAssetDetails::new(account, vec![4, 5, 6, 7]).unwrap(); let asset = NonFungibleAsset::new(&details).unwrap(); let mut asset_bytes = asset.to_bytes(); let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(); // Set invalid Faucet ID Prefix. - asset_bytes[0..8].copy_from_slice(&fungible_faucet_id.prefix().to_bytes()); + asset_bytes[0..AccountId::SERIALIZED_SIZE].copy_from_slice(&fungible_faucet_id.to_bytes()); let err = NonFungibleAsset::read_from_bytes(&asset_bytes).unwrap_err(); assert_matches!(err, DeserializationError::InvalidValue(msg) if msg.contains("must be of type NonFungibleFaucet")); + + Ok(()) + } + + #[test] + fn test_vault_key_for_non_fungible_asset() { + let asset = NonFungibleAsset::mock(&[42]); + + assert_eq!(asset.vault_key().faucet_id(), NonFungibleAsset::mock_issuer()); + assert_eq!(asset.vault_key().asset_id().suffix(), asset.to_value_word()[0]); + assert_eq!(asset.vault_key().asset_id().prefix(), asset.to_value_word()[1]); } } diff --git a/crates/miden-protocol/src/asset/vault/asset_id.rs b/crates/miden-protocol/src/asset/vault/asset_id.rs index 517b5ec340..441ca4c5ad 100644 --- a/crates/miden-protocol/src/asset/vault/asset_id.rs +++ b/crates/miden-protocol/src/asset/vault/asset_id.rs @@ -1,3 +1,5 @@ +use core::fmt::Display; + use crate::Felt; /// The [`AssetId`] in an [`AssetVaultKey`](crate::asset::AssetVaultKey) distinguishes different @@ -24,3 +26,13 @@ impl AssetId { self.prefix } } + +impl Display for AssetId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!( + "0x{:016x}{:016x}", + self.prefix().as_int(), + self.suffix().as_int() + )) + } +} diff --git a/crates/miden-protocol/src/asset/vault/asset_witness.rs b/crates/miden-protocol/src/asset/vault/asset_witness.rs index c3333d4dbd..b11bcb2ed3 100644 --- a/crates/miden-protocol/src/asset/vault/asset_witness.rs +++ b/crates/miden-protocol/src/asset/vault/asset_witness.rs @@ -28,7 +28,7 @@ impl AssetWitness { pub fn new(smt_proof: SmtProof) -> Result { for (vault_key, asset_value) in smt_proof.leaf().entries() { // This ensures that vault key and value are consistent. - Asset::from_words(*vault_key, *asset_value) + Asset::from_key_value_words(*vault_key, *asset_value) .map_err(|err| AssetError::AssetWitnessInvalid(Box::new(err)))?; } @@ -69,7 +69,8 @@ impl AssetWitness { }; entries.iter().map(|(key, value)| { - Asset::from_words(*key, *value).expect("asset witness should track valid assets") + Asset::from_key_value_words(*key, *value) + .expect("asset witness should track valid assets") }) } @@ -128,7 +129,9 @@ mod tests { let err = AssetWitness::new(proof).unwrap_err(); - assert_matches!(err, AssetError::InvalidFaucetAccountId(_)); + assert_matches!(err, AssetError::AssetWitnessInvalid(source) => { + assert_matches!(*source, AssetError::InvalidFaucetAccountId(_)); + }); Ok(()) } @@ -148,9 +151,8 @@ mod tests { let err = AssetWitness::new(proof).unwrap_err(); - assert_matches!(err, AssetError::AssetVaultKeyMismatch { actual, expected } => { - assert_eq!(actual, fungible_asset.vault_key().into()); - assert_eq!(expected, non_fungible_asset.vault_key().into()); + assert_matches!(err, AssetError::AssetWitnessInvalid(source) => { + assert_matches!(*source, AssetError::FungibleAssetValueMostSignificantElementsMustBeZero(_)); }); Ok(()) diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index 3bdaee1958..8514a4621c 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -112,21 +112,22 @@ impl AssetVault { return Err(AssetVaultError::NotAFungibleFaucetId(faucet_id)); } - // if the tree value is [0, 0, 0, 0], the asset is not stored in the vault - match self.asset_tree.get_value( - &AssetVaultKey::new_fungible(faucet_id) - .expect("faucet ID should be of type fungible") - .into(), - ) { - asset if asset == Smt::EMPTY_VALUE => Ok(0), - asset => Ok(FungibleAsset::new_unchecked(asset).amount()), - } + let vault_key = + AssetVaultKey::new_fungible(faucet_id).expect("faucet ID should be of type fungible"); + let asset_value = self.asset_tree.get_value(&vault_key.to_word()); + let asset = FungibleAsset::from_key_value(vault_key, asset_value) + .expect("asset vault should only store valid assets"); + + Ok(asset.amount()) } /// Returns an iterator over the assets stored in the vault. pub fn assets(&self) -> impl Iterator + '_ { // SAFETY: The asset tree tracks only valid assets. - self.asset_tree.entries().map(|(_key, value)| Asset::new_unchecked(*value)) + self.asset_tree.entries().map(|(key, value)| { + Asset::from_key_value_words(*key, *value) + .expect("asset vault should only store valid assets") + }) } /// Returns an iterator over the inner nodes of the underlying [`Smt`]. @@ -171,7 +172,7 @@ impl AssetVault { /// /// # Errors /// Returns an error: - /// - If the total value of assets is greater than or equal to 2^63. + /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`]. /// - If the delta contains an addition/subtraction for a fungible asset that is not stored in /// the vault. /// - If the delta contains a non-fungible asset removal that is not stored in the vault. @@ -202,7 +203,7 @@ impl AssetVault { /// Add the specified asset to the vault. /// /// # Errors - /// - If the total value of two fungible assets is greater than or equal to 2^63. + /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`]. /// - If the vault already contains the same non-fungible asset. /// - The maximum number of leaves per asset is exceeded. pub fn add_asset(&mut self, asset: Asset) -> Result { @@ -216,26 +217,26 @@ impl AssetVault { /// issued by the same faucet, the amounts are added together. /// /// # Errors - /// - If the total value of assets is greater than or equal to 2^63. + /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`]. /// - The maximum number of leaves per asset is exceeded. fn add_fungible_asset( &mut self, - asset: FungibleAsset, + other_asset: FungibleAsset, ) -> Result { - // fetch current asset value from the tree and add the new asset to it. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().to_word()) { - current if current == Smt::EMPTY_VALUE => asset, - current => { - let current = FungibleAsset::new_unchecked(current); - current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? - }, - }; + let current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word()); + let current_asset = + FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value) + .expect("asset vault should store valid assets"); + + let new_asset = current_asset + .add(other_asset) + .map_err(AssetVaultError::AddFungibleAssetBalanceError)?; + self.asset_tree - .insert(new.vault_key().to_word(), new.to_value_word()) + .insert(new_asset.vault_key().to_word(), new_asset.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; - // return the new asset - Ok(new) + Ok(new_asset) } /// Add the specified non-fungible asset to the vault. @@ -291,30 +292,32 @@ impl AssetVault { /// - The maximum number of leaves per asset is exceeded. fn remove_fungible_asset( &mut self, - asset: FungibleAsset, + other_asset: FungibleAsset, ) -> Result { - // fetch the asset from the vault. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().to_word()) { - current if current == Smt::EMPTY_VALUE => { - return Err(AssetVaultError::FungibleAssetNotFound(asset)); - }, - current => { - let current = FungibleAsset::new_unchecked(current); - current.sub(asset).map_err(AssetVaultError::SubtractFungibleAssetBalanceError)? - }, - }; + let current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word()); + let current_asset = + FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value) + .expect("asset vault should store valid assets"); + + let new_asset = current_asset + .sub(other_asset) + .map_err(AssetVaultError::SubtractFungibleAssetBalanceError)?; + + // Note that if new_asset's amount is 0, its value's word representation is equal to + // the empty word, which results in the removal of the entire entry from the corresponding + // leaf. + #[cfg(debug_assertions)] + { + if new_asset.amount() == 0 { + assert!(new_asset.to_value_word().is_empty()) + } + } - // if the amount of the asset is zero, remove the asset from the vault. - let value = match new.amount() { - 0 => Smt::EMPTY_VALUE, - _ => new.to_value_word(), - }; self.asset_tree - .insert(new.vault_key().to_word(), value) + .insert(new_asset.vault_key().to_word(), new_asset.to_value_word()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; - // return the asset that was removed. - Ok(asset) + Ok(other_asset) } /// Remove the specified non-fungible asset from the vault and returns the asset that was just diff --git a/crates/miden-protocol/src/asset/vault/partial.rs b/crates/miden-protocol/src/asset/vault/partial.rs index d99b386501..3dee3bbce5 100644 --- a/crates/miden-protocol/src/asset/vault/partial.rs +++ b/crates/miden-protocol/src/asset/vault/partial.rs @@ -141,7 +141,7 @@ impl PartialVault { ) -> Result<(), PartialAssetVaultError> { for (vault_key, asset_value) in entries { // This ensures that vault key and value are consistent. - Asset::from_words(*vault_key, *asset_value).map_err(|source| { + Asset::from_key_value_words(*vault_key, *asset_value).map_err(|source| { PartialAssetVaultError::InvalidAssetInSmt { entry: *asset_value, source } })?; } @@ -218,10 +218,7 @@ mod tests { let partial_smt = PartialSmt::from_proofs([proof.clone()])?; let err = PartialVault::try_from(partial_smt).unwrap_err(); - assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, source } => { - assert_eq!(actual, invalid_vault_key); - assert_eq!(expected, asset.vault_key()); - }); + assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { .. }); Ok(()) } diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 6cb48d1c09..926a0602a5 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -1,4 +1,5 @@ use alloc::boxed::Box; +use core::fmt; use miden_core::LexicographicWord; use miden_crypto::merkle::smt::LeafIndex; @@ -73,11 +74,6 @@ impl AssetVaultKey { } } - /// Returns `true` if the asset key is for a fungible asset, `false` otherwise. - fn is_fungible(&self) -> bool { - matches!(self.faucet_id.account_type(), AccountType::FungibleFaucet) - } - /// Returns the leaf index of a vault key. pub fn to_leaf_index(&self) -> LeafIndex { LeafIndex::::from(self.to_word()) @@ -129,6 +125,20 @@ impl TryFrom for AssetVaultKey { } } +impl fmt::Display for AssetVaultKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // The faucet ID Display impl includes the 0x prefix. + // Write asset ID limbs manually to avoid 0x prefix from asset ID display impl. + write!( + f, + "{}{:016x}{:016x}", + self.faucet_id, + self.asset_id.prefix().as_int(), + self.asset_id.suffix().as_int() + ) + } +} + impl From for AssetVaultKey { fn from(asset: Asset) -> Self { asset.vault_key() @@ -156,56 +166,23 @@ fn vault_key_to_word(asset_id: AssetId, faucet_id: AccountId) -> Word { ]) } -// TESTS -// ================================================================================================ - #[cfg(test)] mod tests { - use miden_core::Felt; + use std::string::ToString; use super::*; - use crate::account::{AccountIdVersion, AccountStorageMode, AccountType}; - - fn make_non_fungible_key(prefix: u64) -> AssetVaultKey { - let word = [Felt::new(prefix), Felt::new(11), Felt::new(22), Felt::new(33)].into(); - AssetVaultKey::new_unchecked(word) - } + use crate::testing::account_id::ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET; #[test] - fn test_faucet_id_for_fungible_asset() { - let id = AccountId::dummy( - [0xff; 15], - AccountIdVersion::Version0, - AccountType::FungibleFaucet, - AccountStorageMode::Public, + fn asset_vault_key_to_hex() { + let key = AssetVaultKey::new( + AssetId::new(1u32.into(), 2u32.into()), + ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(), ); - let key = - AssetVaultKey::new_fungible(id).expect("Expected AssetVaultKey for FungibleFaucet"); - - // faucet_id_prefix() should match AccountId prefix - assert_eq!(key.faucet_id_prefix(), id.prefix()); - - // faucet_id() should return the same account id - assert_eq!(key.faucet_id().unwrap(), id); - } - - #[test] - fn test_faucet_id_for_non_fungible_asset() { - let id = AccountId::dummy( - [0xff; 15], - AccountIdVersion::Version0, - AccountType::NonFungibleFaucet, - AccountStorageMode::Public, - ); - - let prefix_value = id.prefix().as_u64(); - let key = make_non_fungible_key(prefix_value); - - // faucet_id_prefix() should match AccountId prefix - assert_eq!(key.faucet_id_prefix(), id.prefix()); - - // faucet_id() should return the None - assert_eq!(key.faucet_id(), None); + assert_eq!( + key.to_string(), + "0xfa0000000000bba00000cd000000dd00000000000000020000000000000001" + ) } } diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 52905958dc..cc2091cace 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -25,13 +25,10 @@ use crate::account::{ AccountStorage, AccountType, StorageSlotId, - // StorageValueName, - // StorageValueNameError, - // TemplateTypeError, StorageSlotName, }; use crate::address::AddressType; -use crate::asset::{AssetId, AssetVaultKey}; +use crate::asset::AssetId; use crate::batch::BatchId; use crate::block::BlockNumber; use crate::note::{NoteAssets, NoteAttachmentArray, NoteTag, NoteType, Nullifier}; @@ -459,13 +456,15 @@ pub enum AssetError { )] FungibleFaucetIdTypeMismatch(AccountId), #[error( - "asset ID prefix and suffix in a non-fungible asset's vault key must match indices 0 and 1 in the value" + "asset ID prefix and suffix in a non-fungible asset's vault key must match indices 0 and 1 in the value, but asset ID was {asset_id} and value was {value}" )] - NonFungibleAssetIdMustMatchValue, + NonFungibleAssetIdMustMatchValue { asset_id: AssetId, value: Word }, + #[error("asset ID prefix and suffix in a fungible asset's vault key must be zero but was {0}")] + FungibleAssetIdMustBeZero(AssetId), #[error( - "asset ID prefix and suffix in a fungible asset's vault key must be zero but was {0:?}" + "the three most significant elements in a fungible asset's value must be zero but provided value was {0}" )] - FungibleAssetIdMustBeZero(AssetId), + FungibleAssetValueMostSignificantElementsMustBeZero(Word), #[error( "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets", id_type = .0.account_type(), diff --git a/crates/miden-protocol/src/note/assets.rs b/crates/miden-protocol/src/note/assets.rs index ed2f25ce95..0856940102 100644 --- a/crates/miden-protocol/src/note/assets.rs +++ b/crates/miden-protocol/src/note/assets.rs @@ -255,7 +255,7 @@ mod tests { let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(); let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(); - let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap(); + let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(); let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id_1, 100).unwrap()); let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 50).unwrap()); From 27cf64b998b91a31d0cc8fd384f4715c06b2212f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 11 Feb 2026 14:10:40 +0100 Subject: [PATCH 060/100] chore: add validation in get_asset and get_initial_asset --- crates/miden-protocol/asm/kernels/transaction/api.masm | 6 ++++-- crates/miden-protocol/src/transaction/kernel/procedures.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 4ddc0a77cf..0068d3a79a 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -613,7 +613,8 @@ end #! #! Invocation: dynexec pub proc account_get_asset - # TODO(expand_assets): Validate ASSET_KEY once validation logic exists. + exec.asset::validate_asset_key + # => [ASSET_KEY, pad(12)] exec.account::get_asset # => [ASSET_VALUE, pad(12)] @@ -631,7 +632,8 @@ end #! #! Invocation: dynexec pub proc account_get_initial_asset - # TODO(expand_assets): Validate ASSET_KEY once validation logic exists. + exec.asset::validate_asset_key + # => [ASSET_KEY, pad(12)] exec.account::get_initial_asset # => [ASSET_VALUE, pad(12)] diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 1889202948..53ae5a08dc 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -44,9 +44,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_remove_asset word!("0x48e72eb921fdc829be72b0cff2849e1ae2671d59b1c86bf6e389c43472ebc7bc"), // account_get_asset - word!("0x21dc0ef7e3475f28fbcf26636d9b58c3f7e349da7c7a36e85c1b49e50437fa65"), + word!("0x6192a2afe2b2e224d3eeed3e5c5b24bd5cb8b84d6beebe112f4e0dc272909652"), // account_get_initial_asset - word!("0x8e1fabe016fa23523489a8aec0a0cc3804610b75c8b4a1774e46eb6a9d205cb5"), + word!("0xb9f28381abd3389f60c506eab3b15636c6006f68997a7396641c5bcc9e9f16ea"), // account_compute_delta_commitment word!("0x8ea3f06c69939fa791c60e2fa06f19fd070aa20e20e65ba4c8f7931dcad6c86a"), // account_get_num_procedures From bc34f2ed062589e8ce7ae6f48dc343de44e74dd9 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 09:31:07 +0100 Subject: [PATCH 061/100] chore: adapt miden-standards to changed asset def --- crates/miden-standards/src/account/interface/component.rs | 4 ++-- crates/miden-standards/src/account/interface/mod.rs | 6 +++--- crates/miden-standards/src/note/swap.rs | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/miden-standards/src/account/interface/component.rs b/crates/miden-standards/src/account/interface/component.rs index 185121d1d7..47b0b3d73f 100644 --- a/crates/miden-standards/src/account/interface/component.rs +++ b/crates/miden-standards/src/account/interface/component.rs @@ -247,9 +247,9 @@ impl AccountComponentInterface { let asset = partial_note.assets().iter().next().expect("note should contain an asset"); - if asset.faucet_id_prefix() != sender_account_id.prefix() { + if asset.faucet_id() != sender_account_id { return Err(AccountInterfaceError::IssuanceFaucetMismatch( - asset.faucet_id_prefix(), + asset.faucet_id(), )); } diff --git a/crates/miden-standards/src/account/interface/mod.rs b/crates/miden-standards/src/account/interface/mod.rs index 61203afa8e..e59bc6bd6d 100644 --- a/crates/miden-standards/src/account/interface/mod.rs +++ b/crates/miden-standards/src/account/interface/mod.rs @@ -1,7 +1,7 @@ use alloc::string::String; use alloc::vec::Vec; -use miden_protocol::account::{AccountId, AccountIdPrefix, AccountType}; +use miden_protocol::account::{AccountId, AccountType}; use miden_protocol::note::{NoteAttachmentContent, PartialNote}; use miden_protocol::transaction::TransactionScript; use thiserror::Error; @@ -249,8 +249,8 @@ pub enum NoteAccountCompatibility { /// Account interface related errors. #[derive(Debug, Error)] pub enum AccountInterfaceError { - #[error("note asset is not issued by this faucet: {0}")] - IssuanceFaucetMismatch(AccountIdPrefix), + #[error("note asset is not issued by faucet {0}")] + IssuanceFaucetMismatch(AccountId), #[error("note created by the basic fungible faucet doesn't contain exactly one asset")] FaucetNoteWithoutAsset, #[error("invalid transaction script")] diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index 57250f8b6a..ce6a439f9d 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -156,10 +156,10 @@ impl SwapNote { swap_use_case_id |= (swap_root_bytes[1] >> 2) as u16; // Get bits 0..8 from the faucet IDs of both assets which will form the tag payload. - let offered_asset_id: u64 = offered_asset.faucet_id_prefix().into(); + let offered_asset_id: u64 = offered_asset.faucet_id().prefix().into(); let offered_asset_tag = (offered_asset_id >> 56) as u8; - let requested_asset_id: u64 = requested_asset.faucet_id_prefix().into(); + let requested_asset_id: u64 = requested_asset.faucet_id().prefix().into(); let requested_asset_tag = (requested_asset_id >> 56) as u8; let asset_pair = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16); @@ -216,8 +216,7 @@ mod tests { AccountIdVersion::Version0, AccountType::NonFungibleFaucet, AccountStorageMode::Public, - ) - .prefix(), + ), vec![0xaa, 0xbb, 0xcc, 0xdd], ) .unwrap(), From 4e701b6bf7ded4f0dcc0f4754330ed44a7cfc501 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 09:31:52 +0100 Subject: [PATCH 062/100] chore: adapt miden-tx to changed asset def --- crates/miden-tx/src/executor/mod.rs | 4 +- crates/miden-tx/src/host/tx_event.rs | 63 ++++++++++++++++++---------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 1dc2378404..92d40bafbf 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -274,7 +274,7 @@ where let native_account_vault_root = account.vault().root(); let fee_asset_vault_key = - AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id()) + AssetVaultKey::new_fungible(block_header.fee_parameters().native_asset_id()) .expect("fee asset should be a fungible asset"); let mut tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) @@ -339,7 +339,7 @@ where let initial_fee_asset_balance = { let vault_root = tx_inputs.account().vault().root(); let native_asset_id = tx_inputs.block_header().fee_parameters().native_asset_id(); - let fee_asset_vault_key = AssetVaultKey::from_account_id(native_asset_id) + let fee_asset_vault_key = AssetVaultKey::new_fungible(native_asset_id) .expect("fee asset should be a fungible asset"); let fee_asset = tx_inputs diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 451c9d0d5f..04a6b59c53 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -198,7 +198,12 @@ impl TransactionEvent { let vault_root_ptr = process.get_stack_item(9); let asset_vault_key = - AssetVaultKey::try_from(asset_vault_key).expect("TODO(expand_assets)"); + AssetVaultKey::try_from(asset_vault_key).map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "AccountVaultBefore{Add,Remove}Asset", + source, + } + })?; let current_vault_root = process.get_vault_root(vault_root_ptr)?; on_account_vault_asset_accessed( @@ -210,24 +215,31 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultAfterRemoveAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_remove_asset", - source, - } - })?; + let asset_key = process.get_stack_word_be(1); + let asset_value = process.get_stack_word_be(5); + + let asset = + Asset::from_key_value_words(asset_key, asset_value).map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "AccountVaultAfterRemoveAsset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset }) }, TransactionEventId::AccountVaultAfterAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] + let asset_key = process.get_stack_word_be(1); + let asset_value = process.get_stack_word_be(5); - let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_add_asset", - source, - } - })?; + let asset = + Asset::from_key_value_words(asset_key, asset_value).map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "AccountVaultAfterAddAsset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterAddAsset { asset }) }, @@ -237,7 +249,12 @@ impl TransactionEvent { let asset_key = process.get_stack_word_be(1); let vault_root_ptr = process.get_stack_item(5); - let asset_key = AssetVaultKey::try_from(asset_key).expect("TODO(expand_assets)"); + let asset_key = AssetVaultKey::try_from(asset_key).map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "AccountVaultBeforeGetAsset", + source, + } + })?; let vault_root = process.get_vault_root(vault_root_ptr)?; on_account_vault_asset_accessed(base_host, process, asset_key, vault_root)? @@ -378,15 +395,17 @@ impl TransactionEvent { TransactionEventId::NoteBeforeAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE, note_ptr] + let asset_key = process.get_stack_word_be(1); let asset_value = process.get_stack_word_be(5); let note_ptr = process.get_stack_item(9); - let asset = Asset::try_from(asset_value).map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_note_before_add_asset", - source, - } - })?; + let asset = + Asset::from_key_value_words(asset_key, asset_value).map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "NoteBeforeAddAsset", + source, + } + })?; let note_idx = note_ptr_to_idx(note_ptr)? as usize; Some(TransactionEvent::NoteBeforeAddAsset { note_idx, asset }) @@ -442,10 +461,10 @@ impl TransactionEvent { TransactionEventId::EpilogueBeforeTxFeeRemovedFromAccount => { // Expected stack state: [event, FEE_ASSET_KEY, FEE_ASSET_VALUE] - + let fee_asset_key = process.get_stack_word_be(1); let fee_asset_value = process.get_stack_word_be(5); - let fee_asset = FungibleAsset::try_from(fee_asset_value) + let fee_asset = FungibleAsset::from_key_value_words(fee_asset_key, fee_asset_value) .map_err(TransactionKernelError::FailedToConvertFeeAsset)?; Some(TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount { fee_asset }) From 01ade5bc9127329e867519414e8935f3666762bc Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 10:09:34 +0100 Subject: [PATCH 063/100] feat: return native asset ID and fee amount as tx output --- .../asm/kernels/transaction/lib/epilogue.masm | 58 ++++++++++++------- .../asm/kernels/transaction/main.masm | 16 +++-- .../src/transaction/kernel/mod.rs | 30 ++++++---- .../miden-protocol/src/transaction/outputs.rs | 14 ++++- .../src/kernel_tests/tx/test_epilogue.rs | 19 +++--- 5 files changed, 87 insertions(+), 50 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index af06ee3c39..c20e0d2e53 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -1,5 +1,6 @@ use $kernel::account use $kernel::account_delta +use $kernel::asset use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET use $kernel::asset_vault @@ -310,25 +311,33 @@ end #! check. That's okay, because the logic is entirely determined by the transaction kernel. #! #! Inputs: [] -#! Outputs: [FEE_ASSET_VALUE] +#! Outputs: [native_asset_id_prefix, native_asset_id_suffix, fee_amount] #! #! Where: #! - fee_amount is the computed fee amount of the transaction in the native asset. -#! - FEE_ASSET_VALUE is the fee asset with the faucet ID set to the native asset. +#! - native_asset_id_{prefix,suffix} are the prefix and suffix felts of the faucet that issues the +#! native asset. #! #! Panics if: -#! - the account vault does not contain the computed fee. +#! - the account vault contains less than the computed fee. proc compute_and_remove_fee # compute the fee the tx needs to pay - exec.compute_fee - # => [fee_amount] + exec.compute_fee dup + # => [fee_amount, fee_amount] # build the native asset from the fee amount exec.create_native_fee_asset - # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT_EVENT - # => [FEE_ASSET_KEY, FEE_ASSET_VALUE] + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] + + # prepare the return value + exec.asset::get_vault_key_faucet_id + # => [native_asset_id_prefix, native_asset_id_suffix, FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] + + movdn.9 movdn.9 + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, native_asset_id_prefix, native_asset_id_suffix, fee_amount] # remove the fee from the native account's vault # note that this deliberately does not use account::remove_asset_from_vault, because that @@ -340,11 +349,11 @@ proc compute_and_remove_fee # fetch the vault root ptr exec.memory::get_account_vault_root_ptr movdn.8 - # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, acct_vault_root_ptr] + # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, account_vault_root_ptr, native_asset_id_prefix, native_asset_id_suffix, fee_amount] # remove the asset from the account vault - exec.asset_vault::remove_fungible_asset - # => [FEE_ASSET_VALUE] + exec.asset_vault::remove_fungible_asset dropw + # => [native_asset_id_prefix, native_asset_id_suffix, fee_amount] end # TRANSACTION EPILOGUE PROCEDURE @@ -366,19 +375,21 @@ end #! Inputs: [] #! Outputs: [ #! OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, -#! FEE_ASSET_VALUE, tx_expiration_block_num +#! native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num #! ] #! #! Where: #! - OUTPUT_NOTES_COMMITMENT is the commitment of the output notes. #! - ACCOUNT_UPDATE_COMMITMENT is the hash of the the final account commitment and account #! delta commitment. -#! - FEE_ASSET_VALUE is the fungible asset used as the transaction fee. +#! - fee_amount is the computed fee amount of the transaction denominated in the native asset. +#! - native_asset_id_{prefix,suffix} are the prefix and suffix felts of the faucet that issues the +#! native asset. #! - tx_expiration_block_num is the transaction expiration block number. #! #! Locals: #! - 0..4: OUTPUT_NOTES_COMMITMENT -#! - 4..8: FEE_ASSET_VALUE +#! - 4..8: FEE_ASSET_INFO #! - 8..12: ACCOUNT_DELTA_COMMITMENT #! #! Panics if: @@ -462,10 +473,13 @@ pub proc finalize_transaction # ------ Compute fees ------ exec.compute_and_remove_fee - # => [FEE_ASSET_VALUE] + # => [native_asset_id_prefix, native_asset_id_suffix, fee_amount] + + # pad to word size so we can store the info as a word + push.0 movdn.3 + # => [native_asset_id_prefix, native_asset_id_suffix, fee_amount, 0] - # TODO(programmable_assets): Decide what the public tx output of the fee asset looks like. - # store fee asset in local + # store fee info in local memory loc_storew_be.4 dropw # => [] @@ -507,14 +521,16 @@ pub proc finalize_transaction # ------ Build output stack ------ - exec.memory::get_expiration_block_num movdn.4 - # => [ACCOUNT_UPDATE_COMMITMENT, tx_expiration_block_num] - # load fee asset from local padw loc_loadw_be.4 swapw - # => [ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num] + # => [ACCOUNT_UPDATE_COMMITMENT, [native_asset_id_prefix, native_asset_id_suffix, fee_amount, 0]] + + # replace 0 with expiration block num + exec.memory::get_expiration_block_num swap.8 drop + # => [ACCOUNT_UPDATE_COMMITMENT, [native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num]] # load output notes commitment from local padw loc_loadw_be.0 - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num] + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, + # native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num] end diff --git a/crates/miden-protocol/asm/kernels/transaction/main.masm b/crates/miden-protocol/asm/kernels/transaction/main.masm index 6471f50fe2..37f057d51e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/main.masm +++ b/crates/miden-protocol/asm/kernels/transaction/main.masm @@ -56,7 +56,8 @@ const EPILOGUE_END_EVENT=event("miden::protocol::tx::epilogue_end") #! ] #! Outputs: [ #! OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, -#! FEE_ASSET_VALUE, tx_expiration_block_num, pad(3) +#! native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num, +#! pad(4) #! ] #! #! Where: @@ -68,7 +69,10 @@ const EPILOGUE_END_EVENT=event("miden::protocol::tx::epilogue_end") #! - OUTPUT_NOTES_COMMITMENT is the commitment to the notes created by the transaction. #! - ACCOUNT_UPDATE_COMMITMENT is the hash of the the final account commitment and account #! delta commitment. -#! - FEE_ASSET_VALUE is the fungible asset used as the transaction fee. +#! - fee_amount is the computed fee amount of the transaction denominated in the native asset. +#! - native_asset_id_{prefix,suffix} are the prefix and suffix felts of the faucet that issues the +#! native asset. +#! - tx_expiration_block_num is the transaction expiration block number. @locals(1) proc main # Prologue @@ -170,11 +174,13 @@ proc main # execute the transaction epilogue exec.epilogue::finalize_transaction - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num, pad(16)] + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, + # native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num, pad(16)] # truncate the stack to contain 16 elements in total - repeat.13 movup.13 drop end - # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET_VALUE, tx_expiration_block_num, pad(3)] + repeat.3 movupw.3 dropw end + # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, + # native_asset_id_prefix, native_asset_id_suffix, fee_amount, tx_expiration_block_num, pad(4)] emit.EPILOGUE_END_EVENT end diff --git a/crates/miden-protocol/src/transaction/kernel/mod.rs b/crates/miden-protocol/src/transaction/kernel/mod.rs index 22a7eeb3b3..f54875648a 100644 --- a/crates/miden-protocol/src/transaction/kernel/mod.rs +++ b/crates/miden-protocol/src/transaction/kernel/mod.rs @@ -9,7 +9,7 @@ use crate::account::{AccountHeader, AccountId}; use crate::assembly::Library; use crate::assembly::debuginfo::SourceManagerSync; use crate::assembly::{Assembler, DefaultSourceManager, KernelLibrary}; -use crate::asset::{AssetVaultKey, FungibleAsset}; +use crate::asset::FungibleAsset; use crate::block::BlockNumber; use crate::crypto::SequentialCommit; use crate::errors::TransactionOutputError; @@ -219,12 +219,16 @@ impl TransactionKernel { ) -> StackOutputs { let account_update_commitment = Hasher::merge(&[final_account_commitment, account_delta_commitment]); - let mut outputs: Vec = Vec::with_capacity(9); + + let mut outputs: Vec = Vec::with_capacity(12); outputs.push(Felt::from(expiration_block_num)); - outputs.extend(fee.to_value_word()); + outputs.push(Felt::try_from(fee.amount()).expect("amount should fit into felt")); + outputs.push(fee.faucet_id().suffix()); + outputs.push(fee.faucet_id().prefix().as_felt()); outputs.extend(account_update_commitment); outputs.extend(output_notes_commitment); outputs.reverse(); + StackOutputs::new(outputs) .map_err(|e| e.to_string()) .expect("Invalid stack output") @@ -268,13 +272,19 @@ impl TransactionKernel { .get_stack_word_be(TransactionOutputs::ACCOUNT_UPDATE_COMMITMENT_WORD_IDX * 4) .expect("account_update_commitment (second word) missing"); - let fee = stack - .get_stack_word_be(TransactionOutputs::FEE_ASSET_WORD_IDX * 4) - .expect("fee_asset (third word) missing"); + let native_asset_id_prefix = stack + .get_stack_item(TransactionOutputs::NATIVE_ASSET_ID_PREFIX_ELEMENT_IDX) + .expect("native_asset_id_prefix missing"); + let native_asset_id_suffix = stack + .get_stack_item(TransactionOutputs::NATIVE_ASSET_ID_SUFFIX_ELEMENT_IDX) + .expect("native_asset_id_suffix missing"); + let fee_amount = stack + .get_stack_item(TransactionOutputs::FEE_AMOUNT_ELEMENT_IDX) + .expect("fee_amount missing"); let expiration_block_num = stack .get_stack_item(TransactionOutputs::EXPIRATION_BLOCK_ELEMENT_IDX) - .expect("tx_expiration_block_num (element on index 12) missing"); + .expect("tx_expiration_block_num missing"); let expiration_block_num = u32::try_from(expiration_block_num.as_int()) .map_err(|_| { @@ -294,9 +304,9 @@ impl TransactionKernel { )); } - // TODO(expand_assets): fix when changing tx fee output - let vault_key = AssetVaultKey::try_from(Word::empty()).expect("TODO"); - let fee = FungibleAsset::from_key_value(vault_key, fee) + let native_asset_id = AccountId::try_from([native_asset_id_prefix, native_asset_id_suffix]) + .expect("native asset ID should be validated by the tx kernel"); + let fee = FungibleAsset::new(native_asset_id, fee_amount.as_int()) .map_err(TransactionOutputError::FeeAssetNotFungibleAsset)?; Ok((output_notes_commitment, account_update_commitment, fee, expiration_block_num)) diff --git a/crates/miden-protocol/src/transaction/outputs.rs b/crates/miden-protocol/src/transaction/outputs.rs index 179da30367..161e580ccf 100644 --- a/crates/miden-protocol/src/transaction/outputs.rs +++ b/crates/miden-protocol/src/transaction/outputs.rs @@ -54,11 +54,19 @@ impl TransactionOutputs { /// The index of the word at which the account update commitment is stored on the output stack. pub const ACCOUNT_UPDATE_COMMITMENT_WORD_IDX: usize = 1; - /// The index of the word at which the fee asset is stored on the output stack. - pub const FEE_ASSET_WORD_IDX: usize = 2; + /// The index of the element at which the ID prefix of the faucet that issues the native asset + /// is stored on the output stack. + pub const NATIVE_ASSET_ID_PREFIX_ELEMENT_IDX: usize = 8; + + /// The index of the element at which the ID suffix of the faucet that issues the native asset + /// is stored on the output stack. + pub const NATIVE_ASSET_ID_SUFFIX_ELEMENT_IDX: usize = 9; + + /// The index of the element at which the fee amount is stored on the output stack. + pub const FEE_AMOUNT_ELEMENT_IDX: usize = 10; /// The index of the item at which the expiration block height is stored on the output stack. - pub const EXPIRATION_BLOCK_ELEMENT_IDX: usize = 12; + pub const EXPIRATION_BLOCK_ELEMENT_IDX: usize = 11; } impl Serializable for TransactionOutputs { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index ed9ee01a99..7eed4561d2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -117,22 +117,19 @@ async fn test_transaction_epilogue() -> anyhow::Result<()> { let account_update_commitment = miden_protocol::Hasher::merge(&[final_account.commitment(), account_delta_commitment]); + let fee_asset = FungibleAsset::new( + tx_context.tx_inputs().block_header().fee_parameters().native_asset_id(), + 0, + )?; let mut expected_stack = Vec::with_capacity(16); expected_stack.extend(output_notes.commitment().as_elements().iter().rev()); expected_stack.extend(account_update_commitment.as_elements().iter().rev()); - expected_stack.extend( - FungibleAsset::new( - tx_context.tx_inputs().block_header().fee_parameters().native_asset_id(), - 0, - ) - .unwrap() - .to_value_word() - .iter() - .rev(), - ); + expected_stack.push(fee_asset.faucet_id().prefix().as_felt()); + expected_stack.push(fee_asset.faucet_id().suffix()); + expected_stack.push(Felt::try_from(fee_asset.amount()).unwrap()); expected_stack.push(Felt::from(u32::MAX)); // Value for tx expiration block number - expected_stack.extend((13..16).map(|_| ZERO)); + expected_stack.resize(16, ZERO); assert_eq!( exec_output.stack.as_slice(), From b431aaa5ded08907bac48a6844a917b53bf85f9d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 10:30:42 +0100 Subject: [PATCH 064/100] chore: update create_non_fungible_asset faucet/asset APIs --- crates/miden-protocol/asm/protocol/asset.masm | 27 ++++++++++--------- .../miden-protocol/asm/protocol/faucet.masm | 4 +-- .../asm/shared_utils/util/asset.masm | 23 ++++++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index c08c228fe5..f51d9fdad8 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -32,6 +32,10 @@ const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the #! - ASSET_KEY is the vault key of the created fungible asset. #! - ASSET_VALUE is the value of the created fungible asset. #! +#! Panics if: +#! - the provided faucet ID is not a fungible faucet. +#! - the provided amount exceeds FUNGIBLE_ASSET_MAX_AMOUNT. +#! #! Invocation: exec pub proc create_fungible_asset # assert the faucet is a fungible faucet @@ -43,35 +47,34 @@ pub proc create_fungible_asset assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT # => [faucet_id_prefix, faucet_id_suffix, amount] - # SAFETY: faucet ID and amount are validated + # SAFETY: faucet ID and amount were validated exec.asset::create_fungible_asset_unchecked # => [ASSET_KEY, ASSET_VALUE] end -#! Builds a non fungible asset for the specified non-fungible faucet. +#! Creates a non fungible asset for the specified non-fungible faucet. #! -#! Inputs: [faucet_id_prefix, DATA_HASH] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, DATA_HASH] #! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset #! for. -#! - DATA_HASH is the data hash of the non-fungible asset to build. +#! - DATA_HASH is the data hash of the non-fungible asset to create. #! - ASSET_KEY is the vault key of the created non-fungible asset. -#! - ASSET_VALUE is the value of the created non-fungible asset. +#! - ASSET_VALUE is the value of the created non-fungible asset, which is identical to DATA_HASH. +#! +#! Panics if: +#! - the provided faucet ID is not a non-fungible faucet. #! #! Invocation: exec pub proc create_non_fungible_asset # assert the faucet is a non-fungible faucet dup exec.account_id::is_non_fungible_faucet assert.err=ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID - # => [faucet_id_prefix, hash3, hash2, hash1, hash0] - - # build the asset - swap drop - # => [faucet_id_prefix, hash2, hash1, hash0] - # => [ASSET_VALUE] + # => [faucet_id_prefix, faucet_id_suffix, DATA_HASH] - dupw exec.::miden::protocol::util::asset::build_non_fungible_asset_vault_key + # SAFETY: faucet ID was validated + exec.::miden::protocol::util::asset::create_non_fungible_asset_unchecked # => [ASSET_KEY, ASSET_VALUE] end diff --git a/crates/miden-protocol/asm/protocol/faucet.masm b/crates/miden-protocol/asm/protocol/faucet.masm index 258b2ba189..b02e864853 100644 --- a/crates/miden-protocol/asm/protocol/faucet.masm +++ b/crates/miden-protocol/asm/protocol/faucet.masm @@ -43,8 +43,8 @@ end #! Invocation: exec pub proc create_non_fungible_asset # get the id of the faucet the transaction is being executed against - exec.active_account::get_id swap drop - # => [faucet_id_prefix, DATA_HASH] + exec.active_account::get_id + # => [faucet_id_prefix, faucet_id_suffix, DATA_HASH] # build the non-fungible asset exec.asset::create_non_fungible_asset diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 30eb047ce7..b025d0094d 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -55,6 +55,29 @@ pub proc create_fungible_asset_unchecked # => [ASSET_KEY, ASSET_VALUE] end +#! Creates a non fungible asset for the specified non-fungible faucet. +#! +#! WARNING: Does not validate its inputs. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix, DATA_HASH] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset +#! for. +#! - DATA_HASH is the data hash of the non-fungible asset to create. +#! - ASSET_KEY is the vault key of the created non-fungible asset. +#! - ASSET_VALUE is the value of the created non-fungible asset, which is identical to DATA_HASH. +#! +#! Invocation: exec +pub proc create_non_fungible_asset_unchecked + # copy hashes at indices 0 and 1 in the data hash word to the corresponding index in the key + # word + dup.5 movdn.2 dup.5 movdn.2 + # => [[faucet_id_prefix, faucet_id_suffix, hash1, hash0], DATA_HASH] + # => [ASSET_KEY, ASSET_VALUE] +end + #! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must #! be a valid fungible asset. #! From 15963ee272083be0cc1b7615fd2bdc1ee588cc82 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 10:37:18 +0100 Subject: [PATCH 065/100] chore: adapt tests to new asset layouts --- .../src/kernel_tests/tx/test_account.rs | 5 +- .../src/kernel_tests/tx/test_account_delta.rs | 37 +++++++--- .../src/kernel_tests/tx/test_asset.rs | 14 ++-- .../src/kernel_tests/tx/test_asset_vault.rs | 71 +++++++------------ .../src/kernel_tests/tx/test_faucet.rs | 35 +++++---- .../src/kernel_tests/tx/test_fpi.rs | 2 +- .../src/kernel_tests/tx/test_input_note.rs | 2 +- .../miden-testing/src/tx_context/context.rs | 4 +- 8 files changed, 83 insertions(+), 87 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 5d6ec2ebbc..c40c72f71b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1315,7 +1315,7 @@ async fn test_get_init_asset() -> anyhow::Result<()> { # => [note_idx] push.{REMOVED_ASSET_VALUE} - push.{REMOVED_ASSET_KEY} + push.{ASSET_KEY} exec.util::move_asset_to_note # => [] @@ -1335,9 +1335,8 @@ async fn test_get_init_asset() -> anyhow::Result<()> { assert_eqw.err="initial asset is incorrect" end "#, - ASSET_KEY = fungible_asset_for_note_existing.vault_key(), + ASSET_KEY = fungible_asset_for_note_existing.to_key_word(), REMOVED_ASSET_VALUE = fungible_asset_for_note_existing.to_value_word(), - REMOVED_ASSET_KEY = fungible_asset_for_note_existing.to_key_word(), INITIAL_ASSET_VALUE = fungible_asset_for_account.to_value_word(), FINAL_ASSET = final_asset.to_value_word(), ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index f3c716b849..1a08540a29 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -17,7 +17,13 @@ use miden_protocol::account::{ StorageSlotDelta, StorageSlotName, }; -use miden_protocol::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset}; +use miden_protocol::asset::{ + Asset, + AssetVault, + FungibleAsset, + NonFungibleAsset, + NonFungibleAssetDetails, +}; use miden_protocol::note::{Note, NoteTag, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, @@ -27,7 +33,6 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_SENDER, AccountIdBuilder, }; -use miden_protocol::testing::asset::NonFungibleAssetBuilder; use miden_protocol::testing::constants::{ CONSUMED_ASSET_1_AMOUNT, CONSUMED_ASSET_3_AMOUNT, @@ -519,10 +524,22 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { .account_type(AccountType::NonFungibleFaucet) .build_with_seed(rng.random()); - let asset0 = NonFungibleAssetBuilder::new(faucet0.prefix(), &mut rng)?.build()?; - let asset1 = NonFungibleAssetBuilder::new(faucet1.prefix(), &mut rng)?.build()?; - let asset2 = NonFungibleAssetBuilder::new(faucet2.prefix(), &mut rng)?.build()?; - let asset3 = NonFungibleAssetBuilder::new(faucet3.prefix(), &mut rng)?.build()?; + let asset0 = NonFungibleAsset::new(&NonFungibleAssetDetails::new( + faucet0, + rng.random::<[u8; 32]>().to_vec(), + )?)?; + let asset1 = NonFungibleAsset::new(&NonFungibleAssetDetails::new( + faucet1, + rng.random::<[u8; 32]>().to_vec(), + )?)?; + let asset2 = NonFungibleAsset::new(&NonFungibleAssetDetails::new( + faucet2, + rng.random::<[u8; 32]>().to_vec(), + )?)?; + let asset3 = NonFungibleAsset::new(&NonFungibleAssetDetails::new( + faucet3, + rng.random::<[u8; 32]>().to_vec(), + )?)?; let TestSetup { mock_chain, account_id, notes } = setup_test([], [asset1, asset3].map(Asset::from), [asset0, asset2].map(Asset::from))?; @@ -570,20 +587,20 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { .account_delta() .vault() .added_assets() - .map(|asset| (asset.faucet_id_prefix(), asset.unwrap_non_fungible())) + .map(|asset| (asset.faucet_id(), asset.unwrap_non_fungible())) .collect::>(); let mut removed_assets = executed_tx .account_delta() .vault() .removed_assets() - .map(|asset| (asset.faucet_id_prefix(), asset.unwrap_non_fungible())) + .map(|asset| (asset.faucet_id(), asset.unwrap_non_fungible())) .collect::>(); assert_eq!(added_assets.len(), 1); assert_eq!(removed_assets.len(), 1); - assert_eq!(added_assets.remove(&asset0.faucet_id_prefix()).unwrap(), asset0); - assert_eq!(removed_assets.remove(&asset1.faucet_id_prefix()).unwrap(), asset1); + assert_eq!(added_assets.remove(&asset0.faucet_id()).unwrap(), asset0); + assert_eq!(removed_assets.remove(&asset1.faucet_id()).unwrap(), asset1); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index dfb2d64b27..5885bb76cf 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -1,5 +1,4 @@ -use miden_protocol::Hasher; -use miden_protocol::asset::{FungibleAsset, NonFungibleAsset}; +use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; @@ -46,7 +45,11 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; - let non_fungible_asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA); + let non_fungible_asset_details = NonFungibleAssetDetails::new( + NonFungibleAsset::mock_issuer(), + NON_FUNGIBLE_ASSET_DATA.to_vec(), + )?; + let non_fungible_asset = NonFungibleAsset::new(&non_fungible_asset_details)?; let code = format!( " @@ -57,14 +60,14 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { exec.prologue::prepare_transaction # push non-fungible asset data hash onto the stack - push.{non_fungible_asset_data_hash} + push.{NON_FUNGIBLE_ASSET_DATA_HASH} exec.faucet::create_non_fungible_asset # truncate the stack exec.::miden::core::sys::truncate_stack end ", - non_fungible_asset_data_hash = Hasher::hash(&NON_FUNGIBLE_ASSET_DATA), + NON_FUNGIBLE_ASSET_DATA_HASH = non_fungible_asset.to_value_word(), ); let exec_output = &tx_context.execute_code(&code).await?; @@ -76,6 +79,7 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { } #[tokio::test] +#[ignore = "TODO(expand_assets): refactor"] async fn test_validate_non_fungible_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 8daf04f4fd..a17fdd39d1 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -1,4 +1,5 @@ use assert_matches::assert_matches; +use miden_protocol::ONE; use miden_protocol::account::AccountId; use miden_protocol::asset::{ Asset, @@ -22,7 +23,6 @@ use miden_protocol::testing::account_id::{ }; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; use miden_protocol::transaction::memory; -use miden_protocol::{Felt, ONE, Word, ZERO}; use crate::executor::CodeExecutor; use crate::kernel_tests::tx::ExecutionOutputExt; @@ -69,7 +69,7 @@ async fn get_balance_returns_correct_amount() -> anyhow::Result<()> { async fn peek_asset_returns_correct_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); - let asset_key = AssetVaultKey::from_account_id(faucet_id).unwrap(); + let asset_key = AssetVaultKey::new_fungible(faucet_id).unwrap(); let code = format!( r#" @@ -96,7 +96,7 @@ async fn peek_asset_returns_correct_asset() -> anyhow::Result<()> { swapw dropw end "#, - ASSET_KEY = asset_key + ASSET_KEY = asset_key.to_word() ); let exec_output = tx_context.execute_code(&code).await?; @@ -179,13 +179,7 @@ async fn test_add_fungible_asset_success() -> anyhow::Result<()> { let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT; - let add_fungible_asset = Asset::try_from(Word::new([ - Felt::new(amount), - ZERO, - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ])) - .unwrap(); + let add_fungible_asset = FungibleAsset::new(faucet_id, amount)?; let code = format!( " @@ -210,7 +204,10 @@ async fn test_add_fungible_asset_success() -> anyhow::Result<()> { assert_eq!( exec_output.get_stack_word_be(0), - account_vault.add_asset(add_fungible_asset).unwrap().to_value_word() + account_vault + .add_asset(Asset::Fungible(add_fungible_asset)) + .unwrap() + .to_value_word() ); assert_eq!( @@ -228,13 +225,7 @@ async fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT + 1; - let add_fungible_asset = Asset::try_from(Word::new([ - Felt::new(amount), - ZERO, - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ])) - .unwrap(); + let add_fungible_asset = FungibleAsset::new(faucet_id, amount)?; let code = format!( " @@ -256,7 +247,7 @@ async fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { let exec_result = tx_context.execute_code(&code).await; assert_execution_error!(exec_result, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); - assert!(account_vault.add_asset(add_fungible_asset).is_err()); + assert!(account_vault.add_asset(Asset::Fungible(add_fungible_asset)).is_err()); Ok(()) } @@ -267,7 +258,7 @@ async fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into()?; let mut account_vault = tx_context.account().vault().clone(); let add_non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new( - &NonFungibleAssetDetails::new(faucet_id.prefix(), vec![1, 2, 3, 4, 5, 6, 7, 8]).unwrap(), + &NonFungibleAssetDetails::new(faucet_id, vec![1, 2, 3, 4, 5, 6, 7, 8]).unwrap(), )?); let code = format!( @@ -310,7 +301,7 @@ async fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); let non_fungible_asset_details = - NonFungibleAssetDetails::new(faucet_id.prefix(), NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); + NonFungibleAssetDetails::new(faucet_id, NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&non_fungible_asset_details).unwrap()); @@ -346,13 +337,7 @@ async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Re let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT; - let remove_fungible_asset = Asset::try_from(Word::new([ - Felt::new(amount), - ZERO, - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ])) - .unwrap(); + let remove_fungible_asset = FungibleAsset::new(faucet_id, amount)?; let code = format!( " @@ -377,7 +362,10 @@ async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Re assert_eq!( exec_output.get_stack_word_be(0), - account_vault.remove_asset(remove_fungible_asset).unwrap().to_value_word() + account_vault + .remove_asset(Asset::Fungible(remove_fungible_asset)) + .unwrap() + .to_value_word() ); assert_eq!( @@ -393,13 +381,7 @@ async fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT + 1; - let remove_fungible_asset = Asset::try_from(Word::new([ - Felt::new(amount), - ZERO, - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ])) - .unwrap(); + let remove_fungible_asset = FungibleAsset::new(faucet_id, amount)?; let code = format!( " @@ -434,13 +416,7 @@ async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Resul let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT - 1; - let remove_fungible_asset = Asset::try_from(Word::new([ - Felt::new(amount), - ZERO, - faucet_id.suffix(), - faucet_id.prefix().as_felt(), - ])) - .unwrap(); + let remove_fungible_asset = FungibleAsset::new(faucet_id, amount)?; let code = format!( " @@ -465,7 +441,10 @@ async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Resul assert_eq!( exec_output.get_stack_word_be(0), - account_vault.remove_asset(remove_fungible_asset).unwrap().to_value_word() + account_vault + .remove_asset(Asset::Fungible(remove_fungible_asset)) + .unwrap() + .to_value_word() ); assert_eq!( @@ -483,7 +462,7 @@ async fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> let mut account_vault = tx_context.account().vault().clone(); let non_fungible_asset_details = - NonFungibleAssetDetails::new(faucet_id.prefix(), NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); + NonFungibleAssetDetails::new(faucet_id, NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); let nonfungible = NonFungibleAsset::new(&non_fungible_asset_details).unwrap(); let non_existent_non_fungible_asset = Asset::NonFungible(nonfungible); @@ -527,7 +506,7 @@ async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); let non_fungible_asset_details = - NonFungibleAssetDetails::new(faucet_id.prefix(), NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); + NonFungibleAssetDetails::new(faucet_id, NON_FUNGIBLE_ASSET_DATA.to_vec()).unwrap(); let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&non_fungible_asset_details).unwrap()); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index e7c44b27b0..6dc95431ea 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -96,13 +96,13 @@ async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> use mock::faucet begin - push.{asset_key} - push.{asset_value} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.faucet::mint end ", - asset_key = asset.vault_key(), - asset_value = asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -130,13 +130,13 @@ async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> begin exec.prologue::prepare_transaction - push.{asset_key} - push.{asset_value} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.faucet::mint end ", - asset_key = asset.vault_key(), - asset_value = asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), ); let exec_output = tx_context.execute_code(&code).await; @@ -156,21 +156,18 @@ async fn test_mint_fungible_asset_fails_when_amount_exceeds_max_representable_am begin push.{max_amount_plus_1} push.0 - push.{faucet_id_suffix} - push.{faucet_id_prefix} + push.0 + push.0 # => [ASSET_VALUE] - push.0.0 - push.{faucet_id_suffix} - push.{faucet_id_prefix} + push.{ASSET_KEY} # => [ASSET_KEY, ASSET_VALUE] call.faucet::mint dropw dropw end ", - faucet_id_prefix = FungibleAsset::mock_issuer().prefix().as_felt(), - faucet_id_suffix = FungibleAsset::mock_issuer().suffix(), + ASSET_KEY = FungibleAsset::mock(0).to_key_word(), max_amount_plus_1 = FungibleAsset::MAX_AMOUNT + 1, ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; @@ -277,13 +274,13 @@ async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result use mock::faucet begin - push.{asset_key} - push.{asset_value} + push.{ASSET_VALUE} + push.{ASSET_KEY} call.faucet::mint end ", - asset_key = asset.vault_key(), - asset_value = asset.to_value_word(), + ASSET_KEY = asset.to_key_word(), + ASSET_VALUE = asset.to_value_word(), ); let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index f16a018230..11fad69404 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -682,7 +682,7 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu // Create two different assets. let fungible_asset = Asset::Fungible(FungibleAsset::new(fungible_faucet_id, 1)?); let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new( - &NonFungibleAssetDetails::new(non_fungible_faucet_id.prefix(), vec![1, 2, 3])?, + &NonFungibleAssetDetails::new(non_fungible_faucet_id, vec![1, 2, 3])?, )?); let foreign_account_code_source = format!( diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 7a4da2d5ff..30e564e4fb 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -259,7 +259,7 @@ async fn test_get_assets() -> anyhow::Result<()> { add.{ASSET_SIZE} # => [dest_ptr+ASSET_SIZE, note_index] "#, - NOTE_ASSET_KEY = asset.vault_key().as_word(), + NOTE_ASSET_KEY = asset.to_key_word(), NOTE_ASSET_VALUE = asset.to_value_word(), asset_index = asset_index, note_index = note_index, diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 1ff68e6280..2beaefed64 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -89,7 +89,7 @@ impl TransactionContext { .iter() .flat_map(|note| note.note().assets().iter().map(Asset::vault_key)) .collect::>(); - let fee_asset_vault_key = AssetVaultKey::from_account_id( + let fee_asset_vault_key = AssetVaultKey::new_fungible( self.tx_inputs().block_header().fee_parameters().native_asset_id(), ) .expect("fee asset should be a fungible asset"); @@ -106,7 +106,7 @@ impl TransactionContext { // Add the vault key for the fee asset to the list of asset vault keys which may need to be // accessed at the end of the transaction. let fee_asset_vault_key = - AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id()) + AssetVaultKey::new_fungible(block_header.fee_parameters().native_asset_id()) .expect("fee asset should be a fungible asset"); asset_vault_keys.insert(fee_asset_vault_key); From 93588798e4abf68b81e02a3e1de0b878f08cf8e2 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 10:59:28 +0100 Subject: [PATCH 066/100] feat: validate asset ID prefix in non-fungible assets --- .../asm/kernels/transaction/lib/asset.masm | 30 ++++------ .../transaction/lib/non_fungible_asset.masm | 56 ++++++++++++++----- crates/miden-protocol/src/errors/tx_kernel.rs | 4 ++ .../src/transaction/kernel/procedures.rs | 12 ++-- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index f08a23ea15..525c34899d 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -41,26 +41,20 @@ end #! - ASSET_KEY is the vault key of the asset to validate. #! #! Panics if: -#! - the asset key's account ID is not valid. -#! - the asset key's account ID is neither of type fungible nor of type non-fungible. +#! - the asset key is not a valid fungible or non-fungible asset key (see +#! fungible_asset::validate_key and non_fungible_asset::validate_key). pub proc validate_asset_key - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - dup.1 dup.1 - # => [faucet_id_prefix, faucet_id_suffix, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - exec.account_id::validate - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - dup exec.account_id::is_fungible_faucet - # => [is_fungible_faucet, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - dup.1 exec.account_id::is_non_fungible_faucet - # => [is_fungible_faucet, is_non_fungible_faucet, faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + # check if the asset key is fungible + exec.is_fungible_asset + # => [is_fungible_asset, ASSET_KEY] - # assert the asset key's account ID is either of type fungible or type non-fungible - or assert.err=ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + if.true + exec.fungible_asset::validate_key + # => [ASSET_KEY] + else + exec.non_fungible_asset::validate_key + # => [ASSET_KEY] + end # => [ASSET_KEY] end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index 58c8841ae0..a7ec72bd9b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -10,12 +10,40 @@ const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="ma const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungible asset is not this faucet" +const ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0="the asset ID suffix in a non-fungible asset's vault key must match hash0 of the asset value" + +const ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1="the asset ID prefix in a non-fungible asset's vault key must match hash1 of the asset value" + # PROCEDURES # ================================================================================================= #! Validates that a non fungible asset is well formed. #! -#! The value is not validated since any value is valid. +#! The value itself is not validated since any value is valid. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! - ASSET_VALUE is the value of the asset to validate. +#! +#! Panics if: +#! - the asset key's account ID is not valid. +#! - the asset key's faucet ID is not a non-fungible one. +#! - the asset ID suffix of the key does not match hash0 of the value. +#! - the asset ID prefix of the key does not match hash1 of the value. +pub proc validate + exec.validate_key + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix, hash3, hash2, hash1, hash0] + + # assert that hash0 matches asset_id_suffix and hash1 matches asset_id_prefix + dup.7 dup.4 assert_eq.err=ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0 + dup.6 dup.3 assert_eq.err=ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1 + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix, ASSET_VALUE] +end + +#! Validates that a non fungible asset's key is well formed. #! #! Inputs: [ASSET_KEY] #! Outputs: [ASSET_KEY] @@ -26,8 +54,10 @@ const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungibl #! Panics if: #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a non-fungible one. -pub proc validate - exec.asset::validate_asset_key +pub proc validate_key + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + exec.account_id::validate # => [ASSET_KEY] exec.asset::is_non_fungible_asset @@ -37,29 +67,29 @@ end #! Validates that a non-fungible asset is associated with the provided faucet_id. #! -#! The value is not validated since any value is valid. +#! The value itself is not validated since any value is valid. #! -#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] -#! Outputs: [ASSET_KEY] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY, ASSET_VALUE] +#! Outputs: [ASSET_KEY, ASSET_VALUE] #! #! Where: #! - faucet_id_prefix is the prefix of the faucet's account ID. #! - ASSET_KEY is the vault key of the asset to validate. pub proc validate_origin - movdn.5 movdn.5 - # => [ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + movdn.9 movdn.9 + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the non-fungible asset key is valid exec.validate - # => [ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the origin of the asset is the faucet_id provided via the stack exec.asset::get_vault_key_faucet_id - # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, faucet_id_prefix, faucet_id_suffix] + # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] - movup.7 movup.7 - # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY] + movup.11 movup.11 + # => [faucet_id_prefix, faucet_id_suffix, key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE] exec.account_id::is_equal assert.err=ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN - # => [ASSET_KEY] + # => [ASSET_KEY, ASSET_VALUE] end diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index 29606310bd..d1fb100c11 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -122,6 +122,10 @@ pub const ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: MasmError = MasmError::from_sta pub const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN: MasmError = MasmError::from_static_str("the origin of the non-fungible asset is not this faucet"); /// Error Message: "malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID: MasmError = MasmError::from_static_str("malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id"); +/// Error Message: "the asset ID prefix in a non-fungible asset's vault key must match hash1 of the asset value" +pub const ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1: MasmError = MasmError::from_static_str("the asset ID prefix in a non-fungible asset's vault key must match hash1 of the asset value"); +/// Error Message: "the asset ID suffix in a non-fungible asset's vault key must match hash0 of the asset value" +pub const ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0: MasmError = MasmError::from_static_str("the asset ID suffix in a non-fungible asset's vault key must match hash0 of the asset value"); /// Error Message: "non-fungible asset vault key's account ID must be of type non-fungible faucet" pub const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE: MasmError = MasmError::from_static_str("non-fungible asset vault key's account ID must be of type non-fungible faucet"); diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 53ae5a08dc..fdbde92334 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,13 +40,13 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0xc52f196ec16ad73718286dd7cef276371c7cca1dcbeef8de40c2292ad2b4ca69"), + word!("0x18f2c591cfb71397255e08599a760222a34eb94cc5064f99ed78e3672f10eafa"), // account_remove_asset word!("0x48e72eb921fdc829be72b0cff2849e1ae2671d59b1c86bf6e389c43472ebc7bc"), // account_get_asset - word!("0x6192a2afe2b2e224d3eeed3e5c5b24bd5cb8b84d6beebe112f4e0dc272909652"), + word!("0xe9690cc3f2120cfb4b9a8af911b85554d71a0d0fa999c833df72fb9c3a59d529"), // account_get_initial_asset - word!("0xb9f28381abd3389f60c506eab3b15636c6006f68997a7396641c5bcc9e9f16ea"), + word!("0xea676a7520f3edf947993acea3001aa70867d757243d90936529685064bbc8db"), // account_compute_delta_commitment word!("0x8ea3f06c69939fa791c60e2fa06f19fd070aa20e20e65ba4c8f7931dcad6c86a"), // account_get_num_procedures @@ -58,9 +58,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xb0b63fdd01af0bcb4aacb2412e934cdc7691308647152d416c7ae4fc909da076"), // faucet_mint_asset - word!("0xcb1e09ed949517f3d4fa78f294c9132cdba6a835a9095de4fd4ac092592fdac6"), + word!("0x77971ba41851a1177db5f9180e8fddd5fa310805c660f640365b5a1f8bcf13d4"), // faucet_burn_asset - word!("0x8a07632e2cabc04096efcd2c60a91d6a75c03e302f14f907b7a511dc78fd1060"), + word!("0xb48403d9ffb7fa3d64b02a3c14d6b20931a5a4ac62f88e11371f4f5d0c1c43b7"), // input_note_get_metadata word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info @@ -82,7 +82,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // output_note_get_recipient word!("0x6aeec5901ae0afd538bdbb6f7c5a05da66e75fb9e2100c1ffe2a3fa5d9910b64"), // output_note_add_asset - word!("0x69696c78741f0827b20620c242db91436b03ef55bd9b7cc44c506791098bfc45"), + word!("0x0b06a914c4e9aea8865a161dc68acf0780fdfe846805e9d505f9005377913922"), // output_note_set_attachment word!("0xc33e8568f74b1accf0ee7f5de52fea30fe524b9e0dad7958d7e506e9ba3e3bbe"), // tx_get_num_input_notes From 201e062e07c243e4eb5159721d78b6513c03ba16 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 11:16:57 +0100 Subject: [PATCH 067/100] chore: drop trailing "asset" from `asset::validate_asset` --- .../miden-protocol/asm/kernels/transaction/api.masm | 4 ++-- .../asm/kernels/transaction/lib/asset.masm | 4 ++-- .../kernels/transaction/lib/non_fungible_asset.masm | 3 +-- .../asm/kernels/transaction/lib/output_note.masm | 2 +- .../src/transaction/kernel/procedures.rs | 12 ++++++------ 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 0068d3a79a..04804e0e04 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -613,7 +613,7 @@ end #! #! Invocation: dynexec pub proc account_get_asset - exec.asset::validate_asset_key + exec.asset::validate_key # => [ASSET_KEY, pad(12)] exec.account::get_asset @@ -632,7 +632,7 @@ end #! #! Invocation: dynexec pub proc account_get_initial_asset - exec.asset::validate_asset_key + exec.asset::validate_key # => [ASSET_KEY, pad(12)] exec.account::get_initial_asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 525c34899d..98c8e497d1 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -43,7 +43,7 @@ end #! Panics if: #! - the asset key is not a valid fungible or non-fungible asset key (see #! fungible_asset::validate_key and non_fungible_asset::validate_key). -pub proc validate_asset_key +pub proc validate_key # check if the asset key is fungible exec.is_fungible_asset # => [is_fungible_asset, ASSET_KEY] @@ -84,7 +84,7 @@ end #! #! Panics if: #! - the asset key or value are not well formed. -pub proc validate_asset +pub proc validate # check if the asset is fungible exec.is_fungible_asset # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index a7ec72bd9b..d7f0e8b89b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -55,8 +55,7 @@ end #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a non-fungible one. pub proc validate_key - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - + exec.asset::get_vault_key_faucet_id exec.account_id::validate # => [ASSET_KEY] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index a785c43669..6b620bd529 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -206,7 +206,7 @@ pub proc add_asset # => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr] # validate the asset - exec.asset::validate_asset + exec.asset::validate # => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr] # emit event to signal that a new asset is going to be added to the note. diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index fdbde92334..07237c5187 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,13 +40,13 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x18f2c591cfb71397255e08599a760222a34eb94cc5064f99ed78e3672f10eafa"), + word!("0x733ad192cf73042bce59548eb7ce01a19d1a11214f07fbe3becbe822c0284fff"), // account_remove_asset word!("0x48e72eb921fdc829be72b0cff2849e1ae2671d59b1c86bf6e389c43472ebc7bc"), // account_get_asset - word!("0xe9690cc3f2120cfb4b9a8af911b85554d71a0d0fa999c833df72fb9c3a59d529"), + word!("0x0de30d7e04469fcfe734453d71a5c5c97b53252d50045cee4b17d5fcf2696748"), // account_get_initial_asset - word!("0xea676a7520f3edf947993acea3001aa70867d757243d90936529685064bbc8db"), + word!("0xc1a323770b4213118f93110017f57587e07c10739f775ed11bb710c9412dada4"), // account_compute_delta_commitment word!("0x8ea3f06c69939fa791c60e2fa06f19fd070aa20e20e65ba4c8f7931dcad6c86a"), // account_get_num_procedures @@ -58,9 +58,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xb0b63fdd01af0bcb4aacb2412e934cdc7691308647152d416c7ae4fc909da076"), // faucet_mint_asset - word!("0x77971ba41851a1177db5f9180e8fddd5fa310805c660f640365b5a1f8bcf13d4"), + word!("0x0cb099c4a0d7d38bec98f80b06ba6c841de8cadbb2f39532e64e2e095c8a9670"), // faucet_burn_asset - word!("0xb48403d9ffb7fa3d64b02a3c14d6b20931a5a4ac62f88e11371f4f5d0c1c43b7"), + word!("0xdbbeb76ac726b6c8f9cc684dc80027b65552ece70b9456a5dc5bb4d9c0913301"), // input_note_get_metadata word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info @@ -82,7 +82,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // output_note_get_recipient word!("0x6aeec5901ae0afd538bdbb6f7c5a05da66e75fb9e2100c1ffe2a3fa5d9910b64"), // output_note_add_asset - word!("0x0b06a914c4e9aea8865a161dc68acf0780fdfe846805e9d505f9005377913922"), + word!("0x8022245825922dadf949338c2f7ddd02a4027f7c8e2bef5954c5b4259624be75"), // output_note_set_attachment word!("0xc33e8568f74b1accf0ee7f5de52fea30fe524b9e0dad7958d7e506e9ba3e3bbe"), // tx_get_num_input_notes From 14ae7c819aec7c830cb69f38ce7a6c2236027d93 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 11:28:26 +0100 Subject: [PATCH 068/100] chore: rewrite asset validation tests --- .../src/asset/vault/asset_id.rs | 2 +- .../src/kernel_tests/tx/test_asset.rs | 146 +++++++++++++++--- 2 files changed, 129 insertions(+), 19 deletions(-) diff --git a/crates/miden-protocol/src/asset/vault/asset_id.rs b/crates/miden-protocol/src/asset/vault/asset_id.rs index 441ca4c5ad..e3e8108040 100644 --- a/crates/miden-protocol/src/asset/vault/asset_id.rs +++ b/crates/miden-protocol/src/asset/vault/asset_id.rs @@ -4,7 +4,7 @@ use crate::Felt; /// The [`AssetId`] in an [`AssetVaultKey`](crate::asset::AssetVaultKey) distinguishes different /// assets issued by the same faucet. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct AssetId { suffix: Felt, prefix: Felt, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 5885bb76cf..8264fa06d7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -1,9 +1,26 @@ -use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; -use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; +use miden_protocol::Word; +use miden_protocol::account::AccountId; +use miden_protocol::asset::{AssetId, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; +use miden_protocol::errors::MasmError; +use miden_protocol::errors::tx_kernel::{ + ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT, + ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE, + ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO, + ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO, + ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1, + ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0, + ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE, +}; +use miden_protocol::testing::account_id::{ + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, + ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, +}; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; -use crate::TransactionContextBuilder; +use crate::executor::CodeExecutor; use crate::kernel_tests::tx::ExecutionOutputExt; +use crate::{TransactionContextBuilder, assert_execution_error}; #[tokio::test] async fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { @@ -78,35 +95,128 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { Ok(()) } +#[rstest::rstest] +#[case::account_is_not_non_fungible_faucet( + ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE.try_into()?, + AssetId::default(), + ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE +)] +#[case::asset_id_suffix_mismatch( + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into()?, + AssetId::new(0u32.into(), 4u32.into()), + ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0 +)] +#[case::asset_id_prefix_mismatch( + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into()?, + AssetId::new(5u32.into(), 0u32.into()), + ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1 +)] #[tokio::test] -#[ignore = "TODO(expand_assets): refactor"] -async fn test_validate_non_fungible_asset() -> anyhow::Result<()> { - let tx_context = - TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) - .build()?; - let non_fungible_asset = NonFungibleAsset::mock(&[1, 2, 3]); +async fn test_validate_non_fungible_asset( + #[case] account_id: AccountId, + #[case] asset_id: AssetId, + #[case] expected_err: MasmError, +) -> anyhow::Result<()> { + let code = format!( + " + use $kernel::non_fungible_asset + + begin + # a random asset value + push.5 push.4 push.3 push.2 + # => [2, 3, hash1 = 4, hash0 = 5] + + push.{asset_id_suffix} + push.{asset_id_prefix} + push.{account_id_suffix} + push.{account_id_prefix} + # => [ASSET_KEY, ASSET_VALUE] + + exec.non_fungible_asset::validate + + # truncate the stack + swapdw dropw dropw + end + ", + asset_id_suffix = asset_id.suffix(), + asset_id_prefix = asset_id.prefix(), + account_id_suffix = account_id.suffix(), + account_id_prefix = account_id.prefix().as_felt(), + ); + + let exec_result = CodeExecutor::with_default_host().run(&code).await; + + assert_execution_error!(exec_result, expected_err); + + Ok(()) +} +#[rstest::rstest] +#[case::account_is_not_fungible_faucet( + ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE.try_into()?, + AssetId::default(), + Word::empty(), + ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE +)] +#[case::asset_id_suffix_is_non_zero( + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?, + AssetId::new(1u32.into(), 0u32.into()), + Word::empty(), + ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO +)] +#[case::asset_id_prefix_is_non_zero( + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?, + AssetId::new(0u32.into(), 1u32.into()), + Word::empty(), + ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO +)] +#[case::non_amount_value_is_non_zero( + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?, + AssetId::default(), + Word::from([0, 1, 0, 0u32]), + ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO +)] +#[case::amount_exceeds_max( + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?, + AssetId::default(), + Word::try_from([FungibleAsset::MAX_AMOUNT + 1, 0, 0, 0])?, + ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT +)] +#[tokio::test] +async fn test_validate_fungible_asset( + #[case] account_id: AccountId, + #[case] asset_id: AssetId, + #[case] asset_value: Word, + #[case] expected_err: MasmError, +) -> anyhow::Result<()> { let code = format!( " - use $kernel::asset + use $kernel::fungible_asset begin - push.{NON_FUNGIBLE_ASSET_VALUE} - push.{NON_FUNGIBLE_ASSET_KEY} - exec.asset::validate_non_fungible_asset + push.{ASSET_VALUE} + push.{asset_id_suffix} + push.{asset_id_prefix} + push.{account_id_suffix} + push.{account_id_prefix} + # => [ASSET_KEY, ASSET_VALUE] + + exec.fungible_asset::validate # truncate the stack swapdw dropw dropw end ", - NON_FUNGIBLE_ASSET_KEY = non_fungible_asset.to_key_word(), - NON_FUNGIBLE_ASSET_VALUE = non_fungible_asset.to_value_word(), + asset_id_suffix = asset_id.suffix(), + asset_id_prefix = asset_id.prefix(), + account_id_suffix = account_id.suffix(), + account_id_prefix = account_id.prefix().as_felt(), + ASSET_VALUE = asset_value, ); - let exec_output = &tx_context.execute_code(&code).await?; + let exec_result = CodeExecutor::with_default_host().run(&code).await; - assert_eq!(exec_output.get_stack_word_be(0), non_fungible_asset.to_key_word()); - assert_eq!(exec_output.get_stack_word_be(4), non_fungible_asset.to_value_word()); + assert_execution_error!(exec_result, expected_err); Ok(()) } From 8f8c5774c8d0c83c584827a6ebbbc5a27d50b4d5 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 11:48:50 +0100 Subject: [PATCH 069/100] chore: merge validate_value in `fungible_asset` --- .../asm/kernels/transaction/lib/asset.masm | 3 +- .../transaction/lib/fungible_asset.masm | 46 ++++++++----------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 98c8e497d1..0fbd8dfabc 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -83,7 +83,8 @@ end #! - ASSET_VALUE is the value of the asset to validate. #! #! Panics if: -#! - the asset key or value are not well formed. +#! - the asset is not a valid fungible or non-fungible asset (see fungible_asset::validate and +#! non_fungible_asset::validate_key). pub proc validate # check if the asset is fungible exec.is_fungible_asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index f6fbd8a8da..953011247f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -89,12 +89,28 @@ end #! - ASSET_VALUE is the value of the asset to validate. #! #! Panics if: -#! - the asset key or value are not well formed. +#! - the asset key is invalid (see validate_key). +#! - the three most significant elements in the value are not 0. +#! - the amount exceeds FUNGIBLE_ASSET_MAX_AMOUNT. pub proc validate exec.validate_key # => [ASSET_KEY, ASSET_VALUE] - dupw.1 exec.validate_value + dupw.1 + # => [ASSET_VALUE, ASSET_KEY, ASSET_VALUE] + + # assuming the asset is valid, its layout is: + # => [0, 0, 0, amount, ASSET_KEY, ASSET_VALUE] + + # assert the first three elements are zeros + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO + # => [amount, ASSET_KEY, ASSET_VALUE] + + # assert amount <= FUNGIBLE_ASSET_MAX_AMOUNT + lte.FUNGIBLE_ASSET_MAX_AMOUNT + assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT # => [ASSET_KEY, ASSET_VALUE] end @@ -126,32 +142,6 @@ pub proc validate_key # => [ASSET_KEY] end -#! Validates that a fungible asset value is well formed. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [] -#! -#! Where: -#! - ASSET_VALUE is the asset to validate. -#! -#! Panics if: -#! - the asset value is not well formed. -proc validate_value - # assuming the asset is valid, its layout is: - # => [0, 0, 0, amount] - - # assert the first three elements are zeros - eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO - eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO - eq.0 assert.err=ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO - # => [amount] - - # assert amount <= FUNGIBLE_ASSET_MAX_AMOUNT - lte.FUNGIBLE_ASSET_MAX_AMOUNT - assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_AMOUNT - # => [] -end - #! Validates that a fungible asset is associated with the provided faucet_id. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY, ASSET_VALUE] From 6fe782bc48d344dd135744b41eb2e84a1c4296ae Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 14:30:38 +0100 Subject: [PATCH 070/100] chore: remove unused build_asset_vault_key procedures --- .../kernels/transaction/lib/asset_vault.masm | 8 --- .../transaction/lib/non_fungible_asset.masm | 2 - .../asm/shared_utils/util/asset.masm | 72 +------------------ .../miden-protocol/src/account/delta/mod.rs | 8 +-- crates/miden-protocol/src/errors/tx_kernel.rs | 2 - 5 files changed, 5 insertions(+), 87 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index 5df21d9838..7139fe3dab 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -21,14 +21,6 @@ const ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to re const ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-existent non-fungible asset from the vault" -# RE-EXPORTS -# ================================================================================================= - -# Re-export the shared utility procedures -pub use $kernel::util::asset::build_non_fungible_asset_vault_key -pub use $kernel::util::asset::build_fungible_asset_vault_key -pub use $kernel::util::asset::build_asset_vault_key - # ACCESSORS # ================================================================================================= diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index d7f0e8b89b..cb14a653a1 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -6,8 +6,6 @@ use $kernel::asset const ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE = "non-fungible asset vault key's account ID must be of type non-fungible faucet" -const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID="malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" - const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN="the origin of the non-fungible asset is not this faucet" const ERR_NON_FUNGIBLE_ASSET_ID_SUFFIX_MUST_MATCH_HASH0="the asset ID suffix in a non-fungible asset's vault key must match hash0 of the asset value" diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index b025d0094d..b64d4ea619 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -12,15 +12,12 @@ pub const ASSET_SIZE = 8 # The offset of the asset value in an asset stored in memory. pub const ASSET_VALUE_MEMORY_OFFSET = 4 -# The bitmask that when applied will set the fungible bit to zero. -const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 - # PROCEDURES # ================================================================================================= #! Returns the balance of the given fungible asset. #! -#! Note: Assumes that the given asset is fungible and does NOT validate it. +#! WARNING: Assumes that the given asset value is fungible and does NOT validate it. #! #! Inputs: [ASSET_VALUE] #! Outputs: [balance] @@ -77,70 +74,3 @@ pub proc create_non_fungible_asset_unchecked # => [[faucet_id_prefix, faucet_id_suffix, hash1, hash0], DATA_HASH] # => [ASSET_KEY, ASSET_VALUE] end - -#! Builds the vault key of a fungible asset. The asset is NOT validated and therefore must -#! be a valid fungible asset. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - ASSET_VALUE is the fungible asset for which the vault key is built. -#! - ASSET_KEY is the vault key of the fungible asset. -pub proc build_fungible_asset_vault_key - # => [faucet_id_prefix, faucet_id_suffix, 0, amount] - - push.0.0 - # => [0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] - - dup.3 dup.3 - # => [faucet_id_prefix, faucet_id_suffix, 0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] -end - -#! Builds the vault key of a non fungible asset. The asset is NOT validated and therefore must -#! be a valid non-fungible asset. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY] -#! -#! Where: -#! - ASSET_VALUE is the non-fungible asset for which the vault key is built. -#! - ASSET_KEY is the vault key of the non-fungible asset. -pub proc build_non_fungible_asset_vault_key - # create the asset key from the non-fungible asset by swapping hash0 with the faucet id - # => [faucet_id_prefix, hash2, hash1, hash0] - swap.3 - # => [hash0, hash2, hash1, faucet_id_prefix] - - # disassemble hash0 into u32 limbs - u32split swap - # => [hash0_lo, hash0_hi, hash2, hash1, faucet_id_prefix] - - # set the fungible bit to 0 - u32and.INVERSE_FUNGIBLE_BITMASK_U32 - # => [hash0_lo', hash0_hi, hash2, hash1, faucet_id_prefix] - - # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part - swap mul.0x0100000000 add - # => [ASSET_KEY] -end - -#! Builds an asset vault key from an asset value. -#! -#! Inputs: [ASSET_VALUE] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -pub proc build_asset_vault_key - # check the first element, it will be: - # - zero for a fungible asset - # - non zero for a non-fungible asset - dup.2 eq.0 - # => [is_fungible_asset, ASSET_VALUE] - - if.true - exec.build_fungible_asset_vault_key - # => [ASSET_KEY, ASSET_VALUE] - else - dupw exec.build_non_fungible_asset_vault_key - # => [ASSET_KEY, ASSET_VALUE] - end -end diff --git a/crates/miden-protocol/src/account/delta/mod.rs b/crates/miden-protocol/src/account/delta/mod.rs index 90832696a5..d4a554c6f1 100644 --- a/crates/miden-protocol/src/account/delta/mod.rs +++ b/crates/miden-protocol/src/account/delta/mod.rs @@ -285,10 +285,10 @@ impl AccountDelta { /// ``` /// /// `NEW_VALUE` is user-controllable so it can be crafted to match `NON_FUNGIBLE_ASSET`. Users - /// would have to choose a slot ID that is equal to the faucet ID which is possible since slot - /// IDs can be chosen at account creation time. The domain separator is then the only value that - /// differentiates these two deltas. This shows the importance of placing the domain separators - /// in the same index within each word's layout to ensure users cannot craft an ambiguous delta. + /// would have to choose a slot ID (at account creation time) that is equal to the faucet ID. + /// The domain separator is then the only value that differentiates these two deltas. This shows + /// the importance of placing the domain separators in the same index within each word's layout + /// to ensure users cannot craft an ambiguous delta. /// /// ### Number of Changed Entries /// diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index d1fb100c11..577a92e5e5 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -120,8 +120,6 @@ pub const ERR_LINK_MAP_PROVIDED_KEY_NOT_LESS_THAN_ENTRY_KEY: MasmError = MasmErr pub const ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: MasmError = MasmError::from_static_str("non-fungible asset that already exists in the note cannot be added again"); /// Error Message: "the origin of the non-fungible asset is not this faucet" pub const ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN: MasmError = MasmError::from_static_str("the origin of the non-fungible asset is not this faucet"); -/// Error Message: "malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id" -pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_ELEMENT_THREE_MUST_BE_FUNGIBLE_FAUCET_ID: MasmError = MasmError::from_static_str("malformed non-fungible asset: `ASSET[3]` is not a valid non-fungible faucet id"); /// Error Message: "the asset ID prefix in a non-fungible asset's vault key must match hash1 of the asset value" pub const ERR_NON_FUNGIBLE_ASSET_ID_PREFIX_MUST_MATCH_HASH1: MasmError = MasmError::from_static_str("the asset ID prefix in a non-fungible asset's vault key must match hash1 of the asset value"); /// Error Message: "the asset ID suffix in a non-fungible asset's vault key must match hash0 of the asset value" From b7ebe5b688688226122a7bc90e68e7768763f768 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 14:32:06 +0100 Subject: [PATCH 071/100] chore: remove `LexicographicWord` wrapper in non-fungible asset delta --- .../miden-protocol/src/account/delta/vault.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index 9004e196b2..5b292106be 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -13,7 +13,7 @@ use super::{ }; use crate::account::{AccountId, AccountType}; use crate::asset::{Asset, AssetVaultKey, FungibleAsset, NonFungibleAsset}; -use crate::{Felt, LexicographicWord, ONE, ZERO}; +use crate::{Felt, ONE, ZERO}; // ACCOUNT VAULT DELTA // ================================================================================================ @@ -385,13 +385,13 @@ impl Deserializable for FungibleAssetDelta { /// in-kernel account delta which uses a link map. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct NonFungibleAssetDelta( - BTreeMap, (NonFungibleAsset, NonFungibleDeltaAction)>, + BTreeMap, ); impl NonFungibleAssetDelta { /// Creates a new non-fungible asset delta. pub const fn new( - map: BTreeMap, (NonFungibleAsset, NonFungibleDeltaAction)>, + map: BTreeMap, ) -> Self { Self(map) } @@ -457,7 +457,7 @@ impl NonFungibleAssetDelta { asset: NonFungibleAsset, action: NonFungibleDeltaAction, ) -> Result<(), AccountDeltaError> { - match self.0.entry(LexicographicWord::new(asset.vault_key())) { + match self.0.entry(asset.vault_key()) { Entry::Vacant(entry) => { entry.insert((asset, action)); }, @@ -536,19 +536,13 @@ impl Deserializable for NonFungibleAssetDelta { let num_added = source.read_usize()?; for _ in 0..num_added { let added_asset: NonFungibleAsset = source.read()?; - map.insert( - LexicographicWord::new(added_asset.vault_key()), - (added_asset, NonFungibleDeltaAction::Add), - ); + map.insert(added_asset.vault_key(), (added_asset, NonFungibleDeltaAction::Add)); } let num_removed = source.read_usize()?; for _ in 0..num_removed { let removed_asset: NonFungibleAsset = source.read()?; - map.insert( - LexicographicWord::new(removed_asset.vault_key()), - (removed_asset, NonFungibleDeltaAction::Remove), - ); + map.insert(removed_asset.vault_key(), (removed_asset, NonFungibleDeltaAction::Remove)); } Ok(Self::new(map)) From cfac902f22748423069725976b94af825b4d3520 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 14:42:30 +0100 Subject: [PATCH 072/100] chore: update asset::create_non_fungible_asset signature in docs --- docs/src/protocol_library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 6d4d0c46c6..f60b5a51d3 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -162,4 +162,4 @@ Asset procedures provide utilities for creating fungible and non-fungible assets | Procedure | Description | Context | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `create_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, amount]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Any | -| `create_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, DATA_HASH]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Any | +| `create_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, DATA_HASH]`
**Outputs:** `[ASSET_KEY, ASSET_VALUE]` | Any | From 12370c958028f127b538c87e485277be7a04471b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 14:52:22 +0100 Subject: [PATCH 073/100] fix: test name, vault key display impl, remove unused errors --- crates/miden-protocol/src/asset/mod.rs | 2 +- .../src/asset/vault/vault_key.rs | 31 +------------------ crates/miden-protocol/src/errors/mod.rs | 4 --- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 3cc4053113..8340c0e636 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -318,7 +318,7 @@ mod tests { /// Asset deserialization relies on that fact and if this changes the serialization must /// be updated. #[test] - fn test_account_id_prefix_is_serialized_first() { + fn test_account_id_is_serialized_first() { for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] { let serialized_asset = asset.to_bytes(); let prefix = AccountId::read_from_bytes(&serialized_asset).unwrap(); diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 926a0602a5..ebce856e87 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -127,15 +127,7 @@ impl TryFrom for AssetVaultKey { impl fmt::Display for AssetVaultKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // The faucet ID Display impl includes the 0x prefix. - // Write asset ID limbs manually to avoid 0x prefix from asset ID display impl. - write!( - f, - "{}{:016x}{:016x}", - self.faucet_id, - self.asset_id.prefix().as_int(), - self.asset_id.suffix().as_int() - ) + f.write_str(&self.to_word().to_hex()) } } @@ -165,24 +157,3 @@ fn vault_key_to_word(asset_id: AssetId, faucet_id: AccountId) -> Word { faucet_id.prefix().as_felt(), ]) } - -#[cfg(test)] -mod tests { - use std::string::ToString; - - use super::*; - use crate::testing::account_id::ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET; - - #[test] - fn asset_vault_key_to_hex() { - let key = AssetVaultKey::new( - AssetId::new(1u32.into(), 2u32.into()), - ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(), - ); - - assert_eq!( - key.to_string(), - "0xfa0000000000bba00000cd000000dd00000000000000020000000000000001" - ) - } -} diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index cc2091cace..7231c61aa1 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -436,8 +436,6 @@ pub enum AssetError { FungibleAssetAmountTooBig(u64), #[error("subtracting {subtrahend} from fungible asset amount {minuend} would underflow")] FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 }, - #[error("fungible asset word {0} does not contain expected ZERO at word index 1")] - FungibleAssetExpectedZero(Word), #[error( "cannot add fungible asset with issuer {other_issuer} to fungible asset with issuer {original_issuer}" )] @@ -447,8 +445,6 @@ pub enum AssetError { }, #[error("faucet account ID in asset is invalid")] InvalidFaucetAccountId(#[source] Box), - #[error("faucet account ID in asset has a non-faucet prefix: {}", .0)] - InvalidFaucetAccountIdPrefix(AccountIdPrefix), #[error( "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets", id_type = .0.account_type(), From a3af7a621bccd5c1f2e2eb2c1cff272d133cfab1 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 15:07:28 +0100 Subject: [PATCH 074/100] fix: intra doc links --- crates/miden-protocol/src/account/delta/vault.rs | 4 ++-- crates/miden-protocol/src/asset/mod.rs | 9 ++++----- crates/miden-protocol/src/asset/nonfungible.rs | 10 ++-------- crates/miden-protocol/src/asset/vault/vault_key.rs | 3 --- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index 5b292106be..668d70c949 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -381,8 +381,8 @@ impl Deserializable for FungibleAssetDelta { /// A binary tree map of non-fungible asset changes (addition and removal) in the account vault. /// -/// The [`LexicographicWord`] wrapper is necessary to order the assets in the same way as the -/// in-kernel account delta which uses a link map. +/// The [`AssetVaultKey`] orders the assets in the same way as the in-kernel account delta which +/// uses a link map. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct NonFungibleAssetDelta( BTreeMap, diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 8340c0e636..0fdd922b12 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -33,11 +33,10 @@ pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; /// (4 elements). This makes it is easy to determine the type of an asset both inside and outside /// Miden VM. Specifically: /// -/// The vault key of an asset contains the [`AccountId`](crate::account::AccountId) of the faucet -/// that issues the asset. It can be used to distinguish assets based on the encoded -/// [`AccountId::account_type`](crate::account::AccountId::account_type). In the vault keys of -/// assets, the account type bits at index 4 and 5 determine whether the asset is fungible or -/// non-fungible. +/// The vault key of an asset contains the [`AccountId`] of the faucet that issues the asset. It can +/// be used to distinguish assets based on the encoded [`AccountId::account_type`]. In the vault +/// keys of assets, the account type bits at index 4 and 5 determine whether the asset is fungible +/// or non-fungible. /// /// This property guarantees that there can never be a collision between a fungible and a /// non-fungible asset. diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index 450f1a2188..8ef820f5a8 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -14,13 +14,7 @@ use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, /// A commitment to a non-fungible asset. /// -/// The commitment is constructed as follows: -/// -/// - Hash the asset data producing `[hash0, hash1, hash2, hash3]`. -/// - Replace the value of `hash3` with the prefix of the faucet id (`faucet_id_prefix`) producing -/// `[hash0, hash1, hash2, faucet_id_prefix]`. -/// - This layout ensures that fungible and non-fungible assets are distinguishable by interpreting -/// the 3rd element of an asset as an [`AccountIdPrefix`] and checking its type. +/// See [`Asset`] for details on how it is constructed. /// /// [`NonFungibleAsset`] itself does not contain the actual asset data. The container for this data /// is [`NonFungibleAssetDetails`]. @@ -293,7 +287,7 @@ mod tests { let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(); - // Set invalid Faucet ID Prefix. + // Set invalid faucet ID. asset_bytes[0..AccountId::SERIALIZED_SIZE].copy_from_slice(&fungible_faucet_id.to_bytes()); let err = NonFungibleAsset::read_from_bytes(&asset_bytes).unwrap_err(); diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index ebce856e87..12aab5777e 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -14,9 +14,6 @@ use crate::{Felt, FieldElement, Word}; /// The unique identifier of an [`Asset`] in the [`AssetVault`](crate::asset::AssetVault). /// -/// Note that the asset vault key is not used directly as the key in an asset vault. See -/// the derived [`AssetVaultKeyHash`] for details. -/// /// Its [`Word`] layout is: /// ```text /// [ From ddf536ac77dce82f32a23434568bd2428d534dd8 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 12 Feb 2026 15:19:41 +0100 Subject: [PATCH 075/100] chore: add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc00c17c50..3fbc7f882c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - [BREAKING] Refactored assets in the tx kernel and `miden::protocol` from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396), [#2410](https://github.com/0xMiden/miden-base/pull/2410)). - [BREAKING] Rename `miden::protocol::asset::build_fungible_asset` to `miden::protocol::asset::create_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). - [BREAKING] Rename `miden::protocol::asset::build_non_fungible_asset` to `miden::protocol::asset::create_non_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). +- [BREAKING] Change the layout of fungible and non-fungible assets ([#2437](https://github.com/0xMiden/miden-base/pull/2437)). ## 0.13.3 (2026-01-27) From 15502983ac657a7ea49d85d8000ffdfdb096a152 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:20:18 +0000 Subject: [PATCH 076/100] Initial plan From c4bf16adb8c63de3b240e265fb119b2a85a88667 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:30:45 +0000 Subject: [PATCH 077/100] Address review nits: add load_asset_key_and_value utility, use explicit ASSET_PTR constants, rename variables, add test Co-authored-by: mmagician <8402446+mmagician@users.noreply.github.com> --- .../asm/note_scripts/B2AGG.masm | 7 ++++--- .../asm/kernels/transaction/lib/asset.masm | 19 ++++++++++++++++++ .../asm/kernels/transaction/lib/epilogue.masm | 9 ++------- .../asm/kernels/transaction/lib/prologue.masm | 9 +++------ .../asm/standards/faucets/mod.masm | 7 ++++--- .../asm/standards/notes/swap.masm | 7 ++++--- .../src/kernel_tests/tx/test_asset_vault.rs | 20 +++++++++++++++++++ crates/miden-tx/src/host/tx_event.rs | 8 ++++---- 8 files changed, 60 insertions(+), 26 deletions(-) diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 72701dbbbd..360a560a3d 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -10,6 +10,7 @@ use miden::standards::wallets::basic->basic_wallet # CONSTANTS # ================================================================================================= +const ASSET_PTR=0 const B2AGG_NOTE_NUM_STORAGE_ITEMS=6 # ERRORS @@ -79,7 +80,7 @@ begin # => [pad(16)] # Store note assets -> mem[0..8] - push.0 exec.active_note::get_assets + push.ASSET_PTR exec.active_note::get_assets # => [num_assets, ptr, pad(16)] # Must be exactly 1 asset @@ -90,8 +91,8 @@ begin mem_loadw_be.12 swapw.2 mem_loadw_be.8 swapw # => [EMPTY_WORD, dest_network, dest_address(5), pad(6)] - # Load ASSET_VALUE onto the stack from 0 + ASSET_VALUE_MEMORY_OFFSET - mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # Load ASSET_VALUE onto the stack from ASSET_PTR + ASSET_VALUE_MEMORY_OFFSET + push.ASSET_PTR add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be # => [ASSET_VALUE, dest_network, dest_address(5), pad(6)] call.bridge_out::bridge_out diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 39ad9cf0e4..a6c35c7f52 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -31,6 +31,25 @@ pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET # PROCEDURES # ================================================================================================= +#! Loads an asset key and value from memory given a pointer to the asset. +#! +#! Inputs: [ptr] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - ptr is the memory address of the asset. +#! - ASSET_KEY is the 4-element word representing the asset key. +#! - ASSET_VALUE is the 4-element word representing the asset value. +pub proc load_asset_key_and_value + # load asset value + padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, ptr] + + # load asset key + padw movup.5 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE] +end + #! Validates that a fungible asset is well formed. #! #! Inputs: [ASSET_KEY, ASSET_VALUE] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 8fc298af00..616b9a617c 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -2,6 +2,7 @@ use $kernel::account use $kernel::account_delta use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET +use $kernel::asset::load_asset_key_and_value use $kernel::asset_vault use $kernel::constants::NOTE_MEM_SIZE use $kernel::memory @@ -170,13 +171,7 @@ proc build_output_vault # num_assets, note_data_ptr, output_notes_end_ptr] # read the output note asset from memory - # load the asset value - padw dup.5 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - # => [ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, - # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] - - # load the asset key - padw dup.9 mem_loadw_be + dup.4 exec.load_asset_key_and_value # => [ASSET_KEY, ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index 136fb8d6d8..a73aa581f2 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -8,6 +8,7 @@ use $kernel::account_id use $kernel::asset_vault use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET +use $kernel::asset::load_asset_key_and_value use $kernel::constants::EMPTY_SMT_ROOT use $kernel::constants::MAX_ASSETS_PER_NOTE use $kernel::constants::MAX_INPUT_NOTES_PER_TX @@ -706,12 +707,8 @@ proc add_input_note_assets_to_vault dup.2 # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] - # load asset value - padw dup.5 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - # => [ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] - - # load asset key - padw dup.9 mem_loadw_be + # load asset key and value + dup.4 exec.load_asset_key_and_value # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # the witnesses for the note assets should be added prior to transaction execution and so diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 7e49f03b80..ca5e68ef70 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -9,6 +9,7 @@ use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET # CONSTANTS # ================================================================================================= +const ASSET_PTR=0 const PRIVATE_NOTE=2 # ERRORS @@ -176,15 +177,15 @@ pub proc burn # --------------------------------------------------------------------------------------------- # this will fail if not called from a note context. - push.0 exec.active_note::get_assets + push.ASSET_PTR exec.active_note::get_assets # => [num_assets, dest_ptr, pad(16)] # Verify we have exactly one asset assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS # => [dest_ptr, pad(16)] - # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET - mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # load asset value from ASSET_PTR + ASSET_VALUE_MEMORY_OFFSET + push.ASSET_PTR add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be # => [ASSET_VALUE, pad(16)] # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], pad(16)] diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 3c71b6e63f..315f9f0917 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -6,6 +6,7 @@ use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET # CONSTANTS # ================================================================================================= +const ASSET_PTR=0 const SWAP_NOTE_NUM_STORAGE_ITEMS=16 const PAYBACK_NOTE_TYPE_ADDRESS=0 @@ -111,15 +112,15 @@ pub proc main # --- move assets from the SWAP note into the account ------------------------- # store the number of note assets to memory starting at address 0 - push.0 exec.active_note::get_assets + push.ASSET_PTR exec.active_note::get_assets # => [num_assets, ptr, pad(12)] # make sure the number of assets is 1 assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS # => [ptr, pad(12)] - # load asset value from 0 + ASSET_VALUE_MEMORY_OFFSET - mem_loadw_be.ASSET_VALUE_MEMORY_OFFSET + # load asset value from ASSET_PTR + ASSET_VALUE_MEMORY_OFFSET + push.ASSET_PTR add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be # => [ASSET_VALUE, pad(12)] # add the asset to the account diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 15ac66e1a9..0f4be735d4 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -17,6 +17,7 @@ use miden_protocol::errors::tx_kernel::{ use miden_protocol::errors::{AssetError, AssetVaultError}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, }; @@ -620,3 +621,22 @@ async fn test_merge_fungible_asset_fails_when_max_amount_exceeded() -> anyhow::R Ok(()) } + +/// Tests that merging two different fungible assets fails. +#[tokio::test] +async fn test_merge_different_fungible_assets_fails() -> anyhow::Result<()> { + // Create two fungible assets from different faucets + let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); + let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into().unwrap(); + + let asset0 = FungibleAsset::new(faucet_id1, FUNGIBLE_ASSET_AMOUNT)?; + let asset1 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?; + + // Sanity check that the Rust implementation errors when adding assets from different faucets. + assert_matches!( + asset0.add(asset1).unwrap_err(), + AssetError::FungibleAssetInconsistentFaucetIds { .. } + ); + + Ok(()) +} diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 451c9d0d5f..a43c22d66a 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -210,26 +210,26 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultAfterRemoveAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { + let asset_value: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_remove_asset", source, } })?; - Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset }) + Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset: asset_value }) }, TransactionEventId::AccountVaultAfterAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { + let asset_value: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_add_asset", source, } })?; - Some(TransactionEvent::AccountVaultAfterAddAsset { asset }) + Some(TransactionEvent::AccountVaultAfterAddAsset { asset: asset_value }) }, TransactionEventId::AccountVaultBeforeGetAsset => { // Expected stack state: From e81a8eac89a2e7102ea6aaab570e8d983fb36c85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:35:29 +0000 Subject: [PATCH 078/100] Fix load_asset_key_and_value procedure and usages in prologue and epilogue Co-authored-by: mmagician <8402446+mmagician@users.noreply.github.com> --- crates/miden-protocol/asm/kernels/transaction/lib/asset.masm | 2 +- crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm | 2 +- crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index a6c35c7f52..c499db2460 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -46,7 +46,7 @@ pub proc load_asset_key_and_value # => [ASSET_VALUE, ptr] # load asset key - padw movup.5 mem_loadw_be + padw movup.8 mem_loadw_be # => [ASSET_KEY, ASSET_VALUE] end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 616b9a617c..4d6a8f3977 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -171,7 +171,7 @@ proc build_output_vault # num_assets, note_data_ptr, output_notes_end_ptr] # read the output note asset from memory - dup.4 exec.load_asset_key_and_value + dup.1 exec.load_asset_key_and_value # => [ASSET_KEY, ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index a73aa581f2..c45ab6537e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -708,7 +708,7 @@ proc add_input_note_assets_to_vault # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # load asset key and value - dup.4 exec.load_asset_key_and_value + dup.1 exec.load_asset_key_and_value # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # the witnesses for the note assets should be added prior to transaction execution and so From 7a57963797c85ebf247b8df55cb6f4b12d6e75c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:57:54 +0000 Subject: [PATCH 079/100] Run cargo fmt to fix formatting issues Co-authored-by: mmagician <8402446+mmagician@users.noreply.github.com> --- .../src/kernel_tests/tx/test_asset_vault.rs | 2 +- crates/miden-tx/src/host/tx_event.rs | 26 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 0f4be735d4..a4bd6fca53 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -628,7 +628,7 @@ async fn test_merge_different_fungible_assets_fails() -> anyhow::Result<()> { // Create two fungible assets from different faucets let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into().unwrap(); - + let asset0 = FungibleAsset::new(faucet_id1, FUNGIBLE_ASSET_AMOUNT)?; let asset1 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?; diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index a43c22d66a..0a8c24e1ee 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -210,24 +210,26 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultAfterRemoveAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset_value: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_remove_asset", - source, - } - })?; + let asset_value: Asset = + process.get_stack_word_be(5).try_into().map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "on_account_vault_after_remove_asset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset: asset_value }) }, TransactionEventId::AccountVaultAfterAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset_value: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_add_asset", - source, - } - })?; + let asset_value: Asset = + process.get_stack_word_be(5).try_into().map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "on_account_vault_after_add_asset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterAddAsset { asset: asset_value }) }, From 1ce31fa0167e56eb2612a0076a29baf3ac5b7294 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:53:40 +0000 Subject: [PATCH 080/100] Rename asset_value back to asset in tx_event.rs Co-authored-by: mmagician <8402446+mmagician@users.noreply.github.com> --- crates/miden-tx/src/host/tx_event.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 0a8c24e1ee..2a79d9d2fa 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -210,7 +210,7 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultAfterRemoveAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset_value: Asset = + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_remove_asset", @@ -218,12 +218,12 @@ impl TransactionEvent { } })?; - Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset: asset_value }) + Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset }) }, TransactionEventId::AccountVaultAfterAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset_value: Asset = + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_add_asset", @@ -231,7 +231,7 @@ impl TransactionEvent { } })?; - Some(TransactionEvent::AccountVaultAfterAddAsset { asset: asset_value }) + Some(TransactionEvent::AccountVaultAfterAddAsset { asset }) }, TransactionEventId::AccountVaultBeforeGetAsset => { // Expected stack state: From 7bb2c67f6546147c0324b4344b0fef8ba508c526 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:57:10 +0000 Subject: [PATCH 081/100] Run cargo fmt to fix formatting after variable rename Co-authored-by: mmagician <8402446+mmagician@users.noreply.github.com> --- crates/miden-tx/src/host/tx_event.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 2a79d9d2fa..451c9d0d5f 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -210,26 +210,24 @@ impl TransactionEvent { }, TransactionEventId::AccountVaultAfterRemoveAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset: Asset = - process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_remove_asset", - source, - } - })?; + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "on_account_vault_after_remove_asset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterRemoveAsset { asset }) }, TransactionEventId::AccountVaultAfterAddAsset => { // Expected stack state: [event, ASSET_KEY, ASSET_VALUE] - let asset: Asset = - process.get_stack_word_be(5).try_into().map_err(|source| { - TransactionKernelError::MalformedAssetInEventHandler { - handler: "on_account_vault_after_add_asset", - source, - } - })?; + let asset: Asset = process.get_stack_word_be(5).try_into().map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "on_account_vault_after_add_asset", + source, + } + })?; Some(TransactionEvent::AccountVaultAfterAddAsset { asset }) }, From 2aadfc0e13094877661d6974fddfa04456a8fc8b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 19 Feb 2026 11:17:56 +0100 Subject: [PATCH 082/100] chore: simplify fungible_asset merge and add split --- .../kernels/transaction/lib/asset_vault.masm | 38 +++------ .../transaction/lib/fungible_asset.masm | 65 +++++++++++---- .../src/transaction/kernel/procedures.rs | 10 +-- .../src/kernel_tests/tx/test_asset_vault.rs | 79 +++++++++++++++++++ 4 files changed, 146 insertions(+), 46 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index 7139fe3dab..c73c716b7c 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -15,8 +15,6 @@ const ERR_VAULT_ADD_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to add f const ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="the non-fungible asset already exists in the asset vault" -const ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW="failed to remove the fungible asset from the vault since the amount of the asset in the vault is less than the amount to remove" - const ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to remove fungible asset from the asset vault due to the initial value being invalid" const ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-existent non-fungible asset from the vault" @@ -309,41 +307,29 @@ pub proc remove_fungible_asset loc_storew_be.0 # => [ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - dup.3 dup.12 - # => [peeked_amount, amount, ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - - # assert amount <= peeked_amount - lte assert.err=ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW - # => [ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - # => [[faucet_id_prefix, faucet_id_suffix, 0, amount], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - - dup.11 movup.4 - # => [amount, peeked_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - - # compute peeked_amount - amount - sub - # => [new_amount, [faucet_id_prefix, faucet_id_suffix, 0], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + dupw.2 swapw + # => [ASSET_VALUE, PEEKED_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - movdn.3 - # => [[faucet_id_prefix, faucet_id_suffix, new_amount], ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - # => [ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # compute PEEKED_ASSET_VALUE - ASSET_VALUE + exec.fungible_asset::split + # => [NEW_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] - padw dup.7 - # => [new_amount, EMPTY_WORD, ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + padw dupw.1 exec.fungible_asset::get_amount + # => [new_amount, EMPTY_WORD, NEW_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] eq.0 - # => [is_new_amount_zero, EMPTY_WORD, ASSET_VALUE', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [is_new_amount_zero, EMPTY_WORD, NEW_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] # If is_new_amount_zero EMPTY_WORD remains. - # If !is_new_amount_zero ASSET_VALUE' remains. + # If !is_new_amount_zero NEW_ASSET_VALUE remains. cdropw - # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [EMPTY_WORD_OR_NEW_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] dup.12 padw movup.4 mem_loadw_be - # => [VAULT_ROOT, EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [VAULT_ROOT, EMPTY_WORD_OR_NEW_ASSET_VALUE, ASSET_KEY, PEEKED_ASSET_VALUE, vault_root_ptr] movdnw.2 - # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, VAULT_ROOT, PEEKED_ASSET_VALUE, vault_root_ptr] + # => [EMPTY_WORD_OR_NEW_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, PEEKED_ASSET_VALUE, vault_root_ptr] # update asset in vault and assert the old value is equivalent to the peeked value provided # via peek_asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index 953011247f..ae5bf34a6c 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -24,6 +24,8 @@ const ERR_FUNGIBLE_ASSET_VALUE_MOST_SIGNIFICANT_ELEMENTS_MUST_BE_ZERO="fungible const ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO="fungible asset key asset ID prefix and suffix must be zero" +const ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW="failed to remove the fungible asset from the vault since the amount of the asset in the vault is less than the amount to remove" + # PROCEDURES # ================================================================================================= @@ -31,9 +33,6 @@ const ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO="fungible asset key asset ID #! #! WARNING: This procedure assumes the assets have been validated. #! -#! NOTE: we do not assert that faucet IDs match since this asset layout will soon no longer contain -#! them anyway -#! #! Inputs: [ASSET_VALUE_0, ASSET_VALUE_1] #! Outputs: [MERGED_ASSET_VALUE] #! @@ -44,27 +43,63 @@ const ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO="fungible asset key asset ID #! Panics if: #! - adding the two asset values would exceed FUNGIBLE_ASSET_MAX_AMOUNT. pub proc merge - # extract amount from asset 1 - dup.7 - # => [amount_1, ASSET_VALUE_0, ASSET_VALUE_1] + # extract amounts from assets + exec.get_amount movdn.4 exec.get_amount + # => [amount_1, amount_0] - # compute max_add_amount = FUNGIBLE_ASSET_MAX_AMOUNT - current_amount - # this is the amount that can at most be added to still have a valid asset - push.FUNGIBLE_ASSET_MAX_AMOUNT dup.5 sub - # => [max_add_amount, amount_1, ASSET_VALUE_0, ASSET_VALUE_1] + # compute max_add_amount = FUNGIBLE_ASSET_MAX_AMOUNT - amount_0 + # this is the amount that can at most be added to amount_0 still have a valid asset + dup push.FUNGIBLE_ASSET_MAX_AMOUNT dup.3 sub + # => [max_add_amount, amount_1, amount_1, amount_0] # assert it is safe to add the amounts together, i.e. amount_1 <= max_add_amount lte assert.err=ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED - # => [ASSET_VALUE_0, ASSET_VALUE_1] - - exec.get_amount - # => [current_amount, ASSET_VALUE_1] + # => [amount_1, amount_0] # add the amounts - movup.4 add movdn.3 + add + # => [merged_amount] + + # reconstruct the asset value + push.0.0.0 # => [MERGED_ASSET_VALUE] end +#! Computes ASSET_VALUE_0 - ASSET_VALUE_1 and returns the result. +#! +#! For instance, split(40, 100) returns 60. The operand order matches the `sub` instruction. +#! +#! WARNING: This procedure assumes the assets have been validated. +#! +#! Inputs: [ASSET_VALUE_1, ASSET_VALUE_0] +#! Outputs: [NEW_ASSET_VALUE_0] +#! +#! Where: +#! - ASSET_VALUE_{0, 1} are the assets to split. +#! - NEW_ASSET_VALUE_0 is the result of the split computation. +#! +#! Panics if: +#! - ASSET_VALUE_0 does not contain at least the amount of ASSET_VALUE_1. +pub proc split + # extract amounts from assets + exec.get_amount movdn.4 exec.get_amount swap + # => [amount_1, amount_0] + + # assert amount_1 <= amount_0 so we can safely subtract + dup dup.2 + # => [amount_0, amount_1, amount_1, amount_0] + + lte assert.err=ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW + # => [amount_1, amount_0] + + sub + # => [new_amount] + + # reconstruct the asset value + push.0.0.0 + # => [NEW_ASSET_VALUE] +end + #! Extracts the amount of a fungible asset. #! #! WARNING: This procedure assumes the asset has been validated. diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index e0fc1ee7a1..07338e7824 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,9 +40,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x06d2ed6e186f5a6ed429393c125d3299b8b8c9d374d4a70c07223270a226e4ee"), // account_add_asset - word!("0xa064ca99c71792491291cc252336fec44365dd9ca4dfa8fad215888687859701"), + word!("0xdcbf3b7645e8ceb5b65d6d2bd0f402687d251c0dae5823caf9665e662b280c24"), // account_remove_asset - word!("0xd3bc85b90beb6ededb662c214b3e2267f1e5441561fc901a5bf3b3f4b34c2ab7"), + word!("0xe82f74caa0703488e2d6d0e0e7f4769ec4a05d742185f079055051eb03d618bd"), // account_get_asset word!("0x90b78d22da934a009e1bd08185fd88a0c34396913ead9ec3fbc23b607572ddcb"), // account_get_initial_asset @@ -58,9 +58,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xc6d3f1dcbe531f4aeb6b6a7b2cebd9fbc460f473ac3d25a9a6a6febfbb572777"), // faucet_mint_asset - word!("0x5daeaab23ab216163c06415ea8e094cc745dc0cd7635689de60de64fa09d5b6f"), + word!("0xcc5dc8a811d1d2a4410e9bb77041f164786a51efa31a7c6849dcecb1ee20afe1"), // faucet_burn_asset - word!("0x4390e8666f42ca6d9e2dc608c7252628f9b39764ea4f69e930c19aa4174b7ce4"), + word!("0x6a48451ff9d9c5a2fd7f7eca8522902ebe723746ee71cc9d157955cffd5c5b11"), // input_note_get_metadata word!("0x996bd68ca078fc1d25f354630f9881a65f7de2331cf87ba4729d5bb8934522ce"), // input_note_get_assets_info @@ -82,7 +82,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // output_note_get_recipient word!("0x765cc8fa1073fb3130c07a931b3d219aba07f8a5983441b02efbee62e2bc18ce"), // output_note_add_asset - word!("0xeaad4ac57bc465cc14f82a4f0aea9cb3a711ee5f53be838a151b9cde4a4780f2"), + word!("0xbfa606322f85ab9856ff4ad1939a4a603bf3b73eab19c78333a819f3a734fbdb"), // output_note_set_attachment word!("0xe1e5bba905ea8e7389e3e6ba8c7ebb41a9fdd9cde95eb12809862f43962bbc0b"), // tx_get_num_input_notes diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 61ec293e92..d67a4e927f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -621,6 +621,85 @@ async fn test_merge_fungible_asset_fails_when_max_amount_exceeded() -> anyhow::R Ok(()) } +/// Tests that splitting a fungible asset returns the correct remaining amount. +#[rstest::rstest] +#[case::different_amounts(FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT), FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT - 1))] +#[case::same_amounts( + FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT), + FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT) +)] +#[tokio::test] +async fn test_split_fungible_asset_success( + #[case] asset0: Asset, + #[case] asset1: Asset, +) -> anyhow::Result<()> { + let split_asset = asset0.unwrap_fungible().sub(asset1.unwrap_fungible())?; + + let code = format!( + " + use $kernel::fungible_asset + + begin + push.{ASSET0} + push.{ASSET1} + exec.fungible_asset::split + # => [NEW_ASSET_VALUE_0] + + # truncate the stack + swapw dropw + end + ", + ASSET0 = asset0.to_value_word(), + ASSET1 = asset1.to_value_word(), + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await?; + + assert_eq!(exec_output.get_stack_word_be(0), split_asset.to_value_word()); + + Ok(()) +} + +/// Tests that splitting a fungible asset fails when the amount to withdraw exceeds the balance. +#[tokio::test] +async fn test_split_fungible_asset_fails_when_amount_exceeds_balance() -> anyhow::Result<()> { + let asset0 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT); + let asset1 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT + 1); + + // Sanity check that the Rust implementation errors. + assert_matches!( + asset0.unwrap_fungible().sub(asset1.unwrap_fungible()).unwrap_err(), + AssetError::FungibleAssetAmountNotSufficient { .. } + ); + + let code = format!( + " + use $kernel::fungible_asset + + begin + push.{ASSET0} + push.{ASSET1} + exec.fungible_asset::split + # => [SPLIT_ASSET] + + # truncate the stack + swapw dropw + end + ", + ASSET0 = asset0.to_value_word(), + ASSET1 = asset1.to_value_word(), + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await; + + assert_execution_error!( + exec_output, + ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW + ); + + Ok(()) +} + /// Tests that merging two different fungible assets fails. #[tokio::test] async fn test_merge_different_fungible_assets_fails() -> anyhow::Result<()> { From c37323ddeb2a3e3763053bc9834cb3512d4fddf7 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Sat, 21 Feb 2026 14:30:14 +0100 Subject: [PATCH 083/100] chore: remove outdated edge case handling in vault --- .../asm/kernels/transaction/lib/asset_vault.masm | 8 +------- .../miden-protocol/src/transaction/kernel/procedures.rs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index c73c716b7c..51c19ea442 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -130,14 +130,8 @@ pub proc add_fungible_asset # since we have peeked the value, we need to later assert that the actual value matches this # one, so we'll keep a copy for later + # set the current asset value equal to the current vault value swapw dupw.1 - # => [CUR_VAULT_VALUE, ASSET_KEY, CUR_VAULT_VALUE, ASSET_VALUE] - - # the current vault value may be the empty word if it does not exist and so its faucet id would - # be zeros. We therefore overwrite the faucet id with the faucet id from ASSET_VALUE to account - # for this edge case - drop drop - dup.11 dup.11 # => [CURRENT_ASSET_VALUE, ASSET_KEY, CUR_VAULT_VALUE, ASSET_VALUE] movupw.3 diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 07338e7824..067b05ba25 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -40,7 +40,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x06d2ed6e186f5a6ed429393c125d3299b8b8c9d374d4a70c07223270a226e4ee"), // account_add_asset - word!("0xdcbf3b7645e8ceb5b65d6d2bd0f402687d251c0dae5823caf9665e662b280c24"), + word!("0x45f8873ee1e78b017d00bc71ae85c70109469622f14b5a7d650b87e9ebde4612"), // account_remove_asset word!("0xe82f74caa0703488e2d6d0e0e7f4769ec4a05d742185f079055051eb03d618bd"), // account_get_asset @@ -58,7 +58,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_has_procedure word!("0xc6d3f1dcbe531f4aeb6b6a7b2cebd9fbc460f473ac3d25a9a6a6febfbb572777"), // faucet_mint_asset - word!("0xcc5dc8a811d1d2a4410e9bb77041f164786a51efa31a7c6849dcecb1ee20afe1"), + word!("0x7d4aa382335adc195a60cc28b606fecfa12cb4176423f9c5489d03382c051f2b"), // faucet_burn_asset word!("0x6a48451ff9d9c5a2fd7f7eca8522902ebe723746ee71cc9d157955cffd5c5b11"), // input_note_get_metadata From 97d02dafc0d2596d220e3e7fc06894656b15cf58 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 24 Feb 2026 11:08:53 +0100 Subject: [PATCH 084/100] fix: avoid using undeclared stack --- crates/miden-agglayer/asm/bridge/bridge_out.masm | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/miden-agglayer/asm/bridge/bridge_out.masm b/crates/miden-agglayer/asm/bridge/bridge_out.masm index 9997d1d0e9..ed848867ab 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_out.masm @@ -92,10 +92,10 @@ proc create_burn_note call.output_note::create # => [note_idx] - movdn.8 loc_loadw_be.CREATE_BURN_NOTE_ASSET_VALUE_LOC - # => [ASSET_VALUE, pad(4), note_idx] + padw loc_loadw_be.CREATE_BURN_NOTE_ASSET_VALUE_LOC + # => [ASSET_VALUE, note_idx] - swapw loc_loadw_be.CREATE_BURN_NOTE_ASSET_KEY_LOC + padw loc_loadw_be.CREATE_BURN_NOTE_ASSET_KEY_LOC # => [ASSET_KEY, ASSET_VALUE, note_idx] exec.output_note::add_asset @@ -181,4 +181,9 @@ pub proc bridge_out exec.create_burn_note # => [] + + # TODO: Check where the extra elements are coming from by adding pad comments throughout the + # procedure. + drop drop + # => [] end From 934f0494d8e5a54e81c467e5eff69f3e97da347c Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 24 Feb 2026 11:28:17 +0100 Subject: [PATCH 085/100] feat: reexport asset::mem_load --- .../asm/kernels/transaction/lib/asset.masm | 20 +--------------- .../asm/kernels/transaction/lib/epilogue.masm | 4 ++-- .../asm/kernels/transaction/lib/prologue.masm | 4 ++-- crates/miden-protocol/asm/protocol/asset.masm | 1 + .../asm/shared_utils/util/asset.masm | 19 +++++++++++++++ .../asm/standards/faucets/mod.masm | 11 +-------- .../asm/standards/notes/swap.masm | 23 ++++++------------- 7 files changed, 33 insertions(+), 49 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index c499db2460..9d38107127 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -27,29 +27,11 @@ const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET="account ID in asset vault k pub use ::$kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::$kernel::util::asset::ASSET_SIZE pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET +pub use ::$kernel::util::asset::mem_load # PROCEDURES # ================================================================================================= -#! Loads an asset key and value from memory given a pointer to the asset. -#! -#! Inputs: [ptr] -#! Outputs: [ASSET_KEY, ASSET_VALUE] -#! -#! Where: -#! - ptr is the memory address of the asset. -#! - ASSET_KEY is the 4-element word representing the asset key. -#! - ASSET_VALUE is the 4-element word representing the asset value. -pub proc load_asset_key_and_value - # load asset value - padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - # => [ASSET_VALUE, ptr] - - # load asset key - padw movup.8 mem_loadw_be - # => [ASSET_KEY, ASSET_VALUE] -end - #! Validates that a fungible asset is well formed. #! #! Inputs: [ASSET_KEY, ASSET_VALUE] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 4d6a8f3977..2d3f58e752 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -2,7 +2,7 @@ use $kernel::account use $kernel::account_delta use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET -use $kernel::asset::load_asset_key_and_value +use $kernel::asset use $kernel::asset_vault use $kernel::constants::NOTE_MEM_SIZE use $kernel::memory @@ -171,7 +171,7 @@ proc build_output_vault # num_assets, note_data_ptr, output_notes_end_ptr] # read the output note asset from memory - dup.1 exec.load_asset_key_and_value + dup.1 exec.asset::mem_load # => [ASSET_KEY, ASSET_VALUE, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index 0e82c8f66c..1c9c88c4cb 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -8,7 +8,7 @@ use $kernel::account_id use $kernel::asset_vault use $kernel::asset::ASSET_SIZE use $kernel::asset::ASSET_VALUE_MEMORY_OFFSET -use $kernel::asset::load_asset_key_and_value +use $kernel::asset use $kernel::constants::EMPTY_SMT_ROOT use $kernel::constants::MAX_ASSETS_PER_NOTE use $kernel::constants::MAX_INPUT_NOTES_PER_TX @@ -708,7 +708,7 @@ proc add_input_note_assets_to_vault # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # load asset key and value - dup.1 exec.load_asset_key_and_value + dup.1 exec.asset::mem_load # => [ASSET_KEY, ASSET_VALUE, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # the witnesses for the note assets should be added prior to transaction execution and so diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index aa9fea8b35..a050a11d85 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -6,6 +6,7 @@ use miden::protocol::account_id pub use ::miden::protocol::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::miden::protocol::util::asset::ASSET_SIZE pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET +pub use ::miden::protocol::util::asset::mem_load # ERRORS # ================================================================================================= diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index c72ab37cde..c9fc84712c 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -18,6 +18,25 @@ const INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 # PROCEDURES # ================================================================================================= +#! Loads an asset key and value from memory given a pointer to the asset. +#! +#! Inputs: [ptr] +#! Outputs: [ASSET_KEY, ASSET_VALUE] +#! +#! Where: +#! - ptr is the memory address of the asset. +#! - ASSET_KEY is the 4-element word representing the asset key. +#! - ASSET_VALUE is the 4-element word representing the asset value. +pub proc mem_load + # load asset value + padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be + # => [ASSET_VALUE, ptr] + + # load asset key + padw movup.8 mem_loadw_be + # => [ASSET_KEY, ASSET_VALUE] +end + #! Returns the balance of the given fungible asset. #! #! Note: Assumes that the given asset is fungible and does NOT validate it. diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index c7c2a60169..6e019ba0ef 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -187,16 +187,7 @@ pub proc burn assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS # => [dest_ptr, pad(16)] - # pad stack to empty word (use dest_ptr as padding) - push.0 push.0 push.0 - # => [EMPTY_WORD, pad(16)] - - # load asset value - push.ASSET_PTR add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_be - # => [ASSET_VALUE, pad(16)] - - # load asset key - padw push.ASSET_PTR mem_loadw_be + exec.asset::mem_load # => [ASSET_KEY, ASSET_VALUE, pad(16)] # Burn the asset from the transaction vault diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 5818574866..738300528f 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -17,8 +17,7 @@ const ATTACHMENT_ADDRESS=4 const REQUESTED_ASSET_KEY_ADDRESS=8 const REQUESTED_ASSET_VALUE_ADDRESS=12 const PAYBACK_RECIPIENT_ADDRESS=16 -const ASSET_KEY_ADDRESS=20 -const ASSET_VALUE_ADDRESS=ASSET_KEY_ADDRESS + ASSET_VALUE_MEMORY_OFFSET +const ASSET_ADDRESS=20 # ERRORS # ================================================================================================= @@ -111,24 +110,16 @@ pub proc main # --- move assets from the SWAP note into the account ------------------------- - # store the number of note assets to memory starting at address ASSET_KEY_ADDRESS - push.ASSET_KEY_ADDRESS exec.active_note::get_assets - # => [num_assets, ptr, pad(8)] + # store the number of note assets to memory starting at address ASSET_ADDRESS + push.ASSET_ADDRESS exec.active_note::get_assets + # => [num_assets, asset_ptr, pad(8)] # make sure the number of assets is 1 assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS - # => [ptr, pad(8)] + # => [asset_ptr, pad(8)] - # consider the ptr as padding - push.0 push.0 push.0 - # => [pad(12)] - - # load asset value - mem_loadw_be.ASSET_VALUE_ADDRESS - # => [ASSET_VALUE, pad(8)] - - # load asset key - padw mem_loadw_be.ASSET_KEY_ADDRESS + # load asset + exec.asset::mem_load # => [ASSET_KEY, ASSET_VALUE, pad(8)] # add the asset to the account From 4a552a1cdecf900e086186b4203763d5d3be1abb Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 25 Feb 2026 15:52:21 +0100 Subject: [PATCH 086/100] feat: Add `Asset::as_elements` --- crates/miden-protocol/src/asset/mod.rs | 11 +++++++++++ crates/miden-protocol/src/note/assets.rs | 7 +------ crates/miden-standards/src/note/swap.rs | 3 +-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 0fcd6b1620..ad5ea84667 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -155,6 +155,17 @@ impl Asset { Word::from(*self) } + /// Returns the asset encoded as elements. + /// + /// The first four elements contain the asset key and the last four elements contain the asset + /// value. + pub fn as_elements(&self) -> [Felt; 8] { + let mut elements = [ZERO; 8]; + elements[0..4].copy_from_slice(self.to_key_word().as_elements()); + elements[4..8].copy_from_slice(self.to_value_word().as_elements()); + elements + } + /// Returns the inner [`FungibleAsset`]. /// /// # Panics diff --git a/crates/miden-protocol/src/note/assets.rs b/crates/miden-protocol/src/note/assets.rs index ed2f25ce95..d0fa47dcd5 100644 --- a/crates/miden-protocol/src/note/assets.rs +++ b/crates/miden-protocol/src/note/assets.rs @@ -179,12 +179,7 @@ impl SequentialCommit for NoteAssets { fn to_elements(assets: &[Asset]) -> Vec { let mut elements = Vec::with_capacity(assets.len() * 2 * WORD_SIZE); - - for asset in assets.iter() { - elements.extend(asset.to_key_word().as_elements()); - elements.extend(asset.to_value_word().as_elements()); - } - + elements.extend(assets.iter().flat_map(Asset::as_elements)); elements } diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index ade278d96e..506c44bd85 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -108,8 +108,7 @@ impl SwapNote { attachment_kind, ]); storage.extend_from_slice(attachment.as_elements()); - storage.extend_from_slice(requested_asset.to_key_word().as_elements()); - storage.extend_from_slice(requested_asset.to_value_word().as_elements()); + storage.extend_from_slice(&requested_asset.as_elements()); storage.extend_from_slice(payback_recipient.digest().as_elements()); let inputs = NoteStorage::new(storage)?; From 069ba66cca3938b1bb0a7d69871e64cee78c799c Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 25 Feb 2026 15:57:53 +0100 Subject: [PATCH 087/100] chore: use `asset::mem_load` in swap note --- crates/miden-standards/asm/standards/notes/swap.masm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 738300528f..b26dee85e7 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -14,8 +14,7 @@ const PAYBACK_NOTE_TAG_ADDRESS=1 const ATTACHMENT_KIND_ADDRESS=2 const ATTACHMENT_SCHEME_ADDRESS=3 const ATTACHMENT_ADDRESS=4 -const REQUESTED_ASSET_KEY_ADDRESS=8 -const REQUESTED_ASSET_VALUE_ADDRESS=12 +const REQUESTED_ASSET_ADDRESS=8 const PAYBACK_RECIPIENT_ADDRESS=16 const ASSET_ADDRESS=20 @@ -86,10 +85,7 @@ pub proc main padw push.0.0.0 dup.7 # => [note_idx, pad(7), note_idx] - padw mem_loadw_be.REQUESTED_ASSET_VALUE_ADDRESS - # => [REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] - - padw mem_loadw_be.REQUESTED_ASSET_KEY_ADDRESS + push.REQUESTED_ASSET_ADDRESS exec.asset::mem_load # => [REQUESTED_ASSET_KEY, REQUESTED_ASSET_VALUE, note_idx, pad(7), note_idx] # move asset to the note From ea88aca5f8bef4e697b422f17e7a4c799887f4ae Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 26 Feb 2026 11:08:01 +0100 Subject: [PATCH 088/100] fix: update agglayer::bridge::bridge_out to new asset layout --- .../asm/agglayer/bridge/bridge_out.masm | 24 ++++--- .../asm/note_scripts/B2AGG.masm | 2 +- .../asm/kernels/transaction/lib/asset.masm | 36 ++--------- .../asm/kernels/transaction/lib/epilogue.masm | 2 +- .../transaction/lib/fungible_asset.masm | 6 +- .../transaction/lib/non_fungible_asset.masm | 4 +- crates/miden-protocol/asm/protocol/asset.masm | 5 ++ .../asm/shared_utils/util/asset.masm | 64 +++++++++++++++++++ crates/miden-protocol/src/asset/mod.rs | 4 +- 9 files changed, 94 insertions(+), 53 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index 6c1ec77097..a9e271b6f0 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -215,26 +215,25 @@ end #! #! Invocation: exec proc convert_asset - # => [faucet_id_prefix, faucet_id_suffix, 0, 0, faucet_id_prefix, faucet_id_suffix, 0, amount] - # --- Step 1: Assert faucet is registered --- + swapw + exec.asset::get_balance_from_fungible_asset + movdn.4 + # => [ASSET_KEY, amount] + + exec.asset::into_faucet_id + # => [faucet_id_prefix, faucet_id_suffix, amount] + dup.1 dup.1 exec.bridge_config::assert_faucet_registered - dropw - # => [faucet_id_prefix, faucet_id_suffix, 0, amount] + # => [faucet_id_prefix, faucet_id_suffix, amount] # --- Step 2: FPI to faucet's asset_to_origin_asset --- - # Drop the zero padding between faucet_id and amount. - # TODO(expand_assets): Use asset::get_faucet_id helper. - movup.2 drop - # => [faucet_id_prefix, faucet_id_suffix, amount] - procref.agglayer_faucet::asset_to_origin_asset # => [PROC_MAST_ROOT(4), faucet_id_prefix, faucet_id_suffix, amount] # Move faucet_id above PROC_MAST_ROOT - # TODO(expand_assets): This needs an update in 2437. movup.5 movup.5 # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4), amount] @@ -466,8 +465,7 @@ proc create_burn_note exec.asset::mem_store # => [ASSET_KEY] - # TODO(expand_assets): Use asset::get_faucet_id helper. - dupw movup.2 drop movup.2 drop + dupw exec.asset::get_faucet_id # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] # Create NetworkAccountTarget attachment for the faucet @@ -531,6 +529,6 @@ proc create_burn_note # TODO: Check where the extra elements are coming from by adding pad comments throughout the # procedure. - drop + dropw drop # => [] end diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 02b489a402..1767870a74 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -95,7 +95,7 @@ begin push.STORAGE_START_PTR add.4 mem_loadw_le swapw mem_loadw_le.STORAGE_START_PTR # => [dest_network, dest_address(5), pad(10)] - # Load ASSET_VALUE onto the stack from ASSET_PTR + ASSET_VALUE_MEMORY_OFFSET + # Load asset onto the stack from ASSET_PTR push.ASSET_PTR exec.asset::mem_load # => [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5), pad(10)] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index ec9323ac10..4289b1b27e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -13,6 +13,10 @@ const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET="account ID in asset vault k pub use ::$kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::$kernel::util::asset::ASSET_SIZE pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET +pub use ::$kernel::util::asset::get_faucet_id +pub use ::$kernel::util::asset::into_faucet_id +pub use ::$kernel::util::asset::get_asset_id +pub use ::$kernel::util::asset::into_asset_id pub use ::$kernel::util::asset::mem_store pub use ::$kernel::util::asset::mem_load @@ -103,35 +107,3 @@ pub proc validate end # => [ASSET_KEY, ASSET_VALUE] end - -#! Returns the faucet ID from an asset vault key. -#! -#! WARNING: The faucet ID is not validated. -#! -#! Inputs: [ASSET_KEY] -#! Outputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] -#! -#! Where: -#! - faucet_id is the account ID in the vault key. -#! - ASSET_KEY is the vault key from which to extract the faucet ID. -proc get_vault_key_faucet_id - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - dup.1 dup.1 - # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] -end - -#! Returns the asset ID from an asset vault key. -#! -#! Inputs: [ASSET_KEY] -#! Outputs: [asset_id_prefix, asset_id_suffix, ASSET_KEY] -#! -#! Where: -#! - asset_id is the asset ID in the vault key. -#! - ASSET_KEY is the vault key from which to extract the asset ID. -proc get_vault_key_asset_id - # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] - - dup.3 dup.3 - # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] -end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 8d42a16dd8..6e85af5717 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -327,7 +327,7 @@ proc compute_and_remove_fee # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] # prepare the return value - exec.asset::get_vault_key_faucet_id + exec.asset::get_faucet_id # => [native_asset_id_prefix, native_asset_id_suffix, FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] movdn.9 movdn.9 diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index ae5bf34a6c..576d818cea 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -161,7 +161,7 @@ end #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a fungible one. pub proc validate_key - exec.asset::get_vault_key_faucet_id + exec.asset::get_faucet_id exec.account_id::validate # => [ASSET_KEY] @@ -169,7 +169,7 @@ pub proc validate_key assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE # => [ASSET_KEY] - exec.asset::get_vault_key_asset_id + exec.asset::get_asset_id # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] eq.0 assert.err=ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO @@ -195,7 +195,7 @@ pub proc validate_origin # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the origin of the asset is the faucet_id provided via the stack - exec.asset::get_vault_key_faucet_id + exec.asset::get_faucet_id # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] movup.11 movup.11 diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index cb14a653a1..ae507b974c 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -53,7 +53,7 @@ end #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a non-fungible one. pub proc validate_key - exec.asset::get_vault_key_faucet_id + exec.asset::get_faucet_id exec.account_id::validate # => [ASSET_KEY] @@ -81,7 +81,7 @@ pub proc validate_origin # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the origin of the asset is the faucet_id provided via the stack - exec.asset::get_vault_key_faucet_id + exec.asset::get_faucet_id # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] movup.11 movup.11 diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index aaee02c672..e91a5405d1 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -7,8 +7,13 @@ use miden::protocol::util::asset pub use ::miden::protocol::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::miden::protocol::util::asset::ASSET_SIZE pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET +pub use ::miden::protocol::util::asset::get_faucet_id +pub use ::miden::protocol::util::asset::into_faucet_id +pub use ::miden::protocol::util::asset::get_asset_id +pub use ::miden::protocol::util::asset::into_asset_id pub use ::miden::protocol::util::asset::mem_store pub use ::miden::protocol::util::asset::mem_load +pub use ::miden::protocol::util::asset::get_balance_from_fungible_asset # ERRORS # ================================================================================================= diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index b60b7ab23a..8c2d8edc15 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -73,6 +73,70 @@ pub proc get_balance_from_fungible_asset # => [balance] end +#! Returns the faucet ID from an asset vault key. +#! +#! WARNING: The faucet ID is not validated. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] +#! +#! Where: +#! - faucet_id is the account ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the faucet ID. +pub proc get_faucet_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.1 dup.1 + # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] +end + +#! Returns the faucet ID from an asset vault key and consumes it. +#! +#! WARNING: The faucet ID is not validated. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [faucet_id_prefix, faucet_id_suffix] +#! +#! Where: +#! - faucet_id is the account ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the faucet ID. +pub proc into_faucet_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + movup.2 drop movup.2 drop + # => [faucet_id_prefix, faucet_id_suffix] +end + +#! Returns the asset ID from an asset vault key. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [asset_id_prefix, asset_id_suffix, ASSET_KEY] +#! +#! Where: +#! - asset_id is the asset ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the asset ID. +pub proc get_asset_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + dup.3 dup.3 + # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] +end + +#! Returns the asset ID from an asset vault key and consumes it. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [asset_id_prefix, asset_id_suffix] +#! +#! Where: +#! - asset_id is the asset ID in the vault key. +#! - ASSET_KEY is the vault key from which to extract the asset ID. +pub proc into_asset_id + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + + drop drop + # => [asset_id_prefix, asset_id_suffix] +end + #! Creates a fungible asset for the specified fungible faucet and amount. #! #! WARNING: Does not validate its inputs. diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index 91cb544b8c..f3a09808cf 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -18,6 +18,8 @@ mod nonfungible; pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails}; +use crate::FieldElement; + mod token_symbol; pub use token_symbol::TokenSymbol; @@ -175,7 +177,7 @@ impl Asset { /// The first four elements contain the asset key and the last four elements contain the asset /// value. pub fn as_elements(&self) -> [Felt; 8] { - let mut elements = [ZERO; 8]; + let mut elements = [Felt::ZERO; 8]; elements[0..4].copy_from_slice(self.to_key_word().as_elements()); elements[4..8].copy_from_slice(self.to_value_word().as_elements()); elements From 33602528a853d0cfc7847f36c633af6618e297e7 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 26 Feb 2026 14:35:41 +0100 Subject: [PATCH 089/100] chore: remove `Ord for Asset` impl --- crates/miden-protocol/src/asset/mod.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index f3a09808cf..b23704ab6f 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -208,18 +208,6 @@ impl Asset { } } -impl Ord for Asset { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.vault_key().cmp(&other.vault_key()) - } -} - -impl PartialOrd for Asset { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - // SERIALIZATION // ================================================================================================ From e38c1de32f37cf497da2cfa4dc058f2e783cb548 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 26 Feb 2026 14:35:52 +0100 Subject: [PATCH 090/100] chore: re-add error when asset-to-be-removed is not present --- .../miden-protocol/src/asset/nonfungible.rs | 14 +----------- crates/miden-protocol/src/asset/vault/mod.rs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index 8ef820f5a8..de9f5738db 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -100,19 +100,7 @@ impl NonFungibleAsset { /// Returns the vault key of the [`NonFungibleAsset`]. /// - /// This is the same as the asset with the following modifications, in this order: - /// - Swaps the faucet ID at index 0 and `hash0` at index 3. - /// - Sets the fungible bit for `hash0` to `0`. - /// - /// # Rationale - /// - /// This means `hash0` will be used as the leaf index in the asset SMT which ensures that a - /// non-fungible faucet's assets generally end up in different leaves as the key is not based on - /// the faucet ID. - /// - /// It also ensures that there is never any collision in the leaf index between a non-fungible - /// asset and a fungible asset, as the former's vault key always has the fungible bit set to `0` - /// and the latter's vault key always has the bit set to `1`. + /// See [`Asset`] docs for details on the key. pub fn vault_key(&self) -> AssetVaultKey { let asset_id_suffix = self.value[0]; let asset_id_prefix = self.value[1]; diff --git a/crates/miden-protocol/src/asset/vault/mod.rs b/crates/miden-protocol/src/asset/vault/mod.rs index 8514a4621c..1612b33c91 100644 --- a/crates/miden-protocol/src/asset/vault/mod.rs +++ b/crates/miden-protocol/src/asset/vault/mod.rs @@ -299,6 +299,11 @@ impl AssetVault { FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value) .expect("asset vault should store valid assets"); + // If the asset's amount is 0, we consider it absent from the vault. + if current_asset.amount() == 0 { + return Err(AssetVaultError::FungibleAssetNotFound(other_asset)); + } + let new_asset = current_asset .sub(other_asset) .map_err(AssetVaultError::SubtractFungibleAssetBalanceError)?; @@ -378,3 +383,20 @@ impl Deserializable for AssetVault { Self::new(&assets).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + #[test] + fn vault_fails_on_absent_fungible_asset() { + let mut vault = AssetVault::default(); + let err = vault.remove_asset(FungibleAsset::mock(50)).unwrap_err(); + assert_matches!(err, AssetVaultError::FungibleAssetNotFound(_)); + } +} From 12cdc8485be602173538185c97e9143141804ba4 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 26 Feb 2026 14:37:08 +0100 Subject: [PATCH 091/100] chore: remove whitespace in docs --- crates/miden-protocol/src/asset/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index b23704ab6f..ea408b9374 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -47,7 +47,7 @@ pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; /// /// # Fungible assets /// -/// - A fungible asset's value layout is: `[amount, 0, 0, 0]`. +/// - A fungible asset's value layout is: `[amount, 0, 0, 0]`. /// - A fungible asset's vault key layout is: `[0, 0, faucet_id_suffix, faucet_id_prefix]`. /// /// The most significant elements of a fungible asset's key are set to the prefix From 905d84077561a587e8479f37d9278b98fb43b639 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 11:34:16 +0100 Subject: [PATCH 092/100] chore: reword non-fungible asset key collision resistance --- crates/miden-protocol/src/asset/mod.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index ea408b9374..c80b10b84f 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -69,12 +69,6 @@ pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; /// - A non-fungible asset's vault key layout is: `[hash0, hash1, faucet_id_suffix, /// faucet_id_prefix]`. /// -/// The most significant elements of a fungible asset's key are set to the prefix -/// (`faucet_id_prefix`) and suffix (`faucet_id_suffix`) of the ID of the faucet which issues the -/// asset. The asset ID limbs are set to hashes from the asset's value, which means two instances of -/// the same non-fungible asset will never have the same asset key and thus will never collide when -/// stored in the same account's vault. -/// /// The 4 elements of non-fungible assets are computed by hashing the asset data. This compresses an /// asset of an arbitrary length to 4 field elements: `[hash0, hash1, hash2, hash3]`. /// @@ -82,8 +76,13 @@ pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; /// as the faucet ID is included in the description of the non-fungible asset and this is guaranteed /// to be different as per the faucet creation logic. /// -/// Collision resistance for non-fungible assets issued by the same faucet is ~2^64, due to the -/// 128-bit asset ID that is unique per non-fungible asset. +/// The most significant elements of a fungible asset's key are set to the prefix +/// (`faucet_id_prefix`) and suffix (`faucet_id_suffix`) of the ID of the faucet which issues the +/// asset. The asset ID limbs are set to hashes from the asset's value. This means the collision +/// resistance of non-fungible assets issued by the same faucet is ~2^64, due to the 128-bit asset +/// ID that is unique per non-fungible asset. In other words, two non-fungible assets issued by the +/// same faucet are very unlikely to have the same asset key and thus should not collide when stored +/// in the same account's vault. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Asset { Fungible(FungibleAsset), From 315864b4355de4e97ff5348ee7079501ab857d8f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 11:46:05 +0100 Subject: [PATCH 093/100] chore: add `AssetId::is_empty` --- crates/miden-protocol/src/asset/fungible.rs | 2 +- crates/miden-protocol/src/asset/vault/asset_id.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index 6c93173321..90048317ad 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -72,7 +72,7 @@ impl FungibleAsset { /// - The provided value's amount is greater than [`FungibleAsset::MAX_AMOUNT`] or its three /// most significant elements are not zero. pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result { - if key.asset_id().prefix() != Felt::ZERO || key.asset_id().suffix() != Felt::ZERO { + if !key.asset_id().is_empty() { return Err(AssetError::FungibleAssetIdMustBeZero(key.asset_id())); } diff --git a/crates/miden-protocol/src/asset/vault/asset_id.rs b/crates/miden-protocol/src/asset/vault/asset_id.rs index e3e8108040..14c83f1f8f 100644 --- a/crates/miden-protocol/src/asset/vault/asset_id.rs +++ b/crates/miden-protocol/src/asset/vault/asset_id.rs @@ -1,6 +1,6 @@ use core::fmt::Display; -use crate::Felt; +use crate::{Felt, FieldElement}; /// The [`AssetId`] in an [`AssetVaultKey`](crate::asset::AssetVaultKey) distinguishes different /// assets issued by the same faucet. @@ -25,6 +25,11 @@ impl AssetId { pub fn prefix(&self) -> Felt { self.prefix } + + /// Returns `true` if both prefix and suffix are zero, `false` otherwise. + pub fn is_empty(&self) -> bool { + self.prefix == Felt::ZERO && self.suffix == Felt::ZERO + } } impl Display for AssetId { From cbe97154a3315aa842145fa80199a82b3a315c32 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 11:58:47 +0100 Subject: [PATCH 094/100] chore: remove unnecessary imports in `AssetVault` --- crates/miden-protocol/src/account/delta/vault.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/miden-protocol/src/account/delta/vault.rs b/crates/miden-protocol/src/account/delta/vault.rs index 668d70c949..2b79867dfe 100644 --- a/crates/miden-protocol/src/account/delta/vault.rs +++ b/crates/miden-protocol/src/account/delta/vault.rs @@ -100,8 +100,6 @@ impl AccountVaultDelta { added_assets: impl IntoIterator, removed_assets: impl IntoIterator, ) -> Self { - use crate::asset::Asset; - let mut fungible = FungibleAssetDelta::default(); let mut non_fungible = NonFungibleAssetDelta::default(); @@ -132,7 +130,6 @@ impl AccountVaultDelta { /// Returns an iterator over the added assets in this delta. pub fn added_assets(&self) -> impl Iterator + '_ { - use crate::asset::{Asset, FungibleAsset}; self.fungible .0 .iter() @@ -149,7 +146,6 @@ impl AccountVaultDelta { /// Returns an iterator over the removed assets in this delta. pub fn removed_assets(&self) -> impl Iterator + '_ { - use crate::asset::{Asset, FungibleAsset}; self.fungible .0 .iter() From 7430f7315a875c226d4738c91f315b2a82ea6e67 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 11:59:03 +0100 Subject: [PATCH 095/100] chore: spell out asset key layout in has_non_fungible_asset --- crates/miden-protocol/asm/protocol/active_account.masm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/miden-protocol/asm/protocol/active_account.masm b/crates/miden-protocol/asm/protocol/active_account.masm index 4fb492c1ac..56c858cdf4 100644 --- a/crates/miden-protocol/asm/protocol/active_account.masm +++ b/crates/miden-protocol/asm/protocol/active_account.masm @@ -570,6 +570,8 @@ end #! #! Invocation: exec pub proc has_non_fungible_asset + # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] + # assert that the faucet id is a non-fungible faucet dup exec.account_id::is_non_fungible_faucet assert.err=ERR_VAULT_HAS_NON_FUNGIBLE_ASSET_PROC_CAN_BE_CALLED_ONLY_WITH_NON_FUNGIBLE_ASSET From d3e9b6b8594586bf7f40754e5386149b06ba85a0 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 12:11:14 +0100 Subject: [PATCH 096/100] chore: validate account ID type in AssetVaultKey::new --- crates/miden-protocol/src/asset/fungible.rs | 2 +- .../miden-protocol/src/asset/nonfungible.rs | 3 ++- .../src/asset/vault/vault_key.rs | 25 ++++++++++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/miden-protocol/src/asset/fungible.rs b/crates/miden-protocol/src/asset/fungible.rs index 90048317ad..ed43cdb9d6 100644 --- a/crates/miden-protocol/src/asset/fungible.rs +++ b/crates/miden-protocol/src/asset/fungible.rs @@ -264,7 +264,7 @@ mod tests { let invalid_key = AssetVaultKey::new( AssetId::new(1u32.into(), 2u32.into()), ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into()?, - ); + )?; let err = FungibleAsset::from_key_value(invalid_key, FungibleAsset::mock(5).to_value_word()) diff --git a/crates/miden-protocol/src/asset/nonfungible.rs b/crates/miden-protocol/src/asset/nonfungible.rs index de9f5738db..47405d078d 100644 --- a/crates/miden-protocol/src/asset/nonfungible.rs +++ b/crates/miden-protocol/src/asset/nonfungible.rs @@ -107,6 +107,7 @@ impl NonFungibleAsset { let asset_id = AssetId::new(asset_id_suffix, asset_id_prefix); AssetVaultKey::new(asset_id, self.faucet_id) + .expect("constructors should ensure account ID is of type non-fungible faucet") } /// Returns the ID of the faucet which issued this asset. @@ -234,7 +235,7 @@ mod tests { let invalid_key = AssetVaultKey::new( AssetId::new(1u32.into(), 2u32.into()), ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET.try_into()?, - ); + )?; let err = NonFungibleAsset::from_key_value(invalid_key, Word::from([4, 5, 6, 7u32])).unwrap_err(); diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 12aab5777e..45e763b82a 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -36,8 +36,22 @@ pub struct AssetVaultKey { impl AssetVaultKey { /// Creates an [`AssetVaultKey`] from its parts. - pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Self { - Self { asset_id, faucet_id } + /// + /// # Errors + /// + /// Returns an error if: + /// - the provided ID is not of type + /// [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet) or + /// [`AccountType::NonFungibleFaucet`](crate::account::AccountType::NonFungibleFaucet) + pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Result { + if !faucet_id.is_faucet() { + return Err(AssetError::InvalidFaucetAccountId(Box::from(format!( + "expected account ID of type faucet, found account type {}", + faucet_id.account_type() + )))); + } + + Ok(Self { asset_id, faucet_id }) } /// Returns the word representation of the vault key. @@ -65,7 +79,10 @@ impl AssetVaultKey { pub fn new_fungible(faucet_id: AccountId) -> Option { if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) { let asset_id = AssetId::new(Felt::ZERO, Felt::ZERO); - Some(Self::new(asset_id, faucet_id)) + Some( + Self::new(asset_id, faucet_id) + .expect("we should have account type fungible faucet"), + ) } else { None } @@ -118,7 +135,7 @@ impl TryFrom for AssetVaultKey { let faucet_id = AccountId::try_from([faucet_id_prefix, faucet_id_suffix]) .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?; - Ok(Self::new(asset_id, faucet_id)) + Self::new(asset_id, faucet_id) } } From a613e947953c272c7b3b8ffb88e57087b0965620 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 12:13:28 +0100 Subject: [PATCH 097/100] fix: typo in Asset docs --- crates/miden-protocol/src/asset/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-protocol/src/asset/mod.rs b/crates/miden-protocol/src/asset/mod.rs index c80b10b84f..e32f2d195a 100644 --- a/crates/miden-protocol/src/asset/mod.rs +++ b/crates/miden-protocol/src/asset/mod.rs @@ -76,7 +76,7 @@ pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault}; /// as the faucet ID is included in the description of the non-fungible asset and this is guaranteed /// to be different as per the faucet creation logic. /// -/// The most significant elements of a fungible asset's key are set to the prefix +/// The most significant elements of a non-fungible asset's key are set to the prefix /// (`faucet_id_prefix`) and suffix (`faucet_id_suffix`) of the ID of the faucet which issues the /// asset. The asset ID limbs are set to hashes from the asset's value. This means the collision /// resistance of non-fungible assets issued by the same faucet is ~2^64, due to the 128-bit asset From 206ec8d91af448b1fd67f6dd75e9cc53e6cfcc67 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 14:06:30 +0100 Subject: [PATCH 098/100] chore: prefix `get/into_faucet_id` with `key` --- crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm | 4 ++-- crates/miden-protocol/asm/kernels/transaction/lib/asset.masm | 4 ++-- .../miden-protocol/asm/kernels/transaction/lib/epilogue.masm | 2 +- .../asm/kernels/transaction/lib/fungible_asset.masm | 4 ++-- .../asm/kernels/transaction/lib/non_fungible_asset.masm | 4 ++-- crates/miden-protocol/asm/protocol/asset.masm | 4 ++-- crates/miden-protocol/asm/shared_utils/util/asset.masm | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index a9e271b6f0..9f0ae30060 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -221,7 +221,7 @@ proc convert_asset movdn.4 # => [ASSET_KEY, amount] - exec.asset::into_faucet_id + exec.asset::key_into_faucet_id # => [faucet_id_prefix, faucet_id_suffix, amount] dup.1 dup.1 @@ -465,7 +465,7 @@ proc create_burn_note exec.asset::mem_store # => [ASSET_KEY] - dupw exec.asset::get_faucet_id + dupw exec.asset::key_to_faucet_id # => [faucet_id_prefix, faucet_id_suffix, ASSET_KEY] # Create NetworkAccountTarget attachment for the faucet diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 4289b1b27e..68c1ab55d2 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -13,8 +13,8 @@ const ERR_VAULT_ASSET_KEY_ACCOUNT_ID_MUST_BE_FAUCET="account ID in asset vault k pub use ::$kernel::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::$kernel::util::asset::ASSET_SIZE pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET -pub use ::$kernel::util::asset::get_faucet_id -pub use ::$kernel::util::asset::into_faucet_id +pub use ::$kernel::util::asset::key_to_faucet_id +pub use ::$kernel::util::asset::key_into_faucet_id pub use ::$kernel::util::asset::get_asset_id pub use ::$kernel::util::asset::into_asset_id pub use ::$kernel::util::asset::mem_store diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm index 6e85af5717..a57c0be30f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm @@ -327,7 +327,7 @@ proc compute_and_remove_fee # => [FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] # prepare the return value - exec.asset::get_faucet_id + exec.asset::key_to_faucet_id # => [native_asset_id_prefix, native_asset_id_suffix, FEE_ASSET_KEY, FEE_ASSET_VALUE, fee_amount] movdn.9 movdn.9 diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index 576d818cea..b78defdf07 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -161,7 +161,7 @@ end #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a fungible one. pub proc validate_key - exec.asset::get_faucet_id + exec.asset::key_to_faucet_id exec.account_id::validate # => [ASSET_KEY] @@ -195,7 +195,7 @@ pub proc validate_origin # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the origin of the asset is the faucet_id provided via the stack - exec.asset::get_faucet_id + exec.asset::key_to_faucet_id # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] movup.11 movup.11 diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index ae507b974c..9daea5e804 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -53,7 +53,7 @@ end #! - the asset key's account ID is not valid. #! - the asset key's faucet ID is not a non-fungible one. pub proc validate_key - exec.asset::get_faucet_id + exec.asset::key_to_faucet_id exec.account_id::validate # => [ASSET_KEY] @@ -81,7 +81,7 @@ pub proc validate_origin # => [ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] # assert the origin of the asset is the faucet_id provided via the stack - exec.asset::get_faucet_id + exec.asset::key_to_faucet_id # => [key_faucet_id_prefix, key_faucet_id_suffix, ASSET_KEY, ASSET_VALUE, faucet_id_prefix, faucet_id_suffix] movup.11 movup.11 diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index e91a5405d1..cfd8c23629 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -7,8 +7,8 @@ use miden::protocol::util::asset pub use ::miden::protocol::util::asset::FUNGIBLE_ASSET_MAX_AMOUNT pub use ::miden::protocol::util::asset::ASSET_SIZE pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET -pub use ::miden::protocol::util::asset::get_faucet_id -pub use ::miden::protocol::util::asset::into_faucet_id +pub use ::miden::protocol::util::asset::key_to_faucet_id +pub use ::miden::protocol::util::asset::key_into_faucet_id pub use ::miden::protocol::util::asset::get_asset_id pub use ::miden::protocol::util::asset::into_asset_id pub use ::miden::protocol::util::asset::mem_store diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 8c2d8edc15..9575475b98 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -83,7 +83,7 @@ end #! Where: #! - faucet_id is the account ID in the vault key. #! - ASSET_KEY is the vault key from which to extract the faucet ID. -pub proc get_faucet_id +pub proc key_to_faucet_id # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] dup.1 dup.1 @@ -100,7 +100,7 @@ end #! Where: #! - faucet_id is the account ID in the vault key. #! - ASSET_KEY is the vault key from which to extract the faucet ID. -pub proc into_faucet_id +pub proc key_into_faucet_id # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] movup.2 drop movup.2 drop From fc6fcefc3103a46637484c0651925a92c3ae9339 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 14:07:32 +0100 Subject: [PATCH 099/100] chore: prefix `get/into_asset_id` with `key` --- crates/miden-protocol/asm/kernels/transaction/lib/asset.masm | 4 ++-- .../asm/kernels/transaction/lib/fungible_asset.masm | 2 +- crates/miden-protocol/asm/protocol/asset.masm | 4 ++-- crates/miden-protocol/asm/shared_utils/util/asset.masm | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 68c1ab55d2..3d1f3b5441 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -15,8 +15,8 @@ pub use ::$kernel::util::asset::ASSET_SIZE pub use ::$kernel::util::asset::ASSET_VALUE_MEMORY_OFFSET pub use ::$kernel::util::asset::key_to_faucet_id pub use ::$kernel::util::asset::key_into_faucet_id -pub use ::$kernel::util::asset::get_asset_id -pub use ::$kernel::util::asset::into_asset_id +pub use ::$kernel::util::asset::key_to_asset_id +pub use ::$kernel::util::asset::key_into_asset_id pub use ::$kernel::util::asset::mem_store pub use ::$kernel::util::asset::mem_load diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index b78defdf07..7b4999cf92 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -169,7 +169,7 @@ pub proc validate_key assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE # => [ASSET_KEY] - exec.asset::get_asset_id + exec.asset::key_to_asset_id # => [asset_id_prefix, asset_id_suffix, ASSET_KEY] eq.0 assert.err=ERR_FUNGIBLE_ASSET_KEY_ASSET_ID_MUST_BE_ZERO diff --git a/crates/miden-protocol/asm/protocol/asset.masm b/crates/miden-protocol/asm/protocol/asset.masm index cfd8c23629..18dac40bc2 100644 --- a/crates/miden-protocol/asm/protocol/asset.masm +++ b/crates/miden-protocol/asm/protocol/asset.masm @@ -9,8 +9,8 @@ pub use ::miden::protocol::util::asset::ASSET_SIZE pub use ::miden::protocol::util::asset::ASSET_VALUE_MEMORY_OFFSET pub use ::miden::protocol::util::asset::key_to_faucet_id pub use ::miden::protocol::util::asset::key_into_faucet_id -pub use ::miden::protocol::util::asset::get_asset_id -pub use ::miden::protocol::util::asset::into_asset_id +pub use ::miden::protocol::util::asset::key_to_asset_id +pub use ::miden::protocol::util::asset::key_into_asset_id pub use ::miden::protocol::util::asset::mem_store pub use ::miden::protocol::util::asset::mem_load pub use ::miden::protocol::util::asset::get_balance_from_fungible_asset diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 9575475b98..a091d7f60b 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -115,7 +115,7 @@ end #! Where: #! - asset_id is the asset ID in the vault key. #! - ASSET_KEY is the vault key from which to extract the asset ID. -pub proc get_asset_id +pub proc key_to_asset_id # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] dup.3 dup.3 @@ -130,7 +130,7 @@ end #! Where: #! - asset_id is the asset ID in the vault key. #! - ASSET_KEY is the vault key from which to extract the asset ID. -pub proc into_asset_id +pub proc key_into_asset_id # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] drop drop From 2963fe15b7d5d22d5e738abdacdc0395c5bf019e Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Mar 2026 14:12:33 +0100 Subject: [PATCH 100/100] chore: suffix `is_(non_)fungible_asset` with `key` --- .../asm/kernels/transaction/lib/account_delta.masm | 4 ++-- .../miden-protocol/asm/kernels/transaction/lib/asset.masm | 8 ++++---- .../asm/kernels/transaction/lib/asset_vault.masm | 4 ++-- .../asm/kernels/transaction/lib/faucet.masm | 4 ++-- .../asm/kernels/transaction/lib/fungible_asset.masm | 2 +- .../asm/kernels/transaction/lib/non_fungible_asset.masm | 2 +- .../asm/kernels/transaction/lib/output_note.masm | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm index 10b302a4d6..a65746bd6e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/account_delta.masm @@ -508,7 +508,7 @@ end #! - ASSET_VALUE is the value of the asset that is added. pub proc add_asset # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true @@ -540,7 +540,7 @@ end #! - ASSET_VALUE is the value of the asset that is removed. pub proc remove_asset # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index 3d1f3b5441..0868650673 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -31,7 +31,7 @@ pub use ::$kernel::util::asset::mem_load #! Where: #! - ASSET_KEY is the vault key of the asset to check. #! - is_fungible_asset is a boolean indicating whether the asset is fungible. -pub proc is_fungible_asset +pub proc is_fungible_asset_key # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] dup exec.account_id::is_fungible_faucet @@ -51,7 +51,7 @@ end #! fungible_asset::validate_key and non_fungible_asset::validate_key). pub proc validate_key # check if the asset key is fungible - exec.is_fungible_asset + exec.is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY] if.true @@ -72,7 +72,7 @@ end #! Where: #! - ASSET_KEY is the vault key of the asset to check. #! - is_non_fungible_asset is a boolean indicating whether the asset is non-fungible. -pub proc is_non_fungible_asset +pub proc is_non_fungible_asset_key # => [faucet_id_prefix, faucet_id_suffix, asset_id_prefix, asset_id_suffix] dup exec.account_id::is_non_fungible_faucet @@ -93,7 +93,7 @@ end #! non_fungible_asset::validate_key). pub proc validate # check if the asset is fungible - exec.is_fungible_asset + exec.is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] # if the asset is fungible, validate the fungible asset diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm index 51c19ea442..b5c4ba065f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm @@ -241,7 +241,7 @@ end #! - the vault already contains the same non-fungible asset. pub proc add_asset # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE, vault_root_ptr] # add the asset to the asset vault @@ -394,7 +394,7 @@ end #! - the non-fungible asset is not found in the vault. pub proc remove_asset # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE, vault_root_ptr] # remove the asset from the asset vault diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm index 3c5c09d1da..51cde49e6e 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/faucet.masm @@ -161,7 +161,7 @@ end #! - For non-fungible faucets if the non-fungible asset being minted already exists. pub proc mint # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true @@ -195,7 +195,7 @@ end #! provided as input to the transaction via a note or the accounts vault. pub proc burn # check if the asset is a fungible asset - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key # => [is_fungible_asset, ASSET_KEY, ASSET_VALUE] if.true diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm index 7b4999cf92..6ec79358b7 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -165,7 +165,7 @@ pub proc validate_key exec.account_id::validate # => [ASSET_KEY] - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key assert.err=ERR_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_FUNGIBLE # => [ASSET_KEY] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm index 9daea5e804..9b4693b4ac 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/non_fungible_asset.masm @@ -57,7 +57,7 @@ pub proc validate_key exec.account_id::validate # => [ASSET_KEY] - exec.asset::is_non_fungible_asset + exec.asset::is_non_fungible_asset_key assert.err=ERR_NON_FUNGIBLE_ASSET_KEY_ACCOUNT_ID_MUST_BE_NON_FUNGIBLE # => [ASSET_KEY] end diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index 6b620bd529..3c83e670ec 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -530,7 +530,7 @@ proc add_asset_raw # if the asset exists, do not increment num assets # abort if the asset is non-fungible since it cannot be merged - exec.asset::is_fungible_asset + exec.asset::is_fungible_asset_key assert.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS # => [ASSET_KEY, ASSET_VALUE, asset_ptr, note_ptr]