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 mint;
36pub use mint::{MintNote, MintNoteStorage};
37
38mod p2id;
39pub use p2id::{P2idNote, P2idNoteStorage};
40
41mod p2ide;
42pub use p2ide::{P2ideNote, P2ideNoteStorage};
43
44mod pswap;
45pub use pswap::{PswapNote, PswapNoteAttachment, PswapNoteStorage};
46
47mod swap;
48pub use swap::{SwapNote, SwapNoteStorage, SwapPayback, payback_serial_from_swap};
49
50mod tx_fee;
51pub use tx_fee::TxFeeNote;
52
53mod network_account_target;
54pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
55
56mod network_note;
57pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
58
59mod standard_note_attachment;
60use miden_protocol::errors::NoteError;
61pub use standard_note_attachment::StandardNoteAttachment;
62#[allow(non_camel_case_types)]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum StandardNote {
69 P2ID,
70 P2IDE,
71 SWAP,
72 PSWAP,
73 MINT,
74 BURN,
75 CONSTANT_FEE_POLICY_CONFIG,
76 FAUCET_POLICY_CONFIG,
77 FAUCET_METADATA_CONFIG,
78 MIN_BURN_AMOUNT_CONFIG,
79 ALLOWLIST_CONFIG,
80 BLOCKLIST_CONFIG,
81 PAUSE_CONFIG,
82 OWNER_CONFIG,
83 RBAC_CONFIG,
84 NETWORK_ACCOUNT_CONFIG,
85 FEE_SPONSORSHIP,
86 TX_FEE,
87}
88
89impl StandardNote {
90 pub fn from_script(script: &NoteScript) -> Option<Self> {
96 Self::from_script_root(script.root())
97 }
98
99 pub fn from_script_root(root: NoteScriptRoot) -> Option<Self> {
102 if root == P2idNote::script_root() {
103 return Some(Self::P2ID);
104 }
105 if root == P2ideNote::script_root() {
106 return Some(Self::P2IDE);
107 }
108 if root == SwapNote::script_root() {
109 return Some(Self::SWAP);
110 }
111 if root == PswapNote::script_root() {
112 return Some(Self::PSWAP);
113 }
114 if root == MintNote::script_root() {
115 return Some(Self::MINT);
116 }
117 if root == BurnNote::script_root() {
118 return Some(Self::BURN);
119 }
120 if root == ConstantFeePolicyConfigNote::script_root() {
121 return Some(Self::CONSTANT_FEE_POLICY_CONFIG);
122 }
123 if root == FaucetPolicyConfigNote::script_root() {
124 return Some(Self::FAUCET_POLICY_CONFIG);
125 }
126 if root == FaucetMetadataConfigNote::script_root() {
127 return Some(Self::FAUCET_METADATA_CONFIG);
128 }
129 if root == MinBurnAmountConfigNote::script_root() {
130 return Some(Self::MIN_BURN_AMOUNT_CONFIG);
131 }
132 if root == AllowlistConfigNote::script_root() {
133 return Some(Self::ALLOWLIST_CONFIG);
134 }
135 if root == BlocklistConfigNote::script_root() {
136 return Some(Self::BLOCKLIST_CONFIG);
137 }
138 if root == PauseConfigNote::script_root() {
139 return Some(Self::PAUSE_CONFIG);
140 }
141 if root == OwnerConfigNote::script_root() {
142 return Some(Self::OWNER_CONFIG);
143 }
144 if root == RbacConfigNote::script_root() {
145 return Some(Self::RBAC_CONFIG);
146 }
147 if root == NetworkAccountConfigNote::script_root() {
148 return Some(Self::NETWORK_ACCOUNT_CONFIG);
149 }
150 if root == FeeSponsorshipNote::script_root() {
151 return Some(Self::FEE_SPONSORSHIP);
152 }
153 if root == TxFeeNote::script_root() {
154 return Some(Self::TX_FEE);
155 }
156
157 None
158 }
159
160 pub fn name(&self) -> &'static str {
165 match self {
166 Self::P2ID => "P2ID",
167 Self::P2IDE => "P2IDE",
168 Self::SWAP => "SWAP",
169 Self::PSWAP => "PSWAP",
170 Self::MINT => "MINT",
171 Self::BURN => "BURN",
172 Self::CONSTANT_FEE_POLICY_CONFIG => "CONSTANT_FEE_POLICY_CONFIG",
173 Self::FAUCET_POLICY_CONFIG => "FAUCET_POLICY_CONFIG",
174 Self::FAUCET_METADATA_CONFIG => "FAUCET_METADATA_CONFIG",
175 Self::MIN_BURN_AMOUNT_CONFIG => "MIN_BURN_AMOUNT_CONFIG",
176 Self::ALLOWLIST_CONFIG => "ALLOWLIST_CONFIG",
177 Self::BLOCKLIST_CONFIG => "BLOCKLIST_CONFIG",
178 Self::PAUSE_CONFIG => "PAUSE_CONFIG",
179 Self::OWNER_CONFIG => "OWNER_CONFIG",
180 Self::RBAC_CONFIG => "RBAC_CONFIG",
181 Self::NETWORK_ACCOUNT_CONFIG => "NETWORK_ACCOUNT_CONFIG",
182 Self::FEE_SPONSORSHIP => "FEE_SPONSORSHIP",
183 Self::TX_FEE => "TX_FEE",
184 }
185 }
186
187 pub fn num_storage_items(&self) -> NumStorageItems {
189 match self {
190 Self::P2ID => NumStorageItems::Exact(P2idNote::NUM_STORAGE_ITEMS),
191 Self::P2IDE => NumStorageItems::Exact(P2ideNote::NUM_STORAGE_ITEMS),
192 Self::SWAP => NumStorageItems::Exact(SwapNote::NUM_STORAGE_ITEMS),
193 Self::PSWAP => NumStorageItems::Exact(PswapNote::NUM_STORAGE_ITEMS),
194 Self::MINT => MintNote::NUM_STORAGE_ITEMS,
195 Self::BURN => NumStorageItems::Exact(BurnNote::NUM_STORAGE_ITEMS),
196 Self::CONSTANT_FEE_POLICY_CONFIG => {
197 NumStorageItems::Exact(ConstantFeePolicyConfigNote::NUM_STORAGE_ITEMS)
198 },
199 Self::FAUCET_POLICY_CONFIG => {
200 NumStorageItems::Exact(FaucetPolicyConfigNote::NUM_STORAGE_ITEMS)
201 },
202 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::NUM_STORAGE_ITEMS,
203 Self::MIN_BURN_AMOUNT_CONFIG => {
204 NumStorageItems::Exact(MinBurnAmountConfigNote::NUM_STORAGE_ITEMS)
205 },
206 Self::ALLOWLIST_CONFIG => {
207 NumStorageItems::Exact(AllowlistConfigNote::NUM_STORAGE_ITEMS)
208 },
209 Self::BLOCKLIST_CONFIG => {
210 NumStorageItems::Exact(BlocklistConfigNote::NUM_STORAGE_ITEMS)
211 },
212 Self::PAUSE_CONFIG => NumStorageItems::Exact(PauseConfigNote::NUM_STORAGE_ITEMS),
213 Self::OWNER_CONFIG => OwnerConfigNote::NUM_STORAGE_ITEMS,
214 Self::RBAC_CONFIG => RbacConfigNote::NUM_STORAGE_ITEMS,
215 Self::NETWORK_ACCOUNT_CONFIG => {
216 NumStorageItems::Exact(NetworkAccountConfigNote::NUM_STORAGE_ITEMS)
217 },
218 Self::FEE_SPONSORSHIP => NumStorageItems::Exact(FeeSponsorshipNote::NUM_STORAGE_ITEMS),
219 Self::TX_FEE => NumStorageItems::Exact(TxFeeNote::NUM_STORAGE_ITEMS),
220 }
221 }
222
223 pub fn script(&self) -> NoteScript {
225 match self {
226 Self::P2ID => P2idNote::script(),
227 Self::P2IDE => P2ideNote::script(),
228 Self::SWAP => SwapNote::script(),
229 Self::PSWAP => PswapNote::script(),
230 Self::MINT => MintNote::script(),
231 Self::BURN => BurnNote::script(),
232 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script(),
233 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script(),
234 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script(),
235 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script(),
236 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script(),
237 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script(),
238 Self::PAUSE_CONFIG => PauseConfigNote::script(),
239 Self::OWNER_CONFIG => OwnerConfigNote::script(),
240 Self::RBAC_CONFIG => RbacConfigNote::script(),
241 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script(),
242 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script(),
243 Self::TX_FEE => TxFeeNote::script(),
244 }
245 }
246
247 pub fn script_root(&self) -> NoteScriptRoot {
249 match self {
250 Self::P2ID => P2idNote::script_root(),
251 Self::P2IDE => P2ideNote::script_root(),
252 Self::SWAP => SwapNote::script_root(),
253 Self::PSWAP => PswapNote::script_root(),
254 Self::MINT => MintNote::script_root(),
255 Self::BURN => BurnNote::script_root(),
256 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script_root(),
257 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script_root(),
258 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script_root(),
259 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script_root(),
260 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script_root(),
261 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script_root(),
262 Self::PAUSE_CONFIG => PauseConfigNote::script_root(),
263 Self::OWNER_CONFIG => OwnerConfigNote::script_root(),
264 Self::RBAC_CONFIG => RbacConfigNote::script_root(),
265 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script_root(),
266 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script_root(),
267 Self::TX_FEE => TxFeeNote::script_root(),
268 }
269 }
270
271 pub fn is_consumable(
280 &self,
281 note: &Note,
282 target_account_id: AccountId,
283 block_ref: BlockNumber,
284 ) -> Option<NoteConsumptionStatus> {
285 match self.is_consumable_inner(note, target_account_id, block_ref) {
286 Ok(status) => status,
287 Err(err) => {
288 let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
289 Some(NoteConsumptionStatus::NeverConsumable(err))
290 },
291 }
292 }
293
294 fn is_consumable_inner(
311 &self,
312 note: &Note,
313 target_account_id: AccountId,
314 block_ref: BlockNumber,
315 ) -> Result<Option<NoteConsumptionStatus>, NoteError> {
316 match self {
317 StandardNote::P2ID => {
318 let input_account_id = P2idNoteStorage::try_from(note.storage().items())
319 .map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
320
321 if input_account_id.target() == target_account_id {
322 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
323 } else {
324 Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
325 }
326 },
327 StandardNote::P2IDE => {
328 let storage = P2ideNoteStorage::try_from(note.storage().items())
329 .map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
330
331 let reclaimer_account_id = storage.reclaimer();
332 let receiver_account_id = storage.target();
333
334 let current_block_height = block_ref.as_u32();
335 let reclaim_height = storage.reclaim_height().unwrap_or_default().as_u32();
336 let timelock_height = storage.timelock_height().unwrap_or_default().as_u32();
337
338 let consumable_after = reclaim_height.max(timelock_height);
340
341 if target_account_id == reclaimer_account_id {
343 if current_block_height >= consumable_after {
346 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
347 } else {
348 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
349 consumable_after,
350 ))))
351 }
352 } else if target_account_id == receiver_account_id {
354 if current_block_height >= timelock_height {
358 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
359 } else {
360 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
361 timelock_height,
362 ))))
363 }
364 } else {
367 Ok(Some(NoteConsumptionStatus::NeverConsumable(
368 "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the reclaimer account".into()
369 )))
370 }
371 },
372
373 StandardNote::TX_FEE => {
377 if usize::from(note.storage().num_items()) != TxFeeNote::NUM_STORAGE_ITEMS {
378 Ok(Some(NoteConsumptionStatus::NeverConsumable(
379 "TX_FEE note carries unexpected storage items".into(),
380 )))
381 } else {
382 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
383 }
384 },
385
386 _ => Ok(None),
389 }
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub enum NumStorageItems {
404 Exact(usize),
406 Range { min: usize, max: usize },
408 AnyOf(&'static [NumStorageItems]),
411}
412
413impl NumStorageItems {
414 pub fn accepts(&self, num_items: usize) -> bool {
416 match self {
417 Self::Exact(expected) => num_items == *expected,
418 Self::Range { min, max } => (*min..=*max).contains(&num_items),
419 Self::AnyOf(accepted) => accepted.iter().any(|accepted| accepted.accepts(num_items)),
420 }
421 }
422}
423
424pub(crate) fn decode_optional_block_height(
431 item: Felt,
432 error_msg: &'static str,
433) -> Result<Option<BlockNumber>, NoteError> {
434 if item == Felt::ZERO {
435 return Ok(None);
436 }
437
438 let height: u32 = item
439 .as_canonical_u64()
440 .try_into()
441 .map_err(|e| NoteError::other_with_source(error_msg, e))?;
442
443 Ok(Some(BlockNumber::from(height)))
444}
445
446#[derive(Debug)]
455pub enum NoteConsumptionStatus {
456 Consumable,
458 ConsumableAfter(BlockNumber),
460 ConsumableWithAuthorization,
462 UnconsumableConditions,
465 NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
467}
468
469impl Clone for NoteConsumptionStatus {
470 fn clone(&self) -> Self {
471 match self {
472 NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
473 NoteConsumptionStatus::ConsumableAfter(block_height) => {
474 NoteConsumptionStatus::ConsumableAfter(*block_height)
475 },
476 NoteConsumptionStatus::ConsumableWithAuthorization => {
477 NoteConsumptionStatus::ConsumableWithAuthorization
478 },
479 NoteConsumptionStatus::UnconsumableConditions => {
480 NoteConsumptionStatus::UnconsumableConditions
481 },
482 NoteConsumptionStatus::NeverConsumable(error) => {
483 let err = error.to_string();
484 NoteConsumptionStatus::NeverConsumable(err.into())
485 },
486 }
487 }
488}
489
490#[cfg(test)]
494mod tests {
495 use miden_protocol::MAX_NOTE_STORAGE_ITEMS;
496
497 use super::*;
498
499 #[test]
503 fn mint_accepts_both_the_private_and_the_public_storage_sizes() {
504 for num_items in [MintNote::NUM_STORAGE_ITEMS_PRIVATE, 20, 21, MAX_NOTE_STORAGE_ITEMS] {
505 assert!(
506 StandardNote::MINT.num_storage_items().accepts(num_items),
507 "{num_items} items should be accepted"
508 );
509 }
510
511 for num_items in [0, 12, 14, 19, MAX_NOTE_STORAGE_ITEMS + 1] {
512 assert!(
513 !StandardNote::MINT.num_storage_items().accepts(num_items),
514 "{num_items} items should be rejected"
515 );
516 }
517 }
518
519 #[test]
522 fn config_notes_accept_only_the_sizes_their_actions_use() {
523 for (note, accepted, rejected) in [
524 (StandardNote::OWNER_CONFIG, [1, 3].as_slice(), [0, 2, 4].as_slice()),
525 (StandardNote::RBAC_CONFIG, [2, 3, 4].as_slice(), [0, 1, 5].as_slice()),
526 (
527 StandardNote::FAUCET_METADATA_CONFIG,
528 [2, 32].as_slice(),
529 [0, 3, 31, 33].as_slice(),
530 ),
531 ] {
532 for &num_items in accepted {
533 assert!(
534 note.num_storage_items().accepts(num_items),
535 "{} should accept {num_items} items",
536 note.name()
537 );
538 }
539
540 for &num_items in rejected {
541 assert!(
542 !note.num_storage_items().accepts(num_items),
543 "{} should reject {num_items} items",
544 note.name()
545 );
546 }
547 }
548 }
549
550 #[test]
552 fn fixed_size_notes_report_an_exact_size() {
553 for (note, num_items) in [
554 (StandardNote::P2ID, P2idNote::NUM_STORAGE_ITEMS),
555 (StandardNote::P2IDE, P2ideNote::NUM_STORAGE_ITEMS),
556 (StandardNote::TX_FEE, TxFeeNote::NUM_STORAGE_ITEMS),
557 ] {
558 assert_eq!(note.num_storage_items(), NumStorageItems::Exact(num_items));
559 assert!(!note.num_storage_items().accepts(num_items + 1));
560 }
561 }
562}