Skip to main content

miden_node_proto/domain/
account.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
4use miden_protocol::Word;
5use miden_protocol::account::{
6    Account,
7    AccountHeader,
8    AccountId,
9    AccountStorageHeader,
10    StorageMap,
11    StorageMapKey,
12    StorageSlotHeader,
13    StorageSlotName,
14    StorageSlotType,
15};
16use miden_protocol::asset::Asset;
17use miden_protocol::block::BlockNumber;
18use miden_protocol::block::account_tree::AccountWitness;
19use miden_protocol::crypto::merkle::smt::{PartialSmt, SmtProof};
20use miden_protocol::crypto::merkle::{MerkleError, SparseMerklePath};
21use miden_protocol::utils::serde::{Deserializable, DeserializationError, Serializable};
22
23use super::try_convert;
24use crate::decode;
25use crate::decode::{ConversionResultExt, GrpcDecodeExt};
26use crate::errors::ConversionError;
27use crate::generated::{self as proto};
28
29#[cfg(test)]
30mod tests;
31
32// ACCOUNT ID
33// ================================================================================================
34
35impl Display for proto::account::AccountId {
36    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37        write!(f, "0x")?;
38        for byte in &self.id {
39            write!(f, "{byte:02x}")?;
40        }
41        Ok(())
42    }
43}
44
45impl Debug for proto::account::AccountId {
46    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47        Display::fmt(self, f)
48    }
49}
50
51// FROM PROTO ACCOUNT ID
52// ------------------------------------------------------------------------------------------------
53
54impl TryFrom<proto::account::AccountId> for AccountId {
55    type Error = ConversionError;
56
57    fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
58        AccountId::read_from_bytes(&account_id.id)
59            .map_err(|_| ConversionError::message("value is not in the range 0..MODULUS"))
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 decoder = slot.decoder();
126                let slot_name = StorageSlotName::new(slot.slot_name)?;
127                let slot_type = storage_slot_type_from_raw(slot.slot_type)?;
128                let commitment = decode!(decoder, slot.commitment)?;
129                Ok(StorageSlotHeader::new(slot_name, slot_type, commitment))
130            })
131            .collect::<Result<Vec<_>, ConversionError>>()
132            .context("slots")?;
133
134        Ok(AccountStorageHeader::new(slot_headers)?)
135    }
136}
137
138// ACCOUNT REQUEST
139// ================================================================================================
140
141/// Represents a request for an account proof.
142#[derive(Debug)]
143pub struct AccountRequest {
144    pub account_id: AccountId,
145    // If not present, the latest account proof references the latest available
146    pub block_num: Option<BlockNumber>,
147    pub details: Option<AccountDetailRequest>,
148}
149
150impl TryFrom<proto::rpc::AccountRequest> for AccountRequest {
151    type Error = ConversionError;
152
153    fn try_from(value: proto::rpc::AccountRequest) -> Result<Self, Self::Error> {
154        let decoder = value.decoder();
155        let proto::rpc::AccountRequest { account_id, block_num, details } = value;
156
157        let account_id = decode!(decoder, account_id)?;
158        let block_num = block_num.map(Into::into);
159
160        let details = details.map(TryFrom::try_from).transpose().context("details")?;
161
162        Ok(AccountRequest { account_id, block_num, details })
163    }
164}
165
166/// Represents a request for account details alongside specific storage data.
167#[derive(Debug)]
168pub struct AccountDetailRequest {
169    pub code_commitment: Option<Word>,
170    pub asset_vault_commitment: Option<Word>,
171    pub storage_request: AccountStorageRequest,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum AccountStorageRequest {
176    None,
177    AllStorageMaps,
178    Explicit(Vec<StorageMapRequest>),
179}
180
181impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetailRequest {
182    type Error = ConversionError;
183
184    fn try_from(
185        value: proto::rpc::account_request::AccountDetailRequest,
186    ) -> Result<Self, Self::Error> {
187        use proto::rpc::account_request::account_detail_request::StorageRequest as ProtoStorageRequest;
188
189        let proto::rpc::account_request::AccountDetailRequest {
190            code_commitment,
191            asset_vault_commitment,
192            storage_request,
193        } = value;
194
195        let code_commitment =
196            code_commitment.map(TryFrom::try_from).transpose().context("code_commitment")?;
197        let asset_vault_commitment = asset_vault_commitment
198            .map(TryFrom::try_from)
199            .transpose()
200            .context("asset_vault_commitment")?;
201
202        let storage_request = match storage_request {
203            None => AccountStorageRequest::None,
204            Some(ProtoStorageRequest::AllStorageMaps(true)) => {
205                AccountStorageRequest::AllStorageMaps
206            },
207            Some(ProtoStorageRequest::AllStorageMaps(false)) => {
208                return Err(ConversionError::message("all_storage_maps must be true when set"));
209            },
210            Some(ProtoStorageRequest::StorageMaps(requests)) => {
211                let requests = try_convert(requests.storage_maps)
212                    .collect::<Result<_, _>>()
213                    .context("storage_maps")?;
214                AccountStorageRequest::Explicit(requests)
215            },
216        };
217
218        Ok(AccountDetailRequest {
219            code_commitment,
220            asset_vault_commitment,
221            storage_request,
222        })
223    }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
227pub struct StorageMapRequest {
228    pub slot_name: StorageSlotName,
229    pub slot_data: SlotData,
230}
231
232impl TryFrom<proto::rpc::account_request::account_detail_request::StorageMapDetailRequest>
233    for StorageMapRequest
234{
235    type Error = ConversionError;
236
237    fn try_from(
238        value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest,
239    ) -> Result<Self, Self::Error> {
240        let decoder = value.decoder();
241        let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest {
242            slot_name,
243            slot_data,
244        } = value;
245
246        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
247        let slot_data = decode!(decoder, slot_data)?;
248
249        Ok(StorageMapRequest { slot_name, slot_data })
250    }
251}
252
253/// Request of slot data values.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub enum SlotData {
256    All,
257    MapKeys(Vec<StorageMapKey>),
258}
259
260impl
261    TryFrom<
262        proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
263    > for SlotData
264{
265    type Error = ConversionError;
266
267    fn try_from(
268        value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
269    ) -> Result<Self, Self::Error> {
270        use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData;
271
272        Ok(match value {
273            ProtoSlotData::AllEntries(true) => SlotData::All,
274            ProtoSlotData::AllEntries(false) => {
275                return Err(ConversionError::message("enum variant discriminant out of range"));
276            },
277            ProtoSlotData::MapKeys(keys) => {
278                let keys = try_convert(keys.map_keys).collect::<Result<Vec<_>, _>>()?;
279                if has_duplicate_storage_map_keys(&keys) {
280                    return Err(ConversionError::message(
281                        "storage map key request contains duplicate keys",
282                    ));
283                }
284                SlotData::MapKeys(keys)
285            },
286        })
287    }
288}
289
290fn has_duplicate_storage_map_keys(keys: &[StorageMapKey]) -> bool {
291    keys.iter().enumerate().any(|(index, key)| keys[..index].contains(key))
292}
293
294// ACCOUNT HEADER CONVERSIONS
295//================================================================================================
296
297impl TryFrom<proto::account::AccountHeader> for AccountHeader {
298    type Error = ConversionError;
299
300    fn try_from(value: proto::account::AccountHeader) -> Result<Self, Self::Error> {
301        let decoder = value.decoder();
302        let proto::account::AccountHeader {
303            account_id,
304            vault_root,
305            storage_commitment,
306            code_commitment,
307            nonce,
308        } = value;
309
310        let account_id = decode!(decoder, account_id)?;
311        let vault_root = decode!(decoder, vault_root)?;
312        let storage_commitment = decode!(decoder, storage_commitment)?;
313        let code_commitment = decode!(decoder, code_commitment)?;
314        let nonce = nonce
315            .try_into()
316            .map_err(|e| ConversionError::message(format!("{e}")))
317            .context("nonce")?;
318
319        Ok(AccountHeader::new(
320            account_id,
321            nonce,
322            vault_root,
323            storage_commitment,
324            code_commitment,
325        ))
326    }
327}
328
329impl From<AccountHeader> for proto::account::AccountHeader {
330    fn from(header: AccountHeader) -> Self {
331        proto::account::AccountHeader {
332            account_id: Some(header.id().into()),
333            vault_root: Some(header.vault_root().into()),
334            storage_commitment: Some(header.storage_commitment().into()),
335            code_commitment: Some(header.code_commitment().into()),
336            nonce: header.nonce().as_canonical_u64(),
337        }
338    }
339}
340
341impl From<AccountStorageHeader> for proto::account::AccountStorageHeader {
342    fn from(value: AccountStorageHeader) -> Self {
343        let slots = value
344            .slots()
345            .map(|slot_header| proto::account::account_storage_header::StorageSlot {
346                slot_name: slot_header.name().to_string(),
347                slot_type: storage_slot_type_to_raw(slot_header.slot_type()),
348                commitment: Some(proto::primitives::Digest::from(slot_header.value())),
349            })
350            .collect();
351
352        Self { slots }
353    }
354}
355
356// ACCOUNT VAULT DETAILS
357//================================================================================================
358
359/// Account vault details
360///
361/// When an account contains a large number of assets (>
362/// [`AccountVaultDetails::MAX_RETURN_ENTRIES`]), including all assets in a single RPC response
363/// creates performance issues. In such cases, the `LimitExceeded` variant indicates to the client
364/// to use the `SyncAccountVault` endpoint instead.
365#[derive(Debug, Clone, PartialEq, Eq)]
366pub enum AccountVaultDetails {
367    /// The vault has too many assets to return inline. Clients must use `SyncAccountVault` endpoint
368    /// instead.
369    LimitExceeded,
370
371    /// The assets in the vault (up to `MAX_RETURN_ENTRIES`).
372    Assets(Vec<Asset>),
373}
374
375impl AccountVaultDetails {
376    /// Maximum number of vault entries that can be returned in a single response. Accounts with
377    /// more assets will have `LimitExceeded` variant.
378    pub const MAX_RETURN_ENTRIES: usize = 1000;
379
380    pub fn empty() -> Self {
381        Self::Assets(Vec::new())
382    }
383
384    /// Creates `AccountVaultDetails` from a list of assets.
385    pub fn from_assets(assets: Vec<Asset>) -> Self {
386        if assets.len() > Self::MAX_RETURN_ENTRIES {
387            Self::LimitExceeded
388        } else {
389            Self::Assets(assets)
390        }
391    }
392}
393
394impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
395    type Error = ConversionError;
396
397    fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
398        let proto::rpc::AccountVaultDetails { too_many_assets, assets } = value;
399
400        if too_many_assets {
401            Ok(Self::LimitExceeded)
402        } else {
403            let parsed_assets = assets
404                .into_iter()
405                .map(Asset::try_from)
406                .collect::<Result<Vec<_>, ConversionError>>()?;
407            Ok(Self::Assets(parsed_assets))
408        }
409    }
410}
411
412impl From<AccountVaultDetails> for proto::rpc::AccountVaultDetails {
413    fn from(value: AccountVaultDetails) -> Self {
414        match value {
415            AccountVaultDetails::LimitExceeded => Self {
416                too_many_assets: true,
417                assets: Vec::new(),
418            },
419            AccountVaultDetails::Assets(assets) => Self {
420                too_many_assets: false,
421                assets: assets.into_iter().map(proto::primitives::Asset::from).collect::<Vec<_>>(),
422            },
423        }
424    }
425}
426
427// ACCOUNT STORAGE MAP DETAILS
428//================================================================================================
429
430/// Details about an account storage map slot.
431#[derive(Debug, Clone, PartialEq, Eq)]
432pub struct AccountStorageMapDetails {
433    pub slot_name: StorageSlotName,
434    pub entries: StorageMapEntries,
435}
436
437/// Storage map entries for an account storage slot.
438///
439/// When a storage map contains many entries (> [`AccountStorageMapDetails::MAX_RETURN_ENTRIES`]),
440/// returning all entries in a single RPC response creates performance issues. In such cases,
441/// the `LimitExceeded` variant indicates to the client to use the `SyncAccountStorageMaps` endpoint
442/// instead.
443#[derive(Debug, Clone, PartialEq, Eq)]
444pub enum StorageMapEntries {
445    /// The map has too many entries to return inline. Clients must use `SyncAccountStorageMaps`
446    /// endpoint instead.
447    LimitExceeded,
448
449    /// All storage map entries (key-value pairs) without proofs. Used when all entries are
450    /// requested for small maps.
451    AllEntries(Vec<(StorageMapKey, Word)>),
452
453    /// Specific raw map keys covered by a single partial SMT. Used when specific keys are requested
454    /// from the storage map.
455    PartialMap {
456        map_keys: Vec<StorageMapKey>,
457        partial_smt: PartialSmt,
458    },
459}
460
461impl AccountStorageMapDetails {
462    /// Maximum number of storage map entries that can be returned in a single response.
463    pub const MAX_RETURN_ENTRIES: usize = 1000;
464
465    /// Maximum number of SMT proofs that can be returned in a single response.
466    ///
467    /// This limit is more restrictive than [`Self::MAX_RETURN_ENTRIES`] because SMT proofs
468    /// are larger (up to 64 inner nodes each) and more CPU-intensive to generate.
469    ///
470    /// This is defined by [`QueryParamStorageMapKeyTotalLimit::LIMIT`] and used both in RPC
471    /// validation and store-level enforcement to ensure consistent limits.
472    pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
473
474    /// Creates storage map details with all entries from the storage map.
475    ///
476    /// If the storage map has too many entries (> `MAX_RETURN_ENTRIES`),
477    /// returns `LimitExceeded` variant.
478    pub fn from_all_entries(slot_name: StorageSlotName, storage_map: &StorageMap) -> Self {
479        if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
480            Self {
481                slot_name,
482                entries: StorageMapEntries::LimitExceeded,
483            }
484        } else {
485            let entries = storage_map.entries().map(|(k, v)| (*k, *v)).collect::<Vec<_>>();
486            Self {
487                slot_name,
488                entries: StorageMapEntries::AllEntries(entries),
489            }
490        }
491    }
492
493    /// Creates storage map details from forest-queried entries.
494    ///
495    /// Returns `LimitExceeded` if too many entries.
496    pub fn from_forest_entries(
497        slot_name: StorageSlotName,
498        entries: Vec<(StorageMapKey, Word)>,
499    ) -> Self {
500        if entries.len() > Self::MAX_RETURN_ENTRIES {
501            Self {
502                slot_name,
503                entries: StorageMapEntries::LimitExceeded,
504            }
505        } else {
506            Self {
507                slot_name,
508                entries: StorageMapEntries::AllEntries(entries),
509            }
510        }
511    }
512
513    /// Creates storage map details from pre-computed SMT proofs.
514    ///
515    /// Use this when the caller has already obtained the proofs from an `SmtForest`.
516    /// Returns `LimitExceeded` if too many proofs are provided.
517    pub fn from_proofs(
518        slot_name: StorageSlotName,
519        map_root: Word,
520        map_keys: Vec<StorageMapKey>,
521        proofs: Vec<SmtProof>,
522    ) -> Result<Self, MerkleError> {
523        if map_keys.len() != proofs.len() {
524            return Err(MerkleError::InternalError(format!(
525                "storage map key count {} does not match proof count {}",
526                map_keys.len(),
527                proofs.len()
528            )));
529        }
530        if has_duplicate_storage_map_keys(&map_keys) {
531            return Err(MerkleError::InternalError(
532                "storage map key list contains duplicate keys".into(),
533            ));
534        }
535
536        if map_keys.len() > Self::MAX_SMT_PROOF_ENTRIES {
537            return Ok(Self {
538                slot_name,
539                entries: StorageMapEntries::LimitExceeded,
540            });
541        }
542
543        let partial_smt = if proofs.is_empty() {
544            PartialSmt::new(map_root)
545        } else {
546            PartialSmt::from_proofs(proofs)?
547        };
548
549        if partial_smt.root() != map_root {
550            return Err(MerkleError::ConflictingRoots {
551                expected_root: map_root,
552                actual_root: partial_smt.root(),
553            });
554        }
555
556        for map_key in &map_keys {
557            partial_smt.get_value(&map_key.hash().as_word())?;
558        }
559
560        Ok(Self {
561            slot_name,
562            entries: StorageMapEntries::PartialMap { map_keys, partial_smt },
563        })
564    }
565
566    /// Creates storage map details indicating the limit was exceeded.
567    pub fn limit_exceeded(slot_name: StorageSlotName) -> Self {
568        Self {
569            slot_name,
570            entries: StorageMapEntries::LimitExceeded,
571        }
572    }
573}
574
575impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
576    for AccountStorageMapDetails
577{
578    type Error = ConversionError;
579
580    fn try_from(
581        value: proto::rpc::account_storage_details::AccountStorageMapDetails,
582    ) -> Result<Self, Self::Error> {
583        use proto::rpc::account_storage_details::account_storage_map_details::{
584            AllMapEntries,
585            PartialStorageMap,
586            Result as ProtoResult,
587        };
588
589        let decoder = value.decoder();
590        let proto::rpc::account_storage_details::AccountStorageMapDetails { slot_name, result } =
591            value;
592
593        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
594
595        let entries = match decode!(decoder, result)? {
596            ProtoResult::TooManyEntries(true) => StorageMapEntries::LimitExceeded,
597            ProtoResult::TooManyEntries(false) => {
598                return Err(ConversionError::message("too_many_entries must be true when set"));
599            },
600            ProtoResult::AllEntries(AllMapEntries { entries }) => {
601                let entries = entries
602                    .into_iter()
603                    .map(|entry| {
604                        let decoder = entry.decoder();
605                        let key = StorageMapKey::new(decode!(decoder, entry.key)?);
606                        let value = decode!(decoder, entry.value)?;
607                        Ok((key, value))
608                    })
609                    .collect::<Result<Vec<_>, ConversionError>>()
610                    .context("entries")?;
611                StorageMapEntries::AllEntries(entries)
612            },
613            ProtoResult::PartialMap(PartialStorageMap { map_keys, partial_smt }) => {
614                if map_keys.len() > Self::MAX_SMT_PROOF_ENTRIES {
615                    return Err(ConversionError::message(format!(
616                        "partial storage map contains {} keys, exceeding the limit of {}",
617                        map_keys.len(),
618                        Self::MAX_SMT_PROOF_ENTRIES
619                    )));
620                }
621                let map_keys = map_keys
622                    .into_iter()
623                    .map(|key| Word::try_from(key).map(StorageMapKey::new))
624                    .collect::<Result<Vec<_>, _>>()
625                    .context("map_keys")?;
626                if has_duplicate_storage_map_keys(&map_keys) {
627                    return Err(ConversionError::message(
628                        "partial storage map contains duplicate keys",
629                    ));
630                }
631                let partial_smt: PartialSmt =
632                    decode!(decoder, partial_smt).context("partial_smt")?;
633                for map_key in &map_keys {
634                    partial_smt.get_value(&map_key.hash().as_word()).context("map_keys")?;
635                }
636                StorageMapEntries::PartialMap { map_keys, partial_smt }
637            },
638        };
639
640        Ok(Self { slot_name, entries })
641    }
642}
643
644impl From<AccountStorageMapDetails>
645    for proto::rpc::account_storage_details::AccountStorageMapDetails
646{
647    fn from(value: AccountStorageMapDetails) -> Self {
648        use proto::rpc::account_storage_details::account_storage_map_details::{
649            AllMapEntries,
650            PartialStorageMap,
651            Result as ProtoResult,
652        };
653
654        let AccountStorageMapDetails { slot_name, entries } = value;
655
656        let result = match entries {
657            StorageMapEntries::LimitExceeded => ProtoResult::TooManyEntries(true),
658            StorageMapEntries::AllEntries(entries) => {
659                let all = AllMapEntries {
660                    entries: entries.into_iter().map(|(key, value)| {
661                        proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry {
662                            key: Some(key.into()),
663                            value: Some(value.into()),
664                        }
665                    }).collect::<Vec<_>>(),
666                };
667                ProtoResult::AllEntries(all)
668            },
669            StorageMapEntries::PartialMap { map_keys, partial_smt } => {
670                ProtoResult::PartialMap(PartialStorageMap {
671                    map_keys: map_keys.into_iter().map(Into::into).collect(),
672                    partial_smt: Some(partial_smt.into()),
673                })
674            },
675        };
676
677        Self {
678            slot_name: slot_name.to_string(),
679            result: Some(result),
680        }
681    }
682}
683
684#[derive(Debug, Clone, PartialEq)]
685pub struct AccountStorageDetails {
686    pub header: AccountStorageHeader,
687    pub map_details: Vec<AccountStorageMapDetails>,
688}
689
690impl AccountStorageDetails {
691    /// Creates storage details where all map slots indicate limit exceeded.
692    pub fn all_limits_exceeded(
693        header: AccountStorageHeader,
694        slot_names: impl IntoIterator<Item = StorageSlotName>,
695    ) -> Self {
696        Self {
697            header,
698            map_details: slot_names
699                .into_iter()
700                .map(AccountStorageMapDetails::limit_exceeded)
701                .collect::<Vec<_>>(),
702        }
703    }
704}
705
706impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
707    type Error = ConversionError;
708
709    fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
710        let decoder = value.decoder();
711        let proto::rpc::AccountStorageDetails { header, map_details } = value;
712
713        let header: AccountStorageHeader = decode!(decoder, header)?;
714
715        let map_details: Vec<AccountStorageMapDetails> =
716            try_convert(map_details).collect::<Result<Vec<_>, _>>().context("map_details")?;
717
718        for map_detail in &map_details {
719            let StorageMapEntries::PartialMap { partial_smt, .. } = &map_detail.entries else {
720                continue;
721            };
722
723            let slot = header.find_slot_header_by_name(&map_detail.slot_name).ok_or_else(|| {
724                ConversionError::message(format!(
725                    "partial storage map references unknown slot {}",
726                    map_detail.slot_name
727                ))
728            })?;
729            if slot.slot_type() != StorageSlotType::Map {
730                return Err(ConversionError::message(format!(
731                    "partial storage map references non-map slot {}",
732                    map_detail.slot_name
733                )));
734            }
735            if partial_smt.root() != slot.value() {
736                return Err(ConversionError::message(format!(
737                    "partial storage map root for slot {} does not match storage header",
738                    map_detail.slot_name
739                )));
740            }
741        }
742
743        Ok(Self { header, map_details })
744    }
745}
746
747impl From<AccountStorageDetails> for proto::rpc::AccountStorageDetails {
748    fn from(value: AccountStorageDetails) -> Self {
749        let AccountStorageDetails { header, map_details } = value;
750
751        Self {
752            header: Some(header.into()),
753            map_details: map_details.into_iter().map(Into::into).collect(),
754        }
755    }
756}
757
758fn storage_slot_type_from_raw(slot_type: u32) -> Result<StorageSlotType, ConversionError> {
759    Ok(match slot_type {
760        0 => StorageSlotType::Value,
761        1 => StorageSlotType::Map,
762        _ => {
763            return Err(ConversionError::message("enum variant discriminant out of range"));
764        },
765    })
766}
767
768const fn storage_slot_type_to_raw(slot_type: StorageSlotType) -> u32 {
769    match slot_type {
770        StorageSlotType::Value => 0,
771        StorageSlotType::Map => 1,
772    }
773}
774
775// ACCOUNT PROOF RESPONSE
776//================================================================================================
777
778/// Represents the response to an account proof request.
779pub struct AccountResponse {
780    pub block_num: BlockNumber,
781    pub witness: AccountWitness,
782    pub details: Option<AccountDetails>,
783}
784
785impl TryFrom<proto::rpc::AccountResponse> for AccountResponse {
786    type Error = ConversionError;
787
788    fn try_from(value: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
789        let decoder = value.decoder();
790        let proto::rpc::AccountResponse { block_num, witness, details } = value;
791
792        let block_num = decode!(decoder, block_num)?;
793
794        let witness = decode!(decoder, witness)?;
795
796        let details = details.map(TryFrom::try_from).transpose().context("details")?;
797
798        Ok(AccountResponse { block_num, witness, details })
799    }
800}
801
802impl From<AccountResponse> for proto::rpc::AccountResponse {
803    fn from(value: AccountResponse) -> Self {
804        let AccountResponse { block_num, witness, details } = value;
805
806        Self {
807            witness: Some(witness.into()),
808            details: details.map(Into::into),
809            block_num: Some(block_num.into()),
810        }
811    }
812}
813
814// ACCOUNT DETAILS
815//================================================================================================
816
817/// Represents account details returned in response to an account proof request.
818pub struct AccountDetails {
819    pub account_header: AccountHeader,
820    pub account_code: Option<Vec<u8>>,
821    pub vault_details: AccountVaultDetails,
822    pub storage_details: AccountStorageDetails,
823}
824
825impl AccountDetails {
826    /// Creates account details where all storage map slots indicate limit exceeded.
827    pub fn with_storage_limits_exceeded(
828        account_header: AccountHeader,
829        account_code: Option<Vec<u8>>,
830        vault_details: AccountVaultDetails,
831        storage_header: AccountStorageHeader,
832        slot_names: impl IntoIterator<Item = StorageSlotName>,
833    ) -> Self {
834        Self {
835            account_header,
836            account_code,
837            vault_details,
838            storage_details: AccountStorageDetails::all_limits_exceeded(storage_header, slot_names),
839        }
840    }
841}
842
843impl TryFrom<proto::rpc::account_response::AccountDetails> for AccountDetails {
844    type Error = ConversionError;
845
846    fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result<Self, Self::Error> {
847        let decoder = value.decoder();
848        let proto::rpc::account_response::AccountDetails {
849            header,
850            code,
851            vault_details,
852            storage_details,
853        } = value;
854
855        let account_header = decode!(decoder, header)?;
856
857        let storage_details = decode!(decoder, storage_details)?;
858
859        let vault_details = decode!(decoder, vault_details)?;
860        let account_code = code;
861
862        Ok(AccountDetails {
863            account_header,
864            account_code,
865            vault_details,
866            storage_details,
867        })
868    }
869}
870
871impl From<AccountDetails> for proto::rpc::account_response::AccountDetails {
872    fn from(value: AccountDetails) -> Self {
873        let AccountDetails {
874            account_header,
875            storage_details,
876            account_code,
877            vault_details,
878        } = value;
879
880        let header = Some(proto::account::AccountHeader::from(account_header));
881        let storage_details = Some(storage_details.into());
882        let code = account_code;
883        let vault_details = Some(vault_details.into());
884
885        Self {
886            header,
887            storage_details,
888            code,
889            vault_details,
890        }
891    }
892}
893
894// ACCOUNT WITNESS
895// ================================================================================================
896
897impl TryFrom<proto::account::AccountWitness> for AccountWitness {
898    type Error = ConversionError;
899
900    fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
901        let decoder = account_witness.decoder();
902        let witness_id = decode!(decoder, account_witness.witness_id)?;
903        let commitment = decode!(decoder, account_witness.commitment)?;
904        let path = decode!(decoder, account_witness.path)?;
905
906        AccountWitness::new(witness_id, commitment, path).map_err(|err| {
907            ConversionError::deserialization(
908                "AccountWitness",
909                DeserializationError::InvalidValue(err.to_string()),
910            )
911        })
912    }
913}
914
915impl From<AccountWitness> for proto::account::AccountWitness {
916    fn from(witness: AccountWitness) -> Self {
917        Self {
918            account_id: Some(witness.id().into()),
919            witness_id: Some(witness.id().into()),
920            commitment: Some(witness.state_commitment().into()),
921            path: Some(witness.into_proof().into_parts().0.into()),
922        }
923    }
924}
925
926// ACCOUNT WITNESS RECORD
927// ================================================================================================
928
929#[derive(Clone, Debug, PartialEq, Eq)]
930pub struct AccountWitnessRecord {
931    pub account_id: AccountId,
932    pub witness: AccountWitness,
933}
934
935impl TryFrom<proto::account::AccountWitness> for AccountWitnessRecord {
936    type Error = ConversionError;
937
938    fn try_from(
939        account_witness_record: proto::account::AccountWitness,
940    ) -> Result<Self, Self::Error> {
941        let decoder = account_witness_record.decoder();
942        let witness_id = decode!(decoder, account_witness_record.witness_id)?;
943        let commitment = decode!(decoder, account_witness_record.commitment)?;
944        let account_id = decode!(decoder, account_witness_record.account_id)?;
945        let path: SparseMerklePath = decode!(decoder, account_witness_record.path)?;
946
947        let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| {
948            ConversionError::deserialization(
949                "AccountWitness",
950                DeserializationError::InvalidValue(err.to_string()),
951            )
952        })?;
953
954        Ok(Self { account_id, witness })
955    }
956}
957
958impl From<AccountWitnessRecord> for proto::account::AccountWitness {
959    fn from(from: AccountWitnessRecord) -> Self {
960        Self {
961            account_id: Some(from.account_id.into()),
962            witness_id: Some(from.witness.id().into()),
963            commitment: Some(from.witness.state_commitment().into()),
964            path: Some(from.witness.path().clone().into()),
965        }
966    }
967}
968
969// ASSET
970// ================================================================================================
971
972impl TryFrom<proto::primitives::Asset> for Asset {
973    type Error = ConversionError;
974
975    fn try_from(asset: proto::primitives::Asset) -> Result<Self, Self::Error> {
976        let decoder = asset.decoder();
977        let key_word: Word = decode!(decoder, asset.key)?;
978        let value_word: Word = decode!(decoder, asset.value)?;
979
980        let asset = Asset::from_id_and_value_words(key_word, value_word)?;
981        Ok(asset)
982    }
983}
984
985impl From<Asset> for proto::primitives::Asset {
986    fn from(asset_from: Asset) -> Self {
987        proto::primitives::Asset {
988            key: Some(asset_from.to_id_word().into()),
989            value: Some(asset_from.to_value_word().into()),
990        }
991    }
992}