diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index c4cbac72e3..bbf60a9426 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -67,6 +67,7 @@ use crate::MockChain; /// # Ok(()) /// # } /// ``` +#[derive(Clone)] pub struct TransactionContextBuilder { source_manager: Arc, account: Account, diff --git a/crates/miden-testing/tests/auth/guarded_multisig.rs b/crates/miden-testing/tests/auth/guarded_multisig.rs index 6c995b93d5..52967863ca 100644 --- a/crates/miden-testing/tests/auth/guarded_multisig.rs +++ b/crates/miden-testing/tests/auth/guarded_multisig.rs @@ -199,16 +199,18 @@ async fn test_guarded_multisig_signature_required( let mut mock_chain = mock_chain_builder.build().unwrap(); let salt = Word::from([Felt::new_unchecked(777); 4]); - let tx_context_init = mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt) - .build()?; + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary); @@ -220,12 +222,10 @@ async fn test_guarded_multisig_signature_required( .await?; // Missing guardian signature must fail. - let without_guardian_result = mock_chain - .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) + let without_guardian_result = tx_context_builder + .clone() .add_signature(public_keys[0].to_commitment(), msg, sig_1.clone()) .add_signature(public_keys[1].to_commitment(), msg, sig_2.clone()) - .auth_args(salt) .build()? .execute() .await; @@ -239,13 +239,10 @@ async fn test_guarded_multisig_signature_required( .await?; // With guardian signature the transaction should succeed. - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + let tx_context_execute = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) .add_signature(guardian_public_key.to_commitment(), msg, guardian_signature) - .auth_args(salt) .build()? .execute() .await?; @@ -312,16 +309,18 @@ async fn test_guarded_multisig_update_guardian_public_key( ))?; let update_salt = Word::from([Felt::new_unchecked(991); 4]); - let tx_context_init = mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(update_guardian_script.clone()) - .auth_args(update_salt) - .build()?; + .tx_script(update_guardian_script) + .auth_args(update_salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let update_msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary); @@ -333,12 +332,9 @@ async fn test_guarded_multisig_update_guardian_public_key( .await?; // Guardian key rotation intentionally skips guardian signature for this update tx. - let update_guardian_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(update_guardian_script) + let update_guardian_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), update_msg, sig_1) .add_signature(public_keys[1].to_commitment(), update_msg, sig_2) - .auth_args(update_salt) .build()? .execute() .await?; @@ -364,15 +360,15 @@ async fn test_guarded_multisig_update_guardian_public_key( // Build one tx summary after key update. Old GUARDIAN must fail and new GUARDIAN must pass on // this same transaction. let next_salt = Word::from([Felt::new_unchecked(992); 4]); - let tx_context_init_next = mock_chain + let tx_context_builder_next = mock_chain .build_tx_context(updated_multisig_account.id(), &[], &[])? - .auth_args(next_salt) - .build()?; + .auth_args(next_salt); - let tx_summary_next = match tx_context_init_next.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + let tx_summary_next = + match tx_context_builder_next.clone().build()?.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => anyhow::bail!("expected abort with tx effects: {error}"), + }; let next_msg = tx_summary_next.as_ref().to_commitment(); let tx_summary_next_signing = SigningInputs::TransactionSummary(tx_summary_next); @@ -390,12 +386,11 @@ async fn test_guarded_multisig_update_guardian_public_key( .await?; // Old guardian signature must fail after key update. - let with_old_guardian_result = mock_chain - .build_tx_context(updated_multisig_account.id(), &[], &[])? + let with_old_guardian_result = tx_context_builder_next + .clone() .add_signature(public_keys[0].to_commitment(), next_msg, next_sig_1.clone()) .add_signature(public_keys[1].to_commitment(), next_msg, next_sig_2.clone()) .add_signature(old_guardian_public_key.to_commitment(), next_msg, old_guardian_sig_next) - .auth_args(next_salt) .build()? .execute() .await; @@ -405,12 +400,10 @@ async fn test_guarded_multisig_update_guardian_public_key( )); // New guardian signature must pass. - mock_chain - .build_tx_context(updated_multisig_account.id(), &[], &[])? + tx_context_builder_next .add_signature(public_keys[0].to_commitment(), next_msg, next_sig_1) .add_signature(public_keys[1].to_commitment(), next_msg, next_sig_2) .add_signature(new_guardian_public_key.to_commitment(), next_msg, new_guardian_sig_next) - .auth_args(next_salt) .build()? .execute() .await?; @@ -470,16 +463,18 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( let mock_chain = mock_chain_builder.build().unwrap(); let salt = Word::from([Felt::new_unchecked(993); 4]); - let tx_context_init = mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[receive_asset_note.id()], &[])? - .tx_script(update_guardian_script.clone()) - .auth_args(salt) - .build()?; + .tx_script(update_guardian_script) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary); @@ -490,12 +485,10 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( .get_signature(public_keys[1].to_commitment(), &tx_summary_signing) .await?; - let without_guardian_result = mock_chain - .build_tx_context(multisig_account.id(), &[receive_asset_note.id()], &[])? - .tx_script(update_guardian_script.clone()) + let without_guardian_result = tx_context_builder + .clone() .add_signature(public_keys[0].to_commitment(), msg, sig_1.clone()) .add_signature(public_keys[1].to_commitment(), msg, sig_2.clone()) - .auth_args(salt) .build()? .execute() .await; @@ -508,13 +501,10 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( .get_signature(old_guardian_public_key.to_commitment(), &tx_summary_signing) .await?; - let with_guardian_result = mock_chain - .build_tx_context(multisig_account.id(), &[receive_asset_note.id()], &[])? - .tx_script(update_guardian_script) + let with_guardian_result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) .add_signature(old_guardian_public_key.to_commitment(), msg, old_guardian_signature) - .auth_args(salt) .build()? .execute() .await; @@ -553,18 +543,20 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( .unwrap(); let salt = Word::from([Felt::new_unchecked(994); 4]); - let tx_context_init = mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(update_guardian_with_output_script.clone()) - .add_note_script(note_script.clone()) - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt) - .build()?; - - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + .tx_script(update_guardian_with_output_script) + .add_note_script(note_script) + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + .auth_args(salt); + + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary); @@ -575,14 +567,9 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( .get_signature(public_keys[1].to_commitment(), &tx_summary_signing) .await?; - let result = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(update_guardian_with_output_script) - .add_note_script(note_script) - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + let result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .auth_args(salt) .build()? .execute() .await; @@ -690,17 +677,21 @@ async fn test_guarded_multisig_update_guardian_enforces_no_notes( let salt = Word::from([Felt::new_unchecked(995); 4]); // Dry-run to obtain the tx summary the signers must sign. - let mut init_ctx = mock_chain + let mut tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &input_ids, &[])? - .tx_script(update_guardian_script.clone()) + .tx_script(update_guardian_script) .auth_args(salt); - if let Some(ref out) = output_note { - init_ctx = init_ctx.extend_expected_output_notes(vec![RawOutputNote::Full(out.clone())]); + if let Some(out) = output_note { + tx_context_builder = + tx_context_builder.extend_expected_output_notes(vec![RawOutputNote::Full(out)]); } - let tx_summary = match init_ctx.build()?.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected dry-run abort with tx effects: {error}"), - }; + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let signing = SigningInputs::TransactionSummary(tx_summary); @@ -714,18 +705,13 @@ async fn test_guarded_multisig_update_guardian_enforces_no_notes( .get_signature(old_guardian_public_key.to_commitment(), &signing) .await?; - let mut signed_ctx = mock_chain - .build_tx_context(multisig_account.id(), &input_ids, &[])? - .tx_script(update_guardian_script) - .auth_args(salt) + let result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .add_signature(old_guardian_public_key.to_commitment(), msg, guardian_sig); - if let Some(ref out) = output_note { - signed_ctx = - signed_ctx.extend_expected_output_notes(vec![RawOutputNote::Full(out.clone())]); - } - let result = signed_ctx.build()?.execute().await; + .add_signature(old_guardian_public_key.to_commitment(), msg, guardian_sig) + .build()? + .execute() + .await; // Input check fires first, output check fires only when no input notes are present. match (include_input_note, include_output_note) { diff --git a/crates/miden-testing/tests/auth/hybrid_multisig.rs b/crates/miden-testing/tests/auth/hybrid_multisig.rs index 222cf79dbb..fc1c6bc657 100644 --- a/crates/miden-testing/tests/auth/hybrid_multisig.rs +++ b/crates/miden-testing/tests/auth/hybrid_multisig.rs @@ -15,7 +15,6 @@ use miden_standards::note::P2idNote; use miden_standards::testing::account_interface::get_public_keys_from_account; use miden_testing::utils::create_spawn_note; use miden_testing::{Auth, MockChainBuilder}; -use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -140,17 +139,20 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let salt = Word::from([Felt::ONE; 4]); - // Execute transaction without signatures - should fail - let tx_context_init = mock_chain + // Build transaction context with all config + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt) - .build()?; + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + // Execute transaction without signatures - should fail + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from both approvers let msg = tx_summary.as_ref().to_commitment(); @@ -164,12 +166,9 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { .await?; // Execute transaction with signatures - should succeed - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + let tx_context_execute = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .auth_args(salt) .build()? .execute() .await?; @@ -228,16 +227,18 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { for (i, (signer1_idx, signer2_idx)) in signer_combinations.iter().enumerate() { let salt = Word::from([Felt::new_unchecked(10 + i as u64); 4]); - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .auth_args(salt) - .build()?; + // Build transaction context with all config + let tx_context_builder = + mock_chain.build_tx_context(multisig_account.id(), &[], &[])?.auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from the specific combination of signers let msg = tx_summary.as_ref().to_commitment(); @@ -251,9 +252,7 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { .await?; // Execute transaction with signatures - should succeed for any combination - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .auth_args(salt) + let tx_context_execute = tx_context_builder .add_signature(public_keys[*signer1_idx].to_commitment(), msg, sig_1) .add_signature(public_keys[*signer2_idx].to_commitment(), msg, sig_2) .build()?; @@ -361,27 +360,27 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { .with_dynamically_linked_library(AuthMultisig::code())? .compile_tx_script(tx_script_code)?; - let advice_inputs = AdviceInputs { - map: advice_map.clone(), - ..Default::default() - }; + let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; // Pass the MULTISIG_CONFIG_HASH as the tx_script_args let tx_script_args: Word = multisig_config_hash; - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain + // Build transaction context with all config + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(tx_script_args) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from both approvers let msg = tx_summary.as_ref().to_commitment(); @@ -395,14 +394,9 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { .await?; // Execute transaction with signatures - should succeed - let update_approvers_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script) - .tx_script_args(multisig_config_hash) + let update_approvers_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .auth_args(salt) - .extend_advice_inputs(advice_inputs) .build()? .execute() .await @@ -507,17 +501,20 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { new_mock_chain_builder.add_output_note(RawOutputNote::Full(input_note_new.clone())); let new_mock_chain = new_mock_chain_builder.build().unwrap(); - // Execute transaction without signatures first to get tx summary - let tx_context_init_new = new_mock_chain + // Build transaction context with base config (output notes differ between init and execute) + let tx_context_builder_new = new_mock_chain .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt_new) - .build()?; + .auth_args(salt_new); - let tx_summary_new = match tx_context_init_new.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary_new = tx_context_builder_new + .clone() + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from 3 of the 4 new approvers (threshold is 3) let msg_new = tx_summary_new.as_ref().to_commitment(); @@ -537,13 +534,11 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { // ================================================================================ // Execute transaction with new signatures - should succeed - let tx_context_execute_new = new_mock_chain - .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? + let tx_context_execute_new = tx_context_builder_new .extend_expected_output_notes(vec![RawOutputNote::Full(output_note_new)]) .add_signature(new_public_keys[0].to_commitment(), msg_new, sig_1_new) .add_signature(new_public_keys[1].to_commitment(), msg_new, sig_2_new) .add_signature(new_public_keys[2].to_commitment(), msg_new, sig_3_new) - .auth_args(salt_new) .build()? .execute() .await?; @@ -625,19 +620,22 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { let salt = Word::from([Felt::new_unchecked(3); 4]); - // Execute without signatures to get tx summary - let tx_context_init = mock_chain + // Build transaction context with all config + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute without signatures to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from 4 of the 5 original approvers (threshold is 4) let msg = tx_summary.as_ref().to_commitment(); @@ -657,16 +655,11 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { .await?; // Execute with signatures - let update_approvers_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script) - .tx_script_args(multisig_config_hash) + let update_approvers_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) .add_signature(public_keys[2].to_commitment(), msg, sig_3) .add_signature(public_keys[3].to_commitment(), msg, sig_4) - .auth_args(salt) - .extend_advice_inputs(advice_inputs) .build()? .execute() .await @@ -846,27 +839,27 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu .with_dynamically_linked_library(AuthMultisig::code())? .compile_tx_script(tx_script_code)?; - let advice_inputs = AdviceInputs { - map: advice_map.clone(), - ..Default::default() - }; + let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; // Pass the MULTISIG_CONFIG_HASH as the tx_script_args let tx_script_args: Word = multisig_config_hash; - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain + // Build transaction context with all config + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(tx_script_args) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // SECTION 3: Try to sign the transaction with the NEW approvers (should fail) // ================================================================================ @@ -883,14 +876,9 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu .await?; // Try to execute transaction with NEW signatures - should FAIL - let tx_context_with_new_sigs = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) - .tx_script_args(multisig_config_hash) + let tx_context_with_new_sigs = tx_context_builder .add_signature(new_public_keys[0].to_commitment(), msg, new_sig_1) .add_signature(new_public_keys[1].to_commitment(), msg, new_sig_2) - .auth_args(salt) - .extend_advice_inputs(advice_inputs.clone()) .build()?; // SECTION 4: Verify that only the CURRENT approvers can sign the update transaction diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 629bd032dc..710c893458 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -185,17 +185,20 @@ async fn test_multisig_2_of_2_with_note_creation( let salt = Word::from([Felt::ONE; 4]); - // Execute transaction without signatures - should fail - let tx_context_init = mock_chain + // Build base transaction context + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt) - .build()?; + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + // Execute transaction without signatures - should fail + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from both approvers let msg = tx_summary.as_ref().to_commitment(); @@ -209,12 +212,9 @@ async fn test_multisig_2_of_2_with_note_creation( .await?; // Execute transaction with signatures - should succeed - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + let tx_context_execute = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .auth_args(salt) .build()? .execute() .await?; @@ -284,16 +284,18 @@ async fn test_multisig_2_of_4_all_signer_combinations( for (i, (signer1_idx, signer2_idx)) in signer_combinations.iter().enumerate() { let salt = Word::from([Felt::new_unchecked(10 + i as u64); 4]); - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .auth_args(salt) - .build()?; + // Build base transaction context + let tx_context_builder = + mock_chain.build_tx_context(multisig_account.id(), &[], &[])?.auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => anyhow::bail!("expected abort with tx effects: {error}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from the specific combination of signers let msg = tx_summary.as_ref().to_commitment(); @@ -307,9 +309,7 @@ async fn test_multisig_2_of_4_all_signer_combinations( .await?; // Execute transaction with signatures - should succeed for any combination - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .auth_args(salt) + let tx_context_execute = tx_context_builder .add_signature(public_keys[*signer1_idx].to_commitment(), msg, sig_1) .add_signature(public_keys[*signer2_idx].to_commitment(), msg, sig_2) .build()?; @@ -361,16 +361,18 @@ async fn test_multisig_replay_protection(#[case] auth_scheme: AuthScheme) -> any let salt = Word::from([Felt::new_unchecked(3); 4]); - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .auth_args(salt) - .build()?; + // Build base transaction context + let tx_context_builder = + mock_chain.build_tx_context(multisig_account.id(), &[], &[])?.auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from 2 of the 3 approvers let msg = tx_summary.as_ref().to_commitment(); @@ -384,11 +386,9 @@ async fn test_multisig_replay_protection(#[case] auth_scheme: AuthScheme) -> any .await?; // Execute transaction with signatures - should succeed (first execution) - let tx_context_execute = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? + let tx_context_execute = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1.clone()) .add_signature(public_keys[1].to_commitment(), msg, sig_2.clone()) - .auth_args(salt) .build()? .execute() .await?; @@ -398,6 +398,7 @@ async fn test_multisig_replay_protection(#[case] auth_scheme: AuthScheme) -> any mock_chain.prove_next_block()?; // Attempt to execute the same transaction again - should fail due to replay protection + // Must rebuild from the updated mock chain to pick up the new account state let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .add_signature(public_keys[0].to_commitment(), msg, sig_1) @@ -488,27 +489,27 @@ async fn test_multisig_update_signers(#[case] auth_scheme: AuthScheme) -> anyhow .with_dynamically_linked_library(AuthMultisig::code())? .compile_tx_script(tx_script_code)?; - let advice_inputs = AdviceInputs { - map: advice_map.clone(), - ..Default::default() - }; + let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; // Pass the MULTISIG_CONFIG_HASH as the tx_script_args let tx_script_args: Word = multisig_config_hash; - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain + // Build base transaction context + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(tx_script_args) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from both approvers let msg = tx_summary.as_ref().to_commitment(); @@ -522,14 +523,9 @@ async fn test_multisig_update_signers(#[case] auth_scheme: AuthScheme) -> anyhow .await?; // Execute transaction with signatures - should succeed - let update_approvers_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script) - .tx_script_args(multisig_config_hash) + let update_approvers_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) - .auth_args(salt) - .extend_advice_inputs(advice_inputs) .build()? .execute() .await?; @@ -633,17 +629,20 @@ async fn test_multisig_update_signers(#[case] auth_scheme: AuthScheme) -> anyhow new_mock_chain_builder.add_output_note(RawOutputNote::Full(input_note_new.clone())); let new_mock_chain = new_mock_chain_builder.build().unwrap(); - // Execute transaction without signatures first to get tx summary - let tx_context_init_new = new_mock_chain + // Build base transaction context for the new signers + let tx_context_builder_new = new_mock_chain .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .auth_args(salt_new) - .build()?; + .auth_args(salt_new); - let tx_summary_new = match tx_context_init_new.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary_new = tx_context_builder_new + .clone() + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from 3 of the 4 new approvers (threshold is 3) let msg_new = tx_summary_new.as_ref().to_commitment(); @@ -663,13 +662,11 @@ async fn test_multisig_update_signers(#[case] auth_scheme: AuthScheme) -> anyhow // ================================================================================ // Execute transaction with new signatures - should succeed - let tx_context_execute_new = new_mock_chain - .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? + let tx_context_execute_new = tx_context_builder_new .extend_expected_output_notes(vec![RawOutputNote::Full(output_note_new)]) .add_signature(new_public_keys[0].to_commitment(), msg_new, sig_1_new) .add_signature(new_public_keys[1].to_commitment(), msg_new, sig_2_new) .add_signature(new_public_keys[2].to_commitment(), msg_new, sig_3_new) - .auth_args(salt_new) .build()? .execute() .await?; @@ -739,19 +736,22 @@ async fn test_multisig_update_signers_remove_owner( let salt = Word::from([Felt::new_unchecked(3); 4]); - // Execute without signatures to get tx summary - let tx_context_init = mock_chain + // Build base transaction context + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute without signatures to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signatures from 4 of the 5 original approvers (threshold is 4) let msg = tx_summary.as_ref().to_commitment(); @@ -771,16 +771,11 @@ async fn test_multisig_update_signers_remove_owner( .await?; // Execute with signatures - let update_approvers_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script) - .tx_script_args(multisig_config_hash) + let update_approvers_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_1) .add_signature(public_keys[1].to_commitment(), msg, sig_2) .add_signature(public_keys[2].to_commitment(), msg, sig_3) .add_signature(public_keys[3].to_commitment(), msg, sig_4) - .auth_args(salt) - .extend_advice_inputs(advice_inputs) .build()? .execute() .await?; @@ -1009,27 +1004,27 @@ async fn test_multisig_new_approvers_cannot_sign_before_update( .with_dynamically_linked_library(AuthMultisig::code())? .compile_tx_script(tx_script_code)?; - let advice_inputs = AdviceInputs { - map: advice_map.clone(), - ..Default::default() - }; + let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; // Pass the MULTISIG_CONFIG_HASH as the tx_script_args let tx_script_args: Word = multisig_config_hash; - // Execute transaction without signatures first to get tx summary - let tx_context_init = mock_chain + // Build base transaction context + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) + .tx_script(tx_script) .tx_script_args(tx_script_args) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) - .build()?; + .extend_advice_inputs(advice_inputs) + .auth_args(salt); - let tx_summary = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures first to get tx summary + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // SECTION 3: Try to sign the transaction with the NEW approvers (should fail) // ================================================================================ @@ -1046,14 +1041,9 @@ async fn test_multisig_new_approvers_cannot_sign_before_update( .await?; // Try to execute transaction with NEW signatures - should FAIL - let tx_context_with_new_sigs = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(tx_script.clone()) - .tx_script_args(multisig_config_hash) + let tx_context_with_new_sigs = tx_context_builder .add_signature(new_public_keys[0].to_commitment(), msg, new_sig_1) .add_signature(new_public_keys[1].to_commitment(), msg, new_sig_2) - .auth_args(salt) - .extend_advice_inputs(advice_inputs.clone()) .build()?; // SECTION 4: Verify that only the CURRENT approvers can sign the update transaction @@ -1119,15 +1109,18 @@ async fn test_multisig_proc_threshold_overrides( // 2. consume without signatures let salt = Word::from([Felt::ONE; 4]); - let tx_context = mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .auth_args(salt) - .build()?; + .auth_args(salt); - let tx_summary = match tx_context.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, - error => panic!("expected abort with tx summary: {error:?}"), - }; + // consume without signatures + let tx_summary = tx_context_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // 3. get signature from one approver let msg = tx_summary.as_ref().to_commitment(); @@ -1137,10 +1130,8 @@ async fn test_multisig_proc_threshold_overrides( .await?; // 4. execute with signature - let tx_result = mock_chain - .build_tx_context(multisig_account.id(), &[note.id()], &[])? + let tx_result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig) - .auth_args(salt) .build()? .execute() .await; @@ -1171,18 +1162,21 @@ async fn test_multisig_proc_threshold_overrides( let send_note_transaction_script = multisig_account_interface.build_send_notes_script(&[output_note.clone().into()], None)?; - // Execute transaction without signatures to get tx summary - let tx_context_init = mock_chain + // Build base transaction context for note sending + let tx_context_builder2 = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) - .tx_script(send_note_transaction_script.clone()) - .auth_args(salt2) - .build()?; + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + .tx_script(send_note_transaction_script) + .auth_args(salt2); - let tx_summary2 = match tx_context_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + // Execute transaction without signatures to get tx summary + let tx_summary2 = tx_context_builder2 + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); // Get signature from only ONE approver let msg2 = tx_summary2.as_ref().to_commitment(); let tx_summary2_signing = SigningInputs::TransactionSummary(tx_summary2.clone()); @@ -1192,23 +1186,14 @@ async fn test_multisig_proc_threshold_overrides( .await?; // Try to execute with only 1 signature - should FAIL - let tx_context_one_sig = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) + let result = tx_context_builder2 + .clone() .add_signature(public_keys[0].to_commitment(), msg2, sig_1) - .tx_script(send_note_transaction_script.clone()) - .auth_args(salt2) - .build()?; - - let result = tx_context_one_sig.execute().await; - match result { - Err(TransactionExecutorError::Unauthorized(_)) => { - // Expected: transaction should fail with insufficient signatures - }, - _ => panic!( - "Transaction should fail with Unauthorized error when only 1 signature provided for note sending" - ), - } + .build()? + .execute() + .await; + // Expected: transaction should fail with insufficient signatures + result.unwrap_err().unwrap_unauthorized_err(); // Now get signatures from BOTH approvers let sig_1 = authenticators[0] @@ -1219,13 +1204,9 @@ async fn test_multisig_proc_threshold_overrides( .await?; // Execute with 2 signatures - should SUCCEED - let result = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .extend_expected_output_notes(vec![RawOutputNote::Full(output_note)]) + let result = tx_context_builder2 .add_signature(public_keys[0].to_commitment(), msg2, sig_1) .add_signature(public_keys[1].to_commitment(), msg2, sig_2) - .auth_args(salt2) - .tx_script(send_note_transaction_script) .build()? .execute() .await; @@ -1295,15 +1276,17 @@ async fn test_multisig_set_procedure_threshold( // 1) Set override to 1 (requires default 2 signatures). let set_salt = Word::from([Felt::new_unchecked(50); 4]); - let set_init = mock_chain + let set_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(set_script.clone()) - .auth_args(set_salt) - .build()?; - let set_summary = match set_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + .tx_script(set_script) + .auth_args(set_salt); + let set_summary = set_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let set_msg = set_summary.as_ref().to_commitment(); let set_summary = SigningInputs::TransactionSummary(set_summary); let set_sig_1 = authenticators[0] @@ -1313,12 +1296,9 @@ async fn test_multisig_set_procedure_threshold( .get_signature(public_keys[1].to_commitment(), &set_summary) .await?; - let set_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(set_script) + let set_tx = set_builder .add_signature(public_keys[0].to_commitment(), set_msg, set_sig_1) .add_signature(public_keys[1].to_commitment(), set_msg, set_sig_2) - .auth_args(set_salt) .build()? .execute() .await?; @@ -1330,24 +1310,24 @@ async fn test_multisig_set_procedure_threshold( // 2) Verify receive_asset can now execute with one signature. let one_sig_salt = Word::from([Felt::new_unchecked(51); 4]); - let one_sig_init = mock_chain + let one_sig_builder = mock_chain .build_tx_context(multisig_account.id(), &[one_sig_note.id()], &[])? - .auth_args(one_sig_salt) - .build()?; - let one_sig_summary = match one_sig_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + .auth_args(one_sig_salt); + let one_sig_summary = one_sig_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let one_sig_msg = one_sig_summary.as_ref().to_commitment(); let one_sig_summary = SigningInputs::TransactionSummary(one_sig_summary); let one_sig = authenticators[0] .get_signature(public_keys[0].to_commitment(), &one_sig_summary) .await?; - let one_sig_tx = mock_chain - .build_tx_context(multisig_account.id(), &[one_sig_note.id()], &[])? + let one_sig_tx = one_sig_builder .add_signature(public_keys[0].to_commitment(), one_sig_msg, one_sig) - .auth_args(one_sig_salt) .build()? .execute() .await @@ -1373,15 +1353,17 @@ async fn test_multisig_set_procedure_threshold( .compile_tx_script(clear_script_code)?; let clear_salt = Word::from([Felt::new_unchecked(52); 4]); - let clear_init = mock_chain + let clear_builder = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(clear_script.clone()) - .auth_args(clear_salt) - .build()?; - let clear_summary = match clear_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + .tx_script(clear_script) + .auth_args(clear_salt); + let clear_summary = clear_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let clear_msg = clear_summary.as_ref().to_commitment(); let clear_summary = SigningInputs::TransactionSummary(clear_summary); let clear_sig_1 = authenticators[0] @@ -1391,12 +1373,9 @@ async fn test_multisig_set_procedure_threshold( .get_signature(public_keys[1].to_commitment(), &clear_summary) .await?; - let clear_tx = mock_chain - .build_tx_context(multisig_account.id(), &[], &[])? - .tx_script(clear_script) + let clear_tx = clear_builder .add_signature(public_keys[0].to_commitment(), clear_msg, clear_sig_1) .add_signature(public_keys[1].to_commitment(), clear_msg, clear_sig_2) - .auth_args(clear_salt) .build()? .execute() .await?; @@ -1408,24 +1387,24 @@ async fn test_multisig_set_procedure_threshold( // 4) After clear, one signature should no longer be sufficient for receive_asset. let clear_check_salt = Word::from([Felt::new_unchecked(53); 4]); - let clear_check_init = mock_chain + let clear_check_builder = mock_chain .build_tx_context(multisig_account.id(), &[clear_check_note.id()], &[])? - .auth_args(clear_check_salt) - .build()?; - let clear_check_summary = match clear_check_init.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; + .auth_args(clear_check_salt); + let clear_check_summary = clear_check_builder + .clone() + .build()? + .execute() + .await + .unwrap_err() + .unwrap_unauthorized_err(); let clear_check_msg = clear_check_summary.as_ref().to_commitment(); let clear_check_summary = SigningInputs::TransactionSummary(clear_check_summary); let clear_check_sig = authenticators[0] .get_signature(public_keys[0].to_commitment(), &clear_check_summary) .await?; - let clear_check_result = mock_chain - .build_tx_context(multisig_account.id(), &[clear_check_note.id()], &[])? + let clear_check_result = clear_check_builder .add_signature(public_keys[0].to_commitment(), clear_check_msg, clear_check_sig) - .auth_args(clear_check_salt) .build()? .execute() .await; diff --git a/crates/miden-testing/tests/auth/multisig_smart.rs b/crates/miden-testing/tests/auth/multisig_smart.rs index dbafddee0d..da5f38ad50 100644 --- a/crates/miden-testing/tests/auth/multisig_smart.rs +++ b/crates/miden-testing/tests/auth/multisig_smart.rs @@ -19,7 +19,6 @@ use miden_standards::errors::standards::{ ERR_AUTH_TRANSACTION_MUST_NOT_INCLUDE_OUTPUT_NOTES, }; use miden_testing::{MockChainBuilder, assert_transaction_executor_error}; -use miden_tx::TransactionExecutorError; use miden_tx::auth::{SigningInputs, TransactionAuthenticator}; use rstest::rstest; @@ -103,17 +102,17 @@ async fn test_multisig_smart_receive_asset_policy_overrides_default_three_of_thr let mut mock_chain = mock_chain_builder.build()?; let salt = Word::from([Felt::new_unchecked(1); 4]); - let tx_summary = match mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .auth_args(salt) + .auth_args(salt); + + let tx_summary = tx_context_builder + .clone() .build()? .execute() .await .unwrap_err() - { - TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, - error => panic!("expected abort with tx summary: {error:?}"), - }; + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary); @@ -121,10 +120,8 @@ async fn test_multisig_smart_receive_asset_policy_overrides_default_three_of_thr .get_signature(public_keys[0].to_commitment(), &tx_summary_signing) .await?; - let tx_result = mock_chain - .build_tx_context(multisig_account.id(), &[note.id()], &[])? + let tx_result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, one_signature) - .auth_args(salt) .build()? .execute() .await; @@ -199,10 +196,7 @@ async fn test_multisig_smart_enforces_note_restrictions_on_tx_with_input_notes( ); }, ProcedurePolicyNoteRestriction::None | ProcedurePolicyNoteRestriction::NoOutputNotes => { - match result { - Err(TransactionExecutorError::Unauthorized(_)) => {}, - other => panic!("expected Unauthorized (no signatures provided), got: {other:?}"), - } + result.unwrap_err().unwrap_unauthorized_err(); }, } @@ -277,10 +271,7 @@ async fn test_multisig_smart_enforces_note_restrictions_on_tx_with_output_notes( ); }, ProcedurePolicyNoteRestriction::None | ProcedurePolicyNoteRestriction::NoInputNotes => { - match result { - Err(TransactionExecutorError::Unauthorized(_)) => {}, - other => panic!("expected Unauthorized (no signatures provided), got: {other:?}"), - } + result.unwrap_err().unwrap_unauthorized_err(); }, } @@ -333,21 +324,21 @@ async fn test_multisig_smart_update_signers_and_thresholds( let salt = Word::from([Felt::new_unchecked(3); 4]); - // Dry-run to obtain the tx summary that the current approvers must sign. - let tx_summary = match mock_chain + let tx_context_builder = mock_chain .build_tx_context(account_id, &[], &[])? - .tx_script(update_signers_script.clone()) + .tx_script(update_signers_script) .tx_script_args(multisig_config_hash) - .extend_advice_inputs(advice_inputs.clone()) - .auth_args(salt) + .extend_advice_inputs(advice_inputs) + .auth_args(salt); + + // Dry-run to obtain the tx summary that the current approvers must sign. + let tx_summary = tx_context_builder + .clone() .build()? .execute() .await .unwrap_err() - { - TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, - error => panic!("expected abort with tx summary: {error:?}"), - }; + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let signing_inputs = SigningInputs::TransactionSummary(tx_summary); @@ -358,12 +349,7 @@ async fn test_multisig_smart_update_signers_and_thresholds( .get_signature(public_keys[1].to_commitment(), &signing_inputs) .await?; - let executed_tx = mock_chain - .build_tx_context(account_id, &[], &[])? - .tx_script(update_signers_script) - .tx_script_args(multisig_config_hash) - .extend_advice_inputs(advice_inputs) - .auth_args(salt) + let executed_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_0) .add_signature(public_keys[1].to_commitment(), msg, sig_1) .build()? @@ -439,19 +425,19 @@ async fn test_multisig_smart_set_procedure_policy( let salt = Word::from([Felt::new_unchecked(4); 4]); - // Dry-run to obtain the tx summary that the approvers must sign. - let tx_summary = match mock_chain + let tx_context_builder = mock_chain .build_tx_context(account_id, &[], &[])? - .tx_script(set_policy_script.clone()) - .auth_args(salt) + .tx_script(set_policy_script) + .auth_args(salt); + + // Dry-run to obtain the tx summary that the approvers must sign. + let tx_summary = tx_context_builder + .clone() .build()? .execute() .await .unwrap_err() - { - TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, - error => panic!("expected abort with tx summary: {error:?}"), - }; + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let signing_inputs = SigningInputs::TransactionSummary(tx_summary); @@ -462,10 +448,7 @@ async fn test_multisig_smart_set_procedure_policy( .get_signature(public_keys[1].to_commitment(), &signing_inputs) .await?; - let executed_tx = mock_chain - .build_tx_context(account_id, &[], &[])? - .tx_script(set_policy_script) - .auth_args(salt) + let executed_tx = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_0) .add_signature(public_keys[1].to_commitment(), msg, sig_1) .build()? @@ -546,19 +529,19 @@ async fn test_multisig_smart_unpolicied_proc_call_requires_default_threshold() - let salt = Word::from([Felt::new_unchecked(42); 4]); - // Dry-run to capture the tx summary. - let tx_summary = match mock_chain + let tx_context_builder = mock_chain .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .tx_script(set_policy_script.clone()) - .auth_args(salt) + .tx_script(set_policy_script) + .auth_args(salt); + + // Dry-run to capture the tx summary. + let tx_summary = tx_context_builder + .clone() .build()? .execute() .await .unwrap_err() - { - TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, - error => panic!("expected dry-run abort with tx summary: {error:?}"), - }; + .unwrap_unauthorized_err(); let msg = tx_summary.as_ref().to_commitment(); let signing = SigningInputs::TransactionSummary(tx_summary); @@ -574,26 +557,16 @@ async fn test_multisig_smart_unpolicied_proc_call_requires_default_threshold() - // With only 1 signature (matching the low receive_asset policy), the tx must fail because // the unpolicied set_procedure_policy call contributes `default_threshold = 3`. - let one_sig_result = mock_chain - .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .tx_script(set_policy_script.clone()) - .auth_args(salt) + let one_sig_result = tx_context_builder + .clone() .add_signature(public_keys[0].to_commitment(), msg, sig_0.clone()) .build()? .execute() .await; - match one_sig_result { - Err(TransactionExecutorError::Unauthorized(_)) => {}, - other => { - panic!("expected Unauthorized with 1 sig (escalation would let it pass): {other:?}") - }, - } + one_sig_result.unwrap_err().unwrap_unauthorized_err(); // With all 3 signatures the unpolicied default contribution is met and the tx succeeds. - let three_sig_result = mock_chain - .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .tx_script(set_policy_script) - .auth_args(salt) + let three_sig_result = tx_context_builder .add_signature(public_keys[0].to_commitment(), msg, sig_0) .add_signature(public_keys[1].to_commitment(), msg, sig_1) .add_signature(public_keys[2].to_commitment(), msg, sig_2) diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index f56aea7131..4a57afacc6 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -150,6 +150,16 @@ pub enum TransactionExecutorError { MissingAuthenticator, } +#[cfg(any(test, feature = "testing"))] +impl TransactionExecutorError { + pub fn unwrap_unauthorized_err(self) -> Box { + match self { + TransactionExecutorError::Unauthorized(transaction_summary) => transaction_summary, + other => panic!("expected TransactionExecutorError::Unauthorized, got {other}"), + } + } +} + // TRANSACTION PROVER ERROR // ================================================================================================