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