miden_client/transaction/request/
foreign.rs

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