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