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