miden_client/transaction/request/
foreign.rs

1//! Contains structures and functions related to FPI (Foreign Procedure Invocation) transactions.
2use alloc::{string::ToString, vec::Vec};
3use core::cmp::Ordering;
4
5use miden_objects::{
6    account::{Account, AccountCode, AccountHeader, AccountId, AccountStorageHeader, StorageSlot},
7    crypto::merkle::{MerklePath, SmtProof},
8};
9use miden_tx::utils::{Deserializable, DeserializationError, Serializable};
10
11use super::TransactionRequestError;
12use crate::rpc::domain::account::{AccountProof, AccountStorageRequirements, StateHeaders};
13
14// FOREIGN ACCOUNT
15// ================================================================================================
16
17/// Account types for foreign procedure invocation.
18#[derive(Clone, Debug, PartialEq, Eq)]
19#[allow(clippy::large_enum_variant)]
20pub enum ForeignAccount {
21    /// Public account data will be retrieved from the network at execution time, based on the
22    /// account ID. The second element of the tuple indicates which storage slot indices
23    /// and map keys are desired to be retrieved.
24    Public(AccountId, AccountStorageRequirements),
25    /// Private account data requires [`ForeignAccountInputs`] to be input. Proof of the account's
26    /// existence will be retrieved from the network at execution time.
27    Private(ForeignAccountInputs),
28}
29
30impl ForeignAccount {
31    /// Creates a new [`ForeignAccount::Public`]. The account's components (code, storage header and
32    /// inclusion proof) will be retrieved at execution time, alongside particular storage slot
33    /// maps correspondent to keys passed in `indices`.
34    pub fn public(
35        account_id: AccountId,
36        storage_requirements: AccountStorageRequirements,
37    ) -> Result<Self, TransactionRequestError> {
38        if !account_id.is_public() {
39            return Err(TransactionRequestError::InvalidForeignAccountId(account_id));
40        }
41
42        Ok(Self::Public(account_id, storage_requirements))
43    }
44
45    /// Creates a new [`ForeignAccount::Private`]. A proof of the account's inclusion will be
46    /// retrieved at execution time.
47    pub fn private(
48        account: impl Into<ForeignAccountInputs>,
49    ) -> Result<Self, TransactionRequestError> {
50        let foreign_account: ForeignAccountInputs = account.into();
51        if foreign_account.account_header().id().is_public() {
52            return Err(TransactionRequestError::InvalidForeignAccountId(
53                foreign_account.account_header().id(),
54            ));
55        }
56
57        Ok(Self::Private(foreign_account))
58    }
59
60    pub fn storage_slot_requirements(&self) -> AccountStorageRequirements {
61        match self {
62            ForeignAccount::Public(_, account_storage_requirements) => {
63                account_storage_requirements.clone()
64            },
65            ForeignAccount::Private(_) => AccountStorageRequirements::default(),
66        }
67    }
68
69    /// Returns the foreign account's [`AccountId`].
70    pub fn account_id(&self) -> AccountId {
71        match self {
72            ForeignAccount::Public(account_id, _) => *account_id,
73            ForeignAccount::Private(foreign_account_inputs) => {
74                foreign_account_inputs.account_header.id()
75            },
76        }
77    }
78}
79
80impl Ord for ForeignAccount {
81    fn cmp(&self, other: &Self) -> Ordering {
82        self.account_id().cmp(&other.account_id())
83    }
84}
85
86impl PartialOrd for ForeignAccount {
87    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
88        Some(self.cmp(other))
89    }
90}
91
92impl Serializable for ForeignAccount {
93    fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
94        match self {
95            ForeignAccount::Public(account_id, storage_requirements) => {
96                target.write(0u8);
97                account_id.write_into(target);
98                storage_requirements.write_into(target);
99            },
100            ForeignAccount::Private(foreign_account_inputs) => {
101                target.write(1u8);
102                foreign_account_inputs.write_into(target);
103            },
104        }
105    }
106}
107
108impl Deserializable for ForeignAccount {
109    fn read_from<R: miden_tx::utils::ByteReader>(
110        source: &mut R,
111    ) -> Result<Self, miden_tx::utils::DeserializationError> {
112        let account_type: u8 = source.read_u8()?;
113        match account_type {
114            0 => {
115                let account_id = AccountId::read_from(source)?;
116                let storage_requirements = AccountStorageRequirements::read_from(source)?;
117                Ok(ForeignAccount::Public(account_id, storage_requirements))
118            },
119            1 => {
120                let foreign_inputs = ForeignAccountInputs::read_from(source)?;
121                Ok(ForeignAccount::Private(foreign_inputs))
122            },
123            _ => Err(DeserializationError::InvalidValue("Invalid account type".to_string())),
124        }
125    }
126}
127
128// FOREIGN ACCOUNT INPUTS
129// ================================================================================================
130
131/// Contains information about a foreign account, with everything required to execute its code.
132#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct ForeignAccountInputs {
134    /// Account header of the foreign account.
135    account_header: AccountHeader,
136    /// Header information about the account's storage.
137    storage_header: AccountStorageHeader,
138    /// Code associated with the account.
139    account_code: AccountCode,
140    /// Storage SMT proof for storage map values that the transaction will access.
141    storage_map_proofs: Vec<SmtProof>,
142}
143
144impl ForeignAccountInputs {
145    /// Creates a new [`ForeignAccountInputs`]
146    pub fn new(
147        account_header: AccountHeader,
148        storage_header: AccountStorageHeader,
149        account_code: AccountCode,
150        storage_map_proofs: Vec<SmtProof>,
151    ) -> ForeignAccountInputs {
152        ForeignAccountInputs {
153            account_header,
154            storage_header,
155            account_code,
156            storage_map_proofs,
157        }
158    }
159
160    /// Creates a new [`ForeignAccountInputs`] from an [`Account`] and a list of storage keys.
161    ///
162    /// # Errors
163    ///
164    /// - If one of the specified slots in `storage_requirements` is not a map-type slot or it is
165    ///   not found.
166    pub fn from_account(
167        account: Account,
168        storage_requirements: &AccountStorageRequirements,
169    ) -> Result<ForeignAccountInputs, TransactionRequestError> {
170        // Get required proofs
171        let mut smt_proofs = vec![];
172        for (slot_index, keys) in storage_requirements.inner() {
173            for key in keys {
174                let slot = account.storage().slots().get(*slot_index as usize);
175                match slot {
176                    Some(StorageSlot::Map(map)) => {
177                        smt_proofs.push(map.open(key));
178                    },
179                    Some(StorageSlot::Value(_)) => {
180                        return Err(
181                            TransactionRequestError::ForeignAccountStorageSlotInvalidIndex(
182                                *slot_index,
183                            ),
184                        );
185                    },
186                    None => {
187                        return Err(TransactionRequestError::StorageSlotNotFound(
188                            *slot_index,
189                            account.id(),
190                        ));
191                    },
192                }
193            }
194        }
195
196        let account_code: AccountCode = account.code().clone();
197        let storage_header: AccountStorageHeader = account.storage().get_header();
198        let account_header: AccountHeader = account.into();
199
200        Ok(ForeignAccountInputs::new(
201            account_header,
202            storage_header,
203            account_code,
204            smt_proofs,
205        ))
206    }
207
208    /// Returns the account's [`AccountHeader`]
209    pub fn account_header(&self) -> &AccountHeader {
210        &self.account_header
211    }
212
213    /// Returns the account's [`AccountStorageHeader`].
214    pub fn storage_header(&self) -> &AccountStorageHeader {
215        &self.storage_header
216    }
217
218    /// Returns the account's storage maps.
219    pub fn storage_map_proofs(&self) -> &[SmtProof] {
220        &self.storage_map_proofs
221    }
222
223    /// Returns the account's [`AccountCode`].
224    pub fn account_code(&self) -> &AccountCode {
225        &self.account_code
226    }
227
228    /// Extends the storage proofs with the input `smt_proofs` and returns the new structure
229    #[must_use]
230    pub fn with_storage_map_proofs(
231        mut self,
232        smt_proofs: impl IntoIterator<Item = SmtProof>,
233    ) -> Self {
234        self.storage_map_proofs.extend(smt_proofs);
235        self
236    }
237
238    /// Consumes the [`ForeignAccountInputs`] and returns its parts.
239    pub fn into_parts(self) -> (AccountHeader, AccountStorageHeader, AccountCode, Vec<SmtProof>) {
240        (
241            self.account_header,
242            self.storage_header,
243            self.account_code,
244            self.storage_map_proofs,
245        )
246    }
247}
248
249impl Serializable for ForeignAccountInputs {
250    fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
251        self.account_header.write_into(target);
252        self.storage_header.write_into(target);
253        self.account_code.write_into(target);
254        self.storage_map_proofs.write_into(target);
255    }
256}
257
258impl Deserializable for ForeignAccountInputs {
259    fn read_from<R: miden_tx::utils::ByteReader>(
260        source: &mut R,
261    ) -> Result<Self, miden_tx::utils::DeserializationError> {
262        let account_header = AccountHeader::read_from(source)?;
263        let storage_header = AccountStorageHeader::read_from(source)?;
264        let account_code = AccountCode::read_from(source)?;
265        let storage_maps = Vec::<SmtProof>::read_from(source)?;
266        Ok(ForeignAccountInputs::new(
267            account_header,
268            storage_header,
269            account_code,
270            storage_maps,
271        ))
272    }
273}
274
275impl TryFrom<AccountProof> for (ForeignAccountInputs, MerklePath) {
276    type Error = TransactionRequestError;
277
278    fn try_from(value: AccountProof) -> Result<Self, Self::Error> {
279        let (_, merkle_proof, _, state_headers) = value.into_parts();
280        if let Some(StateHeaders {
281            account_header,
282            storage_header,
283            code,
284            storage_slots,
285        }) = state_headers
286        {
287            // discard slot indices - not needed for execution
288            let slots = storage_slots.into_iter().flat_map(|(_, slots)| slots).collect();
289            let inputs = ForeignAccountInputs::new(account_header, storage_header, code, slots);
290            return Ok((inputs, merkle_proof));
291        }
292        Err(TransactionRequestError::ForeignAccountDataMissing)
293    }
294}