1use alloc::{collections::BTreeMap, vec::Vec};
5
6use miden_lib::{transaction::TransactionKernel, utils::word_to_masm_push_string};
7use miden_objects::{
8 FieldElement,
9 account::{Account, AccountId},
10 assembly::Assembler,
11 asset::{Asset, FungibleAsset, NonFungibleAsset},
12 note::{Note, NoteExecutionHint, NoteId, NoteType},
13 testing::{
14 account_id::{
15 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
16 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
17 ACCOUNT_ID_SENDER,
18 },
19 constants::{
20 CONSUMED_ASSET_1_AMOUNT, CONSUMED_ASSET_2_AMOUNT, CONSUMED_ASSET_3_AMOUNT,
21 NON_FUNGIBLE_ASSET_DATA_2,
22 },
23 note::NoteBuilder,
24 storage::prepare_assets,
25 },
26 transaction::{
27 AccountInputs, OutputNote, TransactionArgs, TransactionInputs, TransactionScript,
28 },
29 vm::AdviceMap,
30};
31use miden_tx::{TransactionMastStore, auth::BasicAuthenticator};
32use rand::{Rng, SeedableRng};
33use rand_chacha::ChaCha20Rng;
34use vm_processor::{AdviceInputs, Felt, Word};
35
36use super::TransactionContext;
37use crate::{MockChain, MockChainNote};
38
39pub type MockAuthenticator = BasicAuthenticator<ChaCha20Rng>;
40
41pub struct TransactionContextBuilder {
72 assembler: Assembler,
73 account: Account,
74 account_seed: Option<Word>,
75 advice_inputs: AdviceInputs,
76 authenticator: Option<MockAuthenticator>,
77 expected_output_notes: Vec<Note>,
78 foreign_account_inputs: Vec<AccountInputs>,
79 input_notes: Vec<Note>,
80 tx_script: Option<TransactionScript>,
81 note_args: BTreeMap<NoteId, Word>,
82 transaction_inputs: Option<TransactionInputs>,
83 rng: ChaCha20Rng,
84}
85
86impl TransactionContextBuilder {
87 pub fn new(account: Account) -> Self {
88 Self {
89 assembler: TransactionKernel::testing_assembler_with_mock_account(),
90 account,
91 account_seed: None,
92 input_notes: Vec::new(),
93 expected_output_notes: Vec::new(),
94 rng: ChaCha20Rng::from_seed([0_u8; 32]),
95 tx_script: None,
96 authenticator: None,
97 advice_inputs: Default::default(),
98 transaction_inputs: None,
99 note_args: BTreeMap::new(),
100 foreign_account_inputs: vec![],
101 }
102 }
103
104 pub fn with_standard_account(nonce: Felt) -> Self {
106 let account = Account::mock(
108 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
109 nonce,
110 TransactionKernel::testing_assembler(),
111 );
112
113 let assembler = TransactionKernel::testing_assembler_with_mock_account();
114
115 Self {
116 assembler: assembler.clone(),
117 account,
118 account_seed: None,
119 authenticator: None,
120 input_notes: Vec::new(),
121 expected_output_notes: Vec::new(),
122 advice_inputs: Default::default(),
123 rng: ChaCha20Rng::from_seed([0_u8; 32]),
124 tx_script: None,
125 transaction_inputs: None,
126 note_args: BTreeMap::new(),
127 foreign_account_inputs: vec![],
128 }
129 }
130
131 pub fn with_fungible_faucet(acct_id: u128, nonce: Felt, initial_balance: Felt) -> Self {
133 let account = Account::mock_fungible_faucet(
134 acct_id,
135 nonce,
136 initial_balance,
137 TransactionKernel::testing_assembler(),
138 );
139
140 Self { account, ..Self::default() }
141 }
142
143 pub fn with_non_fungible_faucet(acct_id: u128, nonce: Felt, empty_reserved_slot: bool) -> Self {
145 let account = Account::mock_non_fungible_faucet(
146 acct_id,
147 nonce,
148 empty_reserved_slot,
149 TransactionKernel::testing_assembler(),
150 );
151
152 Self { account, ..Self::default() }
153 }
154
155 pub fn account_seed(mut self, account_seed: Option<Word>) -> Self {
157 self.account_seed = account_seed;
158 self
159 }
160
161 pub fn advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
163 self.advice_inputs = advice_inputs;
164 self
165 }
166
167 pub fn authenticator(mut self, authenticator: Option<MockAuthenticator>) -> Self {
169 self.authenticator = authenticator;
170 self
171 }
172
173 pub fn foreign_accounts(mut self, inputs: Vec<AccountInputs>) -> Self {
175 self.foreign_account_inputs = inputs;
176 self
177 }
178
179 pub fn input_notes(mut self, input_notes: Vec<Note>) -> Self {
181 self.input_notes.extend(input_notes);
182 self
183 }
184
185 pub fn tx_script(mut self, tx_script: TransactionScript) -> Self {
187 self.tx_script = Some(tx_script);
188 self
189 }
190
191 pub fn tx_inputs(mut self, tx_inputs: TransactionInputs) -> Self {
193 self.transaction_inputs = Some(tx_inputs);
194 self
195 }
196
197 pub fn expected_notes(mut self, output_notes: Vec<OutputNote>) -> Self {
199 let output_notes = output_notes.into_iter().filter_map(|n| match n {
200 OutputNote::Full(note) => Some(note),
201 OutputNote::Partial(_) => None,
202 OutputNote::Header(_) => None,
203 });
204
205 self.expected_output_notes.extend(output_notes);
206 self
207 }
208
209 fn add_output_note(
211 &mut self,
212 inputs: impl IntoIterator<Item = Felt>,
213 assets: impl IntoIterator<Item = Asset>,
214 ) -> Note {
215 let note = NoteBuilder::new(self.account.id(), &mut self.rng)
216 .note_inputs(inputs)
217 .expect("The inputs should be valid")
218 .add_assets(assets)
219 .build(&self.assembler)
220 .expect("The note details should be valid");
221
222 self.expected_output_notes.push(note.clone());
223 note
224 }
225
226 fn input_note_simple(
228 &mut self,
229 sender: AccountId,
230 assets: impl IntoIterator<Item = Asset>,
231 inputs: impl IntoIterator<Item = Felt>,
232 ) -> Note {
233 NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.random()))
234 .note_inputs(inputs)
235 .unwrap()
236 .add_assets(assets)
237 .build(&self.assembler)
238 .unwrap()
239 }
240
241 fn input_note_with_one_output_note(
243 &mut self,
244 sender: AccountId,
245 assets: impl IntoIterator<Item = Asset>,
246 inputs: impl IntoIterator<Item = Felt>,
247 output: &Note,
248 ) -> Note {
249 let var_name = format!(
250 "
251 use.miden::contracts::wallets::basic->wallet
252 use.test::account
253
254 begin
255 # NOTE
256 # ---------------------------------------------------------------------------------
257 padw padw
258 push.{recipient}
259 push.{execution_hint_always}
260 push.{PUBLIC_NOTE}
261 push.{aux}
262 push.{tag}
263 # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)]
264
265 call.wallet::create_note
266 # => [note_idx, pad(15)]
267
268 push.{asset}
269 call.account::add_asset_to_note
270 # => [ASSET, note_idx, pad(15)]
271
272 # clear the stack
273 repeat.5 dropw end
274 # => []
275 end
276 ",
277 PUBLIC_NOTE = NoteType::Public as u8,
278 recipient = word_to_masm_push_string(&output.recipient().digest()),
279 aux = output.metadata().aux(),
280 tag = output.metadata().tag(),
281 asset = prepare_assets(output.assets())[0],
282 execution_hint_always = Felt::from(NoteExecutionHint::always())
283 );
284 let code = var_name;
285
286 NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.random()))
287 .note_inputs(inputs)
288 .unwrap()
289 .add_assets(assets)
290 .code(code)
291 .build(&self.assembler)
292 .unwrap()
293 }
294
295 fn input_note_with_two_output_notes(
297 &mut self,
298 sender: AccountId,
299 inputs: impl IntoIterator<Item = Felt>,
300 output0: &Note,
301 output1: &Note,
302 asset: Asset,
303 ) -> Note {
304 let code = format!(
305 "
306 use.miden::contracts::wallets::basic->wallet
307 use.test::account
308
309 begin
310
311 # NOTE 0
312 # ---------------------------------------------------------------------------------
313
314 padw padw
315 push.{recipient0}
316 push.{execution_hint_always}
317 push.{PUBLIC_NOTE}
318 push.{aux0}
319 push.{tag0}
320 # => [tag_0, aux_0, note_type, execution_hint, RECIPIENT_0, pad(8)]
321
322 call.wallet::create_note
323 # => [note_idx_0, pad(15)]
324
325 push.{asset0}
326 call.account::add_asset_to_note
327 # => [ASSET_0, note_idx_0, pad(15)]
328
329 dropw dropw dropw
330 # => [pad(8)]
331
332 # NOTE 1
333 # ---------------------------------------------------------------------------------
334 push.{recipient1}
335 push.{execution_hint_always}
336 push.{PUBLIC_NOTE}
337 push.{aux1}
338 push.{tag1}
339 # => [tag_1, aux_1, note_type, execution_hint, RECIPIENT_1, pad(8)]
340
341 call.wallet::create_note
342 # => [note_idx_1, pad(15)]
343
344 push.{asset1}
345 call.account::add_asset_to_note
346 # => [ASSET_1, note_idx_1, pad(15)]
347
348 repeat.5 dropw end
349 end
350 ",
351 PUBLIC_NOTE = NoteType::Public as u8,
352 recipient0 = word_to_masm_push_string(&output0.recipient().digest()),
353 aux0 = output0.metadata().aux(),
354 tag0 = output0.metadata().tag(),
355 asset0 = prepare_assets(output0.assets())[0],
356 recipient1 = word_to_masm_push_string(&output1.recipient().digest()),
357 aux1 = output1.metadata().aux(),
358 tag1 = output1.metadata().tag(),
359 asset1 = prepare_assets(output1.assets())[0],
360 execution_hint_always = Felt::from(NoteExecutionHint::always())
361 );
362
363 NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.random()))
364 .note_inputs(inputs)
365 .unwrap()
366 .add_assets([asset])
367 .code(code)
368 .build(&self.assembler)
369 .unwrap()
370 }
371
372 fn input_note_transfer(
373 &mut self,
374 sender: AccountId,
375 assets: impl IntoIterator<Item = Asset>,
376 ) -> Note {
377 let code = "
378 use.miden::note
379 use.miden::contracts::wallets::basic->wallet
380
381 begin
382 # read the assets to memory
383 push.0 exec.note::get_assets
384 # => [num_assets, dest_ptr]
385
386 # assert the number of assets is 3
387 push.3 assert_eq
388 # => [dest_ptr]
389
390 # add the first asset to the vault
391 padw dup.4 mem_loadw
392 # => [ASSET, dest_ptr]
393
394 # pad the stack before call
395 padw swapw padw padw swapdw
396 # => [ASSET, pad(12), dest_ptr]
397
398 # add the first asset to the vault
399 call.wallet::receive_asset dropw movup.12
400 # => [dest_ptr, pad(12)]
401
402 # add the second asset to the vault
403 add.4 dup movdn.13
404 # => [dest_ptr+4, pad(12), dest_ptr+4]
405
406 # load the asset
407 padw movup.4 mem_loadw
408 # => [ASSET, pad(12), dest_ptr+4]
409
410 # add the second asset to the vault
411 call.wallet::receive_asset dropw movup.12
412 # => [dest_ptr+4, pad(12)]
413
414 # add the third asset to the vault
415 add.4 padw movup.4 mem_loadw
416 # => [ASSET, pad(12)]
417
418 call.wallet::receive_asset
419 dropw dropw dropw dropw
420 # => []
421 end
422 ";
423
424 NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.random()))
425 .add_assets(assets)
426 .code(code)
427 .build(&self.assembler)
428 .unwrap()
429 }
430
431 pub fn with_mock_notes_too_few_input(mut self) -> Self {
434 let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
437 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
438 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap();
439 let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap();
440
441 let fungible_asset_1: Asset =
444 FungibleAsset::new(faucet_id_1, CONSUMED_ASSET_1_AMOUNT).unwrap().into();
445 let fungible_asset_2: Asset =
446 FungibleAsset::new(faucet_id_2, CONSUMED_ASSET_2_AMOUNT).unwrap().into();
447 let fungible_asset_3: Asset =
448 FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT).unwrap().into();
449
450 let output_note0 = self.add_output_note([1u32.into()], [fungible_asset_1]);
451 let output_note1 = self.add_output_note([2u32.into()], [fungible_asset_2]);
452
453 let _output_note2 = self.add_output_note([3u32.into()], [fungible_asset_3]);
455
456 let input_note1 = self.input_note_with_two_output_notes(
457 sender,
458 [1u32.into()],
459 &output_note0,
460 &output_note1,
461 fungible_asset_1,
462 );
463
464 self.input_notes(vec![input_note1])
465 }
466
467 pub fn with_mock_notes_preserved(mut self) -> Self {
469 let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
472 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
473 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap();
474 let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap();
475
476 let fungible_asset_1: Asset =
479 FungibleAsset::new(faucet_id_1, CONSUMED_ASSET_1_AMOUNT).unwrap().into();
480 let fungible_asset_2: Asset =
481 FungibleAsset::new(faucet_id_2, CONSUMED_ASSET_2_AMOUNT).unwrap().into();
482 let fungible_asset_3: Asset =
483 FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT).unwrap().into();
484
485 let output_note0 = self.add_output_note([1u32.into()], [fungible_asset_1]);
486 let output_note1 = self.add_output_note([2u32.into()], [fungible_asset_2]);
487 let output_note2 = self.add_output_note([3u32.into()], [fungible_asset_3]);
488
489 let input_note1 = self.input_note_with_two_output_notes(
490 sender,
491 [1u32.into()],
492 &output_note0,
493 &output_note1,
494 fungible_asset_1,
495 );
496 let input_note2 = self.input_note_with_one_output_note(
497 sender,
498 [fungible_asset_2, fungible_asset_3],
499 [1u32.into()],
500 &output_note2,
501 );
502
503 self.input_notes(vec![input_note1, input_note2])
504 }
505
506 pub fn with_mock_notes_preserved_with_account_vault_delta(mut self) -> Self {
507 let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
510 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
511 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap();
512 let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap();
513
514 let fungible_asset_1: Asset =
517 FungibleAsset::new(faucet_id_1, CONSUMED_ASSET_1_AMOUNT).unwrap().into();
518 let fungible_asset_2: Asset =
519 FungibleAsset::new(faucet_id_2, CONSUMED_ASSET_2_AMOUNT).unwrap().into();
520 let fungible_asset_3: Asset =
521 FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT).unwrap().into();
522 let nonfungible_asset_1: Asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2);
523
524 let output_note0 = self.add_output_note([1u32.into()], [fungible_asset_1]);
525 let output_note1 = self.add_output_note([2u32.into()], [fungible_asset_2]);
526 let output_note2 = self.add_output_note([3u32.into()], [fungible_asset_3]);
527
528 let input_note1 = self.input_note_with_two_output_notes(
529 sender,
530 [1u32.into()],
531 &output_note0,
532 &output_note1,
533 fungible_asset_1,
534 );
535 let input_note2 = self.input_note_with_one_output_note(
536 sender,
537 [fungible_asset_2, fungible_asset_3],
538 [1u32.into()],
539 &output_note2,
540 );
541
542 let input_note5 = self
543 .input_note_transfer(sender, [fungible_asset_1, fungible_asset_3, nonfungible_asset_1]);
544
545 self.input_notes(vec![input_note1, input_note2, input_note5])
546 }
547
548 pub fn with_mock_notes_too_many_fungible_input(mut self) -> Self {
549 let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
552 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
553 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap();
554 let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap();
555
556 let fungible_asset_1: Asset =
559 FungibleAsset::new(faucet_id_1, CONSUMED_ASSET_1_AMOUNT).unwrap().into();
560 let fungible_asset_2: Asset =
561 FungibleAsset::new(faucet_id_2, CONSUMED_ASSET_2_AMOUNT).unwrap().into();
562 let fungible_asset_3: Asset =
563 FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT).unwrap().into();
564
565 let output_note0 = self.add_output_note([1u32.into()], [fungible_asset_1]);
566 let output_note1 = self.add_output_note([2u32.into()], [fungible_asset_2]);
567 let output_note2 = self.add_output_note([3u32.into()], [fungible_asset_3]);
568
569 let input_note1 = self.input_note_with_two_output_notes(
570 sender,
571 [1u32.into()],
572 &output_note0,
573 &output_note1,
574 fungible_asset_1,
575 );
576 let input_note2 = self.input_note_with_one_output_note(
577 sender,
578 [fungible_asset_2, fungible_asset_3],
579 [1u32.into()],
580 &output_note2,
581 );
582 let input_note3 =
583 self.input_note_simple(sender, [fungible_asset_2, fungible_asset_3], [2u32.into()]);
584
585 self.input_notes(vec![input_note1, input_note2, input_note3])
586 }
587
588 pub fn with_mock_notes_too_many_non_fungible_input(mut self) -> Self {
589 let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
592 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap();
593 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap();
594 let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap();
595
596 let fungible_asset_1: Asset =
599 FungibleAsset::new(faucet_id_1, CONSUMED_ASSET_1_AMOUNT).unwrap().into();
600 let fungible_asset_2: Asset =
601 FungibleAsset::new(faucet_id_2, CONSUMED_ASSET_2_AMOUNT).unwrap().into();
602 let fungible_asset_3: Asset =
603 FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT).unwrap().into();
604 let nonfungible_asset_1: Asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2);
605
606 let output_note0 = self.add_output_note([1u32.into()], [fungible_asset_1]);
607 let output_note1 = self.add_output_note([2u32.into()], [fungible_asset_2]);
608 let output_note2 = self.add_output_note([3u32.into()], [fungible_asset_3]);
609
610 let input_note1 = self.input_note_with_two_output_notes(
611 sender,
612 [1u32.into()],
613 &output_note0,
614 &output_note1,
615 fungible_asset_1,
616 );
617 let input_note2 = self.input_note_with_one_output_note(
618 sender,
619 [fungible_asset_2, fungible_asset_3],
620 [1u32.into()],
621 &output_note2,
622 );
623 let input_note4 = self.input_note_simple(sender, [nonfungible_asset_1], [1u32.into()]);
624
625 self.input_notes(vec![input_note1, input_note2, input_note4])
626 }
627
628 pub fn build(self) -> TransactionContext {
633 let source_manager = self.assembler.source_manager();
634
635 let tx_inputs = match self.transaction_inputs {
636 Some(tx_inputs) => tx_inputs,
637 None => {
638 let mut mock_chain = MockChain::default();
642 for i in self.input_notes {
643 mock_chain.add_pending_note(OutputNote::Full(i));
644 }
645
646 mock_chain.prove_next_block();
647 mock_chain.prove_next_block();
648
649 let input_note_ids: Vec<NoteId> =
650 mock_chain.committed_notes().values().map(MockChainNote::id).collect();
651
652 mock_chain.get_transaction_inputs(
653 self.account.clone(),
654 self.account_seed,
655 &input_note_ids,
656 &[],
657 )
658 },
659 };
660
661 let mut tx_args = TransactionArgs::new(
662 self.tx_script,
663 Some(self.note_args),
664 AdviceMap::default(),
665 self.foreign_account_inputs,
666 );
667
668 tx_args.extend_advice_inputs(self.advice_inputs.clone());
669 tx_args.extend_output_note_recipients(self.expected_output_notes.clone());
670
671 let mast_store = {
672 let mast_forest_store = TransactionMastStore::new();
673 mast_forest_store.load_transaction_code(
674 tx_inputs.account().code(),
675 tx_inputs.input_notes(),
676 &tx_args,
677 );
678
679 mast_forest_store
680 };
681
682 TransactionContext {
683 expected_output_notes: self.expected_output_notes,
684 tx_args,
685 tx_inputs,
686 mast_store,
687 authenticator: self.authenticator,
688 advice_inputs: self.advice_inputs,
689 source_manager,
690 }
691 }
692}
693
694impl Default for TransactionContextBuilder {
695 fn default() -> Self {
696 Self::with_standard_account(Felt::ZERO)
697 }
698}