Skip to main content

miden_standards/account/interface/
extension.rs

1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_processor::MastNodeExt;
6use miden_protocol::Word;
7use miden_protocol::account::{Account, AccountCode, AccountId, AccountProcedureRoot};
8use miden_protocol::assembly::mast::{MastForest, MastNode, MastNodeId};
9use miden_protocol::note::{Note, NoteScript};
10
11use crate::AuthMethod;
12use crate::account::components::{
13    StandardAccountComponent,
14    basic_fungible_faucet_library,
15    basic_wallet_library,
16    multisig_library,
17    network_fungible_faucet_library,
18    no_auth_library,
19    singlesig_acl_library,
20    singlesig_library,
21};
22use crate::account::interface::{
23    AccountComponentInterface,
24    AccountInterface,
25    NoteAccountCompatibility,
26};
27use crate::note::StandardNote;
28
29// ACCOUNT INTERFACE EXTENSION TRAIT
30// ================================================================================================
31
32/// An extension for [`AccountInterface`] that allows instantiation from higher-level types.
33pub trait AccountInterfaceExt {
34    /// Creates a new [`AccountInterface`] instance from the provided account ID, authentication
35    /// methods and account code.
36    fn from_code(account_id: AccountId, auth: Vec<AuthMethod>, code: &AccountCode) -> Self;
37
38    /// Creates a new [`AccountInterface`] instance from the provided [`Account`].
39    fn from_account(account: &Account) -> Self;
40
41    /// Returns [NoteAccountCompatibility::Maybe] if the provided note is compatible with the
42    /// current [AccountInterface], and [NoteAccountCompatibility::No] otherwise.
43    fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility;
44
45    /// Returns the set of digests of all procedures from all account component interfaces.
46    fn get_procedure_digests(&self) -> BTreeSet<Word>;
47}
48
49impl AccountInterfaceExt for AccountInterface {
50    fn from_code(account_id: AccountId, auth: Vec<AuthMethod>, code: &AccountCode) -> Self {
51        let components = AccountComponentInterface::from_procedures(code.procedures());
52
53        Self::new(account_id, auth, components)
54    }
55
56    fn from_account(account: &Account) -> Self {
57        let components = AccountComponentInterface::from_procedures(account.code().procedures());
58        let mut auth = Vec::new();
59
60        // Find the auth component and extract all auth methods from it
61        // An account should have only one auth component
62        for component in components.iter() {
63            if component.is_auth_component() {
64                auth = component.get_auth_methods(account.storage());
65                break;
66            }
67        }
68
69        Self::new(account.id(), auth, components)
70    }
71
72    /// Returns [NoteAccountCompatibility::Maybe] if the provided note is compatible with the
73    /// current [AccountInterface], and [NoteAccountCompatibility::No] otherwise.
74    fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility {
75        if let Some(standard_note) = StandardNote::from_script_root(note.script().root()) {
76            if standard_note.is_compatible_with(self) {
77                NoteAccountCompatibility::Maybe
78            } else {
79                NoteAccountCompatibility::No
80            }
81        } else {
82            verify_note_script_compatibility(note.script(), self.get_procedure_digests())
83        }
84    }
85
86    fn get_procedure_digests(&self) -> BTreeSet<Word> {
87        let mut component_proc_digests = BTreeSet::new();
88        for component in self.components.iter() {
89            match component {
90                AccountComponentInterface::BasicWallet => {
91                    component_proc_digests
92                        .extend(basic_wallet_library().mast_forest().procedure_digests());
93                },
94                AccountComponentInterface::BasicFungibleFaucet => {
95                    component_proc_digests
96                        .extend(basic_fungible_faucet_library().mast_forest().procedure_digests());
97                },
98                AccountComponentInterface::NetworkFungibleFaucet => {
99                    component_proc_digests.extend(
100                        network_fungible_faucet_library().mast_forest().procedure_digests(),
101                    );
102                },
103                AccountComponentInterface::AuthSingleSig => {
104                    component_proc_digests
105                        .extend(singlesig_library().mast_forest().procedure_digests());
106                },
107                AccountComponentInterface::AuthSingleSigAcl => {
108                    component_proc_digests
109                        .extend(singlesig_acl_library().mast_forest().procedure_digests());
110                },
111                AccountComponentInterface::AuthMultisig => {
112                    component_proc_digests
113                        .extend(multisig_library().mast_forest().procedure_digests());
114                },
115                AccountComponentInterface::AuthNoAuth => {
116                    component_proc_digests
117                        .extend(no_auth_library().mast_forest().procedure_digests());
118                },
119                AccountComponentInterface::Custom(custom_procs) => {
120                    component_proc_digests
121                        .extend(custom_procs.iter().map(|info| *info.mast_root()));
122                },
123            }
124        }
125
126        component_proc_digests
127    }
128}
129
130/// An extension for [`AccountComponentInterface`] that allows instantiation from a set of procedure
131/// roots.
132pub trait AccountComponentInterfaceExt {
133    /// Creates a vector of [`AccountComponentInterface`] instances from the provided set of
134    /// procedures.
135    fn from_procedures(procedures: &[AccountProcedureRoot]) -> Vec<AccountComponentInterface>;
136}
137
138impl AccountComponentInterfaceExt for AccountComponentInterface {
139    fn from_procedures(procedures: &[AccountProcedureRoot]) -> Vec<Self> {
140        let mut component_interface_vec = Vec::new();
141
142        let mut procedures = BTreeSet::from_iter(procedures.iter().copied());
143
144        // Standard component interfaces
145        // ----------------------------------------------------------------------------------------
146
147        // Get all available standard components which could be constructed from the
148        // `procedures` map and push them to the `component_interface_vec`
149        StandardAccountComponent::extract_standard_components(
150            &mut procedures,
151            &mut component_interface_vec,
152        );
153
154        // Custom component interfaces
155        // ----------------------------------------------------------------------------------------
156
157        // All remaining procedures are put into the custom bucket.
158        component_interface_vec
159            .push(AccountComponentInterface::Custom(procedures.into_iter().collect()));
160
161        component_interface_vec
162    }
163}
164
165// HELPER FUNCTIONS
166// ------------------------------------------------------------------------------------------------
167
168/// Verifies that the provided note script is compatible with the target account interfaces.
169///
170/// This is achieved by checking that at least one execution branch in the note script is compatible
171/// with the account procedures vector.
172///
173/// This check relies on the fact that account procedures are the only procedures that are `call`ed
174/// from note scripts, while kernel procedures are `sycall`ed.
175fn verify_note_script_compatibility(
176    note_script: &NoteScript,
177    account_procedures: BTreeSet<Word>,
178) -> NoteAccountCompatibility {
179    // collect call branches of the note script
180    let branches = collect_call_branches(note_script);
181
182    // if none of the branches are compatible with the target account, return a `CheckResult::No`
183    if !branches.iter().any(|call_targets| call_targets.is_subset(&account_procedures)) {
184        return NoteAccountCompatibility::No;
185    }
186
187    NoteAccountCompatibility::Maybe
188}
189
190/// Collect call branches by recursively traversing through program execution branches and
191/// accumulating call targets.
192fn collect_call_branches(note_script: &NoteScript) -> Vec<BTreeSet<Word>> {
193    let mut branches = vec![BTreeSet::new()];
194
195    let entry_node = note_script.entrypoint();
196    recursively_collect_call_branches(entry_node, &mut branches, &note_script.mast());
197    branches
198}
199
200/// Generates a list of calls invoked in each execution branch of the provided code block.
201fn recursively_collect_call_branches(
202    mast_node_id: MastNodeId,
203    branches: &mut Vec<BTreeSet<Word>>,
204    note_script_forest: &Arc<MastForest>,
205) {
206    let mast_node = &note_script_forest[mast_node_id];
207
208    match mast_node {
209        MastNode::Block(_) => {},
210        MastNode::Join(join_node) => {
211            recursively_collect_call_branches(join_node.first(), branches, note_script_forest);
212            recursively_collect_call_branches(join_node.second(), branches, note_script_forest);
213        },
214        MastNode::Split(split_node) => {
215            let current_branch = branches.last().expect("at least one execution branch").clone();
216            recursively_collect_call_branches(split_node.on_false(), branches, note_script_forest);
217
218            // If the previous branch had additional calls we need to create a new branch
219            if branches.last().expect("at least one execution branch").len() > current_branch.len()
220            {
221                branches.push(current_branch);
222            }
223
224            recursively_collect_call_branches(split_node.on_true(), branches, note_script_forest);
225        },
226        MastNode::Loop(loop_node) => {
227            recursively_collect_call_branches(loop_node.body(), branches, note_script_forest);
228        },
229        MastNode::Call(call_node) => {
230            if call_node.is_syscall() {
231                return;
232            }
233
234            let callee_digest = note_script_forest[call_node.callee()].digest();
235
236            branches
237                .last_mut()
238                .expect("at least one execution branch")
239                .insert(callee_digest);
240        },
241        MastNode::Dyn(_) => {},
242        MastNode::External(_) => {},
243    }
244}