Skip to main content

miden_client/transaction/request/
foreign.rs

1//! Contains structures and functions related to FPI (Foreign Procedure Invocation) transactions.
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::cmp::Ordering;
5
6use miden_protocol::account::{
7    AccountId,
8    PartialAccount,
9    PartialStorage,
10    PartialStorageMap,
11    StorageMap,
12};
13use miden_protocol::asset::{AssetVault, PartialVault};
14use miden_protocol::transaction::AccountInputs;
15use miden_tx::utils::{Deserializable, DeserializationError, Serializable};
16
17use super::TransactionRequestError;
18use crate::rpc::domain::account::{
19    AccountDetails,
20    AccountProof,
21    AccountStorageRequirements,
22    StorageMapEntries,
23};
24
25// FOREIGN ACCOUNT
26// ================================================================================================
27
28/// Account types for foreign procedure invocation.
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[allow(clippy::large_enum_variant)]
31pub enum ForeignAccount {
32    /// Public account data will be retrieved from the network at execution time, based on the
33    /// account ID. The second element of the tuple indicates which storage slot indices
34    /// and map keys are desired to be retrieved.
35    Public(AccountId, AccountStorageRequirements),
36    /// Private account data requires [`PartialAccount`] to be passed. An account witness
37    /// will be retrieved from the network at execution time so that it can be used as inputs to
38    /// the transaction kernel.
39    Private(PartialAccount),
40}
41
42impl ForeignAccount {
43    /// Creates a new [`ForeignAccount::Public`]. The account's components (code, storage header and
44    /// inclusion proof) will be retrieved at execution time, alongside particular storage slot
45    /// maps correspondent to keys passed in `indices`.
46    pub fn public(
47        account_id: AccountId,
48        storage_requirements: AccountStorageRequirements,
49    ) -> Result<Self, TransactionRequestError> {
50        if !account_id.is_public() {
51            return Err(TransactionRequestError::InvalidForeignAccountId(account_id));
52        }
53
54        Ok(Self::Public(account_id, storage_requirements))
55    }
56
57    /// Creates a new [`ForeignAccount::Private`]. A proof of the account's inclusion will be
58    /// retrieved at execution time.
59    pub fn private(account: impl Into<PartialAccount>) -> Result<Self, TransactionRequestError> {
60        let partial_account: PartialAccount = account.into();
61        if partial_account.id().is_public() {
62            return Err(TransactionRequestError::InvalidForeignAccountId(partial_account.id()));
63        }
64
65        Ok(Self::Private(partial_account))
66    }
67
68    pub fn storage_slot_requirements(&self) -> AccountStorageRequirements {
69        match self {
70            ForeignAccount::Public(_, account_storage_requirements) => {
71                account_storage_requirements.clone()
72            },
73            ForeignAccount::Private(_) => AccountStorageRequirements::default(),
74        }
75    }
76
77    /// Returns the foreign account's [`AccountId`].
78    pub fn account_id(&self) -> AccountId {
79        match self {
80            ForeignAccount::Public(account_id, _) => *account_id,
81            ForeignAccount::Private(partial_account) => partial_account.id(),
82        }
83    }
84}
85
86impl Ord for ForeignAccount {
87    fn cmp(&self, other: &Self) -> Ordering {
88        self.account_id().cmp(&other.account_id())
89    }
90}
91
92impl PartialOrd for ForeignAccount {
93    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94        Some(self.cmp(other))
95    }
96}
97
98impl Serializable for ForeignAccount {
99    fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
100        match self {
101            ForeignAccount::Public(account_id, storage_requirements) => {
102                target.write(0u8);
103                account_id.write_into(target);
104                storage_requirements.write_into(target);
105            },
106            ForeignAccount::Private(partial_account) => {
107                target.write(1u8);
108                partial_account.write_into(target);
109            },
110        }
111    }
112}
113
114impl Deserializable for ForeignAccount {
115    fn read_from<R: miden_tx::utils::ByteReader>(
116        source: &mut R,
117    ) -> Result<Self, miden_tx::utils::DeserializationError> {
118        let account_type: u8 = source.read_u8()?;
119        match account_type {
120            0 => {
121                let account_id = AccountId::read_from(source)?;
122                let storage_requirements = AccountStorageRequirements::read_from(source)?;
123                Ok(ForeignAccount::Public(account_id, storage_requirements))
124            },
125            1 => {
126                let foreign_inputs = PartialAccount::read_from(source)?;
127                Ok(ForeignAccount::Private(foreign_inputs))
128            },
129            _ => Err(DeserializationError::InvalidValue("Invalid account type".to_string())),
130        }
131    }
132}
133
134/// Converts an [`AccountProof`] to [`AccountInputs`].
135pub fn account_proof_into_inputs(
136    account_proof: AccountProof,
137) -> Result<AccountInputs, TransactionRequestError> {
138    let (witness, account_details) = account_proof.into_parts();
139
140    if let Some(AccountDetails {
141        header: account_header,
142        code,
143        storage_details,
144        vault_details,
145    }) = account_details
146    {
147        // discard slot indices - not needed for execution
148        let account_storage_map_details = storage_details.map_details;
149        let mut storage_map_proofs = Vec::with_capacity(account_storage_map_details.len());
150        for account_storage_detail in account_storage_map_details {
151            let partial_storage = match account_storage_detail.entries {
152                StorageMapEntries::AllEntries(entries) => {
153                    // Full map available - create from all entries
154                    let storage_entries_iter = entries.iter().map(|e| (e.key, e.value));
155                    PartialStorageMap::new_full(
156                        StorageMap::with_entries(storage_entries_iter)
157                            .map_err(TransactionRequestError::StorageMapError)?,
158                    )
159                },
160                StorageMapEntries::EntriesWithProofs(witnesses) => {
161                    // Partial map - create from witnesses
162                    PartialStorageMap::with_witnesses(witnesses)?
163                },
164            };
165            storage_map_proofs.push(partial_storage);
166        }
167
168        let vault = AssetVault::new(&vault_details.assets)?;
169        return Ok(AccountInputs::new(
170            PartialAccount::new(
171                account_header.id(),
172                account_header.nonce(),
173                code,
174                PartialStorage::new(storage_details.header, storage_map_proofs.into_iter())?,
175                PartialVault::new_full(vault),
176                None,
177            )?,
178            witness,
179        ));
180    }
181    Err(TransactionRequestError::ForeignAccountDataMissing)
182}