1use alloc::boxed::Box;
2use alloc::string::ToString;
3use core::error::Error;
4
5use miden_protocol::Felt;
6use miden_protocol::account::AccountId;
7use miden_protocol::block::BlockNumber;
8use miden_protocol::note::{Note, NoteScript, NoteScriptRoot};
9
10use self::config::{
11 AllowlistConfigNote,
12 BlocklistConfigNote,
13 ConstantFeePolicyConfigNote,
14 FaucetMetadataConfigNote,
15 FaucetPolicyConfigNote,
16 MinBurnAmountConfigNote,
17 NetworkAccountConfigNote,
18 OwnerConfigNote,
19 PauseConfigNote,
20 RbacConfigNote,
21};
22
23pub mod config;
24pub mod costs;
25
26mod burn;
27pub use burn::BurnNote;
28
29mod fee_sponsorship;
30pub use fee_sponsorship::{FeeSponsorshipNote, FeeSponsorshipNoteStorage};
31
32mod execution_hint;
33pub use execution_hint::NoteExecutionHint;
34
35mod file;
36pub use file::{NoteFile, NoteSyncHint};
37
38mod mint;
39pub use mint::{MintNote, MintNoteStorage};
40
41mod p2id;
42pub use p2id::{P2idNote, P2idNoteStorage};
43
44mod p2ide;
45pub use p2ide::{P2ideNote, P2ideNoteStorage};
46
47mod pswap;
48pub use pswap::{PswapNote, PswapNoteAttachment, PswapNoteStorage};
49
50mod swap;
51pub use swap::{SwapNote, SwapNoteStorage, SwapPayback, payback_serial_from_swap};
52
53mod tx_fee;
54pub use tx_fee::TxFeeNote;
55
56mod network_account_target;
57pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
58
59mod network_note;
60pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
61
62mod standard_note_attachment;
63use miden_protocol::errors::NoteError;
64pub use standard_note_attachment::StandardNoteAttachment;
65#[allow(non_camel_case_types)]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum StandardNote {
72 P2ID,
73 P2IDE,
74 SWAP,
75 PSWAP,
76 MINT,
77 BURN,
78 CONSTANT_FEE_POLICY_CONFIG,
79 FAUCET_POLICY_CONFIG,
80 FAUCET_METADATA_CONFIG,
81 MIN_BURN_AMOUNT_CONFIG,
82 ALLOWLIST_CONFIG,
83 BLOCKLIST_CONFIG,
84 PAUSE_CONFIG,
85 OWNER_CONFIG,
86 RBAC_CONFIG,
87 NETWORK_ACCOUNT_CONFIG,
88 FEE_SPONSORSHIP,
89 TX_FEE,
90}
91
92impl StandardNote {
93 pub fn from_script(script: &NoteScript) -> Option<Self> {
99 Self::from_script_root(script.root())
100 }
101
102 pub fn from_script_root(root: NoteScriptRoot) -> Option<Self> {
105 if root == P2idNote::script_root() {
106 return Some(Self::P2ID);
107 }
108 if root == P2ideNote::script_root() {
109 return Some(Self::P2IDE);
110 }
111 if root == SwapNote::script_root() {
112 return Some(Self::SWAP);
113 }
114 if root == PswapNote::script_root() {
115 return Some(Self::PSWAP);
116 }
117 if root == MintNote::script_root() {
118 return Some(Self::MINT);
119 }
120 if root == BurnNote::script_root() {
121 return Some(Self::BURN);
122 }
123 if root == ConstantFeePolicyConfigNote::script_root() {
124 return Some(Self::CONSTANT_FEE_POLICY_CONFIG);
125 }
126 if root == FaucetPolicyConfigNote::script_root() {
127 return Some(Self::FAUCET_POLICY_CONFIG);
128 }
129 if root == FaucetMetadataConfigNote::script_root() {
130 return Some(Self::FAUCET_METADATA_CONFIG);
131 }
132 if root == MinBurnAmountConfigNote::script_root() {
133 return Some(Self::MIN_BURN_AMOUNT_CONFIG);
134 }
135 if root == AllowlistConfigNote::script_root() {
136 return Some(Self::ALLOWLIST_CONFIG);
137 }
138 if root == BlocklistConfigNote::script_root() {
139 return Some(Self::BLOCKLIST_CONFIG);
140 }
141 if root == PauseConfigNote::script_root() {
142 return Some(Self::PAUSE_CONFIG);
143 }
144 if root == OwnerConfigNote::script_root() {
145 return Some(Self::OWNER_CONFIG);
146 }
147 if root == RbacConfigNote::script_root() {
148 return Some(Self::RBAC_CONFIG);
149 }
150 if root == NetworkAccountConfigNote::script_root() {
151 return Some(Self::NETWORK_ACCOUNT_CONFIG);
152 }
153 if root == FeeSponsorshipNote::script_root() {
154 return Some(Self::FEE_SPONSORSHIP);
155 }
156 if root == TxFeeNote::script_root() {
157 return Some(Self::TX_FEE);
158 }
159
160 None
161 }
162
163 pub fn name(&self) -> &'static str {
168 match self {
169 Self::P2ID => "P2ID",
170 Self::P2IDE => "P2IDE",
171 Self::SWAP => "SWAP",
172 Self::PSWAP => "PSWAP",
173 Self::MINT => "MINT",
174 Self::BURN => "BURN",
175 Self::CONSTANT_FEE_POLICY_CONFIG => "CONSTANT_FEE_POLICY_CONFIG",
176 Self::FAUCET_POLICY_CONFIG => "FAUCET_POLICY_CONFIG",
177 Self::FAUCET_METADATA_CONFIG => "FAUCET_METADATA_CONFIG",
178 Self::MIN_BURN_AMOUNT_CONFIG => "MIN_BURN_AMOUNT_CONFIG",
179 Self::ALLOWLIST_CONFIG => "ALLOWLIST_CONFIG",
180 Self::BLOCKLIST_CONFIG => "BLOCKLIST_CONFIG",
181 Self::PAUSE_CONFIG => "PAUSE_CONFIG",
182 Self::OWNER_CONFIG => "OWNER_CONFIG",
183 Self::RBAC_CONFIG => "RBAC_CONFIG",
184 Self::NETWORK_ACCOUNT_CONFIG => "NETWORK_ACCOUNT_CONFIG",
185 Self::FEE_SPONSORSHIP => "FEE_SPONSORSHIP",
186 Self::TX_FEE => "TX_FEE",
187 }
188 }
189
190 pub fn num_storage_items(&self) -> NumStorageItems {
192 match self {
193 Self::P2ID => NumStorageItems::Exact(P2idNote::NUM_STORAGE_ITEMS),
194 Self::P2IDE => NumStorageItems::Exact(P2ideNote::NUM_STORAGE_ITEMS),
195 Self::SWAP => NumStorageItems::Exact(SwapNote::NUM_STORAGE_ITEMS),
196 Self::PSWAP => NumStorageItems::Exact(PswapNote::NUM_STORAGE_ITEMS),
197 Self::MINT => MintNote::NUM_STORAGE_ITEMS,
198 Self::BURN => NumStorageItems::Exact(BurnNote::NUM_STORAGE_ITEMS),
199 Self::CONSTANT_FEE_POLICY_CONFIG => {
200 NumStorageItems::Exact(ConstantFeePolicyConfigNote::NUM_STORAGE_ITEMS)
201 },
202 Self::FAUCET_POLICY_CONFIG => {
203 NumStorageItems::Exact(FaucetPolicyConfigNote::NUM_STORAGE_ITEMS)
204 },
205 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::NUM_STORAGE_ITEMS,
206 Self::MIN_BURN_AMOUNT_CONFIG => {
207 NumStorageItems::Exact(MinBurnAmountConfigNote::NUM_STORAGE_ITEMS)
208 },
209 Self::ALLOWLIST_CONFIG => {
210 NumStorageItems::Exact(AllowlistConfigNote::NUM_STORAGE_ITEMS)
211 },
212 Self::BLOCKLIST_CONFIG => {
213 NumStorageItems::Exact(BlocklistConfigNote::NUM_STORAGE_ITEMS)
214 },
215 Self::PAUSE_CONFIG => NumStorageItems::Exact(PauseConfigNote::NUM_STORAGE_ITEMS),
216 Self::OWNER_CONFIG => OwnerConfigNote::NUM_STORAGE_ITEMS,
217 Self::RBAC_CONFIG => RbacConfigNote::NUM_STORAGE_ITEMS,
218 Self::NETWORK_ACCOUNT_CONFIG => {
219 NumStorageItems::Exact(NetworkAccountConfigNote::NUM_STORAGE_ITEMS)
220 },
221 Self::FEE_SPONSORSHIP => NumStorageItems::Exact(FeeSponsorshipNote::NUM_STORAGE_ITEMS),
222 Self::TX_FEE => NumStorageItems::Exact(TxFeeNote::NUM_STORAGE_ITEMS),
223 }
224 }
225
226 pub fn script(&self) -> NoteScript {
228 match self {
229 Self::P2ID => P2idNote::script(),
230 Self::P2IDE => P2ideNote::script(),
231 Self::SWAP => SwapNote::script(),
232 Self::PSWAP => PswapNote::script(),
233 Self::MINT => MintNote::script(),
234 Self::BURN => BurnNote::script(),
235 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script(),
236 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script(),
237 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script(),
238 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script(),
239 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script(),
240 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script(),
241 Self::PAUSE_CONFIG => PauseConfigNote::script(),
242 Self::OWNER_CONFIG => OwnerConfigNote::script(),
243 Self::RBAC_CONFIG => RbacConfigNote::script(),
244 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script(),
245 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script(),
246 Self::TX_FEE => TxFeeNote::script(),
247 }
248 }
249
250 pub fn script_root(&self) -> NoteScriptRoot {
252 match self {
253 Self::P2ID => P2idNote::script_root(),
254 Self::P2IDE => P2ideNote::script_root(),
255 Self::SWAP => SwapNote::script_root(),
256 Self::PSWAP => PswapNote::script_root(),
257 Self::MINT => MintNote::script_root(),
258 Self::BURN => BurnNote::script_root(),
259 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script_root(),
260 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script_root(),
261 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script_root(),
262 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script_root(),
263 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script_root(),
264 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script_root(),
265 Self::PAUSE_CONFIG => PauseConfigNote::script_root(),
266 Self::OWNER_CONFIG => OwnerConfigNote::script_root(),
267 Self::RBAC_CONFIG => RbacConfigNote::script_root(),
268 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script_root(),
269 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script_root(),
270 Self::TX_FEE => TxFeeNote::script_root(),
271 }
272 }
273
274 pub fn is_consumable(
283 &self,
284 note: &Note,
285 target_account_id: AccountId,
286 block_ref: BlockNumber,
287 ) -> Option<NoteConsumptionStatus> {
288 match self.is_consumable_inner(note, target_account_id, block_ref) {
289 Ok(status) => status,
290 Err(err) => {
291 let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
292 Some(NoteConsumptionStatus::NeverConsumable(err))
293 },
294 }
295 }
296
297 fn is_consumable_inner(
314 &self,
315 note: &Note,
316 target_account_id: AccountId,
317 block_ref: BlockNumber,
318 ) -> Result<Option<NoteConsumptionStatus>, NoteError> {
319 match self {
320 StandardNote::P2ID => {
321 let input_account_id = P2idNoteStorage::try_from(note.storage().items())
322 .map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
323
324 if input_account_id.target() == target_account_id {
325 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
326 } else {
327 Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
328 }
329 },
330 StandardNote::P2IDE => {
331 let storage = P2ideNoteStorage::try_from(note.storage().items())
332 .map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
333
334 let reclaimer_account_id = storage.reclaimer();
335 let receiver_account_id = storage.target();
336
337 let current_block_height = block_ref.as_u32();
338 let reclaim_height = storage.reclaim_height().unwrap_or_default().as_u32();
339 let timelock_height = storage.timelock_height().unwrap_or_default().as_u32();
340
341 let consumable_after = reclaim_height.max(timelock_height);
343
344 if target_account_id == reclaimer_account_id {
346 if current_block_height >= consumable_after {
349 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
350 } else {
351 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
352 consumable_after,
353 ))))
354 }
355 } else if target_account_id == receiver_account_id {
357 if current_block_height >= timelock_height {
361 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
362 } else {
363 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
364 timelock_height,
365 ))))
366 }
367 } else {
370 Ok(Some(NoteConsumptionStatus::NeverConsumable(
371 "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the reclaimer account".into()
372 )))
373 }
374 },
375
376 StandardNote::TX_FEE => {
380 if usize::from(note.storage().num_items()) != TxFeeNote::NUM_STORAGE_ITEMS {
381 Ok(Some(NoteConsumptionStatus::NeverConsumable(
382 "TX_FEE note carries unexpected storage items".into(),
383 )))
384 } else {
385 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
386 }
387 },
388
389 _ => Ok(None),
392 }
393 }
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
406pub enum NumStorageItems {
407 Exact(usize),
409 Range { min: usize, max: usize },
411 AnyOf(&'static [NumStorageItems]),
414}
415
416impl NumStorageItems {
417 pub fn accepts(&self, num_items: usize) -> bool {
419 match self {
420 Self::Exact(expected) => num_items == *expected,
421 Self::Range { min, max } => (*min..=*max).contains(&num_items),
422 Self::AnyOf(accepted) => accepted.iter().any(|accepted| accepted.accepts(num_items)),
423 }
424 }
425}
426
427pub(crate) fn decode_optional_block_height(
434 item: Felt,
435 error_msg: &'static str,
436) -> Result<Option<BlockNumber>, NoteError> {
437 if item == Felt::ZERO {
438 return Ok(None);
439 }
440
441 let height: u32 = item
442 .as_canonical_u64()
443 .try_into()
444 .map_err(|e| NoteError::other_with_source(error_msg, e))?;
445
446 Ok(Some(BlockNumber::from(height)))
447}
448
449#[derive(Debug)]
458pub enum NoteConsumptionStatus {
459 Consumable,
461 ConsumableAfter(BlockNumber),
463 ConsumableWithAuthorization,
465 UnconsumableConditions,
468 NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
470}
471
472impl Clone for NoteConsumptionStatus {
473 fn clone(&self) -> Self {
474 match self {
475 NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
476 NoteConsumptionStatus::ConsumableAfter(block_height) => {
477 NoteConsumptionStatus::ConsumableAfter(*block_height)
478 },
479 NoteConsumptionStatus::ConsumableWithAuthorization => {
480 NoteConsumptionStatus::ConsumableWithAuthorization
481 },
482 NoteConsumptionStatus::UnconsumableConditions => {
483 NoteConsumptionStatus::UnconsumableConditions
484 },
485 NoteConsumptionStatus::NeverConsumable(error) => {
486 let err = error.to_string();
487 NoteConsumptionStatus::NeverConsumable(err.into())
488 },
489 }
490 }
491}
492
493#[cfg(test)]
497mod tests {
498 use miden_protocol::MAX_NOTE_STORAGE_ITEMS;
499
500 use super::*;
501
502 #[test]
506 fn mint_accepts_both_the_private_and_the_public_storage_sizes() {
507 for num_items in [MintNote::NUM_STORAGE_ITEMS_PRIVATE, 20, 21, MAX_NOTE_STORAGE_ITEMS] {
508 assert!(
509 StandardNote::MINT.num_storage_items().accepts(num_items),
510 "{num_items} items should be accepted"
511 );
512 }
513
514 for num_items in [0, 12, 14, 19, MAX_NOTE_STORAGE_ITEMS + 1] {
515 assert!(
516 !StandardNote::MINT.num_storage_items().accepts(num_items),
517 "{num_items} items should be rejected"
518 );
519 }
520 }
521
522 #[test]
525 fn config_notes_accept_only_the_sizes_their_actions_use() {
526 for (note, accepted, rejected) in [
527 (StandardNote::OWNER_CONFIG, [1, 3].as_slice(), [0, 2, 4].as_slice()),
528 (StandardNote::RBAC_CONFIG, [2, 3, 4].as_slice(), [0, 1, 5].as_slice()),
529 (
530 StandardNote::FAUCET_METADATA_CONFIG,
531 [2, 32].as_slice(),
532 [0, 3, 31, 33].as_slice(),
533 ),
534 ] {
535 for &num_items in accepted {
536 assert!(
537 note.num_storage_items().accepts(num_items),
538 "{} should accept {num_items} items",
539 note.name()
540 );
541 }
542
543 for &num_items in rejected {
544 assert!(
545 !note.num_storage_items().accepts(num_items),
546 "{} should reject {num_items} items",
547 note.name()
548 );
549 }
550 }
551 }
552
553 #[test]
555 fn fixed_size_notes_report_an_exact_size() {
556 for (note, num_items) in [
557 (StandardNote::P2ID, P2idNote::NUM_STORAGE_ITEMS),
558 (StandardNote::P2IDE, P2ideNote::NUM_STORAGE_ITEMS),
559 (StandardNote::TX_FEE, TxFeeNote::NUM_STORAGE_ITEMS),
560 ] {
561 assert_eq!(note.num_storage_items(), NumStorageItems::Exact(num_items));
562 assert!(!note.num_storage_items().accepts(num_items + 1));
563 }
564 }
565}