miden_standards/note/
mod.rs1use alloc::boxed::Box;
2use alloc::string::ToString;
3use core::error::Error;
4
5use miden_protocol::account::AccountId;
6use miden_protocol::block::BlockNumber;
7use miden_protocol::note::{Note, NoteScript, NoteScriptRoot};
8
9mod burn;
10pub use burn::BurnNote;
11
12mod execution_hint;
13pub use execution_hint::NoteExecutionHint;
14
15mod mint;
16pub use mint::{MintNote, MintNoteStorage};
17
18mod p2id;
19pub use p2id::{P2idNote, P2idNoteStorage};
20
21mod p2ide;
22pub use p2ide::{P2ideNote, P2ideNoteStorage};
23
24mod pswap;
25pub use pswap::{PswapNote, PswapNoteAttachment, PswapNoteStorage};
26
27mod swap;
28pub use swap::{SwapNote, SwapNoteStorage};
29
30mod network_account_target;
31pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
32
33mod network_note;
34pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
35
36mod standard_note_attachment;
37use miden_protocol::errors::NoteError;
38pub use standard_note_attachment::StandardNoteAttachment;
39pub enum StandardNote {
44 P2ID,
45 P2IDE,
46 SWAP,
47 PSWAP,
48 MINT,
49 BURN,
50}
51
52impl StandardNote {
53 pub fn from_script(script: &NoteScript) -> Option<Self> {
59 Self::from_script_root(script.root())
60 }
61
62 pub fn from_script_root(root: NoteScriptRoot) -> Option<Self> {
65 if root == P2idNote::script_root() {
66 return Some(Self::P2ID);
67 }
68 if root == P2ideNote::script_root() {
69 return Some(Self::P2IDE);
70 }
71 if root == SwapNote::script_root() {
72 return Some(Self::SWAP);
73 }
74 if root == PswapNote::script_root() {
75 return Some(Self::PSWAP);
76 }
77 if root == MintNote::script_root() {
78 return Some(Self::MINT);
79 }
80 if root == BurnNote::script_root() {
81 return Some(Self::BURN);
82 }
83
84 None
85 }
86
87 pub fn name(&self) -> &'static str {
92 match self {
93 Self::P2ID => "P2ID",
94 Self::P2IDE => "P2IDE",
95 Self::SWAP => "SWAP",
96 Self::PSWAP => "PSWAP",
97 Self::MINT => "MINT",
98 Self::BURN => "BURN",
99 }
100 }
101
102 pub fn expected_num_storage_items(&self) -> usize {
104 match self {
105 Self::P2ID => P2idNote::NUM_STORAGE_ITEMS,
106 Self::P2IDE => P2ideNote::NUM_STORAGE_ITEMS,
107 Self::SWAP => SwapNote::NUM_STORAGE_ITEMS,
108 Self::PSWAP => PswapNote::NUM_STORAGE_ITEMS,
109 Self::MINT => MintNote::NUM_STORAGE_ITEMS_PRIVATE,
110 Self::BURN => BurnNote::NUM_STORAGE_ITEMS,
111 }
112 }
113
114 pub fn script(&self) -> NoteScript {
116 match self {
117 Self::P2ID => P2idNote::script(),
118 Self::P2IDE => P2ideNote::script(),
119 Self::SWAP => SwapNote::script(),
120 Self::PSWAP => PswapNote::script(),
121 Self::MINT => MintNote::script(),
122 Self::BURN => BurnNote::script(),
123 }
124 }
125
126 pub fn script_root(&self) -> NoteScriptRoot {
128 match self {
129 Self::P2ID => P2idNote::script_root(),
130 Self::P2IDE => P2ideNote::script_root(),
131 Self::SWAP => SwapNote::script_root(),
132 Self::PSWAP => PswapNote::script_root(),
133 Self::MINT => MintNote::script_root(),
134 Self::BURN => BurnNote::script_root(),
135 }
136 }
137
138 pub fn is_consumable(
147 &self,
148 note: &Note,
149 target_account_id: AccountId,
150 block_ref: BlockNumber,
151 ) -> Option<NoteConsumptionStatus> {
152 match self.is_consumable_inner(note, target_account_id, block_ref) {
153 Ok(status) => status,
154 Err(err) => {
155 let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
156 Some(NoteConsumptionStatus::NeverConsumable(err))
157 },
158 }
159 }
160
161 fn is_consumable_inner(
175 &self,
176 note: &Note,
177 target_account_id: AccountId,
178 block_ref: BlockNumber,
179 ) -> Result<Option<NoteConsumptionStatus>, NoteError> {
180 match self {
181 StandardNote::P2ID => {
182 let input_account_id = P2idNoteStorage::try_from(note.storage().items())
183 .map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
184
185 if input_account_id.target() == target_account_id {
186 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
187 } else {
188 Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
189 }
190 },
191 StandardNote::P2IDE => {
192 let P2ideNoteStorage {
193 target: receiver_account_id,
194 reclaim_height,
195 timelock_height,
196 } = P2ideNoteStorage::try_from(note.storage().items())
197 .map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
198
199 let current_block_height = block_ref.as_u32();
200 let reclaim_height = reclaim_height.unwrap_or_default().as_u32();
201 let timelock_height = timelock_height.unwrap_or_default().as_u32();
202
203 let consumable_after = reclaim_height.max(timelock_height);
205
206 if target_account_id == note.metadata().sender() {
208 if current_block_height >= consumable_after {
211 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
212 } else {
213 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
214 consumable_after,
215 ))))
216 }
217 } else if target_account_id == receiver_account_id {
219 if current_block_height >= timelock_height {
223 Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
224 } else {
225 Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
226 timelock_height,
227 ))))
228 }
229 } else {
232 Ok(Some(NoteConsumptionStatus::NeverConsumable(
233 "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the sender account".into()
234 )))
235 }
236 },
237
238 _ => Ok(None),
241 }
242 }
243}
244
245#[derive(Debug)]
257pub enum NoteConsumptionStatus {
258 Consumable,
260 ConsumableAfter(BlockNumber),
262 ConsumableWithAuthorization,
264 UnconsumableConditions,
267 NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
269}
270
271impl Clone for NoteConsumptionStatus {
272 fn clone(&self) -> Self {
273 match self {
274 NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
275 NoteConsumptionStatus::ConsumableAfter(block_height) => {
276 NoteConsumptionStatus::ConsumableAfter(*block_height)
277 },
278 NoteConsumptionStatus::ConsumableWithAuthorization => {
279 NoteConsumptionStatus::ConsumableWithAuthorization
280 },
281 NoteConsumptionStatus::UnconsumableConditions => {
282 NoteConsumptionStatus::UnconsumableConditions
283 },
284 NoteConsumptionStatus::NeverConsumable(error) => {
285 let err = error.to_string();
286 NoteConsumptionStatus::NeverConsumable(err.into())
287 },
288 }
289 }
290}