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]
22macro_rules! assert_execution_error {
23 ($execution_result:expr, $expected_err:expr) => {
24 match $execution_result {
25 Err($crate::ExecError(miden_processor::ExecutionError::OperationError { label: _, source_file: _, err: miden_processor::operation::OperationError::FailedAssertion { err_code, err_msg } })) => {
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::OperationError {
48 label: _,
49 source_file: _,
50 err: miden_processor::operation::OperationError::FailedAssertion {
51 err_code,
52 err_msg,
53 },
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 = RandomCoin::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 RandomCoin,
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 asset_idx in 0..assets.len() {
104 code_body.push_str(&format!(
105 "
106 # => [dest_ptr]
107
108 # current_asset_ptr = dest_ptr + ASSET_SIZE * asset_idx
109 dup push.ASSET_SIZE mul.{asset_idx}
110 # => [current_asset_ptr, dest_ptr]
111
112 padw dup.4 add.ASSET_VALUE_MEMORY_OFFSET mem_loadw_le
113 # => [ASSET_VALUE, current_asset_ptr, dest_ptr]
114
115 padw movup.8 mem_loadw_le
116 # => [ASSET_KEY, ASSET_VALUE, current_asset_ptr, dest_ptr]
117
118 padw padw swapdw
119 # => [ASSET_KEY, ASSET_VALUE, pad(12), dest_ptr]
120
121 call.wallet::receive_asset
122 # => [pad(16), dest_ptr]
123
124 dropw dropw dropw dropw
125 # => [dest_ptr]
126 ",
127 ));
128 }
129 code_body.push_str("dropw dropw dropw dropw");
130
131 let code = format!(
132 r#"
133 use mock::account
134 use miden::protocol::active_note
135 use ::miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET
136 use ::miden::protocol::asset::ASSET_SIZE
137 use miden::standards::wallets::basic->wallet
138
139 @note_script
140 pub proc main
141 # fetch pointer & number of assets
142 push.0 exec.active_note::get_assets # [num_assets]
143
144 # runtime-check we got the expected count
145 push.{num_assets} assert_eq.err="unexpected number of assets" # []
146
147 push.0 # [dest_ptr]
148
149 {code_body}
150 dropw dropw dropw dropw
151 end
152 "#,
153 num_assets = assets.len(),
154 );
155
156 NoteBuilder::new(sender, SmallRng::from_seed([0; 32]))
157 .add_assets(assets.iter().copied())
158 .note_type(note_type)
159 .serial_number(serial_number)
160 .code(code)
161 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
162 .build()
163 .expect("generated note script should compile")
164}
165
166pub fn create_spawn_note<'note, I>(
177 output_notes: impl IntoIterator<Item = &'note Note, IntoIter = I>,
178) -> anyhow::Result<Note>
179where
180 I: ExactSizeIterator<Item = &'note Note>,
181{
182 let mut output_notes = output_notes.into_iter().peekable();
183 if output_notes.len() == 0 {
184 anyhow::bail!("at least one output note is needed to create a SPAWN note");
185 }
186
187 let sender_id = output_notes
188 .peek()
189 .expect("at least one output note should be present")
190 .metadata()
191 .sender();
192
193 let (note_code, advice_map) = note_script_that_creates_notes(sender_id, output_notes)?;
194
195 let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng())
196 .code(note_code)
197 .advice_map(advice_map)
198 .dynamically_linked_libraries(CodeBuilder::mock_libraries())
199 .build()?;
200
201 Ok(note)
202}
203
204fn note_script_that_creates_notes<'note>(
207 sender_id: AccountId,
208 output_notes: impl Iterator<Item = &'note Note>,
209) -> anyhow::Result<(String, AdviceMap)> {
210 let mut advice_map = AdviceMap::default();
211 let mut out = String::from("use miden::protocol::output_note\n\n@note_script\npub proc main\n");
212
213 for (idx, note) in output_notes.into_iter().enumerate() {
214 anyhow::ensure!(
215 note.metadata().sender() == sender_id,
216 "sender IDs of output notes passed to SPAWN note are inconsistent"
217 );
218
219 out.push_str(&format!(
221 r#"exec.::miden::protocol::native_account::get_id
222 # => [native_account_id_suffix, native_account_id_prefix]
223 push.{sender_suffix} assert_eq.err="sender ID suffix does not match native account ID's suffix"
224 # => [native_account_id_prefix]
225 push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix"
226 # => []
227 "#,
228 sender_prefix = sender_id.prefix().as_felt(),
229 sender_suffix = sender_id.suffix()
230 ));
231
232 if idx == 0 {
233 out.push_str("padw padw\n");
234 } else {
235 out.push_str("dropw dropw dropw\n");
236 }
237 out.push_str(&format!(
238 "
239 push.{recipient}
240 push.{note_type}
241 push.{tag}
242 exec.output_note::create\n",
243 recipient = note.recipient().digest(),
244 note_type = note.metadata().note_type() as u8,
245 tag = note.metadata().tag(),
246 ));
247
248 for attachment in note.attachments().iter() {
249 let attachment_scheme = attachment.attachment_scheme().as_u16();
250 let commitment = attachment.content().to_commitment();
251
252 out.push_str(&format!(
253 "
254 dup
255 push.{commitment}
256 push.{attachment_scheme}
257 # => [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx, note_idx]
258 exec.output_note::add_attachment
259 # => [note_idx]
260 ",
261 ));
262
263 advice_map.insert(commitment, attachment.content().to_elements());
265 }
266
267 for asset in note.assets().iter() {
268 out.push_str(&format!(
269 " dup
270 push.{ASSET_VALUE}
271 push.{ASSET_KEY}
272 # => [ASSET_KEY, ASSET_VALUE, note_idx, note_idx]
273 call.::miden::standards::wallets::basic::move_asset_to_note
274 # => [note_idx]
275 ",
276 ASSET_KEY = asset.to_key_word(),
277 ASSET_VALUE = asset.to_value_word(),
278 ));
279 }
280 }
281
282 out.push_str("repeat.5 dropw end\nend");
283
284 Ok((out, advice_map))
285}
286
287pub fn create_p2id_note_exact(
289 sender: AccountId,
290 target: AccountId,
291 assets: Vec<Asset>,
292 note_type: NoteType,
293 serial_num: Word,
294) -> Result<Note, NoteError> {
295 let recipient = P2idNoteStorage::new(target).into_recipient(serial_num);
296
297 let tag = NoteTag::with_account_target(target);
298
299 let metadata = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
300 let vault = NoteAssets::new(assets)?;
301
302 Ok(Note::new(vault, metadata, recipient))
303}