1use std::collections::HashSet;
2use std::fmt::{Debug, Formatter};
3
4use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
5use miden_protobuf::{ConversionResultExt, DecodeMessage, Decoded, RequiredField, Verify, decode};
6use miden_protocol::Word;
7#[cfg(test)]
8use miden_protocol::account::StorageSlotHeader;
9use miden_protocol::account::{
10 Account,
11 AccountCode,
12 AccountHeader,
13 AccountId,
14 AccountStorageHeader,
15 StorageMap,
16 StorageMapKey,
17 StorageSlotName,
18 StorageSlotType,
19};
20use miden_protocol::asset::Asset;
21use miden_protocol::block::BlockNumber;
22use miden_protocol::block::account_tree::AccountWitness;
23use miden_protocol::crypto::merkle::MerkleError;
24use miden_protocol::crypto::merkle::smt::{PartialSmt, SmtProof};
25
26use crate::errors::ConversionError;
27use crate::generated::{self as proto};
28
29#[cfg(test)]
30mod tests;
31
32#[derive(Debug, PartialEq)]
36pub struct AccountSummary {
37 pub account_id: AccountId,
38 pub account_commitment: Word,
39 pub block_num: BlockNumber,
40}
41
42#[derive(Debug, PartialEq)]
43pub struct AccountInfo {
44 pub summary: AccountSummary,
45 pub details: Option<Account>,
46}
47
48impl Debug for proto::miden::node::v1::RegisterAccountRequest {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("RegisterAccountRequest")
54 .field("account_id", &self.account_id)
55 .finish_non_exhaustive()
56 }
57}
58
59#[must_use = "decoded fields have not been verified"]
61pub struct DecodedRegisterAccountRequest {
62 pub invitation_code: String,
63 pub account_id: Decoded<proto::account::AccountId>,
64}
65
66impl Debug for DecodedRegisterAccountRequest {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("DecodedRegisterAccountRequest")
69 .field("account_id", &self.account_id)
70 .finish_non_exhaustive()
71 }
72}
73
74impl DecodeMessage for proto::miden::node::v1::RegisterAccountRequest {
75 type Decoded = DecodedRegisterAccountRequest;
76}
77
78impl TryFrom<proto::miden::node::v1::RegisterAccountRequest> for DecodedRegisterAccountRequest {
79 type Error = ConversionError;
80
81 fn try_from(
82 request: proto::miden::node::v1::RegisterAccountRequest,
83 ) -> Result<Self, Self::Error> {
84 Ok(Self {
85 invitation_code: request.invitation_code,
86 account_id: decode(
87 RequiredField::<proto::miden::node::v1::RegisterAccountRequest, _>::new(
88 "account_id",
89 request.account_id,
90 ),
91 )?,
92 })
93 }
94}
95
96pub struct RegisterAccountRequest {
98 pub invitation_code: String,
99 pub account_id: AccountId,
100}
101
102impl Debug for RegisterAccountRequest {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("RegisterAccountRequest")
105 .field("account_id", &self.account_id)
106 .finish_non_exhaustive()
107 }
108}
109
110impl Verify for DecodedRegisterAccountRequest {
111 type Verified = RegisterAccountRequest;
112 type Error = ConversionError;
113
114 fn verify(self) -> Result<Self::Verified, Self::Error> {
115 Ok(RegisterAccountRequest {
116 invitation_code: self.invitation_code,
117 account_id: self.account_id.verify().context("account_id")?,
118 })
119 }
120}
121
122impl Verify for proto::miden::node::v1::DecodedIsAccountAllowedRequest {
123 type Verified = AccountId;
124 type Error = ConversionError;
125
126 fn verify(self) -> Result<Self::Verified, Self::Error> {
127 self.account_id.verify().context("account_id")
128 }
129}
130
131impl Debug for proto::miden::node::v1::IsInvitationCodeValidRequest {
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 f.debug_struct("IsInvitationCodeValidRequest").finish_non_exhaustive()
137 }
138}
139
140#[must_use = "decoded fields have not been verified"]
142pub struct DecodedIsInvitationCodeValidRequest {
143 pub invitation_code: String,
144}
145
146impl Debug for DecodedIsInvitationCodeValidRequest {
147 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("DecodedIsInvitationCodeValidRequest").finish_non_exhaustive()
149 }
150}
151
152impl DecodeMessage for proto::miden::node::v1::IsInvitationCodeValidRequest {
153 type Decoded = DecodedIsInvitationCodeValidRequest;
154}
155
156impl TryFrom<proto::miden::node::v1::IsInvitationCodeValidRequest>
157 for DecodedIsInvitationCodeValidRequest
158{
159 type Error = ConversionError;
160
161 fn try_from(
162 request: proto::miden::node::v1::IsInvitationCodeValidRequest,
163 ) -> Result<Self, Self::Error> {
164 Ok(Self { invitation_code: request.invitation_code })
165 }
166}
167
168impl Verify for DecodedIsInvitationCodeValidRequest {
171 type Verified = String;
172 type Error = ConversionError;
173
174 fn verify(self) -> Result<Self::Verified, Self::Error> {
175 Ok(self.invitation_code)
176 }
177}
178
179#[derive(Debug)]
184pub struct GetAccountRequest {
185 pub account_id: AccountId,
186 pub block_num: Option<BlockNumber>,
188 pub details: Option<AccountDetailRequest>,
189}
190
191impl Verify for proto::miden::node::v1::DecodedGetAccountRequest {
192 type Verified = GetAccountRequest;
193 type Error = ConversionError;
194
195 fn verify(self) -> Result<Self::Verified, Self::Error> {
196 let Self { account_id, block_num, details } = self;
197
198 let account_id = account_id.verify().context("account_id")?;
199 let block_num = block_num.verify()?;
200
201 let details = details.verify()?;
202
203 Ok(GetAccountRequest { account_id, block_num, details })
204 }
205}
206
207#[derive(Debug)]
209pub struct AccountDetailRequest {
210 pub code_commitment: Option<Word>,
211 pub asset_vault_commitment: Option<Word>,
212 pub storage_request: AccountStorageRequest,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum AccountStorageRequest {
217 None,
218 AllStorageMaps,
219 Explicit(Vec<StorageMapRequest>),
220}
221
222impl Verify for proto::miden::node::v1::get_account_request::DecodedAccountDetailRequest {
223 type Verified = AccountDetailRequest;
224 type Error = ConversionError;
225
226 fn verify(self) -> Result<Self::Verified, Self::Error> {
227 use proto::miden::node::v1::get_account_request::account_detail_request::DecodedStorageRequest as ProtoStorageRequest;
228
229 let Self {
230 code_commitment,
231 asset_vault_commitment,
232 storage_request,
233 } = self;
234
235 let storage_request = match storage_request.into_inner() {
236 None => AccountStorageRequest::None,
237 Some(ProtoStorageRequest::AllStorageMaps(true)) => {
238 AccountStorageRequest::AllStorageMaps
239 },
240 Some(ProtoStorageRequest::AllStorageMaps(false)) => {
241 return Err(ConversionError::message("all_storage_maps must be true when set"));
242 },
243 Some(ProtoStorageRequest::StorageMaps(requests)) => {
244 let requests =
245 requests.storage_maps.verify().context("storage_request.storage_maps")?;
246 AccountStorageRequest::Explicit(requests)
247 },
248 };
249
250 Ok(AccountDetailRequest {
251 code_commitment: code_commitment.into_inner(),
252 asset_vault_commitment: asset_vault_commitment.into_inner(),
253 storage_request,
254 })
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct StorageMapRequest {
260 pub slot_name: StorageSlotName,
261 pub slot_data: SlotData,
262}
263
264impl Verify
265 for proto::miden::node::v1::get_account_request::account_detail_request::DecodedStorageMapDetailRequest
266{
267 type Verified = StorageMapRequest;
268 type Error = ConversionError;
269
270 fn verify(self) -> Result<Self::Verified, Self::Error> {
271 let Self { slot_name, slot_data } = self;
272
273 let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
274 let slot_data = slot_data.verify().context("slot_data")?;
275
276 Ok(StorageMapRequest { slot_name, slot_data })
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
282pub enum SlotData {
283 All,
284 MapKeys(Vec<StorageMapKey>),
285}
286
287impl Verify for proto::miden::node::v1::get_account_request::account_detail_request::storage_map_detail_request::DecodedSlotData {
288 type Verified = SlotData;
289 type Error = ConversionError;
290
291 fn verify(self) -> Result<Self::Verified, Self::Error> {
292 use proto::miden::node::v1::get_account_request::account_detail_request::storage_map_detail_request::DecodedSlotData as ProtoSlotData;
293
294 Ok(match self {
295 ProtoSlotData::AllEntries(true) => SlotData::All,
296 ProtoSlotData::AllEntries(false) => {
297 return Err(ConversionError::message("enum variant discriminant out of range"));
298 },
299 ProtoSlotData::MapKeys(keys) => {
300 let keys = keys
301 .map_keys
302 .map(StorageMapKey::new);
303 if has_duplicate_storage_map_keys(&keys) {
304 return Err(ConversionError::message(
305 "storage map key request contains duplicate keys",
306 ));
307 }
308 SlotData::MapKeys(keys)
309 },
310 })
311 }
312}
313
314fn has_duplicate_storage_map_keys(keys: &[StorageMapKey]) -> bool {
315 let mut seen = HashSet::with_capacity(keys.len());
316 keys.iter().any(|key| !seen.insert(key))
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
329pub enum AccountVaultDetails {
330 LimitExceeded,
333
334 Assets(Vec<Asset>),
336}
337
338impl AccountVaultDetails {
339 pub const MAX_RETURN_ENTRIES: usize = 1000;
342
343 pub fn empty() -> Self {
344 Self::Assets(Vec::new())
345 }
346
347 pub fn from_assets(assets: Vec<Asset>) -> Self {
349 if assets.len() > Self::MAX_RETURN_ENTRIES {
350 Self::LimitExceeded
351 } else {
352 Self::Assets(assets)
353 }
354 }
355}
356
357impl Verify for proto::miden::node::v1::DecodedAccountVaultDetails {
358 type Verified = AccountVaultDetails;
359 type Error = ConversionError;
360
361 fn verify(self) -> Result<Self::Verified, Self::Error> {
362 let Self { too_many_assets, assets } = self;
363
364 if too_many_assets {
365 Ok(AccountVaultDetails::LimitExceeded)
366 } else {
367 let parsed_assets = assets.verify()?;
368 Ok(AccountVaultDetails::Assets(parsed_assets))
369 }
370 }
371}
372
373impl From<AccountVaultDetails> for proto::miden::node::v1::AccountVaultDetails {
374 fn from(value: AccountVaultDetails) -> Self {
375 match value {
376 AccountVaultDetails::LimitExceeded => Self {
377 too_many_assets: true,
378 assets: Vec::new(),
379 },
380 AccountVaultDetails::Assets(assets) => Self {
381 too_many_assets: false,
382 assets: assets.into_iter().map(proto::asset::Asset::from).collect::<Vec<_>>(),
383 },
384 }
385 }
386}
387
388#[derive(Debug, Clone, PartialEq, Eq)]
393pub struct AccountStorageMapDetails {
394 pub slot_name: StorageSlotName,
395 pub entries: StorageMapEntries,
396}
397
398#[derive(Debug, Clone, PartialEq, Eq)]
405pub enum StorageMapEntries {
406 LimitExceeded,
409
410 AllEntries(Vec<(StorageMapKey, Word)>),
413
414 PartialMap {
417 map_keys: Vec<StorageMapKey>,
418 partial_smt: PartialSmt,
419 },
420}
421
422impl AccountStorageMapDetails {
423 pub const MAX_RETURN_ENTRIES: usize = 1000;
425
426 pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
434
435 pub fn from_all_entries(slot_name: StorageSlotName, storage_map: &StorageMap) -> Self {
440 if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
441 Self {
442 slot_name,
443 entries: StorageMapEntries::LimitExceeded,
444 }
445 } else {
446 let entries = storage_map.entries().map(|(k, v)| (*k, *v)).collect::<Vec<_>>();
447 Self {
448 slot_name,
449 entries: StorageMapEntries::AllEntries(entries),
450 }
451 }
452 }
453
454 pub fn from_forest_entries(
458 slot_name: StorageSlotName,
459 entries: Vec<(StorageMapKey, Word)>,
460 ) -> Self {
461 if entries.len() > Self::MAX_RETURN_ENTRIES {
462 Self {
463 slot_name,
464 entries: StorageMapEntries::LimitExceeded,
465 }
466 } else {
467 Self {
468 slot_name,
469 entries: StorageMapEntries::AllEntries(entries),
470 }
471 }
472 }
473
474 pub fn from_proofs(
479 slot_name: StorageSlotName,
480 map_root: Word,
481 map_keys: Vec<StorageMapKey>,
482 proofs: Vec<SmtProof>,
483 ) -> Result<Self, MerkleError> {
484 if map_keys.len() != proofs.len() {
485 return Err(MerkleError::InternalError(format!(
486 "storage map key count {} does not match proof count {}",
487 map_keys.len(),
488 proofs.len()
489 )));
490 }
491 if has_duplicate_storage_map_keys(&map_keys) {
492 return Err(MerkleError::InternalError(
493 "storage map key list contains duplicate keys".into(),
494 ));
495 }
496
497 if map_keys.len() > Self::MAX_SMT_PROOF_ENTRIES {
498 return Ok(Self {
499 slot_name,
500 entries: StorageMapEntries::LimitExceeded,
501 });
502 }
503
504 let partial_smt = if proofs.is_empty() {
505 PartialSmt::new(map_root)
506 } else {
507 PartialSmt::from_proofs(proofs)?
508 };
509
510 if partial_smt.root() != map_root {
511 return Err(MerkleError::ConflictingRoots {
512 expected_root: map_root,
513 actual_root: partial_smt.root(),
514 });
515 }
516
517 for map_key in &map_keys {
518 partial_smt.get_value(&map_key.hash().as_word())?;
519 }
520
521 Ok(Self {
522 slot_name,
523 entries: StorageMapEntries::PartialMap { map_keys, partial_smt },
524 })
525 }
526
527 pub fn limit_exceeded(slot_name: StorageSlotName) -> Self {
529 Self {
530 slot_name,
531 entries: StorageMapEntries::LimitExceeded,
532 }
533 }
534}
535
536impl Verify for proto::miden::node::v1::account_storage_details::DecodedAccountStorageMapDetails {
537 type Verified = AccountStorageMapDetails;
538 type Error = ConversionError;
539
540 fn verify(self) -> Result<Self::Verified, Self::Error> {
541 use proto::miden::node::v1::account_storage_details::account_storage_map_details::{
542 DecodedAllMapEntries,
543 DecodedPartialStorageMap,
544 DecodedResult as ProtoResult,
545 };
546
547 let Self { slot_name, result } = self;
548
549 let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
550
551 let entries = match result {
552 ProtoResult::TooManyEntries(true) => StorageMapEntries::LimitExceeded,
553 ProtoResult::TooManyEntries(false) => {
554 return Err(ConversionError::message("too_many_entries must be true when set"));
555 },
556 ProtoResult::AllEntries(DecodedAllMapEntries { entries }) => {
557 let entries = entries.map(|entry| (StorageMapKey::new(entry.key), entry.value));
558 StorageMapEntries::AllEntries(entries)
559 },
560 ProtoResult::PartialMap(DecodedPartialStorageMap { map_keys, partial_smt }) => {
561 let map_keys = map_keys.into_inner();
562 if map_keys.len() > AccountStorageMapDetails::MAX_SMT_PROOF_ENTRIES {
563 return Err(ConversionError::message(format!(
564 "partial storage map contains {} keys, exceeding the limit of {}",
565 map_keys.len(),
566 AccountStorageMapDetails::MAX_SMT_PROOF_ENTRIES
567 )));
568 }
569 let map_keys = map_keys.into_iter().map(StorageMapKey::new).collect::<Vec<_>>();
570 if has_duplicate_storage_map_keys(&map_keys) {
571 return Err(ConversionError::message(
572 "partial storage map contains duplicate keys",
573 ));
574 }
575 let partial_smt: PartialSmt = partial_smt.verify().context("partial_smt")?;
576 for map_key in &map_keys {
577 partial_smt.get_value(&map_key.hash().as_word()).context("map_keys")?;
578 }
579 StorageMapEntries::PartialMap { map_keys, partial_smt }
580 },
581 };
582
583 Ok(Self::Verified { slot_name, entries })
584 }
585}
586
587impl From<AccountStorageMapDetails>
588 for proto::miden::node::v1::account_storage_details::AccountStorageMapDetails
589{
590 fn from(value: AccountStorageMapDetails) -> Self {
591 use proto::miden::node::v1::account_storage_details::account_storage_map_details::{
592 AllMapEntries,
593 PartialStorageMap,
594 Result as ProtoResult,
595 };
596
597 let AccountStorageMapDetails { slot_name, entries } = value;
598
599 let result = match entries {
600 StorageMapEntries::LimitExceeded => ProtoResult::TooManyEntries(true),
601 StorageMapEntries::AllEntries(entries) => {
602 let all = AllMapEntries {
603 entries: entries.into_iter().map(|(key, value)| {
604 proto::miden::node::v1::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry {
605 key: Some(Word::from(key).into()),
606 value: Some(value.into()),
607 }
608 }).collect::<Vec<_>>(),
609 };
610 ProtoResult::AllEntries(all)
611 },
612 StorageMapEntries::PartialMap { map_keys, partial_smt } => {
613 ProtoResult::PartialMap(PartialStorageMap {
614 map_keys: map_keys
615 .into_iter()
616 .map(|key| proto::primitives::Word::from(Word::from(key)))
617 .collect(),
618 partial_smt: Some(partial_smt.into()),
619 })
620 },
621 };
622
623 Self {
624 slot_name: slot_name.to_string(),
625 result: Some(result),
626 }
627 }
628}
629
630#[derive(Debug, Clone, PartialEq)]
631pub struct AccountStorageDetails {
632 pub header: AccountStorageHeader,
633 pub map_details: Vec<AccountStorageMapDetails>,
634}
635
636impl AccountStorageDetails {
637 pub fn all_limits_exceeded(
639 header: AccountStorageHeader,
640 slot_names: impl IntoIterator<Item = StorageSlotName>,
641 ) -> Self {
642 Self {
643 header,
644 map_details: slot_names
645 .into_iter()
646 .map(AccountStorageMapDetails::limit_exceeded)
647 .collect::<Vec<_>>(),
648 }
649 }
650}
651
652impl Verify for proto::miden::node::v1::DecodedAccountStorageDetails {
653 type Verified = AccountStorageDetails;
654 type Error = ConversionError;
655
656 fn verify(self) -> Result<Self::Verified, Self::Error> {
657 let Self { header, map_details } = self;
658
659 let header: AccountStorageHeader = header.verify().context("header")?;
660
661 let map_details = map_details.verify()?;
662
663 for map_detail in &map_details {
664 let StorageMapEntries::PartialMap { partial_smt, .. } = &map_detail.entries else {
665 continue;
666 };
667
668 let slot = header.find_slot_header_by_name(&map_detail.slot_name).ok_or_else(|| {
669 ConversionError::message(format!(
670 "partial storage map references unknown slot {}",
671 map_detail.slot_name
672 ))
673 })?;
674 if slot.slot_type() != StorageSlotType::Map {
675 return Err(ConversionError::message(format!(
676 "partial storage map references non-map slot {}",
677 map_detail.slot_name
678 )));
679 }
680 if partial_smt.root() != slot.value() {
681 return Err(ConversionError::message(format!(
682 "partial storage map root for slot {} does not match storage header",
683 map_detail.slot_name
684 )));
685 }
686 }
687
688 Ok(Self::Verified { header, map_details })
689 }
690}
691
692impl From<AccountStorageDetails> for proto::miden::node::v1::AccountStorageDetails {
693 fn from(value: AccountStorageDetails) -> Self {
694 let AccountStorageDetails { header, map_details } = value;
695
696 Self {
697 header: Some(header.into()),
698 map_details: map_details.into_iter().map(Into::into).collect(),
699 }
700 }
701}
702
703pub struct GetAccountResponse {
708 pub block_num: BlockNumber,
709 pub witness: AccountWitness,
710 pub details: Option<AccountDetails>,
711}
712
713impl Verify for proto::miden::node::v1::DecodedGetAccountResponse {
714 type Verified = GetAccountResponse;
715 type Error = ConversionError;
716
717 fn verify(self) -> Result<Self::Verified, Self::Error> {
720 let Self { block_num, witness, details } = self;
721
722 let block_num = block_num.verify().context("block_num")?;
723
724 let witness = witness.verify().context("witness")?;
725
726 let details = details.verify()?;
727
728 if let Some(details) = &details {
729 if details.account_header.id() != witness.id() {
730 return Err(ConversionError::message("account ID does not match witness")
731 .context("details.header.account_id"));
732 }
733 if details.account_header.to_commitment() != witness.state_commitment() {
734 return Err(ConversionError::message("account commitment does not match witness")
735 .context("details.header"));
736 }
737 }
738
739 Ok(GetAccountResponse { block_num, witness, details })
740 }
741}
742
743impl From<GetAccountResponse> for proto::miden::node::v1::GetAccountResponse {
744 fn from(value: GetAccountResponse) -> Self {
745 let GetAccountResponse { block_num, witness, details } = value;
746
747 Self {
748 witness: Some(witness.into()),
749 details: details.map(Into::into),
750 block_num: Some(block_num.into()),
751 }
752 }
753}
754
755pub struct AccountDetails {
760 pub account_header: AccountHeader,
761 pub account_code: Option<AccountCode>,
762 pub vault_details: AccountVaultDetails,
763 pub storage_details: AccountStorageDetails,
764}
765
766impl AccountDetails {
767 pub fn with_storage_limits_exceeded(
769 account_header: AccountHeader,
770 account_code: Option<AccountCode>,
771 vault_details: AccountVaultDetails,
772 storage_header: AccountStorageHeader,
773 slot_names: impl IntoIterator<Item = StorageSlotName>,
774 ) -> Self {
775 Self {
776 account_header,
777 account_code,
778 vault_details,
779 storage_details: AccountStorageDetails::all_limits_exceeded(storage_header, slot_names),
780 }
781 }
782}
783
784impl Verify for proto::miden::node::v1::get_account_response::DecodedAccountDetails {
785 type Verified = AccountDetails;
786 type Error = ConversionError;
787
788 fn verify(self) -> Result<Self::Verified, Self::Error> {
791 let Self {
792 header,
793 code,
794 vault_details,
795 storage_details,
796 } = self;
797
798 let account_header = header.verify().context("header")?;
799
800 let storage_details = storage_details.verify().context("storage_details")?;
801 if storage_details.header.to_commitment() != account_header.storage_commitment() {
802 return Err(ConversionError::message(
803 "storage commitment does not match account header",
804 )
805 .context("storage_details.header"));
806 }
807
808 let vault_details = vault_details.verify().context("vault_details")?;
809 let account_code = code.verify()?;
810 if let Some(code) = &account_code
811 && code.commitment() != account_header.code_commitment()
812 {
813 return Err(ConversionError::message("code commitment does not match account header")
814 .context("code"));
815 }
816
817 Ok(AccountDetails {
818 account_header,
819 account_code,
820 vault_details,
821 storage_details,
822 })
823 }
824}
825
826impl From<AccountDetails> for proto::miden::node::v1::get_account_response::AccountDetails {
827 fn from(value: AccountDetails) -> Self {
828 let AccountDetails {
829 account_header,
830 storage_details,
831 account_code,
832 vault_details,
833 } = value;
834
835 let header = Some(proto::account::AccountHeader::from(account_header));
836 let storage_details = Some(storage_details.into());
837 let code = account_code.map(Into::into);
838 let vault_details = Some(vault_details.into());
839
840 Self {
841 header,
842 storage_details,
843 code,
844 vault_details,
845 }
846 }
847}