Skip to main content

miden_client/rpc/domain/
account.rs

1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5
6use miden_protocol::Word;
7use miden_protocol::account::{
8    Account,
9    AccountCode,
10    AccountHeader,
11    AccountId,
12    AccountStorageHeader,
13    StorageMap,
14    StorageMapWitness,
15    StorageSlotHeader,
16    StorageSlotName,
17    StorageSlotType,
18};
19use miden_protocol::asset::Asset;
20use miden_protocol::block::BlockNumber;
21use miden_protocol::block::account_tree::AccountWitness;
22use miden_protocol::crypto::merkle::SparseMerklePath;
23use miden_protocol::crypto::merkle::smt::SmtProof;
24use miden_tx::utils::{Deserializable, Serializable, ToHex};
25use thiserror::Error;
26
27use crate::alloc::string::ToString;
28use crate::rpc::RpcError;
29use crate::rpc::domain::MissingFieldHelper;
30use crate::rpc::errors::RpcConversionError;
31use crate::rpc::generated::rpc::account_request::account_detail_request;
32use crate::rpc::generated::{self as proto};
33
34// FETCHED ACCOUNT
35// ================================================================================================
36
37/// Describes the possible responses from the `GetAccountDetails` endpoint for an account.
38pub enum FetchedAccount {
39    /// Private accounts are stored off-chain. Only a commitment to the state of the account is
40    /// shared with the network. The full account state is to be tracked locally.
41    Private(AccountId, AccountUpdateSummary),
42    /// Public accounts are recorded on-chain. As such, its state is shared with the network and
43    /// can always be retrieved through the appropriate RPC method.
44    Public(Box<Account>, AccountUpdateSummary),
45}
46
47impl FetchedAccount {
48    /// Creates a [`FetchedAccount`] corresponding to a private account tracked only by its ID and
49    /// update summary.
50    pub fn new_private(account_id: AccountId, summary: AccountUpdateSummary) -> Self {
51        Self::Private(account_id, summary)
52    }
53
54    /// Creates a [`FetchedAccount`] for a public account with its full [`Account`] state.
55    pub fn new_public(account: Account, summary: AccountUpdateSummary) -> Self {
56        Self::Public(Box::new(account), summary)
57    }
58
59    /// Returns the account ID.
60    pub fn account_id(&self) -> AccountId {
61        match self {
62            Self::Private(account_id, _) => *account_id,
63            Self::Public(account, _) => account.id(),
64        }
65    }
66
67    // Returns the account update summary commitment
68    pub fn commitment(&self) -> Word {
69        match self {
70            Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
71        }
72    }
73
74    // Returns the associated account if the account is public, otherwise none
75    pub fn account(&self) -> Option<&Account> {
76        match self {
77            Self::Private(..) => None,
78            Self::Public(account, _) => Some(account.as_ref()),
79        }
80    }
81}
82
83impl From<FetchedAccount> for Option<Account> {
84    fn from(acc: FetchedAccount) -> Self {
85        match acc {
86            FetchedAccount::Private(..) => None,
87            FetchedAccount::Public(account, _) => Some(*account),
88        }
89    }
90}
91
92// ACCOUNT UPDATE SUMMARY
93// ================================================================================================
94
95/// Contains public updated information about the account requested.
96pub struct AccountUpdateSummary {
97    /// Commitment of the account, that represents a commitment to its updated state.
98    pub commitment: Word,
99    /// Block number of last account update.
100    pub last_block_num: BlockNumber,
101}
102
103impl AccountUpdateSummary {
104    /// Creates a new [`AccountUpdateSummary`].
105    pub fn new(commitment: Word, last_block_num: BlockNumber) -> Self {
106        Self { commitment, last_block_num }
107    }
108}
109
110// ACCOUNT ID
111// ================================================================================================
112
113impl Display for proto::account::AccountId {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        f.write_fmt(format_args!("0x{}", self.id.to_hex()))
116    }
117}
118
119impl Debug for proto::account::AccountId {
120    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121        Display::fmt(self, f)
122    }
123}
124
125// INTO PROTO ACCOUNT ID
126// ================================================================================================
127
128impl From<AccountId> for proto::account::AccountId {
129    fn from(account_id: AccountId) -> Self {
130        Self { id: account_id.to_bytes() }
131    }
132}
133
134// FROM PROTO ACCOUNT ID
135// ================================================================================================
136
137impl TryFrom<proto::account::AccountId> for AccountId {
138    type Error = RpcConversionError;
139
140    fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
141        AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
142    }
143}
144
145// ACCOUNT HEADER
146// ================================================================================================
147
148impl TryInto<AccountHeader> for proto::account::AccountHeader {
149    type Error = crate::rpc::RpcError;
150
151    fn try_into(self) -> Result<AccountHeader, Self::Error> {
152        use miden_protocol::Felt;
153
154        use crate::rpc::domain::MissingFieldHelper;
155
156        let proto::account::AccountHeader {
157            account_id,
158            nonce,
159            vault_root,
160            storage_commitment,
161            code_commitment,
162        } = self;
163
164        let account_id: AccountId = account_id
165            .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
166            .try_into()?;
167        let vault_root = vault_root
168            .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
169            .try_into()?;
170        let storage_commitment = storage_commitment
171            .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
172            .try_into()?;
173        let code_commitment = code_commitment
174            .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
175            .try_into()?;
176
177        Ok(AccountHeader::new(
178            account_id,
179            Felt::new(nonce),
180            vault_root,
181            storage_commitment,
182            code_commitment,
183        ))
184    }
185}
186
187// ACCOUNT STORAGE HEADER
188// ================================================================================================
189
190impl TryInto<AccountStorageHeader> for proto::account::AccountStorageHeader {
191    type Error = crate::rpc::RpcError;
192
193    fn try_into(self) -> Result<AccountStorageHeader, Self::Error> {
194        use crate::rpc::RpcError;
195        use crate::rpc::domain::MissingFieldHelper;
196
197        let mut header_slots: Vec<StorageSlotHeader> = Vec::with_capacity(self.slots.len());
198
199        for slot in self.slots {
200            let slot_value: Word = slot
201                .commitment
202                .ok_or(proto::account::account_storage_header::StorageSlot::missing_field(
203                    stringify!(commitment),
204                ))?
205                .try_into()?;
206
207            let slot_type = u8::try_from(slot.slot_type)
208                .map_err(|e| RpcError::InvalidResponse(e.to_string()))
209                .and_then(|v| {
210                    StorageSlotType::try_from(v)
211                        .map_err(|e| RpcError::InvalidResponse(e.to_string()))
212                })?;
213            let slot_name = StorageSlotName::new(slot.slot_name)
214                .map_err(|err| RpcError::InvalidResponse(err.to_string()))?;
215
216            header_slots.push(StorageSlotHeader::new(slot_name, slot_type, slot_value));
217        }
218
219        header_slots.sort_by_key(StorageSlotHeader::id);
220        AccountStorageHeader::new(header_slots)
221            .map_err(|err| RpcError::InvalidResponse(err.to_string()))
222    }
223}
224
225// FROM PROTO ACCOUNT HEADERS
226// ================================================================================================
227
228#[cfg(feature = "tonic")]
229impl proto::rpc::account_response::AccountDetails {
230    /// Converts the RPC response into `AccountDetails`.
231    ///
232    /// The RPC response may omit unchanged account codes. If so, this function uses
233    /// `known_account_codes` to fill in the missing code. If a required code cannot be found in
234    /// the response or `known_account_codes`, an error is returned.
235    ///
236    /// # Errors
237    /// - If account code is missing both on `self` and `known_account_codes`
238    /// - If data cannot be correctly deserialized
239    pub fn into_domain(
240        self,
241        known_account_codes: &BTreeMap<Word, AccountCode>,
242        storage_requirements: &AccountStorageRequirements,
243    ) -> Result<AccountDetails, crate::rpc::RpcError> {
244        use crate::rpc::RpcError;
245        use crate::rpc::domain::MissingFieldHelper;
246
247        let proto::rpc::account_response::AccountDetails {
248            header,
249            storage_details,
250            code,
251            vault_details,
252        } = self;
253        let header: AccountHeader = header
254            .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))?
255            .try_into()?;
256
257        let storage_details: AccountStorageDetails = storage_details
258            .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
259                storage_details
260            )))?
261            .try_into()?;
262
263        // Validate that the returned proofs match the originally requested keys.
264        // The node returns hashed SMT keys, so we hash the raw keys and check
265        // they are present in the corresponding proofs.
266        for map_detail in &storage_details.map_details {
267            let requested_keys = storage_requirements
268                .inner()
269                .get(&map_detail.slot_name)
270                .map(Vec::as_slice)
271                .unwrap_or_default();
272
273            if let StorageMapEntries::EntriesWithProofs(witnesses) = &map_detail.entries {
274                for (witness, raw_key) in witnesses.iter().zip(requested_keys.iter()) {
275                    let hashed_key = StorageMap::hash_key(*raw_key);
276                    if witness.proof().get(&hashed_key).is_none() {
277                        return Err(RpcError::InvalidResponse(format!(
278                            "proof for storage map key {} does not match the requested key",
279                            raw_key.to_hex(),
280                        )));
281                    }
282                }
283            }
284        }
285
286        // If an account code was received, it means the previously known account code is no longer
287        // valid. If it was not, it means we sent a code commitment that matched and so our code
288        // is still valid
289        let code = {
290            let received_code = code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
291            match received_code {
292                Some(code) => code,
293                None => known_account_codes
294                    .get(&header.code_commitment())
295                    .ok_or(RpcError::InvalidResponse(
296                        "Account code was not provided, but the response did not contain it either"
297                            .into(),
298                    ))?
299                    .clone(),
300            }
301        };
302
303        let vault_details = vault_details
304            .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(vault_details)))?
305            .try_into()?;
306
307        Ok(AccountDetails {
308            header,
309            storage_details,
310            code,
311            vault_details,
312        })
313    }
314}
315
316// ACCOUNT PROOF
317// ================================================================================================
318
319/// Contains a block number, and a list of account proofs at that block.
320pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
321
322// ACCOUNT DETAILS
323// ================================================================================================
324
325/// An account details.
326#[derive(Clone, Debug)]
327pub struct AccountDetails {
328    pub header: AccountHeader,
329    pub storage_details: AccountStorageDetails,
330    pub code: AccountCode,
331    pub vault_details: AccountVaultDetails,
332}
333
334// ACCOUNT STORAGE DETAILS
335// ================================================================================================
336
337/// Account storage details for `AccountResponse`
338#[derive(Clone, Debug)]
339pub struct AccountStorageDetails {
340    /// Account storage header (storage slot info for up to 256 slots)
341    pub header: AccountStorageHeader,
342    /// Additional data for the requested storage maps
343    pub map_details: Vec<AccountStorageMapDetails>,
344}
345
346impl AccountStorageDetails {
347    /// Find the matching details for a map, given its storage slot name.
348    //  This linear search should be good enough since there can be
349    //  only up to 256 slots, so locality probably wins here.
350    pub fn find_map_details(&self, target: &StorageSlotName) -> Option<&AccountStorageMapDetails> {
351        self.map_details.iter().find(|map_detail| map_detail.slot_name == *target)
352    }
353}
354
355impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
356    type Error = RpcError;
357
358    fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
359        let header = value
360            .header
361            .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
362            .try_into()?;
363        let map_details = value
364            .map_details
365            .into_iter()
366            .map(core::convert::TryInto::try_into)
367            .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
368
369        Ok(Self { header, map_details })
370    }
371}
372
373// ACCOUNT MAP DETAILS
374// ================================================================================================
375
376#[derive(Clone, Debug)]
377pub struct AccountStorageMapDetails {
378    /// Storage slot name of the storage map.
379    pub slot_name: StorageSlotName,
380    /// A flag that is set to `true` if the number of to-be-returned entries in the
381    /// storage map would exceed a threshold. This indicates to the user that `SyncStorageMaps`
382    /// endpoint should be used to get all storage map data.
383    pub too_many_entries: bool,
384    /// Storage map entries - either all entries (for small/full maps) or entries with proofs
385    /// (for partial maps).
386    pub entries: StorageMapEntries,
387}
388
389impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
390    for AccountStorageMapDetails
391{
392    type Error = RpcError;
393
394    fn try_from(
395        value: proto::rpc::account_storage_details::AccountStorageMapDetails,
396    ) -> Result<Self, Self::Error> {
397        use proto::rpc::account_storage_details::account_storage_map_details::Entries;
398
399        let slot_name = StorageSlotName::new(value.slot_name)
400            .map_err(|err| RpcError::ExpectedDataMissing(err.to_string()))?;
401        let too_many_entries = value.too_many_entries;
402
403        let entries = match value.entries {
404            Some(Entries::AllEntries(all_entries)) => {
405                let entries = all_entries
406                    .entries
407                    .into_iter()
408                    .map(core::convert::TryInto::try_into)
409                    .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
410                StorageMapEntries::AllEntries(entries)
411            },
412            Some(Entries::EntriesWithProofs(entries_with_proofs)) => {
413                let witnesses = entries_with_proofs
414                    .entries
415                    .into_iter()
416                    .map(|entry| {
417                        // The node returns the hashed key, not the raw map key.
418                        let hashed_key: Word = entry
419                            .key
420                            .ok_or(RpcError::ExpectedDataMissing("key".into()))?
421                            .try_into()?;
422                        let proof: SmtProof = entry
423                            .proof
424                            .ok_or(RpcError::ExpectedDataMissing("proof".into()))?
425                            .try_into()?;
426
427                        let value = proof.get(&hashed_key).unwrap_or_default();
428                        Ok(StorageMapWitness::new_unchecked(proof, [(hashed_key, value)]))
429                    })
430                    .collect::<Result<Vec<StorageMapWitness>, RpcError>>()?;
431                StorageMapEntries::EntriesWithProofs(witnesses)
432            },
433            None => StorageMapEntries::AllEntries(Vec::new()),
434        };
435
436        Ok(Self { slot_name, too_many_entries, entries })
437    }
438}
439
440// STORAGE MAP ENTRY
441// ================================================================================================
442
443/// A storage map entry containing a key-value pair.
444#[derive(Clone, Debug)]
445pub struct StorageMapEntry {
446    pub key: Word,
447    pub value: Word,
448}
449
450impl TryFrom<proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry>
451    for StorageMapEntry
452{
453    type Error = RpcError;
454
455    fn try_from(value: proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
456        let key = value.key.ok_or(RpcError::ExpectedDataMissing("key".into()))?.try_into()?;
457        let value = value.value.ok_or(RpcError::ExpectedDataMissing("value".into()))?.try_into()?;
458        Ok(Self { key, value })
459    }
460}
461
462// STORAGE MAP ENTRIES
463// ================================================================================================
464
465/// Storage map entries, either all entries (for small/full maps) or entries with proofs
466/// (for partial maps).
467#[derive(Clone, Debug)]
468pub enum StorageMapEntries {
469    /// All entries in the storage map (no proofs needed as the full map is available).
470    AllEntries(Vec<StorageMapEntry>),
471    /// Specific entries with their SMT proofs (for partial maps).
472    EntriesWithProofs(Vec<StorageMapWitness>),
473}
474
475impl StorageMapEntries {
476    /// Converts the entries into a [`StorageMap`].
477    pub fn into_storage_map(self) -> Result<StorageMap, miden_protocol::errors::StorageMapError> {
478        match self {
479            StorageMapEntries::AllEntries(entries) => {
480                StorageMap::with_entries(entries.into_iter().map(|e| (e.key, e.value)))
481            },
482            StorageMapEntries::EntriesWithProofs(witnesses) => {
483                let entries: Vec<_> =
484                    witnesses.iter().flat_map(|w| w.entries().map(|(k, v)| (*k, *v))).collect();
485                StorageMap::with_entries(entries)
486            },
487        }
488    }
489}
490
491// ACCOUNT VAULT DETAILS
492// ================================================================================================
493
494#[derive(Clone, Debug)]
495pub struct AccountVaultDetails {
496    /// A flag that is set to true if the account contains too many assets. This indicates
497    /// to the user that `SyncAccountVault` endpoint should be used to retrieve the
498    /// account's assets
499    pub too_many_assets: bool,
500    /// When `too_many_assets` == false, this will contain the list of assets in the
501    /// account's vault
502    pub assets: Vec<Asset>,
503}
504
505impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
506    type Error = RpcError;
507
508    fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
509        let too_many_assets = value.too_many_assets;
510        let assets = value
511            .assets
512            .into_iter()
513            .map(|asset| {
514                let native_digest: Word = asset
515                    .asset
516                    .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(assets)))?
517                    .try_into()?;
518                native_digest
519                    .try_into()
520                    .map_err(|_| RpcError::DeserializationError("asset".to_string()))
521            })
522            .collect::<Result<Vec<Asset>, RpcError>>()?;
523
524        Ok(Self { too_many_assets, assets })
525    }
526}
527
528// ACCOUNT PROOF
529// ================================================================================================
530
531/// Represents a proof of existence of an account's state at a specific block number.
532#[derive(Clone, Debug)]
533pub struct AccountProof {
534    /// Account witness.
535    account_witness: AccountWitness,
536    /// State headers of public accounts.
537    state_headers: Option<AccountDetails>,
538}
539
540impl AccountProof {
541    /// Creates a new [`AccountProof`].
542    pub fn new(
543        account_witness: AccountWitness,
544        account_details: Option<AccountDetails>,
545    ) -> Result<Self, AccountProofError> {
546        if let Some(AccountDetails {
547            header: account_header,
548            storage_details: _,
549            code,
550            ..
551        }) = &account_details
552        {
553            if account_header.commitment() != account_witness.state_commitment() {
554                return Err(AccountProofError::InconsistentAccountCommitment);
555            }
556            if account_header.id() != account_witness.id() {
557                return Err(AccountProofError::InconsistentAccountId);
558            }
559            if code.commitment() != account_header.code_commitment() {
560                return Err(AccountProofError::InconsistentCodeCommitment);
561            }
562        }
563
564        Ok(Self {
565            account_witness,
566            state_headers: account_details,
567        })
568    }
569
570    /// Returns the account ID related to the account proof.
571    pub fn account_id(&self) -> AccountId {
572        self.account_witness.id()
573    }
574
575    /// Returns the account header, if present.
576    pub fn account_header(&self) -> Option<&AccountHeader> {
577        self.state_headers.as_ref().map(|account_details| &account_details.header)
578    }
579
580    /// Returns the storage header, if present.
581    pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
582        self.state_headers
583            .as_ref()
584            .map(|account_details| &account_details.storage_details.header)
585    }
586
587    /// Returns the full storage details, if available (public accounts only).
588    pub fn storage_details(&self) -> Option<&AccountStorageDetails> {
589        self.state_headers.as_ref().map(|d| &d.storage_details)
590    }
591
592    /// Returns the storage map details for a specific slot, if available.
593    pub fn find_map_details(
594        &self,
595        slot_name: &StorageSlotName,
596    ) -> Option<&AccountStorageMapDetails> {
597        self.state_headers
598            .as_ref()
599            .and_then(|details| details.storage_details.find_map_details(slot_name))
600    }
601
602    /// Returns the account code, if present.
603    pub fn account_code(&self) -> Option<&AccountCode> {
604        self.state_headers.as_ref().map(|headers| &headers.code)
605    }
606
607    /// Returns the code commitment, if account code is present in the state headers.
608    pub fn code_commitment(&self) -> Option<Word> {
609        self.account_code().map(AccountCode::commitment)
610    }
611
612    /// Returns the current state commitment of the account.
613    pub fn account_commitment(&self) -> Word {
614        self.account_witness.state_commitment()
615    }
616
617    pub fn account_witness(&self) -> &AccountWitness {
618        &self.account_witness
619    }
620
621    /// Returns the proof of the account's inclusion.
622    pub fn merkle_proof(&self) -> &SparseMerklePath {
623        self.account_witness.path()
624    }
625
626    /// Deconstructs `AccountProof` into its individual parts.
627    pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
628        (self.account_witness, self.state_headers)
629    }
630}
631
632#[cfg(feature = "tonic")]
633impl TryFrom<proto::rpc::AccountResponse> for AccountProof {
634    type Error = RpcError;
635    fn try_from(account_proof: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
636        let Some(witness) = account_proof.witness else {
637            return Err(RpcError::ExpectedDataMissing(
638                "GetAccountProof returned an account without witness".to_string(),
639            ));
640        };
641
642        let details: Option<AccountDetails> = {
643            match account_proof.details {
644                None => None,
645                Some(details) => Some(
646                    details
647                        .into_domain(&BTreeMap::new(), &AccountStorageRequirements::default())?,
648                ),
649            }
650        };
651        AccountProof::new(witness.try_into()?, details)
652            .map_err(|err| RpcError::InvalidResponse(format!("{err}")))
653    }
654}
655
656// ACCOUNT WITNESS
657// ================================================================================================
658
659impl TryFrom<proto::account::AccountWitness> for AccountWitness {
660    type Error = RpcError;
661
662    fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
663        let state_commitment = account_witness
664            .commitment
665            .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
666            .try_into()?;
667        let merkle_path = account_witness
668            .path
669            .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
670            .try_into()?;
671        let account_id = account_witness
672            .witness_id
673            .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
674            .try_into()?;
675
676        let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
677        Ok(witness)
678    }
679}
680
681// ACCOUNT STORAGE REQUEST
682// ================================================================================================
683
684pub type StorageMapKey = Word;
685
686/// Describes storage slots indices to be requested, as well as a list of keys for each of those
687/// slots.
688///
689/// Note: If no specific keys are provided for a slot, all entries are requested. Though the
690/// node may respond with `too_many_entries` if the map exceeds the response limit.
691#[derive(Clone, Debug, Default, Eq, PartialEq)]
692pub struct AccountStorageRequirements(BTreeMap<StorageSlotName, Vec<StorageMapKey>>);
693
694impl AccountStorageRequirements {
695    pub fn new<'a>(
696        slots_and_keys: impl IntoIterator<
697            Item = (StorageSlotName, impl IntoIterator<Item = &'a StorageMapKey>),
698        >,
699    ) -> Self {
700        let map = slots_and_keys
701            .into_iter()
702            .map(|(slot_name, keys_iter)| {
703                let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
704                (slot_name, keys_vec)
705            })
706            .collect();
707
708        AccountStorageRequirements(map)
709    }
710
711    pub fn inner(&self) -> &BTreeMap<StorageSlotName, Vec<StorageMapKey>> {
712        &self.0
713    }
714}
715
716impl From<AccountStorageRequirements> for Vec<account_detail_request::StorageMapDetailRequest> {
717    fn from(
718        value: AccountStorageRequirements,
719    ) -> Vec<account_detail_request::StorageMapDetailRequest> {
720        use account_detail_request::storage_map_detail_request::{MapKeys, SlotData};
721        let request_map = value.0;
722        let mut requests = Vec::with_capacity(request_map.len());
723        for (slot_name, map_keys) in request_map {
724            let slot_data = if map_keys.is_empty() {
725                Some(SlotData::AllEntries(true))
726            } else {
727                Some(SlotData::MapKeys(MapKeys {
728                    map_keys: map_keys.into_iter().map(Into::into).collect(),
729                }))
730            };
731            requests.push(account_detail_request::StorageMapDetailRequest {
732                slot_name: slot_name.to_string(),
733                slot_data,
734            });
735        }
736        requests
737    }
738}
739
740impl Serializable for AccountStorageRequirements {
741    fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
742        target.write(&self.0);
743    }
744}
745
746impl Deserializable for AccountStorageRequirements {
747    fn read_from<R: miden_tx::utils::ByteReader>(
748        source: &mut R,
749    ) -> Result<Self, miden_tx::utils::DeserializationError> {
750        Ok(AccountStorageRequirements(source.read()?))
751    }
752}
753
754// ERRORS
755// ================================================================================================
756
757#[derive(Debug, Error)]
758pub enum AccountProofError {
759    #[error(
760        "the received account commitment doesn't match the received account header's commitment"
761    )]
762    InconsistentAccountCommitment,
763    #[error("the received account id doesn't match the received account header's id")]
764    InconsistentAccountId,
765    #[error(
766        "the received code commitment doesn't match the received account header's code commitment"
767    )]
768    InconsistentCodeCommitment,
769}