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