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