miden_lib/account/interface/
component.rs

1use alloc::{
2    collections::BTreeMap,
3    string::{String, ToString},
4    vec::Vec,
5};
6
7use miden_objects::{
8    Felt,
9    account::{AccountId, AccountProcedureInfo},
10    note::PartialNote,
11    utils::word_to_masm_push_string,
12};
13
14use crate::account::{
15    components::{basic_fungible_faucet_library, basic_wallet_library, rpo_falcon_512_library},
16    interface::AccountInterfaceError,
17};
18
19// ACCOUNT COMPONENT INTERFACE
20// ================================================================================================
21
22/// The enum holding all possible account interfaces which could be loaded to some account.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum AccountComponentInterface {
25    /// Exposes procedures from the [`BasicWallet`][crate::account::wallets::BasicWallet] module.
26    BasicWallet,
27    /// Exposes procedures from the
28    /// [`BasicFungibleFaucet`][crate::account::faucets::BasicFungibleFaucet] module.
29    ///
30    /// Internal value holds the storage slot index where faucet metadata is stored. This metadata
31    /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`.
32    BasicFungibleFaucet(u8),
33    /// Exposes procedures from the
34    /// [`RpoFalcon512`][crate::account::auth::RpoFalcon512] module.
35    ///
36    /// Internal value holds the storage slot index where the public key for the RpoFalcon512
37    /// authentication scheme is stored.
38    RpoFalcon512(u8),
39    /// A non-standard, custom interface which exposes the contained procedures.
40    ///
41    /// Custom interface holds procedures which are not part of some standard interface which is
42    /// used by this account. Each custom interface holds procedures with the same storage offset.
43    Custom(Vec<AccountProcedureInfo>),
44}
45
46impl AccountComponentInterface {
47    /// Returns a string line with the name of the [AccountComponentInterface] enum variant.
48    ///
49    /// In case of a [AccountComponentInterface::Custom] along with the name of the enum variant  
50    /// the vector of shortened hex representations of the used procedures is returned, e.g.
51    /// `Custom([0x6d93447, 0x0bf23d8])`.
52    pub fn name(&self) -> String {
53        match self {
54            AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
55            AccountComponentInterface::BasicFungibleFaucet(_) => {
56                "Basic Fungible Faucet".to_string()
57            },
58            AccountComponentInterface::RpoFalcon512(_) => "RPO Falcon512".to_string(),
59            AccountComponentInterface::Custom(proc_info_vec) => {
60                let result = proc_info_vec
61                    .iter()
62                    .map(|proc_info| proc_info.mast_root().to_hex()[..9].to_string())
63                    .collect::<Vec<_>>()
64                    .join(", ");
65                format!("Custom([{result}])")
66            },
67        }
68    }
69
70    /// Creates a vector of [AccountComponentInterface] instances. This vector specifies the
71    /// components which were used to create an account with the provided procedures info array.
72    pub fn from_procedures(procedures: &[AccountProcedureInfo]) -> Vec<Self> {
73        let mut component_interface_vec = Vec::new();
74
75        let mut procedures: BTreeMap<_, _> = procedures
76            .iter()
77            .map(|procedure_info| (*procedure_info.mast_root(), procedure_info))
78            .collect();
79
80        // Basic Wallet
81        // ------------------------------------------------------------------------------------------------
82
83        if basic_wallet_library()
84            .mast_forest()
85            .procedure_digests()
86            .all(|proc_digest| procedures.contains_key(&proc_digest))
87        {
88            basic_wallet_library().mast_forest().procedure_digests().for_each(
89                |component_procedure| {
90                    procedures.remove(&component_procedure);
91                },
92            );
93
94            component_interface_vec.push(AccountComponentInterface::BasicWallet);
95        }
96
97        // Basic Fungible Faucet
98        // ------------------------------------------------------------------------------------------------
99
100        if basic_fungible_faucet_library()
101            .mast_forest()
102            .procedure_digests()
103            .all(|proc_digest| procedures.contains_key(&proc_digest))
104        {
105            let mut storage_offset = Default::default();
106            basic_fungible_faucet_library().mast_forest().procedure_digests().for_each(
107                |component_procedure| {
108                    if let Some(proc_info) = procedures.remove(&component_procedure) {
109                        storage_offset = proc_info.storage_offset();
110                    }
111                },
112            );
113
114            component_interface_vec
115                .push(AccountComponentInterface::BasicFungibleFaucet(storage_offset));
116        }
117
118        // RPO Falcon 512
119        // ------------------------------------------------------------------------------------------------
120
121        let rpo_falcon_proc = rpo_falcon_512_library()
122            .mast_forest()
123            .procedure_digests()
124            .next()
125            .expect("rpo falcon 512 component should export exactly one procedure");
126
127        if let Some(proc_info) = procedures.remove(&rpo_falcon_proc) {
128            component_interface_vec
129                .push(AccountComponentInterface::RpoFalcon512(proc_info.storage_offset()));
130        }
131
132        // Custom interfaces
133        // ------------------------------------------------------------------------------------------------
134
135        let mut custom_interface_procs_map = BTreeMap::<u8, Vec<AccountProcedureInfo>>::new();
136        procedures.into_iter().for_each(|(_, proc_info)| {
137            match custom_interface_procs_map.get_mut(&proc_info.storage_offset()) {
138                Some(proc_vec) => proc_vec.push(*proc_info),
139                None => {
140                    custom_interface_procs_map.insert(proc_info.storage_offset(), vec![*proc_info]);
141                },
142            }
143        });
144
145        if !custom_interface_procs_map.is_empty() {
146            for proc_vec in custom_interface_procs_map.into_values() {
147                component_interface_vec.push(AccountComponentInterface::Custom(proc_vec));
148            }
149        }
150
151        component_interface_vec
152    }
153
154    /// Generates a body for the note creation of the `send_note` transaction script. The resulting
155    /// code could use different procedures for note creation, which depends on the used interface.
156    ///
157    /// The body consists of two sections:
158    /// - Pushing the note information on the stack.
159    /// - Creating a note:
160    ///   - For basic fungible faucet: pushing the amount of assets and distributing them.
161    ///   - For basic wallet: creating a note, pushing the assets on the stack and moving them to
162    ///     the created note.
163    ///
164    /// # Examples
165    ///
166    /// Example script for the [`AccountComponentInterface::BasicWallet`] with one note:
167    ///
168    /// ```masm
169    ///     push.{note_information}
170    ///     call.::miden::tx::create_note
171    ///
172    ///     push.{note asset}
173    ///     call.::miden::contracts::wallets::basic::move_asset_to_note dropw
174    ///     dropw dropw dropw drop
175    /// ```
176    ///
177    /// Example script for the [`AccountComponentInterface::BasicFungibleFaucet`] with one note:
178    ///
179    /// ```masm
180    ///     push.{note information}
181    ///     
182    ///     push.{asset amount}
183    ///     call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop
184    /// ```
185    ///
186    /// # Errors:
187    /// Returns an error if:
188    /// - the interface does not support the generation of the standard `send_note` procedure.
189    /// - the sender of the note isn't the account for which the script is being built.
190    /// - the note created by the faucet doesn't contain exactly one asset.
191    /// - a faucet tries to distribute an asset with a different faucet ID.
192    pub(crate) fn send_note_body(
193        &self,
194        sender_account_id: AccountId,
195        notes: &[PartialNote],
196    ) -> Result<String, AccountInterfaceError> {
197        let mut body = String::new();
198
199        for partial_note in notes {
200            if partial_note.metadata().sender() != sender_account_id {
201                return Err(AccountInterfaceError::InvalidSenderAccount(
202                    partial_note.metadata().sender(),
203                ));
204            }
205
206            body.push_str(&format!(
207                "push.{recipient}
208                push.{execution_hint}
209                push.{note_type}
210                push.{aux}
211                push.{tag}\n",
212                recipient = word_to_masm_push_string(&partial_note.recipient_digest()),
213                note_type = Felt::from(partial_note.metadata().note_type()),
214                execution_hint = Felt::from(partial_note.metadata().execution_hint()),
215                aux = partial_note.metadata().aux(),
216                tag = Felt::from(partial_note.metadata().tag()),
217            ));
218            // stack => [tag, aux, note_type, execution_hint, RECIPIENT]
219
220            match self {
221                AccountComponentInterface::BasicFungibleFaucet(_) => {
222                    if partial_note.assets().num_assets() != 1 {
223                        return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
224                    }
225
226                    // SAFETY: We checked that the note contains exactly one asset
227                    let asset =
228                        partial_note.assets().iter().next().expect("note should contain an asset");
229
230                    if asset.faucet_id_prefix() != sender_account_id.prefix() {
231                        return Err(AccountInterfaceError::IssuanceFaucetMismatch(
232                            asset.faucet_id_prefix(),
233                        ));
234                    }
235
236                    body.push_str(&format!(
237                        "push.{amount} 
238                        call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop\n",
239                        amount = asset.unwrap_fungible().amount()
240                    ));
241                    // stack => []
242                },
243                AccountComponentInterface::BasicWallet => {
244                    body.push_str("call.::miden::tx::create_note\n");
245                    // stack => [note_idx]
246
247                    for asset in partial_note.assets().iter() {
248                        body.push_str(&format!(
249                            "push.{asset}
250                            call.::miden::contracts::wallets::basic::move_asset_to_note dropw\n",
251                            asset = word_to_masm_push_string(&asset.into())
252                        ));
253                        // stack => [note_idx]
254                    }
255
256                    body.push_str("dropw dropw dropw drop\n");
257                    // stack => []
258                },
259                _ => {
260                    return Err(AccountInterfaceError::UnsupportedInterface {
261                        interface: self.clone(),
262                    });
263                },
264            }
265        }
266
267        Ok(body)
268    }
269}