diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm index b45b093c61..b703f252f5 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/asset.masm @@ -1,8 +1,9 @@ use $kernel::account_id use $kernel::fungible_asset use $kernel::non_fungible_asset +use $kernel::util::asset->util_asset -# ERRORS +# ERRORS # ================================================================================================= 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" @@ -65,6 +66,30 @@ pub proc validate_key # => [ASSET_KEY] end +#! Validates the issuer (faucet ID) and metadata in an asset vault key. +#! +#! Inputs: [ASSET_KEY] +#! Outputs: [ASSET_KEY] +#! +#! Where: +#! - ASSET_KEY is the vault key of the asset to validate. +#! +#! Panics if: +#! - the asset metadata is invalid (not 0 or 1). +#! - the faucet ID in the key is not a valid account ID. +pub proc validate_issuer + # => [asset_id_suffix, asset_id_prefix, faucet_id_suffix_and_metadata, faucet_id_prefix] + + dup.3 dup.3 exec.util_asset::split_suffix_and_metadata + # => [asset_metadata, faucet_id_suffix, faucet_id_prefix, ASSET_KEY] + + exec.util_asset::validate_metadata + # => [faucet_id_suffix, faucet_id_prefix, ASSET_KEY] + + exec.account_id::validate + # => [ASSET_KEY] +end + #! Returns a boolean indicating whether the asset is non-fungible. #! #! Inputs: [ASSET_KEY] 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 6a89223ba4..a5e5f8d202 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/fungible_asset.masm @@ -148,10 +148,10 @@ end #! #! Panics if: #! - the asset key's account ID is not valid. +#! - the asset key's metadata is not valid. #! - the asset key's faucet ID is not a fungible one. pub proc validate_key - exec.asset::key_to_faucet_id - exec.account_id::validate + exec.asset::validate_issuer # => [ASSET_KEY] exec.asset::is_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 bfef314d73..9f825ce733 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 @@ -51,10 +51,10 @@ end #! #! Panics if: #! - the asset key's account ID is not valid. +#! - the asset key's metadata is not valid. #! - the asset key's faucet ID is not a non-fungible one. pub proc validate_key - exec.asset::key_to_faucet_id - exec.account_id::validate + exec.asset::validate_issuer # => [ASSET_KEY] exec.asset::is_non_fungible_asset_key diff --git a/crates/miden-protocol/asm/shared_utils/util/asset.masm b/crates/miden-protocol/asm/shared_utils/util/asset.masm index 50e20e69a7..5e0a254c31 100644 --- a/crates/miden-protocol/asm/shared_utils/util/asset.masm +++ b/crates/miden-protocol/asm/shared_utils/util/asset.masm @@ -292,7 +292,7 @@ end #! - faucet_id_suffix_and_metadata is the faucet ID suffix merged with the asset metadata. #! - faucet_id_suffix is the suffix of the account ID. #! - asset_metadata is the asset metadata. -proc split_suffix_and_metadata +pub proc split_suffix_and_metadata u32split # => [suffix_metadata_lo, suffix_metadata_hi] @@ -312,6 +312,20 @@ proc split_suffix_and_metadata # => [asset_metadata, faucet_id_suffix] end +#! Validates that asset metadata is well formed and consumes it. +#! +#! Inputs: [asset_metadata] +#! Outputs: [] +#! +#! Panics if: +#! - asset_metadata is not a valid u32 or exceeds CALLBACKS_ENABLED. +pub proc validate_metadata + u32assert.err=ERR_VAULT_INVALID_ENABLE_CALLBACKS + u32lte.CALLBACKS_ENABLED + assert.err=ERR_VAULT_INVALID_ENABLE_CALLBACKS + # => [] +end + #! Creates asset metadata from the provided inputs. #! #! Inputs: [enable_callbacks] @@ -324,9 +338,8 @@ end #! Panics if: #! - enable_callbacks is not 0 or 1. proc create_metadata - u32assert.err=ERR_VAULT_INVALID_ENABLE_CALLBACKS - dup u32lte.CALLBACKS_ENABLED - assert.err=ERR_VAULT_INVALID_ENABLE_CALLBACKS + # for now, enable_callbacks is identical to asset_metadata + dup exec.validate_metadata # => [asset_metadata] end diff --git a/crates/miden-protocol/build.rs b/crates/miden-protocol/build.rs index f981e48ed8..86271e28ff 100644 --- a/crates/miden-protocol/build.rs +++ b/crates/miden-protocol/build.rs @@ -352,12 +352,21 @@ fn copy_shared_modules>(source_dir: T) -> Result<()> { /// The generated files are written to `build_dir` (i.e. `OUT_DIR`) and included via `include!` /// in the source. fn generate_error_constants(asm_source_dir: &Path, build_dir: &str) -> Result<()> { + // Shared utils errors + // For now these are duplicated in the tx kernel and protocol error module. + // ------------------------------------------ + + let shared_utils_dir = asm_source_dir.join(SHARED_UTILS_DIR); + let shared_utils_errors = shared::extract_all_masm_errors(&shared_utils_dir) + .context("failed to extract all masm errors")?; + // Transaction kernel errors // ------------------------------------------ let tx_kernel_dir = asm_source_dir.join(ASM_TX_KERNEL_DIR); - let errors = shared::extract_all_masm_errors(&tx_kernel_dir) + let mut errors = shared::extract_all_masm_errors(&tx_kernel_dir) .context("failed to extract all masm errors")?; + errors.extend_from_slice(&shared_utils_errors); validate_tx_kernel_category(&errors)?; shared::generate_error_file( @@ -373,8 +382,9 @@ fn generate_error_constants(asm_source_dir: &Path, build_dir: &str) -> Result<() // ------------------------------------------ let protocol_dir = asm_source_dir.join(ASM_PROTOCOL_DIR); - let errors = shared::extract_all_masm_errors(&protocol_dir) + let mut errors = shared::extract_all_masm_errors(&protocol_dir) .context("failed to extract all masm errors")?; + errors.extend(shared_utils_errors); shared::generate_error_file( shared::ErrorModule { 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 40425fdf7f..d436abbe05 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::Felt; use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId, AccountType}; use miden_protocol::assembly::DefaultSourceManager; use miden_protocol::asset::{ @@ -14,6 +15,7 @@ use miden_protocol::errors::tx_kernel::{ ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW, + ERR_VAULT_INVALID_ENABLE_CALLBACKS, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND, }; use miden_protocol::testing::account_id::{ @@ -151,6 +153,41 @@ async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> Ok(()) } +/// Tests that minting a fungible asset on a non-faucet account fails when the key has its asset +/// metadata (lower 8 bits) set to u8::MAX. +#[tokio::test] +async fn mint_fungible_asset_fails_on_invalid_asset_metadata() -> anyhow::Result<()> { + let asset = FungibleAsset::mock(50); + + let mut vault_key_word = asset.to_key_word(); + vault_key_word[2] = Felt::try_from(vault_key_word[2].as_canonical_u64() | u8::MAX as u64)?; + + let code = format!( + " + use $kernel::prologue + use mock::faucet + + begin + exec.prologue::prepare_transaction + push.{ASSET_VALUE} + push.{ASSET_KEY} + call.faucet::mint + dropw dropw + end + ", + ASSET_KEY = vault_key_word, + ASSET_VALUE = asset.to_value_word(), + ); + + let result = TransactionContextBuilder::with_fungible_faucet(asset.faucet_id().into()) + .build()? + .execute_code(&code) + .await; + assert_execution_error!(result, ERR_VAULT_INVALID_ENABLE_CALLBACKS); + + Ok(()) +} + /// Tests that minting a fungible asset with [`FungibleAsset::MAX_AMOUNT`] + 1 fails. #[tokio::test] async fn test_mint_fungible_asset_fails_when_amount_exceeds_max_representable_amount()