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