|
| 1 | +//! Assertion macro for note-lifecycle checks in tests. |
| 2 | +
|
| 3 | +use alloc::vec::Vec; |
| 4 | + |
| 5 | +use miden_protocol::account::AccountId; |
| 6 | +use miden_protocol::asset::Asset; |
| 7 | +use miden_protocol::note::NoteType; |
| 8 | +use miden_protocol::transaction::ExecutedTransaction; |
| 9 | + |
| 10 | +/// Spec for [`assert_note_created!`]. Fields left as `None` are skipped. |
| 11 | +#[doc(hidden)] |
| 12 | +#[derive(Default, Debug, Clone)] |
| 13 | +pub struct OutputNoteSpec { |
| 14 | + pub note_type: Option<NoteType>, |
| 15 | + pub sender: Option<AccountId>, |
| 16 | + pub assets: Option<Vec<Asset>>, |
| 17 | +} |
| 18 | + |
| 19 | +/// Returns `true` if at least one output note in `tx` matches `spec`. |
| 20 | +#[doc(hidden)] |
| 21 | +pub fn check_output_note_created(tx: &ExecutedTransaction, spec: &OutputNoteSpec) -> bool { |
| 22 | + tx.output_notes().iter().any(|note| { |
| 23 | + if let Some(expected) = spec.note_type |
| 24 | + && note.metadata().note_type() != expected |
| 25 | + { |
| 26 | + return false; |
| 27 | + } |
| 28 | + if let Some(expected) = spec.sender |
| 29 | + && note.metadata().sender() != expected |
| 30 | + { |
| 31 | + return false; |
| 32 | + } |
| 33 | + if let Some(expected) = spec.assets.as_ref() { |
| 34 | + let actual = note.assets(); |
| 35 | + if actual.num_assets() != expected.len() { |
| 36 | + return false; |
| 37 | + } |
| 38 | + // Each actual matches at most once (otherwise [A,A] would match [A,B]). |
| 39 | + let mut consumed = vec![false; expected.len()]; |
| 40 | + let matched = expected.iter().all(|exp| { |
| 41 | + let slot = actual.iter().enumerate().find(|(i, a)| !consumed[*i] && *a == exp); |
| 42 | + if let Some((i, _)) = slot { |
| 43 | + consumed[i] = true; |
| 44 | + true |
| 45 | + } else { |
| 46 | + false |
| 47 | + } |
| 48 | + }); |
| 49 | + if !matched { |
| 50 | + return false; |
| 51 | + } |
| 52 | + } |
| 53 | + true |
| 54 | + }) |
| 55 | +} |
| 56 | + |
| 57 | +/// Asserts the tx emitted a note matching the spec. Fields are optional; unset ones are skipped. |
| 58 | +/// |
| 59 | +/// # Example |
| 60 | +/// ```ignore |
| 61 | +/// use miden_testing::assert_note_created; |
| 62 | +/// use miden_protocol::note::NoteType; |
| 63 | +/// |
| 64 | +/// assert_note_created!( |
| 65 | +/// executed_tx, |
| 66 | +/// note_type: NoteType::Public, |
| 67 | +/// sender: faucet.id(), |
| 68 | +/// assets: [FungibleAsset::new(faucet.id(), amount)?.into()], |
| 69 | +/// ); |
| 70 | +/// ``` |
| 71 | +#[macro_export] |
| 72 | +macro_rules! assert_note_created { |
| 73 | + ($tx:expr $(, $key:ident : $val:expr)* $(,)?) => {{ |
| 74 | + #[allow(unused_mut)] |
| 75 | + let mut spec = $crate::asserts::OutputNoteSpec::default(); |
| 76 | + $( |
| 77 | + $crate::__assert_note_created_field!(spec, $key, $val); |
| 78 | + )* |
| 79 | + let tx: &::miden_protocol::transaction::ExecutedTransaction = &$tx; |
| 80 | + assert!( |
| 81 | + $crate::asserts::check_output_note_created(tx, &spec), |
| 82 | + "no output note matches spec: {:?}\n tx produced {} output note(s)", |
| 83 | + spec, |
| 84 | + tx.output_notes().num_notes(), |
| 85 | + ); |
| 86 | + }}; |
| 87 | +} |
| 88 | + |
| 89 | +#[doc(hidden)] |
| 90 | +#[macro_export] |
| 91 | +macro_rules! __assert_note_created_field { |
| 92 | + ($spec:ident,note_type, $val:expr) => { |
| 93 | + $spec.note_type = ::core::option::Option::Some($val); |
| 94 | + }; |
| 95 | + ($spec:ident,sender, $val:expr) => { |
| 96 | + $spec.sender = ::core::option::Option::Some($val); |
| 97 | + }; |
| 98 | + ($spec:ident,assets, $val:expr) => { |
| 99 | + $spec.assets = ::core::option::Option::Some( |
| 100 | + ::core::iter::IntoIterator::into_iter($val) |
| 101 | + .map(::core::convert::Into::into) |
| 102 | + .collect::<::alloc::vec::Vec<::miden_protocol::asset::Asset>>(), |
| 103 | + ); |
| 104 | + }; |
| 105 | + ($spec:ident, $key:ident, $val:expr) => { |
| 106 | + ::core::compile_error!(concat!( |
| 107 | + "unknown field in assert_note_created!: `", |
| 108 | + stringify!($key), |
| 109 | + "`. Supported fields: note_type, sender, assets", |
| 110 | + )); |
| 111 | + }; |
| 112 | +} |
0 commit comments