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