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