From af44cea4581084a173bc9d644cd6de60c2845b62 Mon Sep 17 00:00:00 2001 From: tomasarrachea Date: Tue, 30 Jun 2026 17:00:56 -0300 Subject: [PATCH 1/5] feat: migrate transaction scripts to library and add `@transaction_script` attribute --- CHANGELOG.md | 1 + bin/bench-transaction/src/context_setups.rs | 3 +- crates/miden-protocol/src/errors/mod.rs | 4 + .../miden-protocol/src/transaction/tx_args.rs | 81 +++++++++++++++++++ .../miden-standards/src/code_builder/mod.rs | 58 ++++++------- .../src/tx_script/expiration_script.rs | 3 +- .../src/tx_script/send_notes_script.rs | 6 +- .../src/kernel_tests/block/utils.rs | 3 +- .../src/kernel_tests/tx/test_account.rs | 33 +++++--- .../kernel_tests/tx/test_account_update.rs | 24 ++++-- .../src/kernel_tests/tx/test_active_note.rs | 3 +- .../src/kernel_tests/tx/test_array.rs | 6 +- .../src/kernel_tests/tx/test_auth.rs | 3 +- .../src/kernel_tests/tx/test_callbacks.rs | 9 ++- .../src/kernel_tests/tx/test_epilogue.rs | 6 +- .../src/kernel_tests/tx/test_faucet.rs | 15 ++-- .../src/kernel_tests/tx/test_fpi.rs | 27 ++++--- .../src/kernel_tests/tx/test_input_note.rs | 21 +++-- .../src/kernel_tests/tx/test_lazy_loading.rs | 12 ++- .../src/kernel_tests/tx/test_output_note.rs | 27 ++++--- .../src/kernel_tests/tx/test_prologue.rs | 3 +- .../src/kernel_tests/tx/test_tx.rs | 18 +++-- .../src/standards/token_metadata.rs | 36 ++++++--- .../miden-testing/tests/agglayer/bridge_in.rs | 3 +- .../tests/agglayer/merkle_tree_frontier.rs | 6 +- .../agglayer/network_account_regression.rs | 6 +- .../tests/auth/guarded_multisig.rs | 12 +-- .../tests/auth/hybrid_multisig.rs | 8 +- crates/miden-testing/tests/auth/multisig.rs | 22 +++-- .../tests/auth/multisig_smart.rs | 9 ++- .../tests/auth/network_account.rs | 9 ++- crates/miden-testing/tests/auth/singlesig.rs | 6 +- .../miden-testing/tests/auth/singlesig_acl.rs | 15 ++-- .../miden-testing/tests/scripts/allowlist.rs | 6 +- .../miden-testing/tests/scripts/blocklist.rs | 6 +- .../tests/scripts/code_inspection.rs | 3 +- crates/miden-testing/tests/scripts/faucet.rs | 9 ++- .../tests/scripts/non_fungible_faucet.rs | 10 ++- crates/miden-testing/tests/scripts/p2id.rs | 6 +- .../miden-testing/tests/scripts/pausable.rs | 3 +- crates/miden-testing/tests/scripts/swap.rs | 3 +- 41 files changed, 381 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1dd9b5fa7..9267163285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ - [BREAKING] Renamed `create_user_fungible_faucet` to `create_singlesig_user_fungible_faucet` and added the `create_multisig_user_fungible_faucet(auth_component: AuthMultisig, ...)` and `create_guarded_user_fungible_faucet(auth_component: AuthGuardedMultisig, ...)`. `create_network_fungible_faucet` now allowlists the canonical `ExpirationTransactionScript` in its tx-script allowlist ([#3143](https://github.com/0xMiden/protocol/pull/3143)). - Added the `CodeInspection` standard account component, exposing the `has_procedure`, `get_code_commitment`, `get_num_procedures`, and `get_procedure_root` introspection procedures on an account's public interface ([#3162](https://github.com/0xMiden/protocol/pull/3162)). - Updated `AuthRequest` event to carry either signature of TX summary, but not both ([#3157](https://github.com/0xMiden/protocol/pull/3157)). +- [BREAKING] Added `@transaction_script` attribute to mark the script entrypoint. Migrated transaction scripts assembly to `Library` ([#3173](https://github.com/0xMiden/protocol/pull/3173)). ### Fixes diff --git a/bin/bench-transaction/src/context_setups.rs b/bin/bench-transaction/src/context_setups.rs index d998176cf0..4ff10ac571 100644 --- a/bin/bench-transaction/src/context_setups.rs +++ b/bin/bench-transaction/src/context_setups.rs @@ -55,7 +55,8 @@ pub fn tx_create_single_p2id_note() -> Result { use miden::protocol::output_note use miden::core::sys - begin + @transaction_script + pub proc main # create an output note with fungible asset push.{RECIPIENT} push.{note_type} diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 1d6958c9f9..57ce014a01 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -877,6 +877,10 @@ pub enum TransactionScriptError { AssemblyError(Report), #[error("failed to convert package to transaction script:\n{}", PrintDiagnostic::new(.0))] PackageNotProgram(Report), + #[error("library does not contain a procedure with @transaction_script attribute")] + NoProcedureWithAttribute, + #[error("library contains multiple procedures with @transaction_script attribute")] + MultipleProceduresWithAttribute, } // TRANSACTION INPUT ERROR diff --git a/crates/miden-protocol/src/transaction/tx_args.rs b/crates/miden-protocol/src/transaction/tx_args.rs index 0ffc8eeec5..358a54a78b 100644 --- a/crates/miden-protocol/src/transaction/tx_args.rs +++ b/crates/miden-protocol/src/transaction/tx_args.rs @@ -11,6 +11,7 @@ use miden_mast_package::Package; use super::{Felt, Hasher, Word}; use crate::account::auth::{PublicKeyCommitment, Signature}; +use crate::assembly::Library; use crate::errors::TransactionScriptError; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ @@ -318,6 +319,9 @@ impl Deserializable for TransactionScriptRoot { // TRANSACTION SCRIPT // ================================================================================================ +/// The attribute name used to mark the entrypoint procedure in a transaction script library. +const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script"; + /// Transaction script. /// /// A transaction script is a program that is executed in a transaction after all input notes @@ -336,6 +340,8 @@ impl TransactionScript { // -------------------------------------------------------------------------------------------- /// Returns a new [TransactionScript] instantiated with the provided code. + // TODO: we can remove this `Program` based constructor once the compiler integrates the + // `@transaction_script` attribute (https://github.com/0xMiden/compiler/issues/1190). pub fn new(code: Program) -> Self { Self::from_parts(code.mast_forest().clone(), code.entrypoint()) } @@ -364,6 +370,37 @@ impl TransactionScript { Ok(TransactionScript::new(program)) } + /// Returns a new [TransactionScript] instantiated from the provided library. + /// + /// The library must contain exactly one procedure with the `@transaction_script` attribute, + /// which will be used as the entrypoint. + /// + /// # Errors + /// Returns an error if: + /// - The library does not contain a procedure with the `@transaction_script` attribute. + /// - The library contains multiple procedures with the `@transaction_script` attribute. + pub fn from_library(library: &Library) -> Result { + let mut entrypoint = None; + + for export in library.exports() { + if let Some(proc_export) = export.as_procedure() + && proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE) + { + if entrypoint.is_some() { + return Err(TransactionScriptError::MultipleProceduresWithAttribute); + } + entrypoint = Some(proc_export.node); + } + } + + let entrypoint = entrypoint.ok_or(TransactionScriptError::NoProcedureWithAttribute)?; + + Ok(Self { + mast: library.mast_forest().clone(), + entrypoint, + }) + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -461,4 +498,48 @@ mod tests { let stored = mast.advice_map().get(&key).expect("entry should be present"); assert_eq!(stored.as_ref(), value.as_slice()); } + + #[test] + fn test_transaction_script_from_library() { + use assert_matches::assert_matches; + + use super::TransactionScript; + use crate::assembly::Assembler; + use crate::errors::TransactionScriptError; + use crate::utils::serde::{Deserializable, Serializable}; + + let source = " + @transaction_script + pub proc main + push.1 drop + end + "; + let library = Assembler::default().assemble_library([source]).unwrap(); + + let script = TransactionScript::from_library(&library).unwrap(); + + // the script must round-trip through serialization unchanged + let bytes = script.to_bytes(); + let decoded = TransactionScript::read_from_bytes(&bytes).unwrap(); + assert_eq!(script, decoded); + + // a library without the attribute is rejected + let no_attr = Assembler::default() + .assemble_library(["pub proc main push.1 drop end"]) + .unwrap(); + assert_matches!( + TransactionScript::from_library(&no_attr), + Err(TransactionScriptError::NoProcedureWithAttribute) + ); + + // a library with multiple tagged procedures is rejected + let multiple = Assembler::default() + .assemble_library(["@transaction_script pub proc main_a push.1 drop end + @transaction_script pub proc main_b push.2 drop end"]) + .unwrap(); + assert_matches!( + TransactionScript::from_library(&multiple), + Err(TransactionScriptError::MultipleProceduresWithAttribute) + ); + } } diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index 9bb240d79f..3520d2ea47 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -63,7 +63,7 @@ use crate::standards_lib::StandardsLib; /// # use miden_protocol::CoreLibrary; /// # fn example() -> anyhow::Result<()> { /// # let module_code = "pub proc test push.1 add end"; -/// # let script_code = "begin nop end"; +/// # let script_code = "@transaction_script pub proc main nop end"; /// # // Create sample libraries for the example /// # let my_lib: Library = CoreLibrary::default().into(); // Convert CoreLibrary to Library /// # let fpi_lib: Library = CoreLibrary::default().into(); @@ -293,20 +293,6 @@ impl CodeBuilder { // PRIVATE HELPERS // -------------------------------------------------------------------------------------------- - /// Applies the advice map to a program if it's non-empty. - /// - /// This avoids cloning the MAST forest when there are no advice map entries. - fn apply_advice_map( - advice_map: AdviceMap, - program: miden_protocol::vm::Program, - ) -> miden_protocol::vm::Program { - if advice_map.is_empty() { - program - } else { - program.with_advice_map(advice_map) - } - } - /// Applies the advice map to a library if it's non-empty. /// /// This avoids cloning the MAST forest when there are no advice map entries. @@ -363,7 +349,8 @@ impl CodeBuilder { /// The parsed script will have access to all modules that have been added to this builder. /// /// # Arguments - /// * `tx_script` - The transaction script source code + /// - `tx_script` - the transaction script source code which is expected to have a single public + /// procedure marked with the @transaction_script attribute. /// /// # Errors /// Returns an error if: @@ -374,11 +361,23 @@ impl CodeBuilder { ) -> Result { let CodeBuilder { assembler, advice_map, .. } = self; - let program = assembler.assemble_program(tx_script).map_err(|err| { - CodeBuilderError::build_error_with_report("failed to parse transaction script", err) + let tx_script_lib = assembler.assemble_library([tx_script]).map_err(|err| { + CodeBuilderError::build_error_with_report( + "failed to parse transaction script library", + err, + ) })?; - Ok(TransactionScript::new(Self::apply_advice_map(advice_map, program))) + TransactionScript::from_library(&Self::apply_advice_map_to_library( + advice_map, + Arc::unwrap_or_clone(tx_script_lib), + )) + .map_err(|err| { + CodeBuilderError::build_error_with_source( + "failed to create transaction script from library", + err, + ) + }) } /// Compiles the provided MASM code into a [`NoteScript`]. @@ -525,7 +524,7 @@ mod tests { fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> { let builder = CodeBuilder::default(); builder - .compile_tx_script("begin nop end") + .compile_tx_script("@transaction_script pub proc main nop end") .context("failed to parse basic tx script")?; Ok(()) } @@ -535,7 +534,8 @@ mod tests { let script_code = " use external_contract::counter_contract - begin + @transaction_script + pub proc main call.counter_contract::increment end "; @@ -573,7 +573,8 @@ mod tests { let script_code = " use external_contract::counter_contract - begin + @transaction_script + pub proc main call.counter_contract::increment end "; @@ -624,7 +625,8 @@ mod tests { let script_code = " use external_contract::counter_contract - begin + @transaction_script + pub proc main call.counter_contract::increment end "; @@ -656,8 +658,7 @@ mod tests { #[test] fn test_multiple_chained_modules() -> anyhow::Result<()> { - let script_code = - "use test::lib1 use test::lib2 begin exec.lib1::test1 exec.lib2::test2 end"; + let script_code = "use test::lib1 use test::lib2 @transaction_script pub proc main exec.lib1::test1 exec.lib2::test2 end"; // Test chaining multiple modules let builder = CodeBuilder::default() @@ -676,7 +677,8 @@ mod tests { let script_code = " use contracts::static_contract - begin + @transaction_script + pub proc main call.static_contract::increment_1 end "; @@ -732,7 +734,7 @@ mod tests { let script = CodeBuilder::default() .with_advice_map_entry(key, value.clone()) - .compile_tx_script("begin nop end") + .compile_tx_script("@transaction_script pub proc main nop end") .context("failed to compile tx script with advice map")?; let mast = script.mast(); @@ -753,7 +755,7 @@ mod tests { let script = CodeBuilder::default() .with_extended_advice_map(advice_map) - .compile_tx_script("begin nop end") + .compile_tx_script("@transaction_script pub proc main nop end") .context("failed to compile tx script")?; let mast = script.mast(); diff --git a/crates/miden-standards/src/tx_script/expiration_script.rs b/crates/miden-standards/src/tx_script/expiration_script.rs index 8dea281ec5..4eecbdbe20 100644 --- a/crates/miden-standards/src/tx_script/expiration_script.rs +++ b/crates/miden-standards/src/tx_script/expiration_script.rs @@ -22,7 +22,8 @@ use miden::protocol::tx #! - delta is 0 or not a u32 in the range 1..=0xFFFF (ERR_TX_INVALID_EXPIRATION_DELTA). #! #! Invocation: call -begin +@transaction_script +pub proc main exec.tx::update_expiration_block_delta # => [pad(16)] end diff --git a/crates/miden-standards/src/tx_script/send_notes_script.rs b/crates/miden-standards/src/tx_script/send_notes_script.rs index e80c0b9e66..23dd168c3b 100644 --- a/crates/miden-standards/src/tx_script/send_notes_script.rs +++ b/crates/miden-standards/src/tx_script/send_notes_script.rs @@ -37,7 +37,8 @@ use crate::errors::CodeBuilderError; /// [`FungibleFaucet`]: /// /// ```masm -/// begin +/// @transaction_script +/// pub proc main /// push.{expiration_delta} exec.::miden::protocol::tx::update_expiration_block_delta /// /// push.{note information} @@ -101,7 +102,8 @@ impl SendNotesTransactionScript { return Err(SendNotesTransactionScriptError::UnsupportedAccountInterface); }; - let script = format!("begin\n{expiration_prelude}\n{body}\nend"); + let script = + format!("@transaction_script\npub proc main\n{expiration_prelude}\n{body}\nend"); let mut code_builder = CodeBuilder::new(); for note in output_notes { diff --git a/crates/miden-testing/src/kernel_tests/block/utils.rs b/crates/miden-testing/src/kernel_tests/block/utils.rs index f3b5de580e..7361d0fdee 100644 --- a/crates/miden-testing/src/kernel_tests/block/utils.rs +++ b/crates/miden-testing/src/kernel_tests/block/utils.rs @@ -103,7 +103,8 @@ fn update_expiration_tx_script(expiration_delta: u16) -> TransactionScript { " use miden::protocol::tx - begin + @transaction_script + pub proc main push.{expiration_delta} exec.tx::update_expiration_block_delta end 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 c077af913a..7173a7495b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -106,7 +106,8 @@ pub async fn compute_commitment() -> anyhow::Result<()> { const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main exec.active_account::get_initial_commitment # => [INITIAL_COMMITMENT] @@ -454,7 +455,8 @@ async fn test_account_get_item_fails_on_unknown_slot() -> anyhow::Result<()> { const UNKNOWN_SLOT_NAME = word("unknown::slot::name") - begin + @transaction_script + pub proc main push.UNKNOWN_SLOT_NAME[0..2] call.account::get_item end @@ -997,7 +999,8 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { r#" use miden::protocol::active_account - begin + @transaction_script + pub proc main # get the current asset balance push.{ASSET_KEY} exec.active_account::get_balance @@ -1046,7 +1049,8 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { r#" use miden::protocol::active_account - begin + @transaction_script + pub proc main # get the current asset balance push.{ASSET_KEY} exec.active_account::get_balance @@ -1125,7 +1129,8 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { use miden::standards::wallets::basic->wallet use mock::util - begin + @transaction_script + pub proc main # create random note and move the asset into it exec.util::create_default_note # => [note_idx] @@ -1217,7 +1222,8 @@ async fn test_get_init_asset() -> anyhow::Result<()> { use miden::standards::wallets::basic->wallet use mock::util - begin + @transaction_script + pub proc main # create default note and move the asset into it exec.util::create_default_note # => [note_idx] @@ -1345,7 +1351,8 @@ async fn test_was_procedure_called() -> anyhow::Result<()> { const MOCK_VALUE_SLOT1 = word("{mock_value_slot1}") - begin + @transaction_script + pub proc main # First check that get_item procedure hasn't been called yet procref.mock_account::get_item exec.native_account::was_procedure_called @@ -1441,7 +1448,8 @@ async fn transaction_executor_account_code_using_custom_library() -> anyhow::Res let tx_script_src = "\ use account_component::account_module - begin + @transaction_script + pub proc main call.account_module::custom_setter end"; @@ -1530,7 +1538,8 @@ async fn test_has_procedure() -> anyhow::Result<()> { use mock::account->mock_account use miden::protocol::active_account - begin + @transaction_script + pub proc main # check that get_item procedure is available on the mock account procref.mock_account::get_item # => [GET_ITEM_ROOT] @@ -1662,7 +1671,8 @@ async fn test_faucet_has_callbacks( r#" use miden::protocol::faucet - begin + @transaction_script + pub proc main exec.faucet::has_callbacks push.{has_callbacks} assert_eq.err="has_callbacks returned unexpected value" @@ -2021,7 +2031,8 @@ async fn merging_components_with_same_mast_root_succeeds() -> anyhow::Result<()> use component1::interface->comp1_interface use component2::interface->comp2_interface - begin + @transaction_script + pub proc main call.comp1_interface::get_slot_content push.{slot_content1} assert_eqw.err="failed to get slot content1" diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_update.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_update.rs index 92ec1cd580..c3409b39b9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_update.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_update.rs @@ -55,7 +55,8 @@ async fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { r#" use miden::protocol::native_account - begin + @transaction_script + pub proc main exec.native_account::compute_delta_commitment # => [DELTA_COMMITMENT] @@ -133,7 +134,8 @@ async fn storage_patch_for_value_slots() -> anyhow::Result<()> { const SLOT_2_NAME = word("{slot_2_name}") const SLOT_3_NAME = word("{slot_3_name}") - begin + @transaction_script + pub proc main push.{slot_0_tmp_value} push.SLOT_0_NAME[0..2] # => [slot_id_suffix, slot_id_prefix, VALUE] @@ -257,7 +259,8 @@ async fn storage_patch_for_map_slots() -> anyhow::Result<()> { const SLOT_1_NAME = word("{slot_1_name}") const SLOT_2_NAME = word("{slot_2_name}") - begin + @transaction_script + pub proc main push.{key0_final_value} push.{key0} push.SLOT_0_NAME[0..2] # => [slot_id_suffix, slot_id_prefix, KEY, VALUE] @@ -383,7 +386,8 @@ async fn fungible_asset_update() -> anyhow::Result<()> { let tx_script = parse_tx_script(format!( " - begin + @transaction_script + pub proc main push.{ASSET0_VALUE} push.{ASSET0_KEY} exec.util::create_default_note_with_moved_asset # => [] @@ -487,7 +491,8 @@ async fn non_fungible_asset_delta() -> anyhow::Result<()> { let tx_script = parse_tx_script(format!( " - begin + @transaction_script + pub proc main push.{ASSET1_VALUE} push.{ASSET1_KEY} exec.util::create_default_note_with_moved_asset # => [] @@ -625,7 +630,8 @@ async fn asset_and_storage_patch() -> anyhow::Result<()> { const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main ## Update value storage slot push.{updated_slot_value} push.MOCK_VALUE_SLOT0[0..2] @@ -723,7 +729,8 @@ async fn proven_tx_storage_maps_matches_executed_tx_for_new_account() -> anyhow: const MAP_SLOT=word("{map2_slot_name}") - begin + @transaction_script + pub proc main # Update an existing key. push.{value0} push.{existing_key} @@ -888,7 +895,8 @@ async fn patch_for_new_account_retains_empty_map_storage_slots() -> anyhow::Resu const MAP_SLOT=word("{slot_name1}") - begin + @transaction_script + pub proc main # Set the key to a non-empty value. push.{non_empty_value} push.{map_key} 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 e548e4d455..e282f62c56 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 @@ -60,7 +60,8 @@ async fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<() let code = " use miden::protocol::active_note - begin + @transaction_script + pub proc main # try to get the sender from transaction script exec.active_note::get_sender end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_array.rs b/crates/miden-testing/src/kernel_tests/tx/test_array.rs index 56231d5469..5a57adace2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_array.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_array.rs @@ -91,7 +91,8 @@ async fn test_array_get_and_set() -> anyhow::Result<()> { let tx_script_code = r#" use wrapper::component->wrapper - begin + @transaction_script + pub proc main # Step 1: Get value at index 0 (should return [42, 42, 42, 42]) push.0 # => [index, pad(16)] @@ -206,7 +207,8 @@ async fn test_double_word_array_get_and_set() -> anyhow::Result<()> { r#" use wrapper::component->wrapper - begin + @transaction_script + pub proc main # Step 1: Get value at index {index} (should return the initial double-word) push.{index} call.wrapper::test_get diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index f914d1dc7c..ea3cc1fa2e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -76,7 +76,8 @@ async fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { // Create a transaction script that calls the auth procedure let tx_script_source = " - begin + @transaction_script + pub proc main call.::incr_nonce::auth_incr_nonce end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs b/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs index 4fc5f676da..7c57b1fc68 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs @@ -459,7 +459,8 @@ async fn test_blocked_account_cannot_add_asset_to_note( r#" use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -571,7 +572,8 @@ async fn test_on_before_asset_added_to_note_callback_receives_correct_inputs() - r#" use mock::util - begin + @transaction_script + pub proc main # Create note 0 (just to consume index 0) exec.util::create_default_note drop # => [] @@ -646,7 +648,8 @@ async fn test_faucet_with_callback_calls_itself() -> anyhow::Result<()> { let tx_script_code = format!( " - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} 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 bacbc4854b..8f71eb9071 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -265,7 +265,8 @@ async fn epilogue_fails_when_assets_arent_preserved( use mock::account use mock::util - begin + @transaction_script + pub proc main # create a note with the output asset push.{OUTPUT_ASSET_VALUE} push.{OUTPUT_ASSET_KEY} @@ -453,7 +454,8 @@ async fn epilogue_fails_on_account_state_change_without_nonce_increment() -> any const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") - begin + @transaction_script + pub proc main push.91.92.93.94 push.MOCK_VALUE_SLOT0[0..2] repeat.5 movup.5 drop 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 0c8ed427d4..1ffea14312 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -102,7 +102,8 @@ async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> " use mock::faucet - begin + @transaction_script + pub proc main push.{ASSET_VALUE} push.{ASSET_KEY} call.faucet::mint @@ -195,7 +196,8 @@ async fn test_mint_fungible_asset_fails_when_amount_exceeds_max_representable_am " use mock::faucet - begin + @transaction_script + pub proc main push.0 push.0 push.0 @@ -311,7 +313,8 @@ async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result " use mock::faucet - begin + @transaction_script + pub proc main push.{ASSET_VALUE} push.{ASSET_KEY} call.faucet::mint @@ -436,7 +439,8 @@ async fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> " use mock::faucet - begin + @transaction_script + pub proc main push.{FUNGIBLE_ASSET_VALUE} push.{FUNGIBLE_ASSET_KEY} call.faucet::burn @@ -620,7 +624,8 @@ async fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result " use mock::faucet - begin + @transaction_script + pub proc main push.{ASSET_VALUE} push.{ASSET_KEY} call.faucet::burn 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 4e425e0306..88d742b950 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -625,7 +625,8 @@ async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main # => [pad(16)] ### get the storage item ########################################## @@ -812,7 +813,8 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu use miden::protocol::tx - begin + @transaction_script + pub proc main # Get the added balance of two assets from foreign account # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 @@ -918,7 +920,8 @@ async fn foreign_account_get_initial_balance() -> anyhow::Result<()> { use miden::protocol::tx - begin + @transaction_script + pub proc main # Get the initial balance of the fungible asset from the foreign account # pad the stack for the `execute_foreign_procedure` execution @@ -1154,7 +1157,8 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { use miden::core::sys use miden::protocol::tx - begin + @transaction_script + pub proc main # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 # => [pad(15)] @@ -1315,7 +1319,8 @@ async fn test_prove_fpi_two_foreign_accounts_chain() -> anyhow::Result<()> { use miden::core::sys use miden::protocol::tx - begin + @transaction_script + pub proc main # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 # => [pad(15)] @@ -1499,7 +1504,8 @@ async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { use miden::protocol::tx - begin + @transaction_script + pub proc main # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 # => [pad(15)] @@ -1595,7 +1601,8 @@ async fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { use miden::protocol::tx - begin + @transaction_script + pub proc main # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 # => [pad(15)] @@ -1802,7 +1809,8 @@ async fn test_fpi_get_account_id() -> anyhow::Result<()> { use miden::protocol::tx use miden::protocol::account_id - begin + @transaction_script + pub proc main # get the IDs of the foreign and native accounts # pad the stack for the `execute_foreign_procedure` execution padw padw padw push.0.0.0 @@ -1926,7 +1934,8 @@ async fn test_get_initial_item_and_get_initial_map_item_with_foreign_account() - const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main # Test get_initial_item on foreign account padw padw padw push.0.0.0 # => [pad(15)] 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 3bb63a4840..a792a5c7c1 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 @@ -50,7 +50,8 @@ async fn test_get_asset_info() -> anyhow::Result<()> { " use miden::protocol::input_note - begin + @transaction_script + pub proc main {check_note_0} {check_note_1} @@ -107,7 +108,8 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { r#" use miden::protocol::input_note - begin + @transaction_script + pub proc main # get the recipient from the input note push.0 exec.input_note::get_recipient @@ -160,7 +162,8 @@ async fn test_get_sender() -> anyhow::Result<()> { r#" use miden::protocol::input_note - begin + @transaction_script + pub proc main # get the sender from the input note push.0 exec.input_note::get_sender @@ -275,7 +278,8 @@ async fn test_get_assets() -> anyhow::Result<()> { " use miden::protocol::input_note - begin + @transaction_script + pub proc main {check_note_0} {check_note_1} @@ -320,7 +324,8 @@ async fn test_get_storage_info() -> anyhow::Result<()> { r#" use miden::protocol::input_note - begin + @transaction_script + pub proc main # get the storage commitment and length from the input note with index 0 (the only one # we have) push.0 @@ -370,7 +375,8 @@ async fn test_get_script_root() -> anyhow::Result<()> { r#" use miden::protocol::input_note - begin + @transaction_script + pub proc main # get the script root from the input note with index 0 (the only one we have) push.0 exec.input_note::get_script_root @@ -413,7 +419,8 @@ async fn test_get_serial_number() -> anyhow::Result<()> { r#" use miden::protocol::input_note - begin + @transaction_script + pub proc main # get the serial number from the input note with index 0 (the only one we have) push.0 exec.input_note::get_serial_number 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 ca76e441f6..ada8102979 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 @@ -40,7 +40,8 @@ async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<( " use mock::account - begin + @transaction_script + pub proc main push.{FUNGIBLE_ASSET_VALUE1} push.{FUNGIBLE_ASSET_KEY1} call.account::add_asset dropw dropw @@ -92,7 +93,8 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result use mock::account use mock::util - begin + @transaction_script + pub proc main push.{FUNGIBLE_ASSET1_VALUE} push.{FUNGIBLE_ASSET1_KEY} call.account::remove_asset @@ -182,7 +184,8 @@ async fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main # Update an existing key. push.{value0} push.{existing_key} @@ -242,7 +245,8 @@ async fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { const MOCK_MAP_SLOT = word("{mock_map_slot}") - begin + @transaction_script + pub proc main # Fetch value from existing key. push.{existing_key} push.MOCK_MAP_SLOT[0..2] 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 cdd683f69b..0bb3b2925a 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 @@ -910,7 +910,8 @@ async fn test_get_asset_info() -> anyhow::Result<()> { use miden::protocol::output_note use miden::core::sys - begin + @transaction_script + pub proc main # create an output note with fungible asset 0 push.{RECIPIENT} push.{note_type} @@ -1031,7 +1032,8 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { use miden::protocol::output_note use miden::core::sys - begin + @transaction_script + pub proc main # create an output note with one asset {output_note} drop # => [] @@ -1160,7 +1162,8 @@ async fn test_get_assets() -> anyhow::Result<()> { use miden::protocol::output_note use miden::core::sys - begin + @transaction_script + pub proc main {create_note_0} {check_note_0} @@ -1286,7 +1289,8 @@ async fn test_add_fifth_attachment_fails() -> anyhow::Result<()> { use miden::protocol::output_note use mock::util - begin + @transaction_script + pub proc main exec.util::create_default_note # => [note_idx] @@ -1344,7 +1348,8 @@ async fn test_add_word_attachment() -> anyhow::Result<()> { " use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{RECIPIENT} push.{note_type} push.{tag} @@ -1414,7 +1419,8 @@ async fn test_add_attachment_from_memory() -> anyhow::Result<()> { " use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{RECIPIENT} push.{note_type} push.{tag} @@ -1592,7 +1598,8 @@ async fn test_write_attachment_commitments_to_memory() -> anyhow::Result<()> { const DEST_PTR = 0x1000 - begin + @transaction_script + pub proc main push.{RECIPIENT} push.{note_type} push.{tag} @@ -1703,7 +1710,8 @@ async fn test_write_attachment_to_memory() -> anyhow::Result<()> { const ATTACHMENT_DEST_PTR = 2048 - begin + @transaction_script + pub proc main push.{RECIPIENT} push.{note_type} push.{tag} @@ -1839,7 +1847,8 @@ async fn test_find_attachment( const DEST_PTR = 0x1000 - begin + @transaction_script + pub proc main # the spawn note creates output note at index 0; # search for the target scheme on that note push.0 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 6c63f2c6ac..1808560df7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -121,7 +121,8 @@ async fn test_transaction_prologue() -> anyhow::Result<()> { "; let mock_tx_script_code = " - begin + @transaction_script + pub proc main nop end "; 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 f6b4c6bd16..e8f144c258 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -281,7 +281,8 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { ## TRANSACTION SCRIPT ## ======================================================================================== - begin + @transaction_script + pub proc main ## Send some assets from the account vault ## ------------------------------------------------------------------------------------ # partially deplete fungible asset balance @@ -598,7 +599,8 @@ async fn execute_tx_view_script() -> anyhow::Result<()> { use test::module_1 use miden::core::sys - begin + @transaction_script + pub proc main push.1.2 call.module_1::foo exec.sys::truncate_stack @@ -638,7 +640,8 @@ async fn test_tx_script_inputs() -> anyhow::Result<()> { let tx_script_input_value = Word::from([9, 8, 7, 6u32]); let tx_script_src = format!( r#" - begin + @transaction_script + pub proc main # push the tx script input key onto the stack push.{tx_script_input_key} @@ -671,7 +674,8 @@ async fn test_tx_script_args() -> anyhow::Result<()> { let tx_script_src = format!( r#" - begin + @transaction_script + pub proc main # => [TX_SCRIPT_ARGS] # `TX_SCRIPT_ARGS` value is a user provided word, which could be used during the # transaction execution. In this example it is a `[1, 2, 3, 4]` word. @@ -710,7 +714,8 @@ async fn test_tx_script_args() -> anyhow::Result<()> { /// Tests that `tx::get_tx_script_root` returns the root of the executed transaction script. #[tokio::test] async fn test_get_script_root_with_script() -> anyhow::Result<()> { - let tx_script = CodeBuilder::default().compile_tx_script("begin nop end")?; + let tx_script = + CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?; let expected_root = tx_script.root(); let code = format!( @@ -793,7 +798,8 @@ async fn inputs_created_correctly() -> anyhow::Result<()> { let script = r#" adv_map A([1,2,3,4]) = [5,6,7,8] - begin + @transaction_script + pub proc main call.::test::adv_map_component::assert_adv_map # test account code advice map diff --git a/crates/miden-testing/src/standards/token_metadata.rs b/crates/miden-testing/src/standards/token_metadata.rs index 97b6a16093..4c0ad85602 100644 --- a/crates/miden-testing/src/standards/token_metadata.rs +++ b/crates/miden-testing/src/standards/token_metadata.rs @@ -222,7 +222,8 @@ async fn get_name_from_masm() -> anyhow::Result<()> { account, format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::get_name push.{n0} assert_eqw.err="name chunk 0 does not match" @@ -257,7 +258,8 @@ async fn get_name_zeros_returns_empty() -> anyhow::Result<()> { execute_tx_script( account, r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::get_name padw assert_eqw.err="name chunk 0 should be empty" padw assert_eqw.err="name chunk 1 should be empty" @@ -278,7 +280,8 @@ async fn faucet_get_decimals() -> anyhow::Result<()> { build_pol_faucet_account(), format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_decimals push.{expected} assert_eq.err="decimals does not match" push.0 assert_eq.err="clean stack: pad must be 0" @@ -296,7 +299,8 @@ async fn faucet_get_token_symbol() -> anyhow::Result<()> { build_pol_faucet_account(), format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_token_symbol push.{expected} assert_eq.err="token_symbol does not match" push.0 assert_eq.err="clean stack: pad must be 0" @@ -312,7 +316,8 @@ async fn faucet_get_token_supply() -> anyhow::Result<()> { execute_tx_script( build_pol_faucet_account(), r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_token_supply push.0 assert_eq.err="token_supply does not match" push.0 assert_eq.err="clean stack: pad must be 0" @@ -329,7 +334,8 @@ async fn faucet_get_max_supply() -> anyhow::Result<()> { build_pol_faucet_account(), format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_max_supply push.{expected} assert_eq.err="max_supply does not match" push.0 assert_eq.err="clean stack: pad must be 0" @@ -351,7 +357,8 @@ async fn faucet_get_token_config() -> anyhow::Result<()> { build_pol_faucet_account(), format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_token_config push.0 assert_eq.err="token_supply does not match" push.{expected_max_supply} assert_eq.err="max_supply does not match" @@ -375,7 +382,8 @@ async fn faucet_get_decimals_symbol_and_max_supply() -> anyhow::Result<()> { build_pol_faucet_account(), format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::fungible::get_decimals push.{expected_decimals} assert_eq.err="decimals does not match" call.::miden::standards::faucets::fungible::get_token_symbol @@ -415,7 +423,8 @@ async fn get_mutability_config() -> anyhow::Result<()> { execute_tx_script( account, r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::get_mutability_config push.1 assert_eq.err="desc_mutable should be 1" push.0 assert_eq.err="logo_mutable should be 0" @@ -458,7 +467,8 @@ async fn is_field_mutable_checks( execute_tx_script( account, format!( - "begin + "@transaction_script + pub proc main call.{proc_path} push.{expected} assert_eq.err=\"{proc_path} returned unexpected value\" @@ -617,7 +627,8 @@ async fn test_field_setter_immutable_fails( let tx_script_code = format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::faucets::{proc_name} end "# @@ -866,7 +877,8 @@ async fn set_max_supply_immutable_fails() -> anyhow::Result<()> { let mock_chain = builder.build()?; let tx_script_code = r#" - begin + @transaction_script + pub proc main push.2000 call.::miden::standards::faucets::fungible::set_max_supply end diff --git a/crates/miden-testing/tests/agglayer/bridge_in.rs b/crates/miden-testing/tests/agglayer/bridge_in.rs index b83be0a62f..f27ddb49d2 100644 --- a/crates/miden-testing/tests/agglayer/bridge_in.rs +++ b/crates/miden-testing/tests/agglayer/bridge_in.rs @@ -92,7 +92,8 @@ fn merkle_proof_verification_code( r#" use agglayer::bridge::bridge_in - begin + @transaction_script + pub proc main {store_path_source} push.{root_lo} mem_storew_le.256 dropw diff --git a/crates/miden-testing/tests/agglayer/merkle_tree_frontier.rs b/crates/miden-testing/tests/agglayer/merkle_tree_frontier.rs index 4433c6b89c..cfb29afdb5 100644 --- a/crates/miden-testing/tests/agglayer/merkle_tree_frontier.rs +++ b/crates/miden-testing/tests/agglayer/merkle_tree_frontier.rs @@ -81,7 +81,8 @@ impl MerkleTreeFrontier32 { async fn test_append_and_update_frontier() -> anyhow::Result<()> { let mut mtf = MerkleTreeFrontier32::<32>::new(); - let mut source = "use agglayer::bridge::merkle_tree_frontier begin".to_string(); + let mut source = + "use agglayer::bridge::merkle_tree_frontier @transaction_script pub proc main".to_string(); for round in 0..32 { // construct the leaf from the hex representation of the round number @@ -121,7 +122,8 @@ async fn test_check_empty_mtf_root() -> anyhow::Result<()> { let zero_31 = *CANONICAL_ZEROS_32.get(31).expect("zeros should have 32 values total"); let empty_mtf_root = Keccak256::merge(&[zero_31, zero_31]); - let mut source = "use agglayer::bridge::merkle_tree_frontier begin".to_string(); + let mut source = + "use agglayer::bridge::merkle_tree_frontier @transaction_script pub proc main".to_string(); for round in 1..=32 { // check that pushing the zero leaves into the MTF doesn't change its root diff --git a/crates/miden-testing/tests/agglayer/network_account_regression.rs b/crates/miden-testing/tests/agglayer/network_account_regression.rs index e316bd38f1..0b9054069e 100644 --- a/crates/miden-testing/tests/agglayer/network_account_regression.rs +++ b/crates/miden-testing/tests/agglayer/network_account_regression.rs @@ -75,7 +75,8 @@ async fn bridge_rejects_tx_script() -> anyhow::Result<()> { let mock_chain = builder.build()?; - let tx_script = CodeBuilder::default().compile_tx_script("begin nop end")?; + let tx_script = + CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?; let result = mock_chain .build_tx_context(bridge_account.id(), &[], slice::from_ref(&update_ger_note))? @@ -163,7 +164,8 @@ async fn faucet_rejects_tx_script() -> anyhow::Result<()> { let mock_chain = builder.build()?; - let tx_script = CodeBuilder::default().compile_tx_script("begin nop end")?; + let tx_script = + CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?; let result = mock_chain .build_tx_context(faucet.id(), &[], &[])? diff --git a/crates/miden-testing/tests/auth/guarded_multisig.rs b/crates/miden-testing/tests/auth/guarded_multisig.rs index f7f2161981..101090cf54 100644 --- a/crates/miden-testing/tests/auth/guarded_multisig.rs +++ b/crates/miden-testing/tests/auth/guarded_multisig.rs @@ -102,7 +102,8 @@ fn build_update_guardian_script_source( " use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -121,7 +122,8 @@ fn build_update_guardian_script_source( }, None => format!( " - begin + @transaction_script + pub proc main push.{new_guardian_key_word} push.{new_guardian_scheme_id} call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key @@ -320,7 +322,7 @@ async fn test_guarded_multisig_update_guardian_public_key( let update_guardian_script = CodeBuilder::new() .with_dynamically_linked_library(AuthGuardedMultisig::code())? .compile_tx_script(format!( - "begin\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend" + "@transaction_script\npub proc main\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend" ))?; let update_salt = Word::from([Felt::new_unchecked(991); 4]); @@ -467,7 +469,7 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( let update_guardian_script = CodeBuilder::new() .with_dynamically_linked_library(AuthGuardedMultisig::code())? .compile_tx_script(format!( - "begin\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend" + "@transaction_script\npub proc main\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend" ))?; let mut mock_chain_builder = @@ -549,7 +551,7 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( let update_guardian_with_output_script = CodeBuilder::new() .with_dynamically_linked_library(AuthGuardedMultisig::code())? .compile_tx_script(format!( - "use miden::protocol::output_note\nbegin\n push.{recipient}\n push.{note_type}\n push.{tag}\n exec.output_note::create\n swapdw\n dropw\n dropw\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend", + "use miden::protocol::output_note\n@transaction_script\npub proc main\n push.{recipient}\n push.{note_type}\n push.{tag}\n exec.output_note::create\n swapdw\n dropw\n dropw\n push.{new_guardian_key_word}\n push.{new_guardian_scheme_id}\n call.::miden::standards::components::auth::guarded_multisig::update_guardian_public_key\n drop\n dropw\nend", recipient = output_note.recipient().digest(), note_type = NoteType::Public as u8, tag = Felt::from(output_note.metadata().tag()), diff --git a/crates/miden-testing/tests/auth/hybrid_multisig.rs b/crates/miden-testing/tests/auth/hybrid_multisig.rs index ea75bdf6a6..3647252a56 100644 --- a/crates/miden-testing/tests/auth/hybrid_multisig.rs +++ b/crates/miden-testing/tests/auth/hybrid_multisig.rs @@ -358,7 +358,8 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { // Create a transaction script that calls the update_signers procedure let tx_script_code = " - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig::update_signers_and_threshold end "; @@ -626,7 +627,7 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { // Create transaction script let tx_script = CodeBuilder::default() .with_dynamically_linked_library(AuthMultisig::code())? - .compile_tx_script("begin\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; + .compile_tx_script("@transaction_script\npub proc main\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; @@ -842,7 +843,8 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu // Create a transaction script that calls the update_signers procedure let tx_script_code = " - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig::update_signers_and_threshold end "; diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 6d14ca776e..dc75fbedb5 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -482,7 +482,8 @@ async fn test_multisig_update_signers(#[case] auth_scheme: AuthScheme) -> anyhow // Create a transaction script that calls the update_signers procedure let tx_script_code = " - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig::update_signers_and_threshold end "; @@ -737,7 +738,7 @@ async fn test_multisig_update_signers_remove_owner( // Create transaction script let tx_script = CodeBuilder::default() .with_dynamically_linked_library(AuthMultisig::code())? - .compile_tx_script("begin\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; + .compile_tx_script("@transaction_script\npub proc main\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; @@ -919,7 +920,7 @@ async fn test_multisig_update_signers_rejects_unreachable_proc_thresholds( let tx_script = CodeBuilder::default() .with_dynamically_linked_library(AuthMultisig::code())? - .compile_tx_script("begin\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; + .compile_tx_script("@transaction_script\npub proc main\n call.::miden::standards::components::auth::multisig::update_signers_and_threshold\nend")?; let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; let salt = Word::from([Felt::new_unchecked(8); 4]); @@ -1002,7 +1003,8 @@ async fn test_multisig_new_approvers_cannot_sign_before_update( // Create a transaction script that calls the update_signers procedure let tx_script_code = " - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig::update_signers_and_threshold end "; @@ -1268,7 +1270,8 @@ async fn test_multisig_set_procedure_threshold( let set_script_code = format!( r#" - begin + @transaction_script + pub proc main push.{proc_root} push.1 call.::miden::standards::components::auth::multisig::set_procedure_threshold @@ -1347,7 +1350,8 @@ async fn test_multisig_set_procedure_threshold( // 3) Clear override by setting threshold to zero. let clear_script_code = format!( r#" - begin + @transaction_script + pub proc main push.{proc_root} push.0 call.::miden::standards::components::auth::multisig::set_procedure_threshold @@ -1447,7 +1451,8 @@ async fn test_multisig_set_procedure_threshold_rejects_exceeding_approvers( let script_code = format!( r#" - begin + @transaction_script + pub proc main push.{proc_root} push.3 call.::miden::standards::components::auth::multisig::set_procedure_threshold @@ -1522,7 +1527,8 @@ async fn test_multisig_set_procedure_threshold_uses_current_num_approvers( // override of 2 — which exceeds the *current* num_approvers and must be rejected. let script_code = format!( r#" - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig::update_signers_and_threshold push.{proc_root} push.2 diff --git a/crates/miden-testing/tests/auth/multisig_smart.rs b/crates/miden-testing/tests/auth/multisig_smart.rs index 56be451589..df4e693754 100644 --- a/crates/miden-testing/tests/auth/multisig_smart.rs +++ b/crates/miden-testing/tests/auth/multisig_smart.rs @@ -317,7 +317,8 @@ async fn test_multisig_smart_update_signers_and_thresholds( let update_signers_script = compile_multisig_smart_tx_script( " - begin + @transaction_script + pub proc main call.::miden::standards::components::auth::multisig_smart::update_signers_and_threshold end ", @@ -407,7 +408,8 @@ async fn test_multisig_smart_set_procedure_policy( // stack is preserved across the boundary), so we must manually drop the 7 elements we pushed. let set_policy_script = compile_multisig_smart_tx_script(format!( " - begin + @transaction_script + pub proc main push.{root} push.{note_restrictions} push.{delayed_threshold} @@ -500,7 +502,8 @@ async fn test_multisig_smart_unpolicied_proc_call_requires_default_threshold() - let target_root = BasicWallet::move_asset_to_note_root().as_word(); let set_policy_script = compile_multisig_smart_tx_script(format!( " - begin + @transaction_script + pub proc main push.{root} push.0 # note_restrictions push.0 # delayed_threshold diff --git a/crates/miden-testing/tests/auth/network_account.rs b/crates/miden-testing/tests/auth/network_account.rs index 1870d4f6ed..f80ef110bb 100644 --- a/crates/miden-testing/tests/auth/network_account.rs +++ b/crates/miden-testing/tests/auth/network_account.rs @@ -66,7 +66,8 @@ fn expiration_tx_script(delta: u16) -> TransactionScript { " use miden::protocol::tx - begin + @transaction_script + pub proc main push.{delta} exec.tx::update_expiration_block_delta end @@ -92,7 +93,8 @@ async fn test_auth_network_account_rejects_tx_script() -> anyhow::Result<()> { builder.add_account(account.clone())?; let mock_chain = builder.build()?; - let tx_script = CodeBuilder::default().compile_tx_script("begin nop end")?; + let tx_script = + CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?; let result = mock_chain .build_tx_context(account.id(), &[], &[])? @@ -187,7 +189,8 @@ async fn test_auth_network_account_rejects_non_allowlisted_tx_script() -> anyhow builder.add_account(account.clone())?; let mock_chain = builder.build()?; - let other_script = CodeBuilder::default().compile_tx_script("begin nop end")?; + let other_script = + CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?; assert_ne!( other_script.root(), allowed_script.root(), diff --git a/crates/miden-testing/tests/auth/singlesig.rs b/crates/miden-testing/tests/auth/singlesig.rs index bdbba33eed..39e20f101e 100644 --- a/crates/miden-testing/tests/auth/singlesig.rs +++ b/crates/miden-testing/tests/auth/singlesig.rs @@ -79,7 +79,8 @@ async fn test_singlesig_auth_uses_initial_public_key( const PUB_KEY_SLOT = word("{pub_key_slot}") - begin + @transaction_script + pub proc main push.99.98.97.96 push.PUB_KEY_SLOT[0..2] call.account::set_item @@ -138,7 +139,8 @@ async fn test_singlesig_auth_rejects_rotated_key_signature( const PUB_KEY_SLOT = word("{pub_key_slot}") const NEW_PUB_KEY = word("{new_pub_key}") - begin + @transaction_script + pub proc main push.NEW_PUB_KEY push.PUB_KEY_SLOT[0..2] call.account::set_item diff --git a/crates/miden-testing/tests/auth/singlesig_acl.rs b/crates/miden-testing/tests/auth/singlesig_acl.rs index d1594549b5..699d98245f 100644 --- a/crates/miden-testing/tests/auth/singlesig_acl.rs +++ b/crates/miden-testing/tests/auth/singlesig_acl.rs @@ -174,7 +174,8 @@ async fn test_acl_mixed_exempt_and_protected_requires_auth( const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") - begin + @transaction_script + pub proc main push.MOCK_VALUE_SLOT0[0..2] call.account::get_item dropw @@ -241,7 +242,8 @@ async fn test_acl_auth_uses_initial_public_key( const PUB_KEY_SLOT = word("{pub_key_slot}") - begin + @transaction_script + pub proc main push.99.98.97.96 push.PUB_KEY_SLOT[0..2] call.account::set_item @@ -294,7 +296,8 @@ async fn test_acl_auth_rejects_rotated_key_signature( const PUB_KEY_SLOT = word("{pub_key_slot}") const NEW_PUB_KEY = word("{new_pub_key}") - begin + @transaction_script + pub proc main push.NEW_PUB_KEY push.PUB_KEY_SLOT[0..2] call.account::set_item @@ -484,7 +487,8 @@ fn compile_call_get_item_script() -> anyhow::Result { const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") - begin + @transaction_script + pub proc main push.MOCK_VALUE_SLOT0[0..2] call.account::get_item dropw @@ -504,7 +508,8 @@ fn compile_call_set_item_script() -> anyhow::Result { const MOCK_VALUE_SLOT0 = word("{mock_value_slot0}") - begin + @transaction_script + pub proc main push.1.2.3.4 push.MOCK_VALUE_SLOT0[0..2] call.account::set_item diff --git a/crates/miden-testing/tests/scripts/allowlist.rs b/crates/miden-testing/tests/scripts/allowlist.rs index 9de6a82fc8..8bdfe5fa26 100644 --- a/crates/miden-testing/tests/scripts/allowlist.rs +++ b/crates/miden-testing/tests/scripts/allowlist.rs @@ -276,7 +276,8 @@ async fn allow_add_asset_to_note_fails_when_sender_not_allowed() -> anyhow::Resu r#" use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -466,7 +467,8 @@ async fn mint_and_send_on_allowlist_basic_faucet() -> anyhow::Result<()> { let tx_script_code = format!( r#" - begin + @transaction_script + pub proc main push.0 push.0 push.{recipient} diff --git a/crates/miden-testing/tests/scripts/blocklist.rs b/crates/miden-testing/tests/scripts/blocklist.rs index 69082561c6..01dc3e0b2d 100644 --- a/crates/miden-testing/tests/scripts/blocklist.rs +++ b/crates/miden-testing/tests/scripts/blocklist.rs @@ -282,7 +282,8 @@ async fn block_add_asset_to_note_fails_when_sender_blocked() -> anyhow::Result<( r#" use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -459,7 +460,8 @@ async fn mint_and_send_on_blocklist_basic_faucet() -> anyhow::Result<()> { let tx_script_code = format!( r#" - begin + @transaction_script + pub proc main push.0.0 push.{recipient} diff --git a/crates/miden-testing/tests/scripts/code_inspection.rs b/crates/miden-testing/tests/scripts/code_inspection.rs index e3d8e2ce69..f186525170 100644 --- a/crates/miden-testing/tests/scripts/code_inspection.rs +++ b/crates/miden-testing/tests/scripts/code_inspection.rs @@ -39,7 +39,8 @@ async fn run_has_procedure_script(proc_root: Word, body: &str) -> anyhow::Result r#" use miden::standards::components::metadata::code_inspection->code_inspection - begin + @transaction_script + pub proc main # stack: [PROC_ROOT, pad(12)] call.code_inspection::has_procedure # => [is_procedure_available, pad(15)] diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 5b5f3cd712..4b23839e85 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -83,7 +83,8 @@ pub struct FaucetTestParams { pub fn create_mint_script_code(params: &FaucetTestParams, faucet_id: AccountId) -> String { format!( " - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -485,7 +486,8 @@ async fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyho let tx_script_code = format!( " - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} @@ -2260,7 +2262,8 @@ async fn multiple_mints_in_single_tx_produce_correct_amounts() -> anyhow::Result let tx_script_code = format!( " - begin + @transaction_script + pub proc main # --- First mint: mint {amount_1} tokens to recipient_1 --- push.{recipient_1} push.{note_type} diff --git a/crates/miden-testing/tests/scripts/non_fungible_faucet.rs b/crates/miden-testing/tests/scripts/non_fungible_faucet.rs index d4fb0fdf2d..d818d4fb11 100644 --- a/crates/miden-testing/tests/scripts/non_fungible_faucet.rs +++ b/crates/miden-testing/tests/scripts/non_fungible_faucet.rs @@ -84,7 +84,10 @@ fn nft_mint_body(commitment: Word, recipient: Word) -> String { /// Builds a tx script that mints an NFT for `commitment` and sends it to `recipient`. fn nft_mint_script(commitment: Word, recipient: Word) -> String { - format!("begin\n{}\nend", nft_mint_body(commitment, recipient)) + format!( + "@transaction_script\npub proc main\n{}\nend", + nft_mint_body(commitment, recipient) + ) } async fn execute_nft_mint( @@ -152,7 +155,7 @@ async fn nft_mint_duplicate_commitment_fails() -> anyhow::Result<()> { // mint the same commitment twice in one transaction; the second call must fail let body = nft_mint_body(commitment, recipient); - let code = format!("begin\n{body}\n{body}\nend"); + let code = format!("@transaction_script\npub proc main\n{body}\n{body}\nend"); let source_manager = Arc::new(DefaultSourceManager::default()); let tx_script = @@ -380,7 +383,8 @@ async fn nft_public_getters() -> anyhow::Result<()> { let expected_symbol: Felt = TokenSymbol::new("EC")?.into(); let code = format!( " - begin + @transaction_script + pub proc main # status of an unissued asset id is 0 (not issued) push.42.123 call.::miden::standards::faucets::non_fungible::get_asset_status diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index acba3c05cc..8c3239cd83 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -243,7 +243,8 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { let tx_script_src = &format!( " use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient_1} push.{note_type_1} push.{tag_1} @@ -333,7 +334,8 @@ async fn test_p2id_new_constructor() -> anyhow::Result<()> { r#" use miden::standards::notes::p2id - begin + @transaction_script + pub proc main # Push inputs for p2id::new push.{serial_num} push.{note_type} diff --git a/crates/miden-testing/tests/scripts/pausable.rs b/crates/miden-testing/tests/scripts/pausable.rs index 30efc89201..3e46f87dba 100644 --- a/crates/miden-testing/tests/scripts/pausable.rs +++ b/crates/miden-testing/tests/scripts/pausable.rs @@ -636,7 +636,8 @@ async fn pausable_mint_fails_when_paused() -> anyhow::Result<()> { let recipient_word = Word::from([0u32, 1, 2, 3]); let tx_script_code = format!( r#" - begin + @transaction_script + pub proc main padw padw push.0 push.{recipient} push.{note_type} diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index bf1c9084fd..90fce6c993 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -34,7 +34,8 @@ pub async fn prove_send_swap_note() -> anyhow::Result<()> { let tx_script_src = &format!( " use miden::protocol::output_note - begin + @transaction_script + pub proc main push.{recipient} push.{note_type} push.{tag} From 61aad5c4ffba8f3ce4a400eae55031571455339d Mon Sep 17 00:00:00 2001 From: tomasarrachea Date: Thu, 2 Jul 2026 15:41:12 -0300 Subject: [PATCH 2/5] review: use TRANSACTION_SCRIPT_ATTRIBUTE constant --- crates/miden-protocol/src/transaction/mod.rs | 7 ++++++- crates/miden-protocol/src/transaction/tx_args.rs | 2 +- crates/miden-standards/src/tx_script/send_notes_script.rs | 7 ++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/miden-protocol/src/transaction/mod.rs b/crates/miden-protocol/src/transaction/mod.rs index 2046b14bd8..2133f1951e 100644 --- a/crates/miden-protocol/src/transaction/mod.rs +++ b/crates/miden-protocol/src/transaction/mod.rs @@ -33,7 +33,12 @@ pub use outputs::{ pub use partial_blockchain::PartialBlockchain; pub use proven_tx::{InputNoteCommitment, ProvenTransaction, TxAccountUpdate}; pub use transaction_id::TransactionId; -pub use tx_args::{TransactionArgs, TransactionScript, TransactionScriptRoot}; +pub use tx_args::{ + TRANSACTION_SCRIPT_ATTRIBUTE, + TransactionArgs, + TransactionScript, + TransactionScriptRoot, +}; pub use tx_header::TransactionHeader; pub use tx_summary::TransactionSummary; pub use verifier::TransactionVerifier; diff --git a/crates/miden-protocol/src/transaction/tx_args.rs b/crates/miden-protocol/src/transaction/tx_args.rs index 358a54a78b..2e89678f84 100644 --- a/crates/miden-protocol/src/transaction/tx_args.rs +++ b/crates/miden-protocol/src/transaction/tx_args.rs @@ -320,7 +320,7 @@ impl Deserializable for TransactionScriptRoot { // ================================================================================================ /// The attribute name used to mark the entrypoint procedure in a transaction script library. -const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script"; +pub const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script"; /// Transaction script. /// diff --git a/crates/miden-standards/src/tx_script/send_notes_script.rs b/crates/miden-standards/src/tx_script/send_notes_script.rs index 23dd168c3b..f1e63a4236 100644 --- a/crates/miden-standards/src/tx_script/send_notes_script.rs +++ b/crates/miden-standards/src/tx_script/send_notes_script.rs @@ -4,7 +4,7 @@ use core::num::NonZeroU16; use miden_protocol::Felt; use miden_protocol::account::{AccountCodeInterface, AccountId}; use miden_protocol::note::PartialNote; -use miden_protocol::transaction::TransactionScript; +use miden_protocol::transaction::{TRANSACTION_SCRIPT_ATTRIBUTE, TransactionScript}; use thiserror::Error; use crate::account::access::Ownable2Step; @@ -102,8 +102,9 @@ impl SendNotesTransactionScript { return Err(SendNotesTransactionScriptError::UnsupportedAccountInterface); }; - let script = - format!("@transaction_script\npub proc main\n{expiration_prelude}\n{body}\nend"); + let script = format!( + "@{TRANSACTION_SCRIPT_ATTRIBUTE}\npub proc main\n{expiration_prelude}\n{body}\nend" + ); let mut code_builder = CodeBuilder::new(); for note in output_notes { From 70aba3c7264b11d039b41e9143db073f0cd9c295 Mon Sep 17 00:00:00 2001 From: tomasarrachea Date: Thu, 2 Jul 2026 15:59:17 -0300 Subject: [PATCH 3/5] review: add `TransactionScript::from_library_reference` --- crates/miden-protocol/src/errors/mod.rs | 4 + crates/miden-protocol/src/note/script.rs | 20 +--- .../miden-protocol/src/transaction/tx_args.rs | 111 +++++++++++++++++- crates/miden-protocol/src/utils/mod.rs | 17 +++ 4 files changed, 132 insertions(+), 20 deletions(-) diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 05393d4955..8384f7d04c 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -879,6 +879,10 @@ pub enum TransactionScriptError { NoProcedureWithAttribute, #[error("library contains multiple procedures with @transaction_script attribute")] MultipleProceduresWithAttribute, + #[error("procedure at path '{0}' not found in library")] + ProcedureNotFound(Box), + #[error("procedure at path '{0}' does not have @transaction_script attribute")] + ProcedureMissingAttribute(Box), } // TRANSACTION INPUT ERROR diff --git a/crates/miden-protocol/src/note/script.rs b/crates/miden-protocol/src/note/script.rs index e6b08fcc0a..6541099991 100644 --- a/crates/miden-protocol/src/note/script.rs +++ b/crates/miden-protocol/src/note/script.rs @@ -9,9 +9,10 @@ use miden_crypto_derive::WordWrapper; use miden_mast_package::Package; use super::Felt; -use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId}; +use crate::assembly::mast::{MastForest, MastNodeId}; use crate::assembly::{Library, Path}; use crate::errors::NoteError; +use crate::utils::create_external_node_forest; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -376,23 +377,6 @@ impl Display for NoteScript { } } -// HELPER FUNCTIONS -// ================================================================================================ - -/// Creates a minimal [MastForest] containing only an external node referencing the given digest. -/// -/// This is useful for creating lightweight references to procedures without copying entire -/// libraries. The external reference will be resolved at runtime, assuming the source library -/// is loaded into the VM's MastForestStore. -fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) { - let mut mast = MastForest::new(); - let node_id = ExternalNodeBuilder::new(digest) - .add_to_forest(&mut mast) - .expect("adding external node to empty forest should not fail"); - mast.make_root(node_id); - (mast, node_id) -} - // TESTS // ================================================================================================ diff --git a/crates/miden-protocol/src/transaction/tx_args.rs b/crates/miden-protocol/src/transaction/tx_args.rs index 2e89678f84..86e3b3c4ac 100644 --- a/crates/miden-protocol/src/transaction/tx_args.rs +++ b/crates/miden-protocol/src/transaction/tx_args.rs @@ -1,5 +1,5 @@ use alloc::collections::BTreeMap; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Display; @@ -11,9 +11,10 @@ use miden_mast_package::Package; use super::{Felt, Hasher, Word}; use crate::account::auth::{PublicKeyCommitment, Signature}; -use crate::assembly::Library; +use crate::assembly::{Library, Path}; use crate::errors::TransactionScriptError; use crate::note::{NoteId, NoteRecipient}; +use crate::utils::create_external_node_forest; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -401,6 +402,51 @@ impl TransactionScript { }) } + /// Returns a new [TransactionScript] containing only a reference to a procedure in the + /// provided library. + /// + /// This method is useful when a library contains multiple transaction scripts and you need + /// to extract a specific one by its fully qualified path (e.g., + /// `::miden::standards::tx_scripts::send_notes::main`). + /// + /// The procedure at the specified path must have the `@transaction_script` attribute. + /// + /// Note: This method creates a minimal [MastForest] containing only an external node + /// referencing the procedure's digest, rather than copying the entire library. The actual + /// procedure code will be resolved at runtime via the `MastForestStore`. + /// + /// # Errors + /// Returns an error if: + /// - The library does not contain a procedure at the specified path. + /// - The procedure at the specified path does not have the `@transaction_script` attribute. + pub fn from_library_reference( + library: &Library, + path: &Path, + ) -> Result { + // Find the export matching the path + let export = library + .exports() + .find(|e| e.path().as_ref() == path) + .ok_or_else(|| TransactionScriptError::ProcedureNotFound(path.to_string().into()))?; + + // Get the procedure export and verify it has the @transaction_script attribute + let proc_export = export + .as_procedure() + .ok_or_else(|| TransactionScriptError::ProcedureNotFound(path.to_string().into()))?; + + if !proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE) { + return Err(TransactionScriptError::ProcedureMissingAttribute(path.to_string().into())); + } + + // Get the digest of the procedure from the library + let digest = library.mast_forest()[proc_export.node].digest(); + + // Create a minimal MastForest with just an external node referencing the digest + let (mast, entrypoint) = create_external_node_forest(digest); + + Ok(Self { mast: Arc::new(mast), entrypoint }) + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -542,4 +588,65 @@ mod tests { Err(TransactionScriptError::MultipleProceduresWithAttribute) ); } + + #[test] + fn test_transaction_script_from_library_reference() { + use alloc::string::ToString; + + use assert_matches::assert_matches; + use miden_core::mast::MastNodeExt; + + use super::TransactionScript; + use crate::Word; + use crate::assembly::{Assembler, Path}; + use crate::errors::TransactionScriptError; + + let source = " + @transaction_script + pub proc main_a + push.1 drop + end + + @transaction_script + pub proc main_b + push.2 drop + end + + pub proc helper + push.3 drop + end + "; + let library = Assembler::default().assemble_library([source]).unwrap(); + + // each tagged procedure can be extracted selectively, and the resulting script's root + // matches the digest of the referenced procedure + for proc_name in ["main_a", "main_b"] { + let export = library + .exports() + .find(|e| e.path().as_ref().to_string().ends_with(proc_name)) + .unwrap(); + let digest = library.mast_forest()[export.as_procedure().unwrap().node].digest(); + + let script = + TransactionScript::from_library_reference(&library, export.path().as_ref()) + .unwrap(); + assert_eq!(Word::from(script.root()), digest); + } + + // an unknown path is rejected + assert_matches!( + TransactionScript::from_library_reference(&library, Path::new("::foo::bar::main")), + Err(TransactionScriptError::ProcedureNotFound(_)) + ); + + // a procedure without the attribute is rejected + let helper = library + .exports() + .find(|e| e.path().as_ref().to_string().ends_with("helper")) + .unwrap(); + assert_matches!( + TransactionScript::from_library_reference(&library, helper.path().as_ref()), + Err(TransactionScriptError::ProcedureMissingAttribute(_)) + ); + } } diff --git a/crates/miden-protocol/src/utils/mod.rs b/crates/miden-protocol/src/utils/mod.rs index f5f012c425..bf27a1fbdd 100644 --- a/crates/miden-protocol/src/utils/mod.rs +++ b/crates/miden-protocol/src/utils/mod.rs @@ -2,6 +2,9 @@ pub use miden_core::utils::*; pub use miden_crypto::utils::{HexParseError, bytes_to_hex_string, hex_to_bytes}; pub use miden_utils_sync as sync; +use crate::Word; +use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId}; + pub mod serde { pub use miden_crypto::utils::{ BudgetedReader, @@ -17,3 +20,17 @@ pub mod serde { pub mod strings; pub(crate) use strings::ShortCapitalString; + +/// Creates a minimal [MastForest] containing only an external node referencing the given digest. +/// +/// This is useful for creating lightweight references to procedures without copying entire +/// libraries. The external reference will be resolved at runtime, assuming the source library +/// is loaded into the VM's MastForestStore. +pub(crate) fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) { + let mut mast = MastForest::new(); + let node_id = ExternalNodeBuilder::new(digest) + .add_to_forest(&mut mast) + .expect("adding external node to empty forest should not fail"); + mast.make_root(node_id); + (mast, node_id) +} From a5751fbbcd2736d63f57c8a000a403c0ca70e4bf Mon Sep 17 00:00:00 2001 From: tomasarrachea Date: Thu, 2 Jul 2026 16:00:25 -0300 Subject: [PATCH 4/5] review: test script formatting --- crates/miden-standards/src/code_builder/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index 324764815d..d5791d6442 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -658,7 +658,16 @@ mod tests { #[test] fn test_multiple_chained_modules() -> anyhow::Result<()> { - let script_code = "use test::lib1 use test::lib2 @transaction_script pub proc main exec.lib1::test1 exec.lib2::test2 end"; + let script_code = " + use test::lib1 + use test::lib2 + + @transaction_script + pub proc main + exec.lib1::test1 + exec.lib2::test2 + end + "; // Test chaining multiple modules let builder = CodeBuilder::default() From 17e21bce5d7f387fce9c1c8fa081e1a5b4bc454d Mon Sep 17 00:00:00 2001 From: tomasarrachea Date: Thu, 2 Jul 2026 16:08:01 -0300 Subject: [PATCH 5/5] review: use `TransactionScript::from_library` and `NoteScript::from_library` --- .../miden-standards/src/code_builder/mod.rs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index d5791d6442..a3f39c53d4 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -368,16 +368,14 @@ impl CodeBuilder { ) })?; - TransactionScript::from_library(&Self::apply_advice_map_to_library( - advice_map, - Arc::unwrap_or_clone(tx_script_lib), - )) - .map_err(|err| { + let tx_script = TransactionScript::from_library(&tx_script_lib).map_err(|err| { CodeBuilderError::build_error_with_source( "failed to create transaction script from library", err, ) - }) + })?; + + Ok(tx_script.with_advice_map(advice_map)) } /// Compiles the provided MASM code into a [`NoteScript`]. @@ -398,16 +396,14 @@ impl CodeBuilder { CodeBuilderError::build_error_with_report("failed to parse note script library", err) })?; - NoteScript::from_library(&Self::apply_advice_map_to_library( - advice_map, - Arc::unwrap_or_clone(note_script_lib), - )) - .map_err(|err| { + let note_script = NoteScript::from_library(¬e_script_lib).map_err(|err| { CodeBuilderError::build_error_with_source( "failed to create note script from library", err, ) - }) + })?; + + Ok(note_script.with_advice_map(advice_map)) } // ACCESSORS