use alloc::string::ToString;
use alloc::sync::Arc;
use core::slice;
use anyhow::Context;
use assert_matches::assert_matches;
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::account::auth::AuthScheme;
use miden_protocol::account::component::AccountComponentMetadata;
use miden_protocol::account::{
Account,
AccountBuilder,
AccountCode,
AccountComponent,
AccountDelta,
AccountStorage,
AccountStoragePatch,
AccountType,
AccountVaultDelta,
StorageSlot,
StorageSlotName,
};
use miden_protocol::assembly::{DefaultSourceManager, Library, ModuleKind, ModuleParser, Path};
use miden_protocol::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
use miden_protocol::block::BlockNumber;
use miden_protocol::errors::ProvenTransactionError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachmentScheme,
NoteAttachments,
NoteDetailsCommitment,
NoteId,
NoteRecipient,
NoteStorage,
NoteTag,
NoteType,
PartialNote,
PartialNoteMetadata,
};
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PRIVATE_SENDER,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_SENDER,
};
use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA};
use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT;
use miden_protocol::transaction::{
InputNote,
InputNotes,
RawOutputNote,
RawOutputNotes,
TransactionArgs,
TransactionKernel,
TransactionScript,
TransactionSummary,
};
use miden_protocol::{Felt, Hasher, ONE, Word};
use miden_standards::account::interface::{
AccountComponentInterface,
AccountInterface,
AccountInterfaceExt,
};
use miden_standards::account::wallets::BasicWallet;
use miden_standards::code_builder::CodeBuilder;
use miden_standards::testing::account_component::IncrNonceAuthComponent;
use miden_standards::testing::account_interface::get_public_keys_from_account;
use miden_standards::testing::mock_account::MockAccountExt;
use miden_standards::tx_script::SendNotesTransactionScript;
use miden_tx::auth::UnreachableAuth;
use miden_tx::{
LocalTransactionProver,
TransactionExecutor,
TransactionExecutorError,
TransactionProverError,
};
use rstest::rstest;
use crate::kernel_tests::tx::ExecutionOutputExt;
use crate::utils::{create_p2any_note, create_public_p2any_note, create_spawn_note};
use crate::{Auth, MockChain, TestTransactionBuilder};
#[tokio::test]
async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let asset = FungibleAsset::mock(400);
let account1 = builder.add_existing_wallet_with_assets(
Auth::BasicAuth {
auth_scheme: AuthScheme::Falcon512Poseidon2,
},
[asset],
)?;
let account2 = builder.add_existing_wallet_with_assets(
Auth::BasicAuth {
auth_scheme: AuthScheme::Falcon512Poseidon2,
},
[asset],
)?;
let output_note = create_public_p2any_note(account1.id(), [asset]);
let spawn_note = builder.add_spawn_note([&output_note])?;
let mut mock_chain = builder.build()?;
mock_chain.prove_until_block(10u32)?;
let tx = mock_chain
.build_tx_context(account1.id(), &[spawn_note.id()], &[])?
.extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())])
.build()?
.execute()
.await?;
mock_chain.add_pending_executed_transaction(&tx)?;
mock_chain.prove_next_block()?;
let input_note = mock_chain.get_public_note(&output_note.id()).expect("note not found");
assert_eq!(input_note.location().unwrap().block_num().as_u32(), 11);
mock_chain.prove_next_block()?;
mock_chain.prove_next_block()?;
let tx_context = mock_chain.build_tx_context(account2.id(), &[], &[])?.build()?;
let tx_executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context)
.with_source_manager(tx_context.source_manager());
let error = tx_executor
.execute_transaction(
account2.id(),
BlockNumber::from(1),
InputNotes::new(vec![input_note]).unwrap(),
TransactionArgs::default(),
)
.await;
assert_matches::assert_matches!(
error,
Err(TransactionExecutorError::NoteBlockPastReferenceBlock(..))
);
Ok(())
}
#[tokio::test]
async fn test_block_procedures() -> anyhow::Result<()> {
let tx_context = TestTransactionBuilder::with_existing_mock_account().build()?;
let code = "
use miden::protocol::tx
use miden::tx_kernel_core::prologue
begin
exec.prologue::prepare_transaction
# get the block data
exec.tx::get_block_number
exec.tx::get_block_timestamp
exec.tx::get_block_commitment
# => [BLOCK_COMMITMENT, block_timestamp, block_number]
# truncate the stack
swapdw dropw dropw
end
";
let exec_output = &tx_context.execute_code(code).await?;
assert_eq!(
exec_output.get_stack_word(0),
tx_context.tx_inputs().block_header().commitment(),
"top word on the stack should be equal to the block header commitment"
);
assert_eq!(
exec_output.get_stack_element(4).as_canonical_u64(),
tx_context.tx_inputs().block_header().timestamp() as u64,
"fifth element on the stack should be equal to the timestamp of the last block creation"
);
assert_eq!(
exec_output.get_stack_element(5).as_canonical_u64(),
tx_context.tx_inputs().block_header().block_num().as_u64(),
"sixth element on the stack should be equal to the block number"
);
Ok(())
}
#[tokio::test]
async fn executed_transaction_output_notes() -> anyhow::Result<()> {
let executor_account =
Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, IncrNonceAuthComponent);
let account_id = executor_account.id();
let removed_asset_1 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT / 2);
let removed_asset_2 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT / 2);
let combined_asset = Asset::Fungible(
FungibleAsset::new(
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().expect("id is valid"),
FUNGIBLE_ASSET_AMOUNT,
)
.expect("asset is valid"),
);
let removed_asset_3 = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA);
let removed_asset_4 = Asset::Fungible(
FungibleAsset::new(
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().expect("id is valid"),
FUNGIBLE_ASSET_AMOUNT / 2,
)
.expect("asset is valid"),
);
let tag1 = NoteTag::with_account_target(
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(),
);
let tag2 = NoteTag::default();
let tag3 = NoteTag::default();
let attachment2 =
NoteAttachment::with_word(NoteAttachmentScheme::new(28)?, Word::from([2, 3, 4, 5u32]));
let attachment3 = NoteAttachment::with_words(
NoteAttachmentScheme::new(29)?,
vec![Word::from([6, 7, 8, 9u32]), Word::from([10, 11, 12, 13u32])],
)?;
let note_type1 = NoteType::Private;
let note_type2 = NoteType::Public;
let note_type3 = NoteType::Public;
let recipient_1 = Word::from([0, 1, 2, 3u32]);
let serial_num_2 = Word::from([1, 2, 3, 4u32]);
let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?;
let inputs_2 = NoteStorage::new(vec![ONE])?;
let metadata_2 = PartialNoteMetadata::new(account_id, note_type2).with_tag(tag2);
let vault_2 = NoteAssets::new(vec![removed_asset_3, removed_asset_4])?;
let recipient_2 = NoteRecipient::new(serial_num_2, note_script_2, inputs_2);
let attachments_2 = NoteAttachments::from(attachment2.clone());
let expected_output_note_2 =
Note::with_attachments(vault_2, metadata_2, recipient_2, attachments_2);
let serial_num_3 =
Word::from([Felt::from(5_u32), Felt::from(6_u32), Felt::from(7_u32), Felt::from(8_u32)]);
let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?;
let inputs_3 = NoteStorage::new(vec![ONE, Felt::from(2_u32)])?;
let metadata_3 = PartialNoteMetadata::new(account_id, note_type3).with_tag(tag3);
let vault_3 = NoteAssets::new(vec![])?;
let recipient_3 = NoteRecipient::new(serial_num_3, note_script_3, inputs_3);
let attachments_3 = NoteAttachments::from(attachment3.clone());
let expected_output_note_3 =
Note::with_attachments(vault_3, metadata_3, recipient_3, attachments_3);
let tx_script_src = format!(
"\
use miden::core::sys
use miden::protocol::output_note
use mock::util
## TRANSACTION SCRIPT
## ========================================================================================
@transaction_script
pub proc main
## Send some assets from the account vault
## ------------------------------------------------------------------------------------
# partially deplete fungible asset balance
push.{recipient_1} # recipient
push.{NOTETYPE1} # note_type
push.{tag1} # tag
call.::mock::account::create_note
# => [note_idx = 0]
dup
push.{REMOVED_ASSET_VALUE_1}
push.{REMOVED_ASSET_ID_1}
# => [ASSET_ID, ASSET_VALUE, note_idx, note_idx]
exec.util::move_asset_to_note
# => [note_idx]
push.{REMOVED_ASSET_VALUE_2}
push.{REMOVED_ASSET_ID_2}
exec.util::move_asset_to_note
# => []
# send non-fungible asset
push.{RECIPIENT2} # recipient
push.{NOTETYPE2} # note_type
push.{tag2} # tag
call.::mock::account::create_note
# => [note_idx = 1]
dup
push.{REMOVED_ASSET_VALUE_3}
push.{REMOVED_ASSET_ID_3}
exec.util::move_asset_to_note
# => [note_idx]
dup
push.{REMOVED_ASSET_VALUE_4}
push.{REMOVED_ASSET_ID_4}
exec.util::move_asset_to_note
# => [note_idx]
push.{ATTACHMENT2}
push.{attachment_scheme2}
# => [attachment_scheme, ATTACHMENT, note_idx]
exec.output_note::add_word_attachment
# => []
# create a public note without assets
push.{RECIPIENT3} # recipient
push.{NOTETYPE3} # note_type
push.{tag3} # tag
call.::mock::account::create_note
# => [note_idx = 2]
# Store attachment3 words to memory at address 1024
push.{attachment3_word0} mem_storew_le.1024 dropw
push.{attachment3_word1} mem_storew_le.1028 dropw
push.1024
push.{num_attachment3_words}
push.{attachment_scheme3}
# => [attachment_scheme, num_words, ptr, note_idx]
exec.output_note::add_attachment_from_memory
# => []
exec.sys::truncate_stack
end
",
REMOVED_ASSET_ID_1 = removed_asset_1.to_id_word(),
REMOVED_ASSET_VALUE_1 = removed_asset_1.to_value_word(),
REMOVED_ASSET_ID_2 = removed_asset_2.to_id_word(),
REMOVED_ASSET_VALUE_2 = removed_asset_2.to_value_word(),
REMOVED_ASSET_ID_3 = removed_asset_3.to_id_word(),
REMOVED_ASSET_VALUE_3 = removed_asset_3.to_value_word(),
REMOVED_ASSET_ID_4 = removed_asset_4.to_id_word(),
REMOVED_ASSET_VALUE_4 = removed_asset_4.to_value_word(),
RECIPIENT2 = expected_output_note_2.recipient().digest(),
RECIPIENT3 = expected_output_note_3.recipient().digest(),
NOTETYPE1 = note_type1 as u8,
NOTETYPE2 = note_type2 as u8,
NOTETYPE3 = note_type3 as u8,
attachment_scheme2 = attachment2.attachment_scheme().as_u16(),
ATTACHMENT2 = Word::from([2, 3, 4, 5u32]),
attachment_scheme3 = attachment3.attachment_scheme().as_u16(),
attachment3_word0 = attachment3.content().as_words()[0],
attachment3_word1 = attachment3.content().as_words()[1],
num_attachment3_words = attachment3.content().num_words(),
);
let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(tx_script_src)?;
assert!(attachment3.content().num_words() > 1, "expected multi-word attachment");
let tx_context = TestTransactionBuilder::new(executor_account)
.tx_script(tx_script)
.extend_expected_output_notes(vec![
RawOutputNote::Full(expected_output_note_2.clone()),
RawOutputNote::Full(expected_output_note_3.clone()),
])
.build()?;
let executed_transaction = tx_context.execute().await?;
let output_notes = executed_transaction.output_notes();
assert_eq!(output_notes.num_notes(), 3);
let resulting_output_note_1 = executed_transaction.output_notes().get_note(0);
let expected_note_assets_1 = NoteAssets::new(vec![combined_asset])?;
let details_commitment_1 = NoteDetailsCommitment::from_raw_commitments(
recipient_1,
expected_note_assets_1.commitment(),
);
let expected_note_id_1 = NoteId::new(details_commitment_1, resulting_output_note_1.metadata());
assert_eq!(resulting_output_note_1.id(), expected_note_id_1);
let resulting_output_note_2 = executed_transaction.output_notes().get_note(1);
assert_eq!(*resulting_output_note_2.header(), *expected_output_note_2.header());
let resulting_output_note_3 = executed_transaction.output_notes().get_note(2);
assert_eq!(expected_output_note_3.id(), resulting_output_note_3.id());
assert_eq!(expected_output_note_3.assets(), resulting_output_note_3.assets());
let resulting_note_2_recipient =
resulting_output_note_2.recipient().expect("output note 2 is not full");
assert_eq!(
resulting_note_2_recipient.storage().num_items(),
expected_output_note_2.storage().num_items()
);
let resulting_note_3_recipient =
resulting_output_note_3.recipient().expect("output note 3 is not full");
assert_eq!(
resulting_note_3_recipient.storage().num_items(),
expected_output_note_3.storage().num_items()
);
Ok(())
}
#[tokio::test]
async fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> {
let source_code = r#"
use miden::standards::auth
use miden::protocol::tx
const AUTH_UNAUTHORIZED_EVENT=event("miden::protocol::auth::unauthorized")
#! Inputs: [AUTH_ARGS, pad(12)]
#! Outputs: [pad(16)]
@auth_script
pub proc auth_abort_tx
dropw
# => [pad(16)]
exec.::miden::protocol::native_account::incr_nonce
exec.tx::get_block_number
push.0.0
# => [[0, 0, block_num, final_nonce], pad(16)]
# => [SALT, pad(16)]
exec.auth::create_tx_summary
# => [ACCOUNT_DELTA_COMMITMENT, INPUT_NOTES_COMMITMENT, OUTPUT_NOTES_COMMITMENT, SALT]
# insert tx summary into advice provider for extraction by the host
adv.insert_hqword
# => [ACCOUNT_DELTA_COMMITMENT, INPUT_NOTES_COMMITMENT, OUTPUT_NOTES_COMMITMENT, SALT]
exec.auth::hash_tx_summary
# => [MESSAGE, pad(16)]
emit.AUTH_UNAUTHORIZED_EVENT
end
"#;
let auth_code = CodeBuilder::default()
.compile_component_code("test::auth_component", source_code)
.context("failed to parse auth component")?;
let auth_component = AccountComponent::new(
auth_code,
vec![],
AccountComponentMetadata::mock("test::auth_component"),
)
.context("failed to parse auth component")?;
let account = AccountBuilder::new([42; 32])
.account_type(AccountType::Private)
.with_auth_component(auth_component)
.with_component(BasicWallet)
.build_existing()
.context("failed to build account")?;
let mut rng = RandomCoin::new(Word::empty());
let output_note = create_p2any_note(account.id(), NoteType::Private, [], &mut rng);
let input_note = create_spawn_note(vec![&output_note])?;
let mut builder = MockChain::builder();
builder.add_output_note(RawOutputNote::Full(input_note.clone()));
let mock_chain = builder.build()?;
let tx_context = mock_chain.build_tx_context(account, &[input_note.id()], &[])?.build()?;
let ref_block_num = tx_context.tx_inputs().block_header().block_num().as_u32();
let final_nonce = tx_context.account().nonce().as_canonical_u64() as u32 + 1;
let input_notes = tx_context.input_notes().clone();
let output_notes = RawOutputNotes::new(vec![RawOutputNote::Partial(output_note.into())])?;
let error = tx_context.execute().await.unwrap_err();
assert_matches!(error, TransactionExecutorError::Unauthorized(tx_summary) => {
assert!(tx_summary.account_delta().vault().is_empty());
assert!(tx_summary.account_delta().storage().is_empty());
assert_eq!(tx_summary.account_delta().nonce_delta().as_canonical_u64(), 1);
assert_eq!(tx_summary.input_notes(), &input_notes);
assert_eq!(tx_summary.output_notes(), &output_notes);
assert_eq!(tx_summary.salt(), Word::from(
[0, 0, ref_block_num, final_nonce]
));
});
Ok(())
}
#[rstest]
#[case::falcon(AuthScheme::Falcon512Poseidon2)]
#[case::ecdsa(AuthScheme::EcdsaK256Keccak)]
#[tokio::test]
async fn tx_summary_commitment_is_signed_by_auth_singlesig(
#[case] auth_scheme: AuthScheme,
) -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let account = builder.add_existing_mock_account(Auth::BasicAuth { auth_scheme })?;
let mut rng = RandomCoin::new(Word::empty());
let p2any_note = create_p2any_note(account.id(), NoteType::Private, [], &mut rng);
let spawn_note = builder.add_spawn_note([&p2any_note])?;
let chain = builder.build()?;
let tx_builder =
chain.build_tx_context(account.id(), &[], core::slice::from_ref(&spawn_note))?;
let tx = tx_builder.clone().build()?;
let ref_block_num = tx.tx_inputs().block_header().block_num();
let tx = tx.execute().await?;
let nonce_delta = Felt::ONE;
let final_nonce = account.nonce() + nonce_delta;
let account_delta = AccountDelta::new(
account.id(),
AccountStoragePatch::default(),
AccountVaultDelta::default(),
None,
nonce_delta,
)?;
let expected_summary = TransactionSummary::new(
account_delta,
InputNotes::new(vec![InputNote::unauthenticated(spawn_note)])?,
RawOutputNotes::new(vec![RawOutputNote::Partial(PartialNote::from(p2any_note))])?,
Word::from([0, 0, ref_block_num.as_u32(), final_nonce.as_canonical_u64() as u32]),
);
let summary_commitment = expected_summary.to_commitment();
let account_interface = AccountInterface::from_account(&account);
assert!(matches!(
account_interface.auth_component(),
AccountComponentInterface::AuthSingleSig
));
let pub_keys = get_public_keys_from_account(&account);
let pub_key = pub_keys.first().expect("expected at least one public key");
let signature_key = Hasher::merge(&[*pub_key, summary_commitment]);
tx.advice_witness().map.get(&signature_key).unwrap();
Ok(())
}
#[tokio::test]
async fn execute_tx_view_script() -> anyhow::Result<()> {
let test_module_source = "
pub proc foo
push.3.4
add
swapw dropw
end
";
let source_manager = Arc::new(DefaultSourceManager::default());
let library = compile_test_library(
source_manager.clone(),
"test-tx-view-script",
"test::module_1",
test_module_source,
);
let source = "
use test::module_1
use miden::core::sys
@transaction_script
pub proc main
push.1.2
call.module_1::foo
exec.sys::truncate_stack
end
";
let tx_script = CodeBuilder::new()
.with_statically_linked_library(&library)?
.compile_tx_script(source)?;
let tx_context = TestTransactionBuilder::with_existing_mock_account()
.with_source_manager(source_manager.clone())
.tx_script(tx_script.clone())
.build()?;
let account_id = tx_context.account().id();
let block_ref = tx_context.tx_inputs().block_header().block_num();
let advice_inputs = tx_context.tx_args().advice_inputs().clone();
let executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context)
.with_source_manager(source_manager);
let stack_outputs = executor
.execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs)
.await?;
assert_eq!(stack_outputs[..3], [Felt::new_unchecked(7), Felt::new_unchecked(2), ONE]);
Ok(())
}
fn compile_test_library(
source_manager: Arc<DefaultSourceManager>,
name: &str,
path: &str,
source: &str,
) -> Library {
let assembler = TransactionKernel::assembler_with_source_manager(source_manager.clone());
let source = ModuleParser::new(Some(ModuleKind::Library))
.parse_str(Some(Path::new(path)), source, source_manager)
.unwrap();
*assembler.assemble_library(name, source, None::<&str>).unwrap()
}
#[tokio::test]
async fn failed_tx_script_reports_package_debug_message() -> anyhow::Result<()> {
const ERROR_MESSAGE: &str = "transaction script debug message should survive execution";
let tx_script = CodeBuilder::default().compile_tx_script(format!(
r#"
@transaction_script
pub proc main
push.0 assert.err="{ERROR_MESSAGE}"
end
"#
))?;
let tx_context = TestTransactionBuilder::with_existing_mock_account()
.tx_script(tx_script)
.build()?;
let error = tx_context.execute().await.expect_err("transaction script should fail");
assert_transaction_error_contains_debug_message(&error, ERROR_MESSAGE);
Ok(())
}
#[tokio::test]
async fn failed_tx_view_script_reports_package_debug_message() -> anyhow::Result<()> {
const ERROR_MESSAGE: &str = "view script debug message should survive execution";
let tx_script = CodeBuilder::default().compile_tx_script(format!(
r#"
@transaction_script
pub proc main
push.0 assert.err="{ERROR_MESSAGE}"
end
"#
))?;
let tx_context = TestTransactionBuilder::with_existing_mock_account().build()?;
let account_id = tx_context.account().id();
let block_ref = tx_context.tx_inputs().block_header().block_num();
let advice_inputs = tx_context.tx_args().advice_inputs().clone();
let executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context);
let error = executor
.execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs)
.await
.expect_err("transaction view script should fail");
assert_transaction_error_contains_debug_message(&error, ERROR_MESSAGE);
Ok(())
}
fn assert_transaction_error_contains_debug_message(
error: &TransactionExecutorError,
expected_message: &str,
) {
let diagnostic = error.to_string();
assert!(
diagnostic.contains(expected_message),
"expected package debug info to recover the assertion message:\n{diagnostic}"
);
}
#[tokio::test]
async fn test_tx_script_inputs() -> anyhow::Result<()> {
let tx_script_input_key = Word::from([9999, 8888, 9999, 8888u32]);
let tx_script_input_value = Word::from([9, 8, 7, 6u32]);
let tx_script_src = format!(
r#"
@transaction_script
pub proc main
# push the tx script input key onto the stack
push.{tx_script_input_key}
# load the tx script input value from the map and read it onto the stack
adv.push_mapval adv_loadw
# assert that the value is correct
push.{tx_script_input_value} assert_eqw.err="tx script input value mismatch"
end
"#,
);
let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?;
let tx_context = TestTransactionBuilder::with_existing_mock_account()
.tx_script(tx_script)
.extend_advice_map([(tx_script_input_key, tx_script_input_value.to_vec())])
.build()?;
tx_context.execute().await.context("failed to execute transaction")?;
Ok(())
}
#[tokio::test]
async fn test_tx_script_args() -> anyhow::Result<()> {
let tx_script_args = Word::from([1, 2, 3, 4u32]);
let advice_entry = Word::from([5, 6, 7, 8u32]);
let tx_script_src = format!(
r#"
@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.
# assert the correctness of the argument
dupw push.{tx_script_args} assert_eqw.err="provided transaction arguments don't match the expected ones"
# => [TX_SCRIPT_ARGS]
# since we provided an advice map entry with the transaction script arguments as a key,
# we can obtain the value of this entry
adv.push_mapval padw adv_loadw
# => [[map_entry_values], TX_SCRIPT_ARGS]
# assert the correctness of the map entry values
push.{advice_entry} assert_eqw.err="obtained advice map value doesn't match the expected one"
end"#
);
let tx_script = CodeBuilder::default()
.compile_tx_script(tx_script_src)
.context("failed to parse transaction script")?;
let tx_context = TestTransactionBuilder::with_existing_mock_account()
.tx_script(tx_script)
.extend_advice_map([(tx_script_args, advice_entry.as_elements().to_vec())])
.tx_script_args(tx_script_args)
.build()?;
tx_context.execute().await?;
Ok(())
}
#[tokio::test]
async fn test_get_script_root_with_script() -> anyhow::Result<()> {
let tx_script =
CodeBuilder::default().compile_tx_script("@transaction_script pub proc main nop end")?;
let expected_root = tx_script.root();
let code = format!(
r#"
use miden::protocol::tx
use miden::tx_kernel_core::prologue
begin
exec.prologue::prepare_transaction
exec.tx::get_tx_script_root
# => [TX_SCRIPT_ROOT]
push.{expected_root} assert_eqw.err="tx script root mismatch"
end
"#
);
let tx_context = TestTransactionBuilder::with_existing_mock_account()
.tx_script(tx_script)
.build()?;
tx_context.execute_code(&code).await?;
Ok(())
}
#[tokio::test]
async fn test_get_script_root_without_script() -> anyhow::Result<()> {
let code = r#"
use miden::protocol::tx
use miden::tx_kernel_core::prologue
begin
exec.prologue::prepare_transaction
exec.tx::get_tx_script_root
# => [TX_SCRIPT_ROOT]
padw assert_eqw.err="tx script root must be zero when no script is executed"
end
"#;
let tx_context = TestTransactionBuilder::with_existing_mock_account().build()?;
tx_context.execute_code(code).await?;
Ok(())
}
#[tokio::test]
async fn inputs_created_correctly() -> anyhow::Result<()> {
let account_component_masm = r#"
adv_map A([6,7,8,9]) = [10,11,12,13]
@account_procedure
pub proc assert_adv_map
# test tx script advice map
push.[1,2,3,4]
adv.push_mapval adv_loadw
push.[5,6,7,8]
assert_eqw.err="script adv map not found"
end
"#;
let component_code = CodeBuilder::default()
.compile_component_code("test::adv_map_component", account_component_masm)?;
let component = AccountComponent::new(
component_code.clone(),
vec![StorageSlot::with_value(StorageSlotName::mock(0), Word::default())],
AccountComponentMetadata::mock("test::adv_map_component"),
)?;
let account_code =
AccountCode::from_components(&[IncrNonceAuthComponent.into(), component.clone()])?;
let script = r#"
adv_map A([1,2,3,4]) = [5,6,7,8]
@transaction_script
pub proc main
call.::test::adv_map_component::assert_adv_map
# test account code advice map
push.[6,7,8,9]
adv.push_mapval adv_loadw
push.[10,11,12,13]
assert_eqw.err="account code adv map not found"
end
"#;
let tx_script = CodeBuilder::default()
.with_dynamically_linked_library(component_code)?
.compile_tx_script(script)?;
assert!(tx_script.mast().advice_map().get(&Word::try_from([1u64, 2, 3, 4])?).is_some());
assert!(
account_code
.mast()
.advice_map()
.get(&Word::try_from([6u64, 7, 8, 9])?)
.is_some()
);
let account = Account::new_existing(
ACCOUNT_ID_PRIVATE_SENDER.try_into()?,
AssetVault::mock(),
AccountStorage::mock(),
account_code,
Felt::new_unchecked(1u64),
);
let tx_context = crate::TestTransactionBuilder::new(account).tx_script(tx_script).build()?;
_ = tx_context.execute().await?;
Ok(())
}
#[tokio::test]
async fn tx_can_be_reexecuted() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let account = builder.add_existing_mock_account(Auth::BasicAuth {
auth_scheme: AuthScheme::Falcon512Poseidon2,
})?;
let note = builder.add_p2id_note(
ACCOUNT_ID_SENDER.try_into()?,
account.id(),
&[FungibleAsset::mock(3)],
NoteType::Public,
)?;
let chain = builder.build()?;
let tx = chain
.build_tx_context(account.id(), &[note.id()], &[])?
.build()?
.execute()
.await?;
let _reexecuted_tx = chain
.build_tx_context(account.id(), &[note.id()], &[])?
.authenticator(None)
.tx_inputs(tx.tx_inputs().clone())
.build()?
.execute()
.await?;
Ok(())
}
#[tokio::test]
async fn tx_circular_note_dependency_is_rejected() -> anyhow::Result<()> {
let asset = NonFungibleAsset::mock(&[42]);
let mut builder = MockChain::builder();
let account = builder.add_existing_wallet_with_assets(Auth::IncrNonce, [])?;
let chain = builder.build()?;
let mut rng = RandomCoin::new(Word::from([1u32; 4]));
let note_x = create_p2any_note(account.id(), NoteType::Public, [asset], &mut rng);
let script = TransactionScript::from(SendNotesTransactionScript::new(
&account.code_interface(),
&[PartialNote::from(note_x.clone())],
)?);
let executed_tx = chain
.build_tx_context(account.clone(), &[], slice::from_ref(¬e_x))?
.tx_script(script)
.extend_expected_output_notes(vec![RawOutputNote::Full(note_x.clone())])
.build()?
.execute()
.await?;
let error = LocalTransactionProver::default().prove_dummy(executed_tx).unwrap_err();
assert_matches!(error, TransactionProverError::ProvenTransactionBuildFailed(
ProvenTransactionError::NoteCreatedAndConsumed(note_id)) => {
assert_eq!(note_id, note_x.id());
});
Ok(())
}