use alloc::collections::BTreeSet;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use miden_objects::Word;
use miden_objects::account::{Account, AccountCode, AccountId, AccountIdPrefix, AccountType};
use miden_objects::assembly::mast::{MastForest, MastNode, MastNodeId};
use miden_objects::note::{Note, NoteScript, PartialNote};
use miden_objects::transaction::TransactionScript;
use miden_processor::MastNodeExt;
use thiserror::Error;
use crate::AuthScheme;
use crate::account::components::{
basic_fungible_faucet_library,
basic_wallet_library,
ecdsa_k256_keccak_acl_library,
ecdsa_k256_keccak_library,
ecdsa_k256_keccak_multisig_library,
network_fungible_faucet_library,
no_auth_library,
rpo_falcon_512_acl_library,
rpo_falcon_512_library,
rpo_falcon_512_multisig_library,
};
use crate::errors::ScriptBuilderError;
use crate::note::WellKnownNote;
use crate::utils::ScriptBuilder;
#[cfg(test)]
mod test;
mod component;
pub use component::AccountComponentInterface;
pub struct AccountInterface {
account_id: AccountId,
auth: Vec<AuthScheme>,
components: Vec<AccountComponentInterface>,
}
impl AccountInterface {
pub fn new(account_id: AccountId, auth: Vec<AuthScheme>, code: &AccountCode) -> Self {
let components = AccountComponentInterface::from_procedures(code.procedures());
Self { account_id, auth, components }
}
pub fn id(&self) -> &AccountId {
&self.account_id
}
pub fn account_type(&self) -> AccountType {
self.account_id.account_type()
}
pub fn is_faucet(&self) -> bool {
self.account_id.is_faucet()
}
pub fn is_regular_account(&self) -> bool {
self.account_id.is_regular_account()
}
pub fn has_public_state(&self) -> bool {
self.account_id.has_public_state()
}
pub fn is_private(&self) -> bool {
self.account_id.is_private()
}
pub fn is_public(&self) -> bool {
self.account_id.is_public()
}
pub fn is_network(&self) -> bool {
self.account_id.is_network()
}
pub fn auth(&self) -> &Vec<AuthScheme> {
&self.auth
}
pub fn components(&self) -> &Vec<AccountComponentInterface> {
&self.components
}
pub fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility {
if let Some(well_known_note) = WellKnownNote::from_note(note) {
if well_known_note.is_compatible_with(self) {
NoteAccountCompatibility::Maybe
} else {
NoteAccountCompatibility::No
}
} else {
verify_note_script_compatibility(note.script(), self.get_procedure_digests())
}
}
pub(crate) fn get_procedure_digests(&self) -> BTreeSet<Word> {
let mut component_proc_digests = BTreeSet::new();
for component in self.components.iter() {
match component {
AccountComponentInterface::BasicWallet => {
component_proc_digests
.extend(basic_wallet_library().mast_forest().procedure_digests());
},
AccountComponentInterface::BasicFungibleFaucet(_) => {
component_proc_digests
.extend(basic_fungible_faucet_library().mast_forest().procedure_digests());
},
AccountComponentInterface::NetworkFungibleFaucet(_) => {
component_proc_digests.extend(
network_fungible_faucet_library().mast_forest().procedure_digests(),
);
},
AccountComponentInterface::AuthEcdsaK256Keccak(_) => {
component_proc_digests
.extend(ecdsa_k256_keccak_library().mast_forest().procedure_digests());
},
AccountComponentInterface::AuthEcdsaK256KeccakAcl(_) => {
component_proc_digests
.extend(ecdsa_k256_keccak_acl_library().mast_forest().procedure_digests());
},
AccountComponentInterface::AuthEcdsaK256KeccakMultisig(_) => {
component_proc_digests.extend(
ecdsa_k256_keccak_multisig_library().mast_forest().procedure_digests(),
);
},
AccountComponentInterface::AuthRpoFalcon512(_) => {
component_proc_digests
.extend(rpo_falcon_512_library().mast_forest().procedure_digests());
},
AccountComponentInterface::AuthRpoFalcon512Acl(_) => {
component_proc_digests
.extend(rpo_falcon_512_acl_library().mast_forest().procedure_digests());
},
AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
component_proc_digests.extend(
rpo_falcon_512_multisig_library().mast_forest().procedure_digests(),
);
},
AccountComponentInterface::AuthNoAuth => {
component_proc_digests
.extend(no_auth_library().mast_forest().procedure_digests());
},
AccountComponentInterface::Custom(custom_procs) => {
component_proc_digests
.extend(custom_procs.iter().map(|info| *info.mast_root()));
},
}
}
component_proc_digests
}
}
impl AccountInterface {
pub fn build_send_notes_script(
&self,
output_notes: &[PartialNote],
expiration_delta: Option<u16>,
in_debug_mode: bool,
) -> Result<TransactionScript, AccountInterfaceError> {
let note_creation_source = self.build_create_notes_section(output_notes)?;
let script = format!(
"begin\n{}\n{}\nend",
self.build_set_tx_expiration_section(expiration_delta),
note_creation_source,
);
let tx_script = ScriptBuilder::new(in_debug_mode)
.compile_tx_script(script)
.map_err(AccountInterfaceError::InvalidTransactionScript)?;
Ok(tx_script)
}
fn build_create_notes_section(
&self,
output_notes: &[PartialNote],
) -> Result<String, AccountInterfaceError> {
if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet(_))
}) {
basic_fungible_faucet.send_note_body(*self.id(), output_notes)
} else if let Some(_network_fungible_faucet) =
self.components().iter().find(|component_interface| {
matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet(_))
})
{
Err(AccountInterfaceError::UnsupportedAccountInterface)
} else if self.components().contains(&AccountComponentInterface::BasicWallet) {
AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
} else {
Err(AccountInterfaceError::UnsupportedAccountInterface)
}
}
fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
if let Some(expiration_delta) = expiration_delta {
format!("push.{expiration_delta} exec.::miden::tx::update_expiration_block_delta\n")
} else {
String::new()
}
}
}
impl From<&Account> for AccountInterface {
fn from(account: &Account) -> Self {
let components = AccountComponentInterface::from_procedures(account.code().procedures());
let mut auth = Vec::new();
for component in components.iter() {
if component.is_auth_component() {
auth = component.get_auth_schemes(account.storage());
break;
}
}
Self {
account_id: account.id(),
auth,
components,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteAccountCompatibility {
No,
Maybe,
Yes,
}
fn verify_note_script_compatibility(
note_script: &NoteScript,
account_procedures: BTreeSet<Word>,
) -> NoteAccountCompatibility {
let branches = collect_call_branches(note_script);
if !branches.iter().any(|call_targets| call_targets.is_subset(&account_procedures)) {
return NoteAccountCompatibility::No;
}
NoteAccountCompatibility::Maybe
}
fn collect_call_branches(note_script: &NoteScript) -> Vec<BTreeSet<Word>> {
let mut branches = vec![BTreeSet::new()];
let entry_node = note_script.entrypoint();
recursively_collect_call_branches(entry_node, &mut branches, ¬e_script.mast());
branches
}
fn recursively_collect_call_branches(
mast_node_id: MastNodeId,
branches: &mut Vec<BTreeSet<Word>>,
note_script_forest: &Arc<MastForest>,
) {
let mast_node = ¬e_script_forest[mast_node_id];
match mast_node {
MastNode::Block(_) => {},
MastNode::Join(join_node) => {
recursively_collect_call_branches(join_node.first(), branches, note_script_forest);
recursively_collect_call_branches(join_node.second(), branches, note_script_forest);
},
MastNode::Split(split_node) => {
let current_branch = branches.last().expect("at least one execution branch").clone();
recursively_collect_call_branches(split_node.on_false(), branches, note_script_forest);
if branches.last().expect("at least one execution branch").len() > current_branch.len()
{
branches.push(current_branch);
}
recursively_collect_call_branches(split_node.on_true(), branches, note_script_forest);
},
MastNode::Loop(loop_node) => {
recursively_collect_call_branches(loop_node.body(), branches, note_script_forest);
},
MastNode::Call(call_node) => {
if call_node.is_syscall() {
return;
}
let callee_digest = note_script_forest[call_node.callee()].digest();
branches
.last_mut()
.expect("at least one execution branch")
.insert(callee_digest);
},
MastNode::Dyn(_) => {},
MastNode::External(_) => {},
}
}
#[derive(Debug, Error)]
pub enum AccountInterfaceError {
#[error("note asset is not issued by this faucet: {0}")]
IssuanceFaucetMismatch(AccountIdPrefix),
#[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
FaucetNoteWithoutAsset,
#[error("invalid transaction script")]
InvalidTransactionScript(#[source] ScriptBuilderError),
#[error("invalid sender account: {0}")]
InvalidSenderAccount(AccountId),
#[error("{} interface does not support the generation of the standard send_note script", interface.name())]
UnsupportedInterface { interface: AccountComponentInterface },
#[error(
"account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
)]
UnsupportedAccountInterface,
}