miden_testing/
utils.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3
4use miden_lib::testing::note::NoteBuilder;
5use miden_lib::transaction::{TransactionKernel, memory};
6use miden_objects::account::AccountId;
7use miden_objects::asset::Asset;
8use miden_objects::note::Note;
9use miden_objects::testing::storage::prepare_assets;
10use miden_processor::Felt;
11use rand::SeedableRng;
12use rand::rngs::SmallRng;
13
14// HELPER MACROS
15// ================================================================================================
16
17#[macro_export]
18macro_rules! assert_execution_error {
19    ($execution_result:expr, $expected_err:expr) => {
20        match $execution_result {
21            Err(miden_processor::ExecutionError::FailedAssertion { label: _, source_file: _, clk: _, err_code, err_msg }) => {
22                if let Some(ref msg) = err_msg {
23                  assert_eq!(msg.as_ref(), $expected_err.message(), "error messages did not match");
24                }
25
26                assert_eq!(
27                    err_code, $expected_err.code(),
28                    "Execution failed on assertion with an unexpected error (Actual code: {}, msg: {}, Expected code: {}).",
29                    err_code, err_msg.as_ref().map(|string| string.as_ref()).unwrap_or("<no message>"), $expected_err,
30                );
31            },
32            Ok(_) => panic!("Execution was unexpectedly successful"),
33            Err(err) => panic!("Execution error was not as expected: {err}"),
34        }
35    };
36}
37
38#[macro_export]
39macro_rules! assert_transaction_executor_error {
40    ($execution_result:expr, $expected_err:expr) => {
41        match $execution_result {
42            Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed(
43                miden_processor::ExecutionError::FailedAssertion {
44                    label: _,
45                    source_file: _,
46                    clk: _,
47                    err_code,
48                    err_msg,
49                },
50            )) => {
51                if let Some(ref msg) = err_msg {
52                  assert_eq!(msg.as_ref(), $expected_err.message(), "error messages did not match");
53                }
54
55                assert_eq!(
56                  err_code, $expected_err.code(),
57                  "Execution failed on assertion with an unexpected error (Actual code: {}, msg: {}, Expected: {}).",
58                  err_code, err_msg.as_ref().map(|string| string.as_ref()).unwrap_or("<no message>"), $expected_err);
59            },
60            Ok(_) => panic!("Execution was unexpectedly successful"),
61            Err(err) => panic!("Execution error was not as expected: {err}"),
62        }
63    };
64}
65
66// TEST UTILITIES
67// ================================================================================================
68
69pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress {
70    memory::INPUT_NOTE_DATA_SECTION_OFFSET + note_idx * memory::NOTE_MEM_SIZE
71}
72
73// HELPER NOTES
74// ================================================================================================
75
76/// Creates a `P2ANY` note.
77///
78/// A `P2ANY` note carries `assets` and a script that moves the assets into the executing account's
79/// vault.
80///
81/// The created note does not require authentication and can be consumed by any account.
82pub fn create_p2any_note(sender: AccountId, assets: &[Asset]) -> Note {
83    let mut code_body = String::new();
84    for i in 0..assets.len() {
85        if i == 0 {
86            // first asset (dest_ptr is already on stack)
87            code_body.push_str(
88                "
89                # add first asset
90
91                padw dup.4 mem_loadw
92                padw swapw padw padw swapdw
93                call.wallet::receive_asset
94                dropw movup.12
95                # => [dest_ptr, pad(12)]
96                ",
97            );
98        } else {
99            code_body.push_str(
100                "
101                # add next asset
102
103                add.4 dup movdn.13
104                padw movup.4 mem_loadw
105                call.wallet::receive_asset
106                dropw movup.12
107                # => [dest_ptr, pad(12)]",
108            );
109        }
110    }
111    code_body.push_str("dropw dropw dropw dropw");
112
113    let code = format!(
114        "
115        use.mock::account
116        use.miden::note
117        use.miden::contracts::wallets::basic->wallet
118
119        begin
120            # fetch pointer & number of assets
121            push.0 exec.note::get_assets          # [num_assets, dest_ptr]
122
123            # runtime-check we got the expected count
124            push.{num_assets} assert_eq             # [dest_ptr]
125
126            {code_body}
127            dropw dropw dropw dropw
128        end
129        ",
130        num_assets = assets.len(),
131    );
132
133    NoteBuilder::new(sender, SmallRng::from_seed([0; 32]))
134        .add_assets(assets.iter().copied())
135        .code(code)
136        .dynamically_linked_libraries(TransactionKernel::mock_libraries())
137        .build()
138        .expect("generated note script should compile")
139}
140
141/// Creates a `SPAWN` note.
142///
143///  A `SPAWN` note contains a note script that creates all `output_notes` that get passed as a
144///  parameter.
145pub fn create_spawn_note(sender_id: AccountId, output_notes: Vec<&Note>) -> anyhow::Result<Note> {
146    let note_code = note_script_that_creates_notes(output_notes);
147
148    let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng())
149        .code(note_code)
150        .dynamically_linked_libraries(TransactionKernel::mock_libraries())
151        .build()?;
152
153    Ok(note)
154}
155
156/// Returns the code for a note that creates all notes in `output_notes`
157fn note_script_that_creates_notes(output_notes: Vec<&Note>) -> String {
158    let mut out = String::from("use.miden::tx\nuse.mock::account\n\nbegin\n");
159
160    for (idx, note) in output_notes.iter().enumerate() {
161        if idx == 0 {
162            out.push_str("padw padw\n");
163        } else {
164            out.push_str("dropw dropw dropw\n");
165        }
166        let assets_str = prepare_assets(note.assets());
167        out.push_str(&format!(
168            " push.{recipient}
169              push.{hint}
170              push.{note_type}
171              push.{aux}
172              push.{tag}
173              call.tx::create_note\n",
174            recipient = note.recipient().digest(),
175            hint = Felt::from(note.metadata().execution_hint()),
176            note_type = note.metadata().note_type() as u8,
177            aux = note.metadata().aux(),
178            tag = note.metadata().tag(),
179        ));
180
181        for asset in assets_str {
182            out.push_str(&format!(
183                " push.{asset}
184                  call.tx::add_asset_to_note\n",
185            ));
186        }
187    }
188
189    out.push_str("repeat.5 dropw end\nend");
190    out
191}