1use alloc::string::String;
2use alloc::vec::Vec;
3
4use miden_processor::crypto::random::RandomCoin;
5use miden_protocol::Word;
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, NoteTag, NoteType, PartialNoteMetadata};
11use miden_protocol::vm::AdviceMap;
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]
29macro_rules! assert_execution_error {
30 ($execution_result:expr, matches $pat:pat $(if $guard:expr)? $(,)?) => {
31 match $execution_result {
32 Err($crate::ExecError($pat)) $(if $guard)? => {},
33 Ok(_) => ::core::panic!(
34 "Execution was unexpectedly successful\nexpected error: {}",
35 ::core::stringify!($pat),
36 ),
37 Err(err) => ::core::panic!(
38 "Execution error did not match:\nexpected: {}\nactual: {}",
39 ::core::stringify!($pat),
40 err,
41 ),
42 }
43 };
44
45 ($execution_result:expr, $expected:expr $(,)?) => {
46 match $execution_result {
47 Err($crate::ExecError(actual)) => {
48 if !$expected.matches_execution_error(&actual) {
49 ::core::panic!(
50 "Execution error did not match:\nexpected: {}\nactual: {}",
51 $expected,
52 actual,
53 );
54 }
55 },
56 Ok(_) => ::core::panic!(
57 "Execution was unexpectedly successful\nexpected error: {}",
58 $expected,
59 ),
60 }
61 };
62}
63
64#[macro_export]
69macro_rules! assert_transaction_executor_error {
70 ($execution_result:expr, matches $pat:pat $(if $guard:expr)? $(,)?) => {
71 match $execution_result {
72 Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed($pat))
73 $(if $guard)? => {},
74 Ok(_) => ::core::panic!(
75 "Execution was unexpectedly successful\nexpected error: {}",
76 ::core::stringify!($pat),
77 ),
78 Err(err) => ::core::panic!(
79 "Execution error did not match:\nexpected: {}\nactual: {}",
80 ::core::stringify!($pat),
81 err,
82 ),
83 }
84 };
85
86 ($execution_result:expr, $expected:expr $(,)?) => {
87 match $execution_result {
88 Err(miden_tx::TransactionExecutorError::TransactionProgramExecutionFailed(actual)) => {
89 if !$expected.matches_execution_error(&actual) {
90 ::core::panic!(
91 "Execution error did not match:\nexpected: {}\nactual: {}",
92 $expected,
93 actual,
94 );
95 }
96 },
97 Ok(_) => ::core::panic!(
98 "Execution was unexpectedly successful\nexpected error: {}",
99 $expected,
100 ),
101 Err(err) => ::core::panic!(
102 "Execution error did not match:\nexpected: {}\nactual: {}",
103 $expected,
104 err,
105 ),
106 }
107 };
108}
109
110pub fn create_public_p2any_note(
120 sender: AccountId,
121 assets: impl IntoIterator<Item = Asset>,
122) -> Note {
123 let mut rng = RandomCoin::new(Default::default());
124 create_p2any_note(sender, NoteType::Public, assets, &mut rng)
125}
126
127pub fn create_p2any_note(
134 sender: AccountId,
135 note_type: NoteType,
136 assets: impl IntoIterator<Item = Asset>,
137 rng: &mut RandomCoin,
138) -> Note {
139 let serial_number = rng.draw_word();
140 let assets: Vec<_> = assets.into_iter().collect();
141 let mut code_body = String::new();
142 for asset_idx in 0..assets.len() {
143 code_body.push_str(&format!(
144 "
145 # => [dest_ptr]
146
147 # current_asset_ptr = dest_ptr + ASSET_SIZE * asset_idx
148 dup push.ASSET_SIZE mul.{asset_idx}
149 # => [current_asset_ptr, dest_ptr]
150
151 padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_le
152 # => [ASSET_VALUE, current_asset_ptr, dest_ptr]
153
154 padw movup.8 mem_loadw_le
155 # => [ASSET_KEY, ASSET_VALUE, current_asset_ptr, dest_ptr]
156
157 padw padw swapdw
158 # => [ASSET_KEY, ASSET_VALUE, pad(12), dest_ptr]
159
160 call.wallet::receive_asset
161 # => [pad(16), dest_ptr]
162
163 dropw dropw dropw dropw
164 # => [dest_ptr]
165 ",
166 ));
167 }
168 code_body.push_str("dropw dropw dropw dropw");
169
170 let code = format!(
171 r#"
172 use mock::account
173 use miden::protocol::active_note
174 use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET
175 use ::miden::protocol::asset::ASSET_SIZE
176 use miden::standards::wallets::basic->wallet
177
178 @note_script
179 pub proc main
180 # fetch pointer & number of assets
181 push.0 exec.active_note::get_assets # [num_assets]
182
183 # runtime-check we got the expected count
184 push.{num_assets} assert_eq.err="unexpected number of assets" # []
185
186 push.0 # [dest_ptr]
187
188 {code_body}
189 dropw dropw dropw dropw
190 end
191 "#,
192 num_assets = assets.len(),
193 );
194
195 NoteBuilder::new(sender, SmallRng::from_seed([0; 32]))
196 .add_assets(assets.iter().copied())
197 .note_type(note_type)
198 .serial_number(serial_number)
199 .code(code)
200 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
201 .build()
202 .expect("generated note script should compile")
203}
204
205pub fn create_spawn_note<'note, I>(
216 output_notes: impl IntoIterator<Item = &'note Note, IntoIter = I>,
217) -> anyhow::Result<Note>
218where
219 I: ExactSizeIterator<Item = &'note Note>,
220{
221 let mut output_notes = output_notes.into_iter().peekable();
222 if output_notes.len() == 0 {
223 anyhow::bail!("at least one output note is needed to create a SPAWN note");
224 }
225
226 let sender_id = output_notes
227 .peek()
228 .expect("at least one output note should be present")
229 .metadata()
230 .sender();
231
232 let (note_code, advice_map) = note_script_that_creates_notes(sender_id, output_notes)?;
233
234 let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng())
235 .code(note_code)
236 .advice_map(advice_map)
237 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
238 .build()?;
239
240 Ok(note)
241}
242
243fn note_script_that_creates_notes<'note>(
246 sender_id: AccountId,
247 output_notes: impl Iterator<Item = &'note Note>,
248) -> anyhow::Result<(String, AdviceMap)> {
249 let mut advice_map = AdviceMap::default();
250 let mut out = String::from("use miden::protocol::output_note\n\n@note_script\npub proc main\n");
251
252 for (idx, note) in output_notes.into_iter().enumerate() {
253 anyhow::ensure!(
254 note.metadata().sender() == sender_id,
255 "sender IDs of output notes passed to SPAWN note are inconsistent"
256 );
257
258 out.push_str(&format!(
260 r#"exec.::miden::protocol::native_account::get_id
261 # => [native_account_id_suffix, native_account_id_prefix]
262 push.{sender_suffix} assert_eq.err="sender ID suffix does not match native account ID's suffix"
263 # => [native_account_id_prefix]
264 push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix"
265 # => []
266 "#,
267 sender_prefix = sender_id.prefix().as_felt(),
268 sender_suffix = sender_id.suffix()
269 ));
270
271 if idx == 0 {
272 out.push_str("padw padw\n");
273 } else {
274 out.push_str("dropw dropw dropw\n");
275 }
276 out.push_str(&format!(
277 "
278 push.{recipient}
279 push.{note_type}
280 push.{tag}
281 exec.output_note::create\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_KEY}
311 # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx]
312 call.::miden::standards::wallets::basic::move_asset_to_note
313 # => [note_idx]
314 ",
315 ASSET_KEY = asset.to_key_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}
325
326pub fn create_p2id_note_exact(
328 sender: AccountId,
329 target: AccountId,
330 assets: Vec<Asset>,
331 note_type: NoteType,
332 serial_num: Word,
333) -> Result<Note, NoteError> {
334 let recipient = P2idNoteStorage::new(target).into_recipient(serial_num);
335
336 let tag = NoteTag::with_account_target(target);
337
338 let metadata = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
339 let vault = NoteAssets::new(assets)?;
340
341 Ok(Note::new(vault, metadata, recipient))
342}