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 thiserror::Error;
12
13use crate::AuthScheme;
14use crate::account::components::{
15 basic_fungible_faucet_library,
16 basic_wallet_library,
17 multisig_library,
18 no_auth_library,
19 rpo_falcon_512_acl_library,
20 rpo_falcon_512_library,
21};
22use crate::errors::ScriptBuilderError;
23use crate::note::well_known_note::WellKnownNote;
24use crate::utils::ScriptBuilder;
25
26#[cfg(test)]
27mod test;
28
29mod component;
30pub use component::AccountComponentInterface;
31
32pub struct AccountInterface {
40 account_id: AccountId,
41 auth: Vec<AuthScheme>,
42 components: Vec<AccountComponentInterface>,
43}
44
45impl AccountInterface {
48 pub fn new(account_id: AccountId, auth: Vec<AuthScheme>, code: &AccountCode) -> Self {
54 let components = AccountComponentInterface::from_procedures(code.procedures());
55
56 Self { account_id, auth, components }
57 }
58
59 pub fn id(&self) -> &AccountId {
64 &self.account_id
65 }
66
67 pub fn account_type(&self) -> AccountType {
69 self.account_id.account_type()
70 }
71
72 pub fn is_faucet(&self) -> bool {
74 self.account_id.is_faucet()
75 }
76
77 pub fn is_regular_account(&self) -> bool {
79 self.account_id.is_regular_account()
80 }
81
82 pub fn is_onchain(&self) -> bool {
87 self.account_id.is_onchain()
88 }
89
90 pub fn is_private(&self) -> bool {
92 self.account_id.is_private()
93 }
94
95 pub fn is_public(&self) -> bool {
97 self.account_id.is_public()
98 }
99
100 pub fn is_network(&self) -> bool {
102 self.account_id.is_network()
103 }
104
105 pub fn auth(&self) -> &Vec<AuthScheme> {
107 &self.auth
108 }
109
110 pub fn components(&self) -> &Vec<AccountComponentInterface> {
112 &self.components
113 }
114
115 pub fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility {
118 if let Some(well_known_note) = WellKnownNote::from_note(note) {
119 if well_known_note.is_compatible_with(self) {
120 NoteAccountCompatibility::Maybe
121 } else {
122 NoteAccountCompatibility::No
123 }
124 } else {
125 verify_note_script_compatibility(note.script(), self.get_procedure_digests())
126 }
127 }
128
129 pub(crate) fn get_procedure_digests(&self) -> BTreeSet<Word> {
131 let mut component_proc_digests = BTreeSet::new();
132 for component in self.components.iter() {
133 match component {
134 AccountComponentInterface::BasicWallet => {
135 component_proc_digests
136 .extend(basic_wallet_library().mast_forest().procedure_digests());
137 },
138 AccountComponentInterface::BasicFungibleFaucet(_) => {
139 component_proc_digests
140 .extend(basic_fungible_faucet_library().mast_forest().procedure_digests());
141 },
142 AccountComponentInterface::AuthRpoFalcon512(_) => {
143 component_proc_digests
144 .extend(rpo_falcon_512_library().mast_forest().procedure_digests());
145 },
146 AccountComponentInterface::AuthRpoFalcon512Acl(_) => {
147 component_proc_digests
148 .extend(rpo_falcon_512_acl_library().mast_forest().procedure_digests());
149 },
150 AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
151 component_proc_digests
152 .extend(multisig_library().mast_forest().procedure_digests());
153 },
154 AccountComponentInterface::AuthNoAuth => {
155 component_proc_digests
156 .extend(no_auth_library().mast_forest().procedure_digests());
157 },
158 AccountComponentInterface::Custom(custom_procs) => {
159 component_proc_digests
160 .extend(custom_procs.iter().map(|info| *info.mast_root()));
161 },
162 }
163 }
164
165 component_proc_digests
166 }
167}
168
169impl AccountInterface {
172 pub fn build_send_notes_script(
213 &self,
214 output_notes: &[PartialNote],
215 expiration_delta: Option<u16>,
216 in_debug_mode: bool,
217 ) -> Result<TransactionScript, AccountInterfaceError> {
218 let note_creation_source = self.build_create_notes_section(output_notes)?;
219
220 let script = format!(
221 "begin\n{}\n{}\nend",
222 self.build_set_tx_expiration_section(expiration_delta),
223 note_creation_source,
224 );
225
226 let tx_script = ScriptBuilder::new(in_debug_mode)
227 .compile_tx_script(script)
228 .map_err(AccountInterfaceError::InvalidTransactionScript)?;
229
230 Ok(tx_script)
231 }
232
233 fn build_create_notes_section(
246 &self,
247 output_notes: &[PartialNote],
248 ) -> Result<String, AccountInterfaceError> {
249 if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
250 matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet(_))
251 }) {
252 basic_fungible_faucet.send_note_body(*self.id(), output_notes)
253 } else if self.components().contains(&AccountComponentInterface::BasicWallet) {
254 AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
255 } else {
256 Err(AccountInterfaceError::UnsupportedAccountInterface)
257 }
258 }
259
260 fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
262 if let Some(expiration_delta) = expiration_delta {
263 format!("push.{expiration_delta} exec.::miden::tx::update_expiration_block_delta\n")
264 } else {
265 String::new()
266 }
267 }
268}
269
270impl From<&Account> for AccountInterface {
271 fn from(account: &Account) -> Self {
272 let components = AccountComponentInterface::from_procedures(account.code().procedures());
273 let mut auth = Vec::new();
274
275 for component in components.iter() {
278 if component.is_auth_component() {
279 auth = component.get_auth_schemes(account.storage());
280 break;
281 }
282 }
283
284 Self {
285 account_id: account.id(),
286 auth,
287 components,
288 }
289 }
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum NoteAccountCompatibility {
298 No,
303 Maybe,
306 Yes,
308}
309
310fn verify_note_script_compatibility(
321 note_script: &NoteScript,
322 account_procedures: BTreeSet<Word>,
323) -> NoteAccountCompatibility {
324 let branches = collect_call_branches(note_script);
326
327 if !branches.iter().any(|call_targets| call_targets.is_subset(&account_procedures)) {
329 return NoteAccountCompatibility::No;
330 }
331
332 NoteAccountCompatibility::Maybe
333}
334
335fn collect_call_branches(note_script: &NoteScript) -> Vec<BTreeSet<Word>> {
338 let mut branches = vec![BTreeSet::new()];
339
340 let entry_node = note_script.entrypoint();
341 recursively_collect_call_branches(entry_node, &mut branches, ¬e_script.mast());
342 branches
343}
344
345fn recursively_collect_call_branches(
347 mast_node_id: MastNodeId,
348 branches: &mut Vec<BTreeSet<Word>>,
349 note_script_forest: &Arc<MastForest>,
350) {
351 let mast_node = ¬e_script_forest[mast_node_id];
352
353 match mast_node {
354 MastNode::Block(_) => {},
355 MastNode::Join(join_node) => {
356 recursively_collect_call_branches(join_node.first(), branches, note_script_forest);
357 recursively_collect_call_branches(join_node.second(), branches, note_script_forest);
358 },
359 MastNode::Split(split_node) => {
360 let current_branch = branches.last().expect("at least one execution branch").clone();
361 recursively_collect_call_branches(split_node.on_false(), branches, note_script_forest);
362
363 if branches.last().expect("at least one execution branch").len() > current_branch.len()
365 {
366 branches.push(current_branch);
367 }
368
369 recursively_collect_call_branches(split_node.on_true(), branches, note_script_forest);
370 },
371 MastNode::Loop(loop_node) => {
372 recursively_collect_call_branches(loop_node.body(), branches, note_script_forest);
373 },
374 MastNode::Call(call_node) => {
375 if call_node.is_syscall() {
376 return;
377 }
378
379 let callee_digest = note_script_forest[call_node.callee()].digest();
380
381 branches
382 .last_mut()
383 .expect("at least one execution branch")
384 .insert(callee_digest);
385 },
386 MastNode::Dyn(_) => {},
387 MastNode::External(_) => {},
388 }
389}
390
391#[derive(Debug, Error)]
396pub enum AccountInterfaceError {
397 #[error("note asset is not issued by this faucet: {0}")]
398 IssuanceFaucetMismatch(AccountIdPrefix),
399 #[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
400 FaucetNoteWithoutAsset,
401 #[error("invalid transaction script")]
402 InvalidTransactionScript(#[source] ScriptBuilderError),
403 #[error("invalid sender account: {0}")]
404 InvalidSenderAccount(AccountId),
405 #[error("{} interface does not support the generation of the standard send_note script", interface.name())]
406 UnsupportedInterface { interface: AccountComponentInterface },
407 #[error(
408 "account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
409 )]
410 UnsupportedAccountInterface,
411}