Skip to main content

miden_node_proto/domain/
account.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use miden_node_utils::formatting::format_opt;
4use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
5use miden_protocol::Word;
6use miden_protocol::account::{
7    Account,
8    AccountHeader,
9    AccountId,
10    AccountStorageHeader,
11    StorageMap,
12    StorageMapKey,
13    StorageSlotHeader,
14    StorageSlotName,
15    StorageSlotType,
16};
17use miden_protocol::asset::{Asset, AssetVault};
18use miden_protocol::block::BlockNumber;
19use miden_protocol::block::account_tree::AccountWitness;
20use miden_protocol::crypto::merkle::SparseMerklePath;
21use miden_protocol::crypto::merkle::smt::SmtProof;
22use miden_protocol::note::NoteAttachment;
23use miden_protocol::utils::{Deserializable, DeserializationError, Serializable};
24use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError};
25use thiserror::Error;
26
27use super::try_convert;
28use crate::errors::{ConversionError, MissingFieldHelper};
29use crate::generated::{self as proto};
30
31#[cfg(test)]
32mod tests;
33
34// ACCOUNT ID
35// ================================================================================================
36
37impl Display for proto::account::AccountId {
38    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39        write!(f, "0x")?;
40        for byte in &self.id {
41            write!(f, "{byte:02x}")?;
42        }
43        Ok(())
44    }
45}
46
47impl Debug for proto::account::AccountId {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        Display::fmt(self, f)
50    }
51}
52
53// FROM PROTO ACCOUNT ID
54// ------------------------------------------------------------------------------------------------
55
56impl TryFrom<proto::account::AccountId> for AccountId {
57    type Error = ConversionError;
58
59    fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
60        AccountId::read_from_bytes(&account_id.id).map_err(|_| ConversionError::NotAValidFelt)
61    }
62}
63
64// INTO PROTO ACCOUNT ID
65// ------------------------------------------------------------------------------------------------
66
67impl From<&AccountId> for proto::account::AccountId {
68    fn from(account_id: &AccountId) -> Self {
69        (*account_id).into()
70    }
71}
72
73impl From<AccountId> for proto::account::AccountId {
74    fn from(account_id: AccountId) -> Self {
75        Self { id: account_id.to_bytes() }
76    }
77}
78
79// ACCOUNT UPDATE
80// ================================================================================================
81
82#[derive(Debug, PartialEq)]
83pub struct AccountSummary {
84    pub account_id: AccountId,
85    pub account_commitment: Word,
86    pub block_num: BlockNumber,
87}
88
89impl From<&AccountSummary> for proto::account::AccountSummary {
90    fn from(update: &AccountSummary) -> Self {
91        Self {
92            account_id: Some(update.account_id.into()),
93            account_commitment: Some(update.account_commitment.into()),
94            block_num: update.block_num.as_u32(),
95        }
96    }
97}
98
99#[derive(Debug, PartialEq)]
100pub struct AccountInfo {
101    pub summary: AccountSummary,
102    pub details: Option<Account>,
103}
104
105impl From<&AccountInfo> for proto::account::AccountDetails {
106    fn from(AccountInfo { summary, details }: &AccountInfo) -> Self {
107        Self {
108            summary: Some(summary.into()),
109            details: details.as_ref().map(Serializable::to_bytes),
110        }
111    }
112}
113
114// ACCOUNT STORAGE HEADER
115//================================================================================================
116
117impl TryFrom<proto::account::AccountStorageHeader> for AccountStorageHeader {
118    type Error = ConversionError;
119
120    fn try_from(value: proto::account::AccountStorageHeader) -> Result<Self, Self::Error> {
121        let proto::account::AccountStorageHeader { slots } = value;
122
123        let slot_headers = slots
124            .into_iter()
125            .map(|slot| {
126                let slot_name = StorageSlotName::new(slot.slot_name)?;
127                let slot_type = storage_slot_type_from_raw(slot.slot_type)?;
128                let commitment =
129                    slot.commitment.ok_or(ConversionError::NotAValidFelt)?.try_into()?;
130                Ok(StorageSlotHeader::new(slot_name, slot_type, commitment))
131            })
132            .collect::<Result<Vec<_>, ConversionError>>()?;
133
134        Ok(AccountStorageHeader::new(slot_headers)?)
135    }
136}
137
138// ACCOUNT REQUEST
139// ================================================================================================
140
141/// Represents a request for an account proof.
142pub struct AccountRequest {
143    pub account_id: AccountId,
144    // If not present, the latest account proof references the latest available
145    pub block_num: Option<BlockNumber>,
146    pub details: Option<AccountDetailRequest>,
147}
148
149impl TryFrom<proto::rpc::AccountRequest> for AccountRequest {
150    type Error = ConversionError;
151
152    fn try_from(value: proto::rpc::AccountRequest) -> Result<Self, Self::Error> {
153        let proto::rpc::AccountRequest { account_id, block_num, details } = value;
154
155        let account_id = account_id
156            .ok_or(proto::rpc::AccountRequest::missing_field(stringify!(account_id)))?
157            .try_into()?;
158        let block_num = block_num.map(Into::into);
159
160        let details = details.map(TryFrom::try_from).transpose()?;
161
162        Ok(AccountRequest { account_id, block_num, details })
163    }
164}
165
166/// Represents a request for account details alongside specific storage data.
167pub struct AccountDetailRequest {
168    pub code_commitment: Option<Word>,
169    pub asset_vault_commitment: Option<Word>,
170    pub storage_requests: Vec<StorageMapRequest>,
171}
172
173impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetailRequest {
174    type Error = ConversionError;
175
176    fn try_from(
177        value: proto::rpc::account_request::AccountDetailRequest,
178    ) -> Result<Self, Self::Error> {
179        let proto::rpc::account_request::AccountDetailRequest {
180            code_commitment,
181            asset_vault_commitment,
182            storage_maps,
183        } = value;
184
185        let code_commitment = code_commitment.map(TryFrom::try_from).transpose()?;
186        let asset_vault_commitment = asset_vault_commitment.map(TryFrom::try_from).transpose()?;
187        let storage_requests = try_convert(storage_maps).collect::<Result<_, _>>()?;
188
189        Ok(AccountDetailRequest {
190            code_commitment,
191            asset_vault_commitment,
192            storage_requests,
193        })
194    }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct StorageMapRequest {
199    pub slot_name: StorageSlotName,
200    pub slot_data: SlotData,
201}
202
203impl TryFrom<proto::rpc::account_request::account_detail_request::StorageMapDetailRequest>
204    for StorageMapRequest
205{
206    type Error = ConversionError;
207
208    fn try_from(
209        value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest,
210    ) -> Result<Self, Self::Error> {
211        let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest {
212            slot_name,
213            slot_data,
214        } = value;
215
216        let slot_name = StorageSlotName::new(slot_name)?;
217        let slot_data = slot_data.ok_or(proto::rpc::account_request::account_detail_request::StorageMapDetailRequest::missing_field(stringify!(slot_data)))?.try_into()?;
218
219        Ok(StorageMapRequest { slot_name, slot_data })
220    }
221}
222
223/// Request of slot data values.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub enum SlotData {
226    All,
227    MapKeys(Vec<StorageMapKey>),
228}
229
230impl
231    TryFrom<
232        proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
233    > for SlotData
234{
235    type Error = ConversionError;
236
237    fn try_from(
238        value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
239    ) -> Result<Self, Self::Error> {
240        use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData;
241
242        Ok(match value {
243            ProtoSlotData::AllEntries(true) => SlotData::All,
244            ProtoSlotData::AllEntries(false) => {
245                return Err(ConversionError::EnumDiscriminantOutOfRange);
246            },
247            ProtoSlotData::MapKeys(keys) => {
248                let keys = try_convert(keys.map_keys).collect::<Result<Vec<_>, _>>()?;
249                SlotData::MapKeys(keys)
250            },
251        })
252    }
253}
254
255// ACCOUNT HEADER CONVERSIONS
256//================================================================================================
257
258impl TryFrom<proto::account::AccountHeader> for AccountHeader {
259    type Error = ConversionError;
260
261    fn try_from(value: proto::account::AccountHeader) -> Result<Self, Self::Error> {
262        let proto::account::AccountHeader {
263            account_id,
264            vault_root,
265            storage_commitment,
266            code_commitment,
267            nonce,
268        } = value;
269
270        let account_id = account_id
271            .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
272            .try_into()?;
273        let vault_root = vault_root
274            .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
275            .try_into()?;
276        let storage_commitment = storage_commitment
277            .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
278            .try_into()?;
279        let code_commitment = code_commitment
280            .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
281            .try_into()?;
282        let nonce = nonce.try_into().map_err(|_e| ConversionError::NotAValidFelt)?;
283
284        Ok(AccountHeader::new(
285            account_id,
286            nonce,
287            vault_root,
288            storage_commitment,
289            code_commitment,
290        ))
291    }
292}
293
294impl From<AccountHeader> for proto::account::AccountHeader {
295    fn from(header: AccountHeader) -> Self {
296        proto::account::AccountHeader {
297            account_id: Some(header.id().into()),
298            vault_root: Some(header.vault_root().into()),
299            storage_commitment: Some(header.storage_commitment().into()),
300            code_commitment: Some(header.code_commitment().into()),
301            nonce: header.nonce().as_int(),
302        }
303    }
304}
305
306impl From<AccountStorageHeader> for proto::account::AccountStorageHeader {
307    fn from(value: AccountStorageHeader) -> Self {
308        let slots = value
309            .slots()
310            .map(|slot_header| proto::account::account_storage_header::StorageSlot {
311                slot_name: slot_header.name().to_string(),
312                slot_type: storage_slot_type_to_raw(slot_header.slot_type()),
313                commitment: Some(proto::primitives::Digest::from(slot_header.value())),
314            })
315            .collect();
316
317        Self { slots }
318    }
319}
320
321// ACCOUNT VAULT DETAILS
322//================================================================================================
323
324/// Account vault details
325///
326/// When an account contains a large number of assets (>
327/// [`AccountVaultDetails::MAX_RETURN_ENTRIES`]), including all assets in a single RPC response
328/// creates performance issues. In such cases, the `LimitExceeded` variant indicates to the client
329/// to use the `SyncAccountVault` endpoint instead.
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub enum AccountVaultDetails {
332    /// The vault has too many assets to return inline.
333    /// Clients must use `SyncAccountVault` endpoint instead.
334    LimitExceeded,
335
336    /// The assets in the vault (up to `MAX_RETURN_ENTRIES`).
337    Assets(Vec<Asset>),
338}
339
340impl AccountVaultDetails {
341    /// Maximum number of vault entries that can be returned in a single response.
342    /// Accounts with more assets will have `LimitExceeded` variant.
343    pub const MAX_RETURN_ENTRIES: usize = 1000;
344
345    pub fn new(vault: &AssetVault) -> Self {
346        if vault.assets().nth(Self::MAX_RETURN_ENTRIES).is_some() {
347            Self::LimitExceeded
348        } else {
349            Self::Assets(Vec::from_iter(vault.assets()))
350        }
351    }
352
353    pub fn empty() -> Self {
354        Self::Assets(Vec::new())
355    }
356
357    /// Creates `AccountVaultDetails` from a list of assets.
358    pub fn from_assets(assets: Vec<Asset>) -> Self {
359        if assets.len() > Self::MAX_RETURN_ENTRIES {
360            Self::LimitExceeded
361        } else {
362            Self::Assets(assets)
363        }
364    }
365}
366
367impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
368    type Error = ConversionError;
369
370    fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
371        let proto::rpc::AccountVaultDetails { too_many_assets, assets } = value;
372
373        if too_many_assets {
374            Ok(Self::LimitExceeded)
375        } else {
376            let parsed_assets =
377                Result::<Vec<_>, ConversionError>::from_iter(assets.into_iter().map(|asset| {
378                    let asset = asset
379                        .asset
380                        .ok_or(proto::primitives::Asset::missing_field(stringify!(asset)))?;
381                    let asset = Word::try_from(asset)?;
382                    Asset::try_from(asset).map_err(ConversionError::AssetError)
383                }))?;
384            Ok(Self::Assets(parsed_assets))
385        }
386    }
387}
388
389impl From<AccountVaultDetails> for proto::rpc::AccountVaultDetails {
390    fn from(value: AccountVaultDetails) -> Self {
391        match value {
392            AccountVaultDetails::LimitExceeded => Self {
393                too_many_assets: true,
394                assets: Vec::new(),
395            },
396            AccountVaultDetails::Assets(assets) => Self {
397                too_many_assets: false,
398                assets: Vec::from_iter(assets.into_iter().map(|asset| proto::primitives::Asset {
399                    asset: Some(proto::primitives::Digest::from(Word::from(asset))),
400                })),
401            },
402        }
403    }
404}
405
406// ACCOUNT STORAGE MAP DETAILS
407//================================================================================================
408
409/// Details about an account storage map slot.
410#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct AccountStorageMapDetails {
412    pub slot_name: StorageSlotName,
413    pub entries: StorageMapEntries,
414}
415
416/// Storage map entries for an account storage slot.
417///
418/// When a storage map contains many entries (> [`AccountStorageMapDetails::MAX_RETURN_ENTRIES`]),
419/// returning all entries in a single RPC response creates performance issues. In such cases,
420/// the `LimitExceeded` variant indicates to the client to use the `SyncAccountStorageMaps` endpoint
421/// instead.
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub enum StorageMapEntries {
424    /// The map has too many entries to return inline.
425    /// Clients must use `SyncAccountStorageMaps` endpoint instead.
426    LimitExceeded,
427
428    /// All storage map entries (key-value pairs) without proofs.
429    /// Used when all entries are requested for small maps.
430    AllEntries(Vec<(StorageMapKey, Word)>),
431
432    /// Specific entries with their SMT proofs for client-side verification.
433    /// Used when specific keys are requested from the storage map.
434    EntriesWithProofs(Vec<SmtProof>),
435}
436
437impl AccountStorageMapDetails {
438    /// Maximum number of storage map entries that can be returned in a single response.
439    pub const MAX_RETURN_ENTRIES: usize = 1000;
440
441    /// Maximum number of SMT proofs that can be returned in a single response.
442    ///
443    /// This limit is more restrictive than [`Self::MAX_RETURN_ENTRIES`] because SMT proofs
444    /// are larger (up to 64 inner nodes each) and more CPU-intensive to generate.
445    ///
446    /// This is defined by [`QueryParamStorageMapKeyTotalLimit::LIMIT`] and used both in RPC
447    /// validation and store-level enforcement to ensure consistent limits.
448    pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
449
450    /// Creates storage map details with all entries from the storage map.
451    ///
452    /// If the storage map has too many entries (> `MAX_RETURN_ENTRIES`),
453    /// returns `LimitExceeded` variant.
454    pub fn from_all_entries(slot_name: StorageSlotName, storage_map: &StorageMap) -> Self {
455        if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
456            Self {
457                slot_name,
458                entries: StorageMapEntries::LimitExceeded,
459            }
460        } else {
461            let entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v)));
462            Self {
463                slot_name,
464                entries: StorageMapEntries::AllEntries(entries),
465            }
466        }
467    }
468
469    /// Creates storage map details from forest-queried entries.
470    ///
471    /// Returns `LimitExceeded` if too many entries.
472    pub fn from_forest_entries(
473        slot_name: StorageSlotName,
474        entries: Vec<(StorageMapKey, Word)>,
475    ) -> Self {
476        if entries.len() > Self::MAX_RETURN_ENTRIES {
477            Self {
478                slot_name,
479                entries: StorageMapEntries::LimitExceeded,
480            }
481        } else {
482            Self {
483                slot_name,
484                entries: StorageMapEntries::AllEntries(entries),
485            }
486        }
487    }
488
489    /// Creates storage map details from pre-computed SMT proofs.
490    ///
491    /// Use this when the caller has already obtained the proofs from an `SmtForest`.
492    /// Returns `LimitExceeded` if too many proofs are provided.
493    pub fn from_proofs(slot_name: StorageSlotName, proofs: Vec<SmtProof>) -> Self {
494        if proofs.len() > Self::MAX_SMT_PROOF_ENTRIES {
495            Self {
496                slot_name,
497                entries: StorageMapEntries::LimitExceeded,
498            }
499        } else {
500            Self {
501                slot_name,
502                entries: StorageMapEntries::EntriesWithProofs(proofs),
503            }
504        }
505    }
506
507    /// Creates storage map details indicating the limit was exceeded.
508    pub fn limit_exceeded(slot_name: StorageSlotName) -> Self {
509        Self {
510            slot_name,
511            entries: StorageMapEntries::LimitExceeded,
512        }
513    }
514}
515
516impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
517    for AccountStorageMapDetails
518{
519    type Error = ConversionError;
520
521    fn try_from(
522        value: proto::rpc::account_storage_details::AccountStorageMapDetails,
523    ) -> Result<Self, Self::Error> {
524        use proto::rpc::account_storage_details::account_storage_map_details::{
525            all_map_entries::StorageMapEntry,
526            map_entries_with_proofs::StorageMapEntryWithProof,
527            AllMapEntries,
528            Entries as ProtoEntries,
529            MapEntriesWithProofs,
530        };
531
532        let proto::rpc::account_storage_details::AccountStorageMapDetails {
533            slot_name,
534            too_many_entries,
535            entries,
536        } = value;
537
538        let slot_name = StorageSlotName::new(slot_name)?;
539
540        let entries = if too_many_entries {
541            StorageMapEntries::LimitExceeded
542        } else {
543            match entries {
544                None => {
545                    return Err(
546                        proto::rpc::account_storage_details::AccountStorageMapDetails::missing_field(
547                            stringify!(entries),
548                        ),
549                    );
550                },
551                Some(ProtoEntries::AllEntries(AllMapEntries { entries })) => {
552                    let entries = entries
553                        .into_iter()
554                        .map(|entry| {
555                            let key = entry
556                                .key
557                                .ok_or(StorageMapEntry::missing_field(stringify!(key)))?
558                                .try_into()
559                                .map(StorageMapKey::new)?;
560                            let value = entry
561                                .value
562                                .ok_or(StorageMapEntry::missing_field(stringify!(value)))?
563                                .try_into()?;
564                            Ok((key, value))
565                        })
566                        .collect::<Result<Vec<_>, ConversionError>>()?;
567                    StorageMapEntries::AllEntries(entries)
568                },
569                Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => {
570                    let proofs = entries
571                        .into_iter()
572                        .map(|entry| {
573                            let smt_opening = entry.proof.ok_or(
574                                StorageMapEntryWithProof::missing_field(stringify!(proof)),
575                            )?;
576                            SmtProof::try_from(smt_opening)
577                        })
578                        .collect::<Result<Vec<_>, ConversionError>>()?;
579                    StorageMapEntries::EntriesWithProofs(proofs)
580                },
581            }
582        };
583
584        Ok(Self { slot_name, entries })
585    }
586}
587
588impl From<AccountStorageMapDetails>
589    for proto::rpc::account_storage_details::AccountStorageMapDetails
590{
591    fn from(value: AccountStorageMapDetails) -> Self {
592        use proto::rpc::account_storage_details::account_storage_map_details::{
593            AllMapEntries,
594            Entries as ProtoEntries,
595            MapEntriesWithProofs,
596        };
597
598        let AccountStorageMapDetails { slot_name, entries } = value;
599
600        let (too_many_entries, proto_entries) = match entries {
601            StorageMapEntries::LimitExceeded => (true, None),
602            StorageMapEntries::AllEntries(entries) => {
603                let all = AllMapEntries {
604                    entries: Vec::from_iter(entries.into_iter().map(|(key, value)| {
605                        proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry {
606                            key: Some(key.into()),
607                            value: Some(value.into()),
608                        }
609                    })),
610                };
611                (false, Some(ProtoEntries::AllEntries(all)))
612            },
613            StorageMapEntries::EntriesWithProofs(proofs) => {
614                use miden_protocol::crypto::merkle::smt::SmtLeaf;
615
616                let with_proofs = MapEntriesWithProofs {
617                    entries: Vec::from_iter(proofs.into_iter().map(|proof| {
618                        // Get key/value from the leaf before consuming the proof
619                        let (key, value) = match proof.leaf() {
620                            SmtLeaf::Empty(_) => {
621                                (miden_protocol::EMPTY_WORD, miden_protocol::EMPTY_WORD)
622                            },
623                            SmtLeaf::Single((k, v)) => (*k, *v),
624                            SmtLeaf::Multiple(entries) => entries.iter().next().map_or(
625                                (miden_protocol::EMPTY_WORD, miden_protocol::EMPTY_WORD),
626                                |(k, v)| (*k, *v),
627                            ),
628                        };
629                        let smt_opening = proto::primitives::SmtOpening::from(proof);
630                        proto::rpc::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof {
631                            key: Some(key.into()),
632                            value: Some(value.into()),
633                            proof: Some(smt_opening),
634                        }
635                    })),
636                };
637                (false, Some(ProtoEntries::EntriesWithProofs(with_proofs)))
638            },
639        };
640
641        Self {
642            slot_name: slot_name.to_string(),
643            too_many_entries,
644            entries: proto_entries,
645        }
646    }
647}
648
649#[derive(Debug, Clone, PartialEq)]
650pub struct AccountStorageDetails {
651    pub header: AccountStorageHeader,
652    pub map_details: Vec<AccountStorageMapDetails>,
653}
654
655impl AccountStorageDetails {
656    /// Creates storage details where all map slots indicate limit exceeded.
657    pub fn all_limits_exceeded(
658        header: AccountStorageHeader,
659        slot_names: impl IntoIterator<Item = StorageSlotName>,
660    ) -> Self {
661        Self {
662            header,
663            map_details: Vec::from_iter(
664                slot_names.into_iter().map(AccountStorageMapDetails::limit_exceeded),
665            ),
666        }
667    }
668}
669
670impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
671    type Error = ConversionError;
672
673    fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
674        let proto::rpc::AccountStorageDetails { header, map_details } = value;
675
676        let header = header
677            .ok_or(proto::rpc::AccountStorageDetails::missing_field(stringify!(header)))?
678            .try_into()?;
679
680        let map_details = try_convert(map_details).collect::<Result<Vec<_>, _>>()?;
681
682        Ok(Self { header, map_details })
683    }
684}
685
686impl From<AccountStorageDetails> for proto::rpc::AccountStorageDetails {
687    fn from(value: AccountStorageDetails) -> Self {
688        let AccountStorageDetails { header, map_details } = value;
689
690        Self {
691            header: Some(header.into()),
692            map_details: map_details.into_iter().map(Into::into).collect(),
693        }
694    }
695}
696
697const fn storage_slot_type_from_raw(slot_type: u32) -> Result<StorageSlotType, ConversionError> {
698    Ok(match slot_type {
699        0 => StorageSlotType::Value,
700        1 => StorageSlotType::Map,
701        _ => return Err(ConversionError::EnumDiscriminantOutOfRange),
702    })
703}
704
705const fn storage_slot_type_to_raw(slot_type: StorageSlotType) -> u32 {
706    match slot_type {
707        StorageSlotType::Value => 0,
708        StorageSlotType::Map => 1,
709    }
710}
711
712// ACCOUNT PROOF RESPONSE
713//================================================================================================
714
715/// Represents the response to an account proof request.
716pub struct AccountResponse {
717    pub block_num: BlockNumber,
718    pub witness: AccountWitness,
719    pub details: Option<AccountDetails>,
720}
721
722impl TryFrom<proto::rpc::AccountResponse> for AccountResponse {
723    type Error = ConversionError;
724
725    fn try_from(value: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
726        let proto::rpc::AccountResponse { block_num, witness, details } = value;
727
728        let block_num = block_num
729            .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(block_num)))?
730            .into();
731
732        let witness = witness
733            .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(witness)))?
734            .try_into()?;
735
736        let details = details.map(TryFrom::try_from).transpose()?;
737
738        Ok(AccountResponse { block_num, witness, details })
739    }
740}
741
742impl From<AccountResponse> for proto::rpc::AccountResponse {
743    fn from(value: AccountResponse) -> Self {
744        let AccountResponse { block_num, witness, details } = value;
745
746        Self {
747            witness: Some(witness.into()),
748            details: details.map(Into::into),
749            block_num: Some(block_num.into()),
750        }
751    }
752}
753
754// ACCOUNT DETAILS
755//================================================================================================
756
757/// Represents account details returned in response to an account proof request.
758pub struct AccountDetails {
759    pub account_header: AccountHeader,
760    pub account_code: Option<Vec<u8>>,
761    pub vault_details: AccountVaultDetails,
762    pub storage_details: AccountStorageDetails,
763}
764
765impl AccountDetails {
766    /// Creates account details where all storage map slots indicate limit exceeded.
767    pub fn with_storage_limits_exceeded(
768        account_header: AccountHeader,
769        account_code: Option<Vec<u8>>,
770        vault_details: AccountVaultDetails,
771        storage_header: AccountStorageHeader,
772        slot_names: impl IntoIterator<Item = StorageSlotName>,
773    ) -> Self {
774        Self {
775            account_header,
776            account_code,
777            vault_details,
778            storage_details: AccountStorageDetails::all_limits_exceeded(storage_header, slot_names),
779        }
780    }
781}
782
783impl TryFrom<proto::rpc::account_response::AccountDetails> for AccountDetails {
784    type Error = ConversionError;
785
786    fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result<Self, Self::Error> {
787        let proto::rpc::account_response::AccountDetails {
788            header,
789            code,
790            vault_details,
791            storage_details,
792        } = value;
793
794        let account_header = header
795            .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))?
796            .try_into()?;
797
798        let storage_details = storage_details
799            .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
800                storage_details
801            )))?
802            .try_into()?;
803
804        let vault_details = vault_details
805            .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
806                vault_details
807            )))?
808            .try_into()?;
809        let account_code = code;
810
811        Ok(AccountDetails {
812            account_header,
813            account_code,
814            vault_details,
815            storage_details,
816        })
817    }
818}
819
820impl From<AccountDetails> for proto::rpc::account_response::AccountDetails {
821    fn from(value: AccountDetails) -> Self {
822        let AccountDetails {
823            account_header,
824            storage_details,
825            account_code,
826            vault_details,
827        } = value;
828
829        let header = Some(proto::account::AccountHeader::from(account_header));
830        let storage_details = Some(storage_details.into());
831        let code = account_code;
832        let vault_details = Some(vault_details.into());
833
834        Self {
835            header,
836            storage_details,
837            code,
838            vault_details,
839        }
840    }
841}
842
843// ACCOUNT WITNESS
844// ================================================================================================
845
846impl TryFrom<proto::account::AccountWitness> for AccountWitness {
847    type Error = ConversionError;
848
849    fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
850        let witness_id = account_witness
851            .witness_id
852            .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
853            .try_into()?;
854        let commitment = account_witness
855            .commitment
856            .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))?
857            .try_into()?;
858        let path = account_witness
859            .path
860            .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))?
861            .try_into()?;
862
863        AccountWitness::new(witness_id, commitment, path).map_err(|err| {
864            ConversionError::deserialization_error(
865                "AccountWitness",
866                DeserializationError::InvalidValue(err.to_string()),
867            )
868        })
869    }
870}
871
872impl From<AccountWitness> for proto::account::AccountWitness {
873    fn from(witness: AccountWitness) -> Self {
874        Self {
875            account_id: Some(witness.id().into()),
876            witness_id: Some(witness.id().into()),
877            commitment: Some(witness.state_commitment().into()),
878            path: Some(witness.into_proof().into_parts().0.into()),
879        }
880    }
881}
882
883// ACCOUNT WITNESS RECORD
884// ================================================================================================
885
886#[derive(Clone, Debug, PartialEq, Eq)]
887pub struct AccountWitnessRecord {
888    pub account_id: AccountId,
889    pub witness: AccountWitness,
890}
891
892impl TryFrom<proto::account::AccountWitness> for AccountWitnessRecord {
893    type Error = ConversionError;
894
895    fn try_from(
896        account_witness_record: proto::account::AccountWitness,
897    ) -> Result<Self, Self::Error> {
898        let witness_id = account_witness_record
899            .witness_id
900            .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
901            .try_into()?;
902        let commitment = account_witness_record
903            .commitment
904            .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))?
905            .try_into()?;
906        let path: SparseMerklePath = account_witness_record
907            .path
908            .as_ref()
909            .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))?
910            .clone()
911            .try_into()?;
912
913        let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| {
914            ConversionError::deserialization_error(
915                "AccountWitness",
916                DeserializationError::InvalidValue(err.to_string()),
917            )
918        })?;
919
920        Ok(Self {
921            account_id: account_witness_record
922                .account_id
923                .ok_or(proto::account::AccountWitness::missing_field(stringify!(account_id)))?
924                .try_into()?,
925            witness,
926        })
927    }
928}
929
930impl From<AccountWitnessRecord> for proto::account::AccountWitness {
931    fn from(from: AccountWitnessRecord) -> Self {
932        Self {
933            account_id: Some(from.account_id.into()),
934            witness_id: Some(from.witness.id().into()),
935            commitment: Some(from.witness.state_commitment().into()),
936            path: Some(from.witness.path().clone().into()),
937        }
938    }
939}
940
941// ACCOUNT STATE
942// ================================================================================================
943
944/// Information needed from the store to verify account in transaction.
945#[derive(Debug)]
946pub struct AccountState {
947    /// Account ID
948    pub account_id: AccountId,
949    /// The account commitment in the store corresponding to tx's account ID
950    pub account_commitment: Option<Word>,
951}
952
953impl Display for AccountState {
954    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
955        f.write_fmt(format_args!(
956            "{{ account_id: {}, account_commitment: {} }}",
957            self.account_id,
958            format_opt(self.account_commitment.as_ref()),
959        ))
960    }
961}
962
963impl TryFrom<proto::store::transaction_inputs::AccountTransactionInputRecord> for AccountState {
964    type Error = ConversionError;
965
966    fn try_from(
967        from: proto::store::transaction_inputs::AccountTransactionInputRecord,
968    ) -> Result<Self, Self::Error> {
969        let account_id = from
970            .account_id
971            .ok_or(proto::store::transaction_inputs::AccountTransactionInputRecord::missing_field(
972                stringify!(account_id),
973            ))?
974            .try_into()?;
975
976        let account_commitment = from
977            .account_commitment
978            .ok_or(proto::store::transaction_inputs::AccountTransactionInputRecord::missing_field(
979                stringify!(account_commitment),
980            ))?
981            .try_into()?;
982
983        // If the commitment is equal to `Word::empty()`, it signifies that this is a new
984        // account which is not yet present in the Store.
985        let account_commitment = if account_commitment == Word::empty() {
986            None
987        } else {
988            Some(account_commitment)
989        };
990
991        Ok(Self { account_id, account_commitment })
992    }
993}
994
995impl From<AccountState> for proto::store::transaction_inputs::AccountTransactionInputRecord {
996    fn from(from: AccountState) -> Self {
997        Self {
998            account_id: Some(from.account_id.into()),
999            account_commitment: from.account_commitment.map(Into::into),
1000        }
1001    }
1002}
1003
1004// ASSET
1005// ================================================================================================
1006
1007impl TryFrom<proto::primitives::Asset> for Asset {
1008    type Error = ConversionError;
1009
1010    fn try_from(value: proto::primitives::Asset) -> Result<Self, Self::Error> {
1011        let inner = value.asset.ok_or(proto::primitives::Asset::missing_field("asset"))?;
1012        let word = Word::try_from(inner)?;
1013
1014        Asset::try_from(word).map_err(ConversionError::AssetError)
1015    }
1016}
1017
1018impl From<Asset> for proto::primitives::Asset {
1019    fn from(asset_from: Asset) -> Self {
1020        proto::primitives::Asset {
1021            asset: Some(Word::from(asset_from).into()),
1022        }
1023    }
1024}
1025
1026// NETWORK ACCOUNT PREFIX
1027// ================================================================================================
1028
1029pub type AccountPrefix = u32;
1030
1031/// Newtype wrapper for network account IDs.
1032///
1033/// Provides type safety for accounts that are meant for network execution.
1034/// This wraps the full `AccountId` of a network account, typically extracted
1035/// from a `NetworkAccountTarget` attachment.
1036#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
1037pub struct NetworkAccountId(AccountId);
1038
1039impl std::fmt::Display for NetworkAccountId {
1040    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1041        std::fmt::Display::fmt(&self.0, f)
1042    }
1043}
1044
1045impl NetworkAccountId {
1046    /// Returns the inner `AccountId`.
1047    pub fn inner(&self) -> AccountId {
1048        self.0
1049    }
1050
1051    /// Gets the 30-bit prefix of the account ID used for tag matching.
1052    pub fn prefix(&self) -> AccountPrefix {
1053        get_account_id_tag_prefix(self.0)
1054    }
1055}
1056
1057impl TryFrom<AccountId> for NetworkAccountId {
1058    type Error = NetworkAccountError;
1059
1060    fn try_from(id: AccountId) -> Result<Self, Self::Error> {
1061        if !id.is_network() {
1062            return Err(NetworkAccountError::NotNetworkAccount(id));
1063        }
1064        Ok(NetworkAccountId(id))
1065    }
1066}
1067
1068impl TryFrom<&NoteAttachment> for NetworkAccountId {
1069    type Error = NetworkAccountError;
1070
1071    fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
1072        let target = NetworkAccountTarget::try_from(attachment)
1073            .map_err(NetworkAccountError::InvalidAttachment)?;
1074        Ok(NetworkAccountId(target.target_id()))
1075    }
1076}
1077
1078impl TryFrom<NoteAttachment> for NetworkAccountId {
1079    type Error = NetworkAccountError;
1080
1081    fn try_from(attachment: NoteAttachment) -> Result<Self, Self::Error> {
1082        NetworkAccountId::try_from(&attachment)
1083    }
1084}
1085
1086impl From<NetworkAccountId> for AccountId {
1087    fn from(value: NetworkAccountId) -> Self {
1088        value.inner()
1089    }
1090}
1091
1092impl From<NetworkAccountId> for u32 {
1093    /// Returns the 30-bit prefix of the network account ID.
1094    /// This is used for note tag matching.
1095    fn from(value: NetworkAccountId) -> Self {
1096        value.prefix()
1097    }
1098}
1099
1100#[derive(Debug, Error)]
1101pub enum NetworkAccountError {
1102    #[error("account ID {0} is not a valid network account ID")]
1103    NotNetworkAccount(AccountId),
1104    #[error("invalid network account attachment: {0}")]
1105    InvalidAttachment(#[source] NetworkAccountTargetError),
1106}
1107
1108/// Gets the 30-bit prefix of the account ID.
1109fn get_account_id_tag_prefix(id: AccountId) -> AccountPrefix {
1110    (id.prefix().as_u64() >> 34) as AccountPrefix
1111}