1use alloc::string::String;
2use alloc::vec::Vec;
3
4use miden_processor::crypto::random::RandomCoin;
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::vm::AdviceMap;
10use miden_standards::code_builder::CodeBuilder;
11use miden_standards::testing::note::NoteBuilder;
12use rand::SeedableRng;
13use rand::rngs::SmallRng;
14
15#[macro_export]
26macro_rules! assert_execution_error {
27 ($execution_result:expr, matches $pat:pat $(if $guard:expr)? $(,)?) => {
28 match $execution_result {
29 Err($crate::ExecError($pat)) $(if $guard)? => {},
30 Ok(_) => ::core::panic!(
31 "Execution was unexpectedly successful\nexpected error: {}",
32 ::core::stringify!($pat),
33 ),
34 Err(err) => ::core::panic!(
35 "Execution error did not match:\nexpected: {}\nactual: {}",
36 ::core::stringify!($pat),
37 err,
38 ),
39 }
40 };
41
42 ($execution_result:expr, $expected:expr $(,)?) => {
43 match $execution_result {
44 Err($crate::ExecError(actual)) => {
45 if !$expected.matches_execution_error(&actual) {
46 ::core::panic!(
47 "Execution error did not match:\nexpected: {}\nactual: {}",
48 $expected,
49 actual,
50 );
51 }
52 },
53 Ok(_) => ::core::panic!(
54 "Execution was unexpectedly successful\nexpected error: {}",
55 $expected,
56 ),
57 }
58 };
59}
60
61#[macro_export]
66macro_rules! assert_transaction_executor_error {
67 ($execution_result:expr, matches $pat:pat $(if $guard:expr)? $(,)?) => {
68 match $execution_result {
69 Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed($pat))
70 $(if $guard)? => {},
71 Ok(_) => ::core::panic!(
72 "Execution was unexpectedly successful\nexpected error: {}",
73 ::core::stringify!($pat),
74 ),
75 Err(err) => ::core::panic!(
76 "Execution error did not match:\nexpected: {}\nactual: {}",
77 ::core::stringify!($pat),
78 err,
79 ),
80 }
81 };
82
83 ($execution_result:expr, $expected:expr $(,)?) => {
84 match $execution_result {
85 Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed(actual)) => {
86 if !$expected.matches_execution_error(&actual) {
87 ::core::panic!(
88 "Execution error did not match:\nexpected: {}\nactual: {}",
89 $expected,
90 actual,
91 );
92 }
93 },
94 Ok(_) => ::core::panic!(
95 "Execution was unexpectedly successful\nexpected error: {}",
96 $expected,
97 ),
98 Err(err) => ::core::panic!(
99 "Execution error did not match:\nexpected: {}\nactual: {}",
100 $expected,
101 err,
102 ),
103 }
104 };
105}
106
107pub fn create_public_p2any_note(
117 sender: AccountId,
118 assets: impl IntoIterator<Item = Asset>,
119) -> Note {
120 let mut rng = RandomCoin::new(Default::default());
121 create_p2any_note(sender, NoteType::Public, assets, &mut rng)
122}
123
124pub fn create_p2any_note(
131 sender: AccountId,
132 note_type: NoteType,
133 assets: impl IntoIterator<Item = Asset>,
134 rng: &mut RandomCoin,
135) -> Note {
136 let serial_number = rng.draw_word();
137 let assets: Vec<_> = assets.into_iter().collect();
138 let mut code_body = String::new();
139 for asset_idx in 0..assets.len() {
140 code_body.push_str(&format!(
141 "
142 # => [dest_ptr]
143
144 # current_asset_ptr = dest_ptr + ASSET_SIZE * asset_idx
145 dup push.ASSET_SIZE mul.{asset_idx}
146 # => [current_asset_ptr, dest_ptr]
147
148 padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_le
149 # => [ASSET_VALUE, current_asset_ptr, dest_ptr]
150
151 padw movup.8 mem_loadw_le
152 # => [ASSET_ID, ASSET_VALUE, current_asset_ptr, dest_ptr]
153
154 padw padw swapdw
155 # => [ASSET_ID, ASSET_VALUE, pad(12), dest_ptr]
156
157 call.wallet::receive_asset
158 # => [pad(16), dest_ptr]
159
160 dropw dropw dropw dropw
161 # => [dest_ptr]
162 ",
163 ));
164 }
165 code_body.push_str("dropw dropw dropw dropw");
166
167 let code = format!(
168 r#"
169 use mock::account
170 use miden::protocol::active_note
171 use {{ASSET_SIZE, ASSET_VALUE_MEMORY_OFFSET}} from miden::protocol::asset
172 use miden::standards::wallets::basic as wallet
173
174 @note_script
175 pub proc main
176 # fetch pointer & number of assets
177 push.0 exec.active_note::remove_all_assets # [num_assets]
178
179 # runtime-check we got the expected count
180 push.{num_assets} assert_eq.err="unexpected number of assets" # []
181
182 push.0 # [dest_ptr]
183
184 {code_body}
185 dropw dropw dropw dropw
186 end
187 "#,
188 num_assets = assets.len(),
189 );
190
191 NoteBuilder::new(sender, SmallRng::from_seed([0; 32]))
192 .add_assets(assets.iter().copied())
193 .note_type(note_type)
194 .serial_number(serial_number)
195 .code(code)
196 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
197 .build()
198 .expect("generated note script should compile")
199}
200
201pub fn create_spawn_note<'note, I>(
212 output_notes: impl IntoIterator<Item = &'note Note, IntoIter = I>,
213) -> anyhow::Result<Note>
214where
215 I: ExactSizeIterator<Item = &'note Note>,
216{
217 let mut output_notes = output_notes.into_iter().peekable();
218 if output_notes.len() == 0 {
219 anyhow::bail!("at least one output note is needed to create a SPAWN note");
220 }
221
222 let sender_id = output_notes
223 .peek()
224 .expect("at least one output note should be present")
225 .metadata()
226 .sender();
227
228 let (note_code, advice_map) = note_script_that_creates_notes(sender_id, output_notes)?;
229
230 let note = NoteBuilder::new(sender_id, SmallRng::from_rng(&mut rand::rng()))
231 .code(note_code)
232 .advice_map(advice_map)
233 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
234 .build()?;
235
236 Ok(note)
237}
238
239fn note_script_that_creates_notes<'note>(
242 sender_id: AccountId,
243 output_notes: impl Iterator<Item = &'note Note>,
244) -> anyhow::Result<(String, AdviceMap)> {
245 let mut advice_map = AdviceMap::default();
246 let mut out = String::from("use miden::protocol::output_note\n\n@note_script\npub proc main\n");
247
248 for (idx, note) in output_notes.into_iter().enumerate() {
249 anyhow::ensure!(
250 note.metadata().sender() == sender_id,
251 "sender IDs of output notes passed to SPAWN note are inconsistent"
252 );
253
254 out.push_str(&format!(
256 r#"exec.::miden::protocol::native_account::get_id
257 # => [native_account_id_suffix, native_account_id_prefix]
258 push.{sender_suffix} assert_eq.err="sender ID suffix does not match native account ID's suffix"
259 # => [native_account_id_prefix]
260 push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix"
261 # => []
262 "#,
263 sender_prefix = sender_id.prefix().as_felt(),
264 sender_suffix = sender_id.suffix()
265 ));
266
267 if idx == 0 {
268 out.push_str("padw padw\n");
269 } else {
270 out.push_str("dropw dropw dropw\n");
271 }
272 out.push_str(&format!(
275 "
276 push.{recipient}
277 push.{note_type}
278 push.{tag}
279 call.::mock::account::create_note
280 # drop the 15 pad elements the account-procedure call convention leaves
281 repeat.15 swap drop end\n",
282 recipient = note.recipient().digest(),
283 note_type = note.metadata().note_type() as u8,
284 tag = note.metadata().tag(),
285 ));
286
287 for attachment in note.attachments().iter() {
288 let attachment_scheme = attachment.attachment_scheme().as_u16();
289 let commitment = attachment.content().to_commitment();
290
291 out.push_str(&format!(
292 "
293 dup
294 push.{commitment}
295 push.{attachment_scheme}
296 # => [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx, note_idx]
297 exec.output_note::add_attachment
298 # => [note_idx]
299 ",
300 ));
301
302 advice_map.insert(commitment, attachment.content().to_elements());
304 }
305
306 for asset in note.assets().iter() {
307 out.push_str(&format!(
308 " dup
309 push.{ASSET_VALUE}
310 push.{ASSET_ID}
311 # => [ASSET_ID, ASSET_VALUE, note_idx, note_idx]
312 call.::miden::standards::wallets::basic::move_asset_to_note
313 # => [note_idx]
314 ",
315 ASSET_ID = asset.to_id_word(),
316 ASSET_VALUE = asset.to_value_word(),
317 ));
318 }
319 }
320
321 out.push_str("repeat.5 dropw end\nend");
322
323 Ok((out, advice_map))
324}