1use alloc::string::String;
2use alloc::vec::Vec;
3
4use miden_processor::crypto::RpoRandomCoin;
5use miden_protocol::account::AccountId;
6use miden_protocol::asset::Asset;
7use miden_protocol::crypto::rand::FeltRng;
8use miden_protocol::note::{Note, NoteType};
9use miden_protocol::testing::storage::prepare_assets;
10use miden_standards::code_builder::CodeBuilder;
11use miden_standards::testing::note::NoteBuilder;
12use rand::SeedableRng;
13use rand::rngs::SmallRng;
14
15#[macro_export]
19macro_rules! assert_execution_error {
20 ($execution_result:expr, $expected_err:expr) => {
21 match $execution_result {
22 Err(miden_processor::ExecutionError::FailedAssertion { label: _, source_file: _, clk: _, err_code, err_msg, err: _ }) => {
23 if let Some(ref msg) = err_msg {
24 assert_eq!(msg.as_ref(), $expected_err.message(), "error messages did not match");
25 }
26
27 assert_eq!(
28 err_code, $expected_err.code(),
29 "Execution failed on assertion with an unexpected error (Actual code: {}, msg: {}, Expected code: {}).",
30 err_code, err_msg.as_ref().map(|string| string.as_ref()).unwrap_or("<no message>"), $expected_err,
31 );
32 },
33 Ok(_) => panic!("Execution was unexpectedly successful"),
34 Err(err) => panic!("Execution error was not as expected: {err}"),
35 }
36 };
37}
38
39#[macro_export]
40macro_rules! assert_transaction_executor_error {
41 ($execution_result:expr, $expected_err:expr) => {
42 match $execution_result {
43 Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed(
44 miden_processor::ExecutionError::FailedAssertion {
45 label: _,
46 source_file: _,
47 clk: _,
48 err_code,
49 err_msg,
50 err: _,
51 },
52 )) => {
53 if let Some(ref msg) = err_msg {
54 assert_eq!(msg.as_ref(), $expected_err.message(), "error messages did not match");
55 }
56
57 assert_eq!(
58 err_code, $expected_err.code(),
59 "Execution failed on assertion with an unexpected error (Actual code: {}, msg: {}, Expected: {}).",
60 err_code, err_msg.as_ref().map(|string| string.as_ref()).unwrap_or("<no message>"), $expected_err);
61 },
62 Ok(_) => panic!("Execution was unexpectedly successful"),
63 Err(err) => panic!("Execution error was not as expected: {err}"),
64 }
65 };
66}
67
68pub fn create_public_p2any_note(
78 sender: AccountId,
79 assets: impl IntoIterator<Item = Asset>,
80) -> Note {
81 let mut rng = RpoRandomCoin::new(Default::default());
82 create_p2any_note(sender, NoteType::Public, assets, &mut rng)
83}
84
85pub fn create_p2any_note(
92 sender: AccountId,
93 note_type: NoteType,
94 assets: impl IntoIterator<Item = Asset>,
95 rng: &mut RpoRandomCoin,
96) -> Note {
97 let serial_number = rng.draw_word();
98 let assets: Vec<_> = assets.into_iter().collect();
99 let mut code_body = String::new();
100 for i in 0..assets.len() {
101 if i == 0 {
102 code_body.push_str(
104 "
105 # add first asset
106
107 padw dup.4 mem_loadw_be
108 padw swapw padw padw swapdw
109 call.wallet::receive_asset
110 dropw movup.12
111 # => [dest_ptr, pad(12)]
112 ",
113 );
114 } else {
115 code_body.push_str(
116 "
117 # add next asset
118
119 add.4 dup movdn.13
120 padw movup.4 mem_loadw_be
121 call.wallet::receive_asset
122 dropw movup.12
123 # => [dest_ptr, pad(12)]",
124 );
125 }
126 }
127 code_body.push_str("dropw dropw dropw dropw");
128
129 let code = format!(
130 r#"
131 use mock::account
132 use miden::protocol::active_note
133 use miden::standards::wallets::basic->wallet
134
135 begin
136 # fetch pointer & number of assets
137 push.0 exec.active_note::get_assets # [num_assets, dest_ptr]
138
139 # runtime-check we got the expected count
140 push.{num_assets} assert_eq.err="unexpected number of assets" # [dest_ptr]
141
142 {code_body}
143 dropw dropw dropw dropw
144 end
145 "#,
146 num_assets = assets.len(),
147 );
148
149 NoteBuilder::new(sender, SmallRng::from_seed([0; 32]))
150 .add_assets(assets.iter().copied())
151 .note_type(note_type)
152 .serial_number(serial_number)
153 .code(code)
154 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
155 .build()
156 .expect("generated note script should compile")
157}
158
159pub fn create_spawn_note<'note, I>(
170 output_notes: impl IntoIterator<Item = &'note Note, IntoIter = I>,
171) -> anyhow::Result<Note>
172where
173 I: ExactSizeIterator<Item = &'note Note>,
174{
175 let mut output_notes = output_notes.into_iter().peekable();
176 if output_notes.len() == 0 {
177 anyhow::bail!("at least one output note is needed to create a SPAWN note");
178 }
179
180 let sender_id = output_notes
181 .peek()
182 .expect("at least one output note should be present")
183 .metadata()
184 .sender();
185
186 let note_code = note_script_that_creates_notes(sender_id, output_notes)?;
187
188 let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng())
189 .code(note_code)
190 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
191 .build()?;
192
193 Ok(note)
194}
195
196fn note_script_that_creates_notes<'note>(
198 sender_id: AccountId,
199 output_notes: impl Iterator<Item = &'note Note>,
200) -> anyhow::Result<String> {
201 let mut out = String::from("use miden::protocol::output_note\n\nbegin\n");
202
203 for (idx, note) in output_notes.into_iter().enumerate() {
204 anyhow::ensure!(
205 note.metadata().sender() == sender_id,
206 "sender IDs of output notes passed to SPAWN note are inconsistent"
207 );
208
209 out.push_str(&format!(
211 r#"exec.::miden::protocol::native_account::get_id
212 # => [native_account_id_prefix, native_account_id_suffix]
213 push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix"
214 # => [native_account_id_suffix]
215 push.{sender_suffix} assert_eq.err="sender ID suffix does not match native account ID's suffix"
216 # => []
217 "#,
218 sender_prefix = sender_id.prefix().as_felt(),
219 sender_suffix = sender_id.suffix()
220 ));
221
222 if idx == 0 {
223 out.push_str("padw padw\n");
224 } else {
225 out.push_str("dropw dropw dropw\n");
226 }
227 out.push_str(&format!(
228 "
229 push.{recipient}
230 push.{note_type}
231 push.{tag}
232 exec.output_note::create\n",
233 recipient = note.recipient().digest(),
234 note_type = note.metadata().note_type() as u8,
235 tag = note.metadata().tag(),
236 ));
237
238 out.push_str(&format!(
239 "
240 push.{ATTACHMENT}
241 push.{attachment_scheme}
242 push.{attachment_kind}
243 dup.6
244 # => [note_idx, attachment_kind, attachment_scheme, ATTACHMENT, note_idx]
245 exec.output_note::set_attachment
246 # => [note_idx]
247 ",
248 ATTACHMENT = note.metadata().to_attachment_word(),
249 attachment_scheme = note.metadata().attachment().attachment_scheme().as_u32(),
250 attachment_kind = note.metadata().attachment().content().attachment_kind().as_u8(),
251 ));
252
253 let assets_str = prepare_assets(note.assets());
254 for asset in assets_str {
255 out.push_str(&format!(
256 " push.{asset}
257 call.::miden::standards::wallets::basic::move_asset_to_note\n",
258 ));
259 }
260 }
261
262 out.push_str("repeat.5 dropw end\nend");
263
264 Ok(out)
265}