miden_testing/tx_context/
builder.rs

1// TRANSACTION CONTEXT BUILDER
2// ================================================================================================
3
4use 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
41// TRANSACTION CONTEXT BUILDER
42// ================================================================================================
43
44/// [TransactionContextBuilder] is a utility to construct [TransactionContext] for testing
45/// purposes. It allows users to build accounts, create notes, provide advice inputs, and
46/// execute code. The VM process can be inspected afterward.
47///
48/// # Examples
49///
50/// Create a new account and execute code:
51/// ```
52/// # use miden_testing::TransactionContextBuilder;
53/// # use miden_objects::{account::AccountBuilder,Felt, FieldElement};
54/// # use miden_lib::transaction::TransactionKernel;
55/// let tx_context = TransactionContextBuilder::with_standard_account(Felt::ONE).build();
56///
57/// let code = "
58/// use.kernel::prologue
59/// use.test::account
60///
61/// begin
62///     exec.prologue::prepare_transaction
63///     push.5
64///     swap drop
65/// end
66/// ";
67///
68/// let process = tx_context.execute_code(code).unwrap();
69/// assert_eq!(process.stack.get(0), Felt::new(5),);
70/// ```
71pub 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    /// Initializes a [TransactionContextBuilder] with a mocked standard wallet.
105    pub fn with_standard_account(nonce: Felt) -> Self {
106        // Build standard account with normal assembler because the testing one already contains it
107        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    /// Initializes a [TransactionContextBuilder] with a mocked fungible faucet.
132    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    /// Initializes a [TransactionContextBuilder] with a mocked non-fungible faucet.
144    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    /// Override and set the account seed manually
156    pub fn account_seed(mut self, account_seed: Option<Word>) -> Self {
157        self.account_seed = account_seed;
158        self
159    }
160
161    /// Override and set the [AdviceInputs]
162    pub fn advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
163        self.advice_inputs = advice_inputs;
164        self
165    }
166
167    /// Set the authenticator for the transaction (if needed)
168    pub fn authenticator(mut self, authenticator: Option<MockAuthenticator>) -> Self {
169        self.authenticator = authenticator;
170        self
171    }
172
173    /// Set foreign account codes that are used by the transaction
174    pub fn foreign_accounts(mut self, inputs: Vec<AccountInputs>) -> Self {
175        self.foreign_account_inputs = inputs;
176        self
177    }
178
179    /// Extend the set of used input notes
180    pub fn input_notes(mut self, input_notes: Vec<Note>) -> Self {
181        self.input_notes.extend(input_notes);
182        self
183    }
184
185    /// Set the desired transaction script
186    pub fn tx_script(mut self, tx_script: TransactionScript) -> Self {
187        self.tx_script = Some(tx_script);
188        self
189    }
190
191    /// Set the desired transaction inputs
192    pub fn tx_inputs(mut self, tx_inputs: TransactionInputs) -> Self {
193        self.transaction_inputs = Some(tx_inputs);
194        self
195    }
196
197    /// Defines the expected output notes
198    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    /// Creates a new output [Note] for the transaction corresponding to this context.
210    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    /// Add a note from a [NoteBuilder]
227    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    /// Adds one input note with a note script that creates another output note.
242    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    /// Adds one input note with a note script that creates 2 output notes.
296    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    /// Adds a set of input notes that output notes where inputs are smaller than needed and
432    /// do not add up to match the output.
433    pub fn with_mock_notes_too_few_input(mut self) -> Self {
434        // ACCOUNT IDS
435        // --------------------------------------------------------------------------------------------
436        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        // ASSETS
442        // --------------------------------------------------------------------------------------------
443        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        // expected by `output_notes_data_procedure`
454        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    /// Adds a set of input notes that output notes in an asset-preserving manner.
468    pub fn with_mock_notes_preserved(mut self) -> Self {
469        // ACCOUNT IDS
470        // --------------------------------------------------------------------------------------------
471        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        // ASSETS
477        // --------------------------------------------------------------------------------------------
478        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        // ACCOUNT IDS
508        // --------------------------------------------------------------------------------------------
509        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        // ASSETS
515        // --------------------------------------------------------------------------------------------
516        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        // ACCOUNT IDS
550        // --------------------------------------------------------------------------------------------
551        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        // ASSETS
557        // --------------------------------------------------------------------------------------------
558        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        // ACCOUNT IDS
590        // --------------------------------------------------------------------------------------------
591        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        // ASSETS
597        // --------------------------------------------------------------------------------------------
598        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    /// Builds the [TransactionContext].
629    ///
630    /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order
631    /// to generate valid block data for the required notes.
632    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                // If no specific transaction inputs was provided, initialize an ad-hoc mockchain
639                // to generate valid block header/MMR data
640
641                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}