Skip to main content

miden_standards/note/
mod.rs

1use 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 faucet_policy_action;
13pub use faucet_policy_action::{FaucetPolicyAction, FaucetPolicyActionNote};
14
15mod execution_hint;
16pub use execution_hint::NoteExecutionHint;
17
18mod file;
19pub use file::{NoteFile, NoteSyncHint};
20
21mod mint;
22pub use mint::{MintNote, MintNoteStorage};
23
24mod owner_action;
25pub use owner_action::{OwnerAction, OwnerActionNote};
26
27mod p2id;
28pub use p2id::{P2idNote, P2idNoteStorage};
29
30mod p2ide;
31pub use p2ide::{P2ideNote, P2ideNoteStorage};
32
33mod pause_action;
34pub use pause_action::{PauseAction, PauseActionNote};
35
36mod pswap;
37pub use pswap::{PswapNote, PswapNoteAttachment, PswapNoteStorage};
38
39mod rbac_action;
40pub use rbac_action::{RbacAction, RbacActionNote};
41
42mod swap;
43pub use swap::{SwapNote, SwapNoteStorage, SwapPayback, payback_serial_from_swap};
44
45mod network_account_target;
46pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
47
48mod network_note;
49pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
50
51mod standard_note_attachment;
52use miden_protocol::errors::NoteError;
53pub use standard_note_attachment::StandardNoteAttachment;
54// STANDARD NOTE
55// ================================================================================================
56
57/// The enum holding the types of standard notes provided by `miden-standards`.
58#[allow(non_camel_case_types)]
59pub enum StandardNote {
60    P2ID,
61    P2IDE,
62    SWAP,
63    PSWAP,
64    MINT,
65    BURN,
66    FAUCET_POLICY_ACTION,
67    PAUSE_ACTION,
68    OWNER_ACTION,
69    RBAC_ACTION,
70}
71
72impl StandardNote {
73    // CONSTRUCTOR
74    // --------------------------------------------------------------------------------------------
75
76    /// Returns a [`StandardNote`] instance based on the provided [`NoteScript`]. Returns `None`
77    /// if the provided script does not match any standard note script.
78    pub fn from_script(script: &NoteScript) -> Option<Self> {
79        Self::from_script_root(script.root())
80    }
81
82    /// Returns a [`StandardNote`] instance based on the provided script root. Returns `None` if
83    /// the provided root does not match any standard note script.
84    pub fn from_script_root(root: NoteScriptRoot) -> Option<Self> {
85        if root == P2idNote::script_root() {
86            return Some(Self::P2ID);
87        }
88        if root == P2ideNote::script_root() {
89            return Some(Self::P2IDE);
90        }
91        if root == SwapNote::script_root() {
92            return Some(Self::SWAP);
93        }
94        if root == PswapNote::script_root() {
95            return Some(Self::PSWAP);
96        }
97        if root == MintNote::script_root() {
98            return Some(Self::MINT);
99        }
100        if root == BurnNote::script_root() {
101            return Some(Self::BURN);
102        }
103        if root == FaucetPolicyActionNote::script_root() {
104            return Some(Self::FAUCET_POLICY_ACTION);
105        }
106        if root == PauseActionNote::script_root() {
107            return Some(Self::PAUSE_ACTION);
108        }
109        if root == OwnerActionNote::script_root() {
110            return Some(Self::OWNER_ACTION);
111        }
112        if root == RbacActionNote::script_root() {
113            return Some(Self::RBAC_ACTION);
114        }
115
116        None
117    }
118
119    // PUBLIC ACCESSORS
120    // --------------------------------------------------------------------------------------------
121
122    /// Returns the name of this [`StandardNote`] variant as a string.
123    pub fn name(&self) -> &'static str {
124        match self {
125            Self::P2ID => "P2ID",
126            Self::P2IDE => "P2IDE",
127            Self::SWAP => "SWAP",
128            Self::PSWAP => "PSWAP",
129            Self::MINT => "MINT",
130            Self::BURN => "BURN",
131            Self::FAUCET_POLICY_ACTION => "FAUCET_POLICY_ACTION",
132            Self::PAUSE_ACTION => "PAUSE_ACTION",
133            Self::OWNER_ACTION => "OWNER_ACTION",
134            Self::RBAC_ACTION => "RBAC_ACTION",
135        }
136    }
137
138    /// Returns the expected number of storage items of the active note.
139    pub fn expected_num_storage_items(&self) -> usize {
140        match self {
141            Self::P2ID => P2idNote::NUM_STORAGE_ITEMS,
142            Self::P2IDE => P2ideNote::NUM_STORAGE_ITEMS,
143            Self::SWAP => SwapNote::NUM_STORAGE_ITEMS,
144            Self::PSWAP => PswapNote::NUM_STORAGE_ITEMS,
145            Self::MINT => MintNote::NUM_STORAGE_ITEMS_PRIVATE,
146            Self::BURN => BurnNote::NUM_STORAGE_ITEMS,
147            Self::FAUCET_POLICY_ACTION => FaucetPolicyActionNote::NUM_STORAGE_ITEMS,
148            Self::PAUSE_ACTION => PauseActionNote::NUM_STORAGE_ITEMS,
149            // OwnerAction storage is variable per action; this returns the upper bound.
150            Self::OWNER_ACTION => OwnerActionNote::MAX_NUM_STORAGE_ITEMS,
151            // RbacAction storage is variable per action; this returns the upper bound.
152            Self::RBAC_ACTION => RbacActionNote::MAX_NUM_STORAGE_ITEMS,
153        }
154    }
155
156    /// Returns the note script of the current [StandardNote] instance.
157    pub fn script(&self) -> NoteScript {
158        match self {
159            Self::P2ID => P2idNote::script(),
160            Self::P2IDE => P2ideNote::script(),
161            Self::SWAP => SwapNote::script(),
162            Self::PSWAP => PswapNote::script(),
163            Self::MINT => MintNote::script(),
164            Self::BURN => BurnNote::script(),
165            Self::FAUCET_POLICY_ACTION => FaucetPolicyActionNote::script(),
166            Self::PAUSE_ACTION => PauseActionNote::script(),
167            Self::OWNER_ACTION => OwnerActionNote::script(),
168            Self::RBAC_ACTION => RbacActionNote::script(),
169        }
170    }
171
172    /// Returns the script root of the current [StandardNote] instance.
173    pub fn script_root(&self) -> NoteScriptRoot {
174        match self {
175            Self::P2ID => P2idNote::script_root(),
176            Self::P2IDE => P2ideNote::script_root(),
177            Self::SWAP => SwapNote::script_root(),
178            Self::PSWAP => PswapNote::script_root(),
179            Self::MINT => MintNote::script_root(),
180            Self::BURN => BurnNote::script_root(),
181            Self::FAUCET_POLICY_ACTION => FaucetPolicyActionNote::script_root(),
182            Self::PAUSE_ACTION => PauseActionNote::script_root(),
183            Self::OWNER_ACTION => OwnerActionNote::script_root(),
184            Self::RBAC_ACTION => RbacActionNote::script_root(),
185        }
186    }
187
188    /// Performs the inputs check of the provided standard note against the target account and the
189    /// block number.
190    ///
191    /// This function returns:
192    /// - `Some` if we can definitively determine whether the note can be consumed not by the target
193    ///   account.
194    /// - `None` if the consumption status of the note cannot be determined conclusively and further
195    ///   checks are necessary.
196    pub fn is_consumable(
197        &self,
198        note: &Note,
199        target_account_id: AccountId,
200        block_ref: BlockNumber,
201    ) -> Option<NoteConsumptionStatus> {
202        match self.is_consumable_inner(note, target_account_id, block_ref) {
203            Ok(status) => status,
204            Err(err) => {
205                let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
206                Some(NoteConsumptionStatus::NeverConsumable(err))
207            },
208        }
209    }
210
211    /// Performs the inputs check of the provided note against the target account and the block
212    /// number.
213    ///
214    /// It performs:
215    /// - for `P2ID` note:
216    ///     - check that note storage has correct number of values.
217    ///     - assertion that the account ID provided by the note storage is equal to the target
218    ///       account ID.
219    /// - for `P2IDE` note:
220    ///     - check that note storage has correct number of values.
221    ///     - check that the target account is either the receiver account or the reclaimer account.
222    ///     - check that depending on whether the target account is reclaimer or receiver, it could
223    ///       be either consumed, or consumed after timelock height, or consumed after reclaim
224    ///       height.
225    fn is_consumable_inner(
226        &self,
227        note: &Note,
228        target_account_id: AccountId,
229        block_ref: BlockNumber,
230    ) -> Result<Option<NoteConsumptionStatus>, NoteError> {
231        match self {
232            StandardNote::P2ID => {
233                let input_account_id = P2idNoteStorage::try_from(note.storage().items())
234                    .map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
235
236                if input_account_id.target() == target_account_id {
237                    Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
238                } else {
239                    Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
240                }
241            },
242            StandardNote::P2IDE => {
243                let storage = P2ideNoteStorage::try_from(note.storage().items())
244                    .map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
245
246                let reclaimer_account_id = storage.reclaimer();
247                let receiver_account_id = storage.target();
248
249                let current_block_height = block_ref.as_u32();
250                let reclaim_height = storage.reclaim_height().unwrap_or_default().as_u32();
251                let timelock_height = storage.timelock_height().unwrap_or_default().as_u32();
252
253                // block height after which the reclaimer account can consume the note
254                let consumable_after = reclaim_height.max(timelock_height);
255
256                // handle the case when the target account of the transaction is the reclaimer
257                if target_account_id == reclaimer_account_id {
258                    // For the reclaimer, the current block height needs to have reached both
259                    // reclaim and timelock height to be consumable.
260                    if current_block_height >= consumable_after {
261                        Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
262                    } else {
263                        Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
264                            consumable_after,
265                        ))))
266                    }
267                // handle the case when the target account of the transaction is receiver
268                } else if target_account_id == receiver_account_id {
269                    // For the receiver, the current block height needs to have reached only the
270                    // timelock height to be consumable: we can ignore the reclaim height in this
271                    // case
272                    if current_block_height >= timelock_height {
273                        Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
274                    } else {
275                        Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
276                            timelock_height,
277                        ))))
278                    }
279                // if the target account is neither the reclaimer nor the receiver (from the
280                // note's storage), then this account cannot consume the note
281                } else {
282                    Ok(Some(NoteConsumptionStatus::NeverConsumable(
283            "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the reclaimer account".into()
284        )))
285                }
286            },
287
288            // the consumption status of any other note cannot be determined by the static analysis,
289            // further checks are necessary.
290            _ => Ok(None),
291        }
292    }
293}
294
295// HELPER FUNCTIONS
296// ================================================================================================
297
298// HELPER STRUCTURES
299// ================================================================================================
300
301/// Describes if a note could be consumed under a specific conditions: target account state
302/// and block height.
303///
304/// The status does not account for any authorization that may be required to consume the
305/// note, nor does it indicate whether the account has sufficient fees to consume it.
306#[derive(Debug)]
307pub enum NoteConsumptionStatus {
308    /// The note can be consumed by the account at the specified block height.
309    Consumable,
310    /// The note can be consumed by the account after the required block height is achieved.
311    ConsumableAfter(BlockNumber),
312    /// The note can be consumed by the account if proper authorization is provided.
313    ConsumableWithAuthorization,
314    /// The note cannot be consumed by the account at the specified conditions (i.e., block
315    /// height and account state).
316    UnconsumableConditions,
317    /// The note cannot be consumed by the specified account under any conditions.
318    NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
319}
320
321impl Clone for NoteConsumptionStatus {
322    fn clone(&self) -> Self {
323        match self {
324            NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
325            NoteConsumptionStatus::ConsumableAfter(block_height) => {
326                NoteConsumptionStatus::ConsumableAfter(*block_height)
327            },
328            NoteConsumptionStatus::ConsumableWithAuthorization => {
329                NoteConsumptionStatus::ConsumableWithAuthorization
330            },
331            NoteConsumptionStatus::UnconsumableConditions => {
332                NoteConsumptionStatus::UnconsumableConditions
333            },
334            NoteConsumptionStatus::NeverConsumable(error) => {
335                let err = error.to_string();
336                NoteConsumptionStatus::NeverConsumable(err.into())
337            },
338        }
339    }
340}