miden_lib/account/interface/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::string::String;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_objects::Word;
7use miden_objects::account::{Account, AccountCode, AccountId, AccountIdPrefix, AccountType};
8use miden_objects::assembly::mast::{MastForest, MastNode, MastNodeId};
9use miden_objects::note::{Note, NoteScript, PartialNote};
10use miden_objects::transaction::TransactionScript;
11use miden_processor::MastNodeExt;
12use thiserror::Error;
13
14use crate::AuthScheme;
15use crate::account::components::{
16 basic_fungible_faucet_library,
17 basic_wallet_library,
18 network_fungible_faucet_library,
19 no_auth_library,
20 rpo_falcon_512_acl_library,
21 rpo_falcon_512_library,
22 rpo_falcon_512_multisig_library,
23};
24use crate::errors::ScriptBuilderError;
25use crate::note::WellKnownNote;
26use crate::utils::ScriptBuilder;
27
28#[cfg(test)]
29mod test;
30
31mod component;
32pub use component::AccountComponentInterface;
33
34pub struct AccountInterface {
42 account_id: AccountId,
43 auth: Vec<AuthScheme>,
44 components: Vec<AccountComponentInterface>,
45}
46
47impl AccountInterface {
50 pub fn new(account_id: AccountId, auth: Vec<AuthScheme>, code: &AccountCode) -> Self {
56 let components = AccountComponentInterface::from_procedures(code.procedures());
57
58 Self { account_id, auth, components }
59 }
60
61 pub fn id(&self) -> &AccountId {
66 &self.account_id
67 }
68
69 pub fn account_type(&self) -> AccountType {
71 self.account_id.account_type()
72 }
73
74 pub fn is_faucet(&self) -> bool {
76 self.account_id.is_faucet()
77 }
78
79 pub fn is_regular_account(&self) -> bool {
81 self.account_id.is_regular_account()
82 }
83
84 pub fn has_public_state(&self) -> bool {
89 self.account_id.has_public_state()
90 }
91
92 pub fn is_private(&self) -> bool {
94 self.account_id.is_private()
95 }
96
97 pub fn is_public(&self) -> bool {
99 self.account_id.is_public()
100 }
101
102 pub fn is_network(&self) -> bool {
104 self.account_id.is_network()
105 }
106
107 pub fn auth(&self) -> &Vec<AuthScheme> {
109 &self.auth
110 }
111
112 pub fn components(&self) -> &Vec<AccountComponentInterface> {
114 &self.components
115 }
116
117 pub fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility {
120 if let Some(well_known_note) = WellKnownNote::from_note(note) {
121 if well_known_note.is_compatible_with(self) {
122 NoteAccountCompatibility::Maybe
123 } else {
124 NoteAccountCompatibility::No
125 }
126 } else {
127 verify_note_script_compatibility(note.script(), self.get_procedure_digests())
128 }
129 }
130
131 pub(crate) fn get_procedure_digests(&self) -> BTreeSet<Word> {
133 let mut component_proc_digests = BTreeSet::new();
134 for component in self.components.iter() {
135 match component {
136 AccountComponentInterface::BasicWallet => {
137 component_proc_digests
138 .extend(basic_wallet_library().mast_forest().procedure_digests());
139 },
140 AccountComponentInterface::BasicFungibleFaucet(_) => {
141 component_proc_digests
142 .extend(basic_fungible_faucet_library().mast_forest().procedure_digests());
143 },
144 AccountComponentInterface::NetworkFungibleFaucet(_) => {
145 component_proc_digests.extend(
146 network_fungible_faucet_library().mast_forest().procedure_digests(),
147 );
148 },
149 AccountComponentInterface::AuthRpoFalcon512(_) => {
150 component_proc_digests
151 .extend(rpo_falcon_512_library().mast_forest().procedure_digests());
152 },
153 AccountComponentInterface::AuthRpoFalcon512Acl(_) => {
154 component_proc_digests
155 .extend(rpo_falcon_512_acl_library().mast_forest().procedure_digests());
156 },
157 AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
158 component_proc_digests.extend(
159 rpo_falcon_512_multisig_library().mast_forest().procedure_digests(),
160 );
161 },
162 AccountComponentInterface::AuthNoAuth => {
163 component_proc_digests
164 .extend(no_auth_library().mast_forest().procedure_digests());
165 },
166 AccountComponentInterface::Custom(custom_procs) => {
167 component_proc_digests
168 .extend(custom_procs.iter().map(|info| *info.mast_root()));
169 },
170 }
171 }
172
173 component_proc_digests
174 }
175}
176
177impl AccountInterface {
180 pub fn build_send_notes_script(
221 &self,
222 output_notes: &[PartialNote],
223 expiration_delta: Option<u16>,
224 in_debug_mode: bool,
225 ) -> Result<TransactionScript, AccountInterfaceError> {
226 let note_creation_source = self.build_create_notes_section(output_notes)?;
227
228 let script = format!(
229 "begin\n{}\n{}\nend",
230 self.build_set_tx_expiration_section(expiration_delta),
231 note_creation_source,
232 );
233
234 let tx_script = ScriptBuilder::new(in_debug_mode)
235 .compile_tx_script(script)
236 .map_err(AccountInterfaceError::InvalidTransactionScript)?;
237
238 Ok(tx_script)
239 }
240
241 fn build_create_notes_section(
254 &self,
255 output_notes: &[PartialNote],
256 ) -> Result<String, AccountInterfaceError> {
257 if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
258 matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet(_))
259 }) {
260 basic_fungible_faucet.send_note_body(*self.id(), output_notes)
261 } else if let Some(_network_fungible_faucet) =
262 self.components().iter().find(|component_interface| {
263 matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet(_))
264 })
265 {
266 Err(AccountInterfaceError::UnsupportedAccountInterface)
269 } else if self.components().contains(&AccountComponentInterface::BasicWallet) {
270 AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
271 } else {
272 Err(AccountInterfaceError::UnsupportedAccountInterface)
273 }
274 }
275
276 fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
278 if let Some(expiration_delta) = expiration_delta {
279 format!("push.{expiration_delta} exec.::miden::tx::update_expiration_block_delta\n")
280 } else {
281 String::new()
282 }
283 }
284}
285
286impl From<&Account> for AccountInterface {
287 fn from(account: &Account) -> Self {
288 let components = AccountComponentInterface::from_procedures(account.code().procedures());
289 let mut auth = Vec::new();
290
291 for component in components.iter() {
294 if component.is_auth_component() {
295 auth = component.get_auth_schemes(account.storage());
296 break;
297 }
298 }
299
300 Self {
301 account_id: account.id(),
302 auth,
303 components,
304 }
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum NoteAccountCompatibility {
314 No,
319 Maybe,
322 Yes,
324}
325
326fn verify_note_script_compatibility(
337 note_script: &NoteScript,
338 account_procedures: BTreeSet<Word>,
339) -> NoteAccountCompatibility {
340 let branches = collect_call_branches(note_script);
342
343 if !branches.iter().any(|call_targets| call_targets.is_subset(&account_procedures)) {
345 return NoteAccountCompatibility::No;
346 }
347
348 NoteAccountCompatibility::Maybe
349}
350
351fn collect_call_branches(note_script: &NoteScript) -> Vec<BTreeSet<Word>> {
354 let mut branches = vec![BTreeSet::new()];
355
356 let entry_node = note_script.entrypoint();
357 recursively_collect_call_branches(entry_node, &mut branches, ¬e_script.mast());
358 branches
359}
360
361fn recursively_collect_call_branches(
363 mast_node_id: MastNodeId,
364 branches: &mut Vec<BTreeSet<Word>>,
365 note_script_forest: &Arc<MastForest>,
366) {
367 let mast_node = ¬e_script_forest[mast_node_id];
368
369 match mast_node {
370 MastNode::Block(_) => {},
371 MastNode::Join(join_node) => {
372 recursively_collect_call_branches(join_node.first(), branches, note_script_forest);
373 recursively_collect_call_branches(join_node.second(), branches, note_script_forest);
374 },
375 MastNode::Split(split_node) => {
376 let current_branch = branches.last().expect("at least one execution branch").clone();
377 recursively_collect_call_branches(split_node.on_false(), branches, note_script_forest);
378
379 if branches.last().expect("at least one execution branch").len() > current_branch.len()
381 {
382 branches.push(current_branch);
383 }
384
385 recursively_collect_call_branches(split_node.on_true(), branches, note_script_forest);
386 },
387 MastNode::Loop(loop_node) => {
388 recursively_collect_call_branches(loop_node.body(), branches, note_script_forest);
389 },
390 MastNode::Call(call_node) => {
391 if call_node.is_syscall() {
392 return;
393 }
394
395 let callee_digest = note_script_forest[call_node.callee()].digest();
396
397 branches
398 .last_mut()
399 .expect("at least one execution branch")
400 .insert(callee_digest);
401 },
402 MastNode::Dyn(_) => {},
403 MastNode::External(_) => {},
404 }
405}
406
407#[derive(Debug, Error)]
412pub enum AccountInterfaceError {
413 #[error("note asset is not issued by this faucet: {0}")]
414 IssuanceFaucetMismatch(AccountIdPrefix),
415 #[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
416 FaucetNoteWithoutAsset,
417 #[error("invalid transaction script")]
418 InvalidTransactionScript(#[source] ScriptBuilderError),
419 #[error("invalid sender account: {0}")]
420 InvalidSenderAccount(AccountId),
421 #[error("{} interface does not support the generation of the standard send_note script", interface.name())]
422 UnsupportedInterface { interface: AccountComponentInterface },
423 #[error(
424 "account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
425 )]
426 UnsupportedAccountInterface,
427}