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 upgrade;
54pub use upgrade::UpgradeNote;
55
56mod network_account_target;
57pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
58
59mod account_code_upgrade_attachment;
60pub use account_code_upgrade_attachment::{
61 AccountCodeUpgradeAttachment,
62 AccountCodeUpgradeAttachmentError,
63};
64
65mod network_note;
66pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
67
68mod standard_note_attachment;
69use miden_protocol::errors::NoteError;
70pub use standard_note_attachment::StandardNoteAttachment;
71#[allow(non_camel_case_types)]
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum StandardNote {
78 P2ID,
79 P2IDE,
80 SWAP,
81 PSWAP,
82 MINT,
83 BURN,
84 CONSTANT_FEE_POLICY_CONFIG,
85 FAUCET_POLICY_CONFIG,
86 FAUCET_METADATA_CONFIG,
87 MIN_BURN_AMOUNT_CONFIG,
88 ALLOWLIST_CONFIG,
89 BLOCKLIST_CONFIG,
90 PAUSE_CONFIG,
91 OWNER_CONFIG,
92 RBAC_CONFIG,
93 NETWORK_ACCOUNT_CONFIG,
94 UPGRADE,
95 FEE_SPONSORSHIP,
96 TX_FEE,
97}
98
99impl StandardNote {
100 pub fn from_script(script: &NoteScript) -> Option<Self> {
106 Self::from_script_root(script.root())
107 }
108
109 pub fn from_script_root(root: NoteScriptRoot) -> Option<Self> {
112 if root == P2idNote::script_root() {
113 return Some(Self::P2ID);
114 }
115 if root == P2ideNote::script_root() {
116 return Some(Self::P2IDE);
117 }
118 if root == SwapNote::script_root() {
119 return Some(Self::SWAP);
120 }
121 if root == PswapNote::script_root() {
122 return Some(Self::PSWAP);
123 }
124 if root == MintNote::script_root() {
125 return Some(Self::MINT);
126 }
127 if root == BurnNote::script_root() {
128 return Some(Self::BURN);
129 }
130 if root == ConstantFeePolicyConfigNote::script_root() {
131 return Some(Self::CONSTANT_FEE_POLICY_CONFIG);
132 }
133 if root == FaucetPolicyConfigNote::script_root() {
134 return Some(Self::FAUCET_POLICY_CONFIG);
135 }
136 if root == FaucetMetadataConfigNote::script_root() {
137 return Some(Self::FAUCET_METADATA_CONFIG);
138 }
139 if root == MinBurnAmountConfigNote::script_root() {
140 return Some(Self::MIN_BURN_AMOUNT_CONFIG);
141 }
142 if root == AllowlistConfigNote::script_root() {
143 return Some(Self::ALLOWLIST_CONFIG);
144 }
145 if root == BlocklistConfigNote::script_root() {
146 return Some(Self::BLOCKLIST_CONFIG);
147 }
148 if root == PauseConfigNote::script_root() {
149 return Some(Self::PAUSE_CONFIG);
150 }
151 if root == OwnerConfigNote::script_root() {
152 return Some(Self::OWNER_CONFIG);
153 }
154 if root == RbacConfigNote::script_root() {
155 return Some(Self::RBAC_CONFIG);
156 }
157 if root == NetworkAccountConfigNote::script_root() {
158 return Some(Self::NETWORK_ACCOUNT_CONFIG);
159 }
160 if root == UpgradeNote::script_root() {
161 return Some(Self::UPGRADE);
162 }
163 if root == FeeSponsorshipNote::script_root() {
164 return Some(Self::FEE_SPONSORSHIP);
165 }
166 if root == TxFeeNote::script_root() {
167 return Some(Self::TX_FEE);
168 }
169
170 None
171 }
172
173 pub fn name(&self) -> &'static str {
178 match self {
179 Self::P2ID => "P2ID",
180 Self::P2IDE => "P2IDE",
181 Self::SWAP => "SWAP",
182 Self::PSWAP => "PSWAP",
183 Self::MINT => "MINT",
184 Self::BURN => "BURN",
185 Self::CONSTANT_FEE_POLICY_CONFIG => "CONSTANT_FEE_POLICY_CONFIG",
186 Self::FAUCET_POLICY_CONFIG => "FAUCET_POLICY_CONFIG",
187 Self::FAUCET_METADATA_CONFIG => "FAUCET_METADATA_CONFIG",
188 Self::MIN_BURN_AMOUNT_CONFIG => "MIN_BURN_AMOUNT_CONFIG",
189 Self::ALLOWLIST_CONFIG => "ALLOWLIST_CONFIG",
190 Self::BLOCKLIST_CONFIG => "BLOCKLIST_CONFIG",
191 Self::PAUSE_CONFIG => "PAUSE_CONFIG",
192 Self::OWNER_CONFIG => "OWNER_CONFIG",
193 Self::RBAC_CONFIG => "RBAC_CONFIG",
194 Self::NETWORK_ACCOUNT_CONFIG => "NETWORK_ACCOUNT_CONFIG",
195 Self::UPGRADE => "UPGRADE",
196 Self::FEE_SPONSORSHIP => "FEE_SPONSORSHIP",
197 Self::TX_FEE => "TX_FEE",
198 }
199 }
200
201 pub fn num_storage_items(&self) -> NumStorageItems {
203 match self {
204 Self::P2ID => NumStorageItems::Exact(P2idNote::NUM_STORAGE_ITEMS),
205 Self::P2IDE => NumStorageItems::Exact(P2ideNote::NUM_STORAGE_ITEMS),
206 Self::SWAP => NumStorageItems::Exact(SwapNote::NUM_STORAGE_ITEMS),
207 Self::PSWAP => NumStorageItems::Exact(PswapNote::NUM_STORAGE_ITEMS),
208 Self::MINT => MintNote::NUM_STORAGE_ITEMS,
209 Self::BURN => NumStorageItems::Exact(BurnNote::NUM_STORAGE_ITEMS),
210 Self::CONSTANT_FEE_POLICY_CONFIG => {
211 NumStorageItems::Exact(ConstantFeePolicyConfigNote::NUM_STORAGE_ITEMS)
212 },
213 Self::FAUCET_POLICY_CONFIG => {
214 NumStorageItems::Exact(FaucetPolicyConfigNote::NUM_STORAGE_ITEMS)
215 },
216 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::NUM_STORAGE_ITEMS,
217 Self::MIN_BURN_AMOUNT_CONFIG => {
218 NumStorageItems::Exact(MinBurnAmountConfigNote::NUM_STORAGE_ITEMS)
219 },
220 Self::ALLOWLIST_CONFIG => {
221 NumStorageItems::Exact(AllowlistConfigNote::NUM_STORAGE_ITEMS)
222 },
223 Self::BLOCKLIST_CONFIG => {
224 NumStorageItems::Exact(BlocklistConfigNote::NUM_STORAGE_ITEMS)
225 },
226 Self::PAUSE_CONFIG => NumStorageItems::Exact(PauseConfigNote::NUM_STORAGE_ITEMS),
227 Self::OWNER_CONFIG => OwnerConfigNote::NUM_STORAGE_ITEMS,
228 Self::RBAC_CONFIG => RbacConfigNote::NUM_STORAGE_ITEMS,
229 Self::NETWORK_ACCOUNT_CONFIG => {
230 NumStorageItems::Exact(NetworkAccountConfigNote::NUM_STORAGE_ITEMS)
231 },
232 Self::UPGRADE => NumStorageItems::Exact(UpgradeNote::NUM_STORAGE_ITEMS),
233 Self::FEE_SPONSORSHIP => NumStorageItems::Exact(FeeSponsorshipNote::NUM_STORAGE_ITEMS),
234 Self::TX_FEE => NumStorageItems::Exact(TxFeeNote::NUM_STORAGE_ITEMS),
235 }
236 }
237
238 pub fn script(&self) -> NoteScript {
240 match self {
241 Self::P2ID => P2idNote::script(),
242 Self::P2IDE => P2ideNote::script(),
243 Self::SWAP => SwapNote::script(),
244 Self::PSWAP => PswapNote::script(),
245 Self::MINT => MintNote::script(),
246 Self::BURN => BurnNote::script(),
247 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script(),
248 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script(),
249 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script(),
250 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script(),
251 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script(),
252 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script(),
253 Self::PAUSE_CONFIG => PauseConfigNote::script(),
254 Self::OWNER_CONFIG => OwnerConfigNote::script(),
255 Self::RBAC_CONFIG => RbacConfigNote::script(),
256 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script(),
257 Self::UPGRADE => UpgradeNote::script(),
258 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script(),
259 Self::TX_FEE => TxFeeNote::script(),
260 }
261 }
262
263 pub fn script_root(&self) -> NoteScriptRoot {
265 match self {
266 Self::P2ID => P2idNote::script_root(),
267 Self::P2IDE => P2ideNote::script_root(),
268 Self::SWAP => SwapNote::script_root(),
269 Self::PSWAP => PswapNote::script_root(),
270 Self::MINT => MintNote::script_root(),
271 Self::BURN => BurnNote::script_root(),
272 Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::script_root(),
273 Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::script_root(),
274 Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::script_root(),
275 Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::script_root(),
276 Self::ALLOWLIST_CONFIG => AllowlistConfigNote::script_root(),
277 Self::BLOCKLIST_CONFIG => BlocklistConfigNote::script_root(),
278 Self::PAUSE_CONFIG => PauseConfigNote::script_root(),
279 Self::OWNER_CONFIG => OwnerConfigNote::script_root(),
280 Self::RBAC_CONFIG => RbacConfigNote::script_root(),
281 Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::script_root(),
282 Self::UPGRADE => UpgradeNote::script_root(),
283 Self::FEE_SPONSORSHIP => FeeSponsorshipNote::script_root(),
284 Self::TX_FEE => TxFeeNote::script_root(),
285 }
286 }
287
288 pub fn is_consumable(
297 &self,
298 note: &Note,
299 target_account_id: AccountId,
300 block_ref: BlockNumber,
301 ) -> Option<NoteConsumptionStatus> {
302 match self.is_consumable_inner(note, target_account_id, block_ref) {
303 Ok(status) => status,
304 Err(err) => {
305 let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
306 Some(NoteConsumptionStatus::NeverConsumable(err))
307 },
308 }
309 }
310
311 fn is_consumable_inner(
328 &self,
329 note: &Note,
330 target_account_id: AccountId,
331 block_ref: BlockNumber,
332 ) -> Result<Option<NoteConsumptionStatus>, NoteError> {
333 match self {
334 StandardNote::P2ID => {
335 let input_account_id = P2idNoteStorage::try_from(note.storage().items())
336 .map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
337
338 if input_account_id.target() == target_account_id {
339 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
340 } else {
341 Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
342 }
343 },
344 StandardNote::P2IDE => {
345 let storage = P2ideNoteStorage::try_from(note.storage().items())
346 .map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
347
348 let reclaimer_account_id = storage.reclaimer();
349 let receiver_account_id = storage.target();
350
351 let current_block_height = block_ref.as_u32();
352 let reclaim_height = storage.reclaim_height().unwrap_or_default().as_u32();
353 let timelock_height = storage.timelock_height().unwrap_or_default().as_u32();
354
355 let consumable_after = reclaim_height.max(timelock_height);
357
358 if target_account_id == reclaimer_account_id {
360 if current_block_height >= consumable_after {
363 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
364 } else {
365 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
366 consumable_after,
367 ))))
368 }
369 } else if target_account_id == receiver_account_id {
371 if current_block_height >= timelock_height {
375 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
376 } else {
377 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
378 timelock_height,
379 ))))
380 }
381 } else {
384 Ok(Some(NoteConsumptionStatus::NeverConsumable(
385 "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the reclaimer account".into()
386 )))
387 }
388 },
389
390 StandardNote::TX_FEE => {
394 if usize::from(note.storage().num_items()) != TxFeeNote::NUM_STORAGE_ITEMS {
395 Ok(Some(NoteConsumptionStatus::NeverConsumable(
396 "TX_FEE note carries unexpected storage items".into(),
397 )))
398 } else {
399 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
400 }
401 },
402
403 _ => Ok(None),
406 }
407 }
408}
409
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum NumStorageItems {
421 Exact(usize),
423 Range { min: usize, max: usize },
425 AnyOf(&'static [NumStorageItems]),
428}
429
430impl NumStorageItems {
431 pub fn accepts(&self, num_items: usize) -> bool {
433 match self {
434 Self::Exact(expected) => num_items == *expected,
435 Self::Range { min, max } => (*min..=*max).contains(&num_items),
436 Self::AnyOf(accepted) => accepted.iter().any(|accepted| accepted.accepts(num_items)),
437 }
438 }
439}
440
441pub(crate) fn decode_optional_block_height(
448 item: Felt,
449 error_msg: &'static str,
450) -> Result<Option<BlockNumber>, NoteError> {
451 if item == Felt::ZERO {
452 return Ok(None);
453 }
454
455 let height: u32 = item
456 .as_canonical_u64()
457 .try_into()
458 .map_err(|e| NoteError::other_with_source(error_msg, e))?;
459
460 Ok(Some(BlockNumber::from(height)))
461}
462
463#[derive(Debug)]
472pub enum NoteConsumptionStatus {
473 Consumable,
475 ConsumableAfter(BlockNumber),
477 ConsumableWithAuthorization,
479 UnconsumableConditions,
482 NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
484}
485
486impl Clone for NoteConsumptionStatus {
487 fn clone(&self) -> Self {
488 match self {
489 NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
490 NoteConsumptionStatus::ConsumableAfter(block_height) => {
491 NoteConsumptionStatus::ConsumableAfter(*block_height)
492 },
493 NoteConsumptionStatus::ConsumableWithAuthorization => {
494 NoteConsumptionStatus::ConsumableWithAuthorization
495 },
496 NoteConsumptionStatus::UnconsumableConditions => {
497 NoteConsumptionStatus::UnconsumableConditions
498 },
499 NoteConsumptionStatus::NeverConsumable(error) => {
500 let err = error.to_string();
501 NoteConsumptionStatus::NeverConsumable(err.into())
502 },
503 }
504 }
505}
506
507#[cfg(test)]
511mod tests {
512 use miden_protocol::MAX_NOTE_STORAGE_ITEMS;
513
514 use super::*;
515
516 #[test]
520 fn mint_accepts_both_the_private_and_the_public_storage_sizes() {
521 for num_items in [MintNote::NUM_STORAGE_ITEMS_PRIVATE, 20, 21, MAX_NOTE_STORAGE_ITEMS] {
522 assert!(
523 StandardNote::MINT.num_storage_items().accepts(num_items),
524 "{num_items} items should be accepted"
525 );
526 }
527
528 for num_items in [0, 12, 14, 19, MAX_NOTE_STORAGE_ITEMS + 1] {
529 assert!(
530 !StandardNote::MINT.num_storage_items().accepts(num_items),
531 "{num_items} items should be rejected"
532 );
533 }
534 }
535
536 #[test]
539 fn config_notes_accept_only_the_sizes_their_actions_use() {
540 for (note, accepted, rejected) in [
541 (StandardNote::OWNER_CONFIG, [1, 3].as_slice(), [0, 2, 4].as_slice()),
542 (StandardNote::RBAC_CONFIG, [2, 3, 4].as_slice(), [0, 1, 5].as_slice()),
543 (
544 StandardNote::FAUCET_METADATA_CONFIG,
545 [2, 32].as_slice(),
546 [0, 3, 31, 33].as_slice(),
547 ),
548 ] {
549 for &num_items in accepted {
550 assert!(
551 note.num_storage_items().accepts(num_items),
552 "{} should accept {num_items} items",
553 note.name()
554 );
555 }
556
557 for &num_items in rejected {
558 assert!(
559 !note.num_storage_items().accepts(num_items),
560 "{} should reject {num_items} items",
561 note.name()
562 );
563 }
564 }
565 }
566
567 #[test]
569 fn fixed_size_notes_report_an_exact_size() {
570 for (note, num_items) in [
571 (StandardNote::P2ID, P2idNote::NUM_STORAGE_ITEMS),
572 (StandardNote::P2IDE, P2ideNote::NUM_STORAGE_ITEMS),
573 (StandardNote::TX_FEE, TxFeeNote::NUM_STORAGE_ITEMS),
574 ] {
575 assert_eq!(note.num_storage_items(), NumStorageItems::Exact(num_items));
576 assert!(!note.num_storage_items().accepts(num_items + 1));
577 }
578 }
579}