miden_node_proto/domain/
account.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use miden_node_utils::formatting::format_opt;
4use miden_objects::Word;
5use miden_objects::account::{
6    Account,
7    AccountHeader,
8    AccountId,
9    AccountStorageHeader,
10    StorageMap,
11    StorageSlotType,
12};
13use miden_objects::asset::{Asset, AssetVault};
14use miden_objects::block::{AccountWitness, BlockNumber};
15use miden_objects::crypto::merkle::SparseMerklePath;
16use miden_objects::note::{NoteExecutionMode, NoteTag};
17use miden_objects::utils::{Deserializable, DeserializationError, Serializable};
18use thiserror::Error;
19
20use super::try_convert;
21use crate::errors::{ConversionError, MissingFieldHelper};
22use crate::generated::{self as proto};
23
24// ACCOUNT ID
25// ================================================================================================
26
27impl Display for proto::account::AccountId {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        write!(f, "0x")?;
30        for byte in &self.id {
31            write!(f, "{byte:02x}")?;
32        }
33        Ok(())
34    }
35}
36
37impl Debug for proto::account::AccountId {
38    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39        Display::fmt(self, f)
40    }
41}
42
43// FROM PROTO ACCOUNT ID
44// ------------------------------------------------------------------------------------------------
45
46impl TryFrom<proto::account::AccountId> for AccountId {
47    type Error = ConversionError;
48
49    fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
50        AccountId::read_from_bytes(&account_id.id).map_err(|_| ConversionError::NotAValidFelt)
51    }
52}
53
54// INTO PROTO ACCOUNT ID
55// ------------------------------------------------------------------------------------------------
56
57impl From<&AccountId> for proto::account::AccountId {
58    fn from(account_id: &AccountId) -> Self {
59        (*account_id).into()
60    }
61}
62
63impl From<AccountId> for proto::account::AccountId {
64    fn from(account_id: AccountId) -> Self {
65        Self { id: account_id.to_bytes() }
66    }
67}
68
69// ACCOUNT UPDATE
70// ================================================================================================
71
72#[derive(Debug, PartialEq)]
73pub struct AccountSummary {
74    pub account_id: AccountId,
75    pub account_commitment: Word,
76    pub block_num: BlockNumber,
77}
78
79impl From<&AccountSummary> for proto::account::AccountSummary {
80    fn from(update: &AccountSummary) -> Self {
81        Self {
82            account_id: Some(update.account_id.into()),
83            account_commitment: Some(update.account_commitment.into()),
84            block_num: update.block_num.as_u32(),
85        }
86    }
87}
88
89#[derive(Debug, PartialEq)]
90pub struct AccountInfo {
91    pub summary: AccountSummary,
92    pub details: Option<Account>,
93}
94
95impl From<&AccountInfo> for proto::account::AccountDetails {
96    fn from(AccountInfo { summary, details }: &AccountInfo) -> Self {
97        Self {
98            summary: Some(summary.into()),
99            details: details.as_ref().map(miden_objects::utils::Serializable::to_bytes),
100        }
101    }
102}
103
104// ACCOUNT PROOF REQUEST
105// ================================================================================================
106
107/// Represents a request for an account proof.
108pub struct AccountProofRequest {
109    pub account_id: AccountId,
110    // If not present, the latest account proof references the latest available
111    pub block_num: Option<BlockNumber>,
112    pub details: Option<AccountDetailRequest>,
113}
114
115impl TryFrom<proto::rpc_store::AccountProofRequest> for AccountProofRequest {
116    type Error = ConversionError;
117
118    fn try_from(value: proto::rpc_store::AccountProofRequest) -> Result<Self, Self::Error> {
119        let proto::rpc_store::AccountProofRequest { account_id, block_num, details } = value;
120
121        let account_id = account_id
122            .ok_or(proto::rpc_store::AccountProofRequest::missing_field(stringify!(account_id)))?
123            .try_into()?;
124        let block_num = block_num.map(Into::into);
125
126        let details = details.map(TryFrom::try_from).transpose()?;
127
128        Ok(AccountProofRequest { account_id, block_num, details })
129    }
130}
131
132/// Represents a request for account details alongside specific storage data.
133pub struct AccountDetailRequest {
134    pub code_commitment: Option<Word>,
135    pub asset_vault_commitment: Option<Word>,
136    pub storage_requests: Vec<StorageMapRequest>,
137}
138
139impl TryFrom<proto::rpc_store::account_proof_request::AccountDetailRequest>
140    for AccountDetailRequest
141{
142    type Error = ConversionError;
143
144    fn try_from(
145        value: proto::rpc_store::account_proof_request::AccountDetailRequest,
146    ) -> Result<Self, Self::Error> {
147        let proto::rpc_store::account_proof_request::AccountDetailRequest {
148            code_commitment,
149            asset_vault_commitment,
150            storage_maps,
151        } = value;
152
153        let code_commitment = code_commitment.map(TryFrom::try_from).transpose()?;
154        let asset_vault_commitment = asset_vault_commitment.map(TryFrom::try_from).transpose()?;
155        let storage_requests = try_convert(storage_maps).collect::<Result<_, _>>()?;
156
157        Ok(AccountDetailRequest {
158            code_commitment,
159            asset_vault_commitment,
160            storage_requests,
161        })
162    }
163}
164
165impl TryFrom<proto::account::AccountStorageHeader> for AccountStorageHeader {
166    type Error = ConversionError;
167
168    fn try_from(value: proto::account::AccountStorageHeader) -> Result<Self, Self::Error> {
169        let proto::account::AccountStorageHeader { slots } = value;
170
171        let items = slots
172            .into_iter()
173            .map(|slot| {
174                let slot_type = storage_slot_type_from_raw(slot.slot_type)?;
175                let commitment =
176                    slot.commitment.ok_or(ConversionError::NotAValidFelt)?.try_into()?;
177                Ok((slot_type, commitment))
178            })
179            .collect::<Result<Vec<_>, ConversionError>>()?;
180
181        Ok(AccountStorageHeader::new(items))
182    }
183}
184
185impl TryFrom<proto::rpc_store::account_storage_details::AccountStorageMapDetails>
186    for AccountStorageMapDetails
187{
188    type Error = ConversionError;
189
190    fn try_from(
191        value: proto::rpc_store::account_storage_details::AccountStorageMapDetails,
192    ) -> Result<Self, Self::Error> {
193        let proto::rpc_store::account_storage_details::AccountStorageMapDetails {
194            slot_index,
195            too_many_entries,
196            entries,
197        } = value;
198
199        let slot_index = slot_index.try_into().map_err(ConversionError::TryFromIntError)?;
200
201        // Extract map_entries from the MapEntries message
202        let map_entries = if let Some(entries) = entries {
203            entries
204                .entries
205                .into_iter()
206                .map(|entry| {
207                    let key = entry
208                        .key
209                        .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(
210                            stringify!(key),
211                        ))?
212                        .try_into()?;
213                    let value = entry
214                        .value
215                        .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(
216                            stringify!(value),
217                        ))?
218                        .try_into()?;
219                    Ok((key, value))
220                })
221                .collect::<Result<Vec<_>, ConversionError>>()?
222        } else {
223            Vec::new()
224        };
225
226        Ok(Self {
227            slot_index,
228            too_many_entries,
229            map_entries,
230        })
231    }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct StorageMapRequest {
236    pub slot_index: u8,
237    pub slot_data: SlotData,
238}
239
240impl
241    TryFrom<
242        proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest,
243    > for StorageMapRequest
244{
245    type Error = ConversionError;
246
247    fn try_from(
248        value: proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest,
249    ) -> Result<Self, Self::Error> {
250        let proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest {
251            slot_index,
252            slot_data,
253        } = value;
254
255        let slot_index = slot_index.try_into()?;
256        let slot_data = slot_data.ok_or(proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest::missing_field(stringify!(slot_data)))?.try_into()?;
257
258        Ok(StorageMapRequest { slot_index, slot_data })
259    }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub enum SlotData {
264    All,
265    MapKeys(Vec<Word>),
266}
267
268impl TryFrom<proto::rpc_store::account_proof_request::account_detail_request::storage_map_detail_request::SlotData>
269    for SlotData
270{
271    type Error = ConversionError;
272
273    fn try_from(value: proto::rpc_store::account_proof_request::account_detail_request::storage_map_detail_request::SlotData) -> Result<Self, Self::Error> {
274        use proto::rpc_store::account_proof_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData;
275
276        Ok(match value {
277            ProtoSlotData::AllEntries(true) => SlotData::All,
278            ProtoSlotData::AllEntries(false) => return Err(ConversionError::EnumDiscriminantOutOfRange),
279            ProtoSlotData::MapKeys(keys) => {
280                let keys = try_convert(keys.map_keys).collect::<Result<Vec<_>, _>>()?;
281                SlotData::MapKeys(keys)
282            },
283        })
284    }
285}
286
287// ACCOUNT HEADER CONVERSIONS
288//================================================================================================
289
290impl TryFrom<proto::account::AccountHeader> for AccountHeader {
291    type Error = ConversionError;
292
293    fn try_from(value: proto::account::AccountHeader) -> Result<Self, Self::Error> {
294        let proto::account::AccountHeader {
295            account_id,
296            vault_root,
297            storage_commitment,
298            code_commitment,
299            nonce,
300        } = value;
301
302        let account_id = account_id
303            .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
304            .try_into()?;
305        let vault_root = vault_root
306            .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
307            .try_into()?;
308        let storage_commitment = storage_commitment
309            .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
310            .try_into()?;
311        let code_commitment = code_commitment
312            .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
313            .try_into()?;
314        let nonce = nonce.try_into().map_err(|_e| ConversionError::NotAValidFelt)?;
315
316        Ok(AccountHeader::new(
317            account_id,
318            nonce,
319            vault_root,
320            storage_commitment,
321            code_commitment,
322        ))
323    }
324}
325
326impl From<AccountHeader> for proto::account::AccountHeader {
327    fn from(header: AccountHeader) -> Self {
328        proto::account::AccountHeader {
329            account_id: Some(header.id().into()),
330            vault_root: Some(header.vault_root().into()),
331            storage_commitment: Some(header.storage_commitment().into()),
332            code_commitment: Some(header.code_commitment().into()),
333            nonce: header.nonce().as_int(),
334        }
335    }
336}
337
338impl From<AccountStorageHeader> for proto::account::AccountStorageHeader {
339    fn from(value: AccountStorageHeader) -> Self {
340        let slots = value
341            .slots()
342            .map(|(slot_type, slot_value)| proto::account::account_storage_header::StorageSlot {
343                slot_type: storage_slot_type_to_raw(*slot_type),
344                commitment: Some(proto::primitives::Digest::from(*slot_value)),
345            })
346            .collect();
347
348        Self { slots }
349    }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq)]
353pub struct AccountVaultDetails {
354    pub too_many_assets: bool,
355    pub assets: Vec<Asset>,
356}
357impl AccountVaultDetails {
358    const MAX_RETURN_ENTRIES: usize = 1000;
359
360    pub fn new(vault: &AssetVault) -> Self {
361        if vault.assets().nth(Self::MAX_RETURN_ENTRIES).is_some() {
362            Self::too_many()
363        } else {
364            Self {
365                too_many_assets: false,
366                assets: Vec::from_iter(vault.assets()),
367            }
368        }
369    }
370
371    pub fn empty() -> Self {
372        Self {
373            too_many_assets: false,
374            assets: Vec::new(),
375        }
376    }
377
378    fn too_many() -> Self {
379        Self {
380            too_many_assets: true,
381            assets: Vec::new(),
382        }
383    }
384}
385
386impl TryFrom<proto::rpc_store::AccountVaultDetails> for AccountVaultDetails {
387    type Error = ConversionError;
388
389    fn try_from(value: proto::rpc_store::AccountVaultDetails) -> Result<Self, Self::Error> {
390        let proto::rpc_store::AccountVaultDetails { too_many_assets, assets } = value;
391
392        let assets =
393            Result::<Vec<_>, ConversionError>::from_iter(assets.into_iter().map(|asset| {
394                let asset = asset
395                    .asset
396                    .ok_or(proto::primitives::Asset::missing_field(stringify!(asset)))?;
397                let asset = Word::try_from(asset)?;
398                Asset::try_from(asset).map_err(ConversionError::AssetError)
399            }))?;
400        Ok(Self { too_many_assets, assets })
401    }
402}
403
404impl From<AccountVaultDetails> for proto::rpc_store::AccountVaultDetails {
405    fn from(value: AccountVaultDetails) -> Self {
406        let AccountVaultDetails { too_many_assets, assets } = value;
407
408        Self {
409            too_many_assets,
410            assets: Vec::from_iter(assets.into_iter().map(|asset| proto::primitives::Asset {
411                asset: Some(proto::primitives::Digest::from(Word::from(asset))),
412            })),
413        }
414    }
415}
416
417#[derive(Debug, Clone, PartialEq, Eq)]
418pub struct AccountStorageMapDetails {
419    pub slot_index: u8,
420    pub too_many_entries: bool,
421    pub map_entries: Vec<(Word, Word)>,
422}
423
424impl AccountStorageMapDetails {
425    const MAX_RETURN_ENTRIES: usize = 1000;
426
427    pub fn new(slot_index: u8, slot_data: SlotData, storage_map: &StorageMap) -> Self {
428        match slot_data {
429            SlotData::All => Self::from_all_entries(slot_index, storage_map),
430            SlotData::MapKeys(keys) => Self::from_specific_keys(slot_index, &keys[..], storage_map),
431        }
432    }
433
434    fn from_all_entries(slot_index: u8, storage_map: &StorageMap) -> Self {
435        if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
436            Self::too_many_entries(slot_index)
437        } else {
438            let map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v)));
439            Self {
440                slot_index,
441                too_many_entries: false,
442                map_entries,
443            }
444        }
445    }
446
447    fn from_specific_keys(slot_index: u8, keys: &[Word], storage_map: &StorageMap) -> Self {
448        if keys.len() > Self::MAX_RETURN_ENTRIES {
449            Self::too_many_entries(slot_index)
450        } else {
451            // TODO For now, we return all entries instead of specific keys with proofs
452            Self::from_all_entries(slot_index, storage_map)
453        }
454    }
455
456    pub fn too_many_entries(slot_index: u8) -> Self {
457        Self {
458            slot_index,
459            too_many_entries: true,
460            map_entries: Vec::new(),
461        }
462    }
463}
464
465#[derive(Debug, Clone, PartialEq, Eq)]
466pub struct AccountStorageDetails {
467    pub header: AccountStorageHeader,
468    pub map_details: Vec<AccountStorageMapDetails>,
469}
470
471impl TryFrom<proto::rpc_store::AccountStorageDetails> for AccountStorageDetails {
472    type Error = ConversionError;
473
474    fn try_from(value: proto::rpc_store::AccountStorageDetails) -> Result<Self, Self::Error> {
475        let proto::rpc_store::AccountStorageDetails { header, map_details } = value;
476
477        let header = header
478            .ok_or(proto::rpc_store::AccountStorageDetails::missing_field(stringify!(header)))?
479            .try_into()?;
480
481        let map_details = try_convert(map_details).collect::<Result<Vec<_>, _>>()?;
482
483        Ok(Self { header, map_details })
484    }
485}
486
487impl From<AccountStorageDetails> for proto::rpc_store::AccountStorageDetails {
488    fn from(value: AccountStorageDetails) -> Self {
489        let AccountStorageDetails { header, map_details } = value;
490
491        Self {
492            header: Some(header.into()),
493            map_details: map_details.into_iter().map(Into::into).collect(),
494        }
495    }
496}
497
498const fn storage_slot_type_from_raw(slot_type: u32) -> Result<StorageSlotType, ConversionError> {
499    Ok(match slot_type {
500        0 => StorageSlotType::Map,
501        1 => StorageSlotType::Value,
502        _ => return Err(ConversionError::EnumDiscriminantOutOfRange),
503    })
504}
505
506const fn storage_slot_type_to_raw(slot_type: StorageSlotType) -> u32 {
507    match slot_type {
508        StorageSlotType::Map => 0,
509        StorageSlotType::Value => 1,
510    }
511}
512
513/// Represents account details returned in response to an account proof request.
514pub struct AccountDetails {
515    pub account_header: AccountHeader,
516    pub account_code: Option<Vec<u8>>,
517    pub vault_details: AccountVaultDetails,
518    pub storage_details: AccountStorageDetails,
519}
520
521/// Represents the response to an account proof request.
522pub struct AccountProofResponse {
523    pub block_num: BlockNumber,
524    pub witness: AccountWitness,
525    pub details: Option<AccountDetails>,
526}
527
528impl TryFrom<proto::rpc_store::AccountProofResponse> for AccountProofResponse {
529    type Error = ConversionError;
530
531    fn try_from(value: proto::rpc_store::AccountProofResponse) -> Result<Self, Self::Error> {
532        let proto::rpc_store::AccountProofResponse { block_num, witness, details } = value;
533
534        let block_num = block_num
535            .ok_or(proto::rpc_store::AccountProofResponse::missing_field(stringify!(block_num)))?
536            .into();
537
538        let witness = witness
539            .ok_or(proto::rpc_store::AccountProofResponse::missing_field(stringify!(witness)))?
540            .try_into()?;
541
542        let details = details.map(TryFrom::try_from).transpose()?;
543
544        Ok(AccountProofResponse { block_num, witness, details })
545    }
546}
547
548impl From<AccountProofResponse> for proto::rpc_store::AccountProofResponse {
549    fn from(value: AccountProofResponse) -> Self {
550        let AccountProofResponse { block_num, witness, details } = value;
551
552        Self {
553            witness: Some(witness.into()),
554            details: details.map(Into::into),
555            block_num: Some(block_num.into()),
556        }
557    }
558}
559
560impl TryFrom<proto::rpc_store::account_proof_response::AccountDetails> for AccountDetails {
561    type Error = ConversionError;
562
563    fn try_from(
564        value: proto::rpc_store::account_proof_response::AccountDetails,
565    ) -> Result<Self, Self::Error> {
566        let proto::rpc_store::account_proof_response::AccountDetails {
567            header,
568            code,
569            vault_details,
570            storage_details,
571        } = value;
572
573        let account_header = header
574            .ok_or(proto::rpc_store::account_proof_response::AccountDetails::missing_field(
575                stringify!(header),
576            ))?
577            .try_into()?;
578
579        let storage_details = storage_details
580            .ok_or(proto::rpc_store::account_proof_response::AccountDetails::missing_field(
581                stringify!(storage_details),
582            ))?
583            .try_into()?;
584
585        let vault_details = vault_details
586            .ok_or(proto::rpc_store::account_proof_response::AccountDetails::missing_field(
587                stringify!(vault_details),
588            ))?
589            .try_into()?;
590        let account_code = code;
591
592        Ok(AccountDetails {
593            account_header,
594            account_code,
595            vault_details,
596            storage_details,
597        })
598    }
599}
600
601impl From<AccountDetails> for proto::rpc_store::account_proof_response::AccountDetails {
602    fn from(value: AccountDetails) -> Self {
603        let AccountDetails {
604            account_header,
605            storage_details,
606            account_code,
607            vault_details,
608        } = value;
609
610        let header = Some(proto::account::AccountHeader::from(account_header));
611        let storage_details = Some(storage_details.into());
612        let code = account_code;
613        let vault_details = Some(vault_details.into());
614
615        Self {
616            header,
617            storage_details,
618            code,
619            vault_details,
620        }
621    }
622}
623
624impl From<AccountStorageMapDetails>
625    for proto::rpc_store::account_storage_details::AccountStorageMapDetails
626{
627    fn from(value: AccountStorageMapDetails) -> Self {
628        use proto::rpc_store::account_storage_details::account_storage_map_details;
629
630        let AccountStorageMapDetails {
631            slot_index,
632            too_many_entries,
633            map_entries,
634        } = value;
635
636        let entries = Some(account_storage_map_details::MapEntries {
637            entries: Vec::from_iter(map_entries.into_iter().map(|(key, value)| {
638                account_storage_map_details::map_entries::StorageMapEntry {
639                    key: Some(key.into()),
640                    value: Some(value.into()),
641                }
642            })),
643        });
644
645        Self {
646            slot_index: u32::from(slot_index),
647            too_many_entries,
648            entries,
649        }
650    }
651}
652
653// ACCOUNT WITNESS
654// ================================================================================================
655
656impl TryFrom<proto::account::AccountWitness> for AccountWitness {
657    type Error = ConversionError;
658
659    fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
660        let witness_id = account_witness
661            .witness_id
662            .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
663            .try_into()?;
664        let commitment = account_witness
665            .commitment
666            .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))?
667            .try_into()?;
668        let path = account_witness
669            .path
670            .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))?
671            .try_into()?;
672
673        AccountWitness::new(witness_id, commitment, path).map_err(|err| {
674            ConversionError::deserialization_error(
675                "AccountWitness",
676                DeserializationError::InvalidValue(err.to_string()),
677            )
678        })
679    }
680}
681
682impl From<AccountWitness> for proto::account::AccountWitness {
683    fn from(witness: AccountWitness) -> Self {
684        Self {
685            account_id: Some(witness.id().into()),
686            witness_id: Some(witness.id().into()),
687            commitment: Some(witness.state_commitment().into()),
688            path: Some(witness.into_proof().into_parts().0.into()),
689        }
690    }
691}
692
693// ACCOUNT WITNESS RECORD
694// ================================================================================================
695
696#[derive(Clone, Debug, PartialEq, Eq)]
697pub struct AccountWitnessRecord {
698    pub account_id: AccountId,
699    pub witness: AccountWitness,
700}
701
702impl TryFrom<proto::account::AccountWitness> for AccountWitnessRecord {
703    type Error = ConversionError;
704
705    fn try_from(
706        account_witness_record: proto::account::AccountWitness,
707    ) -> Result<Self, Self::Error> {
708        let witness_id = account_witness_record
709            .witness_id
710            .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
711            .try_into()?;
712        let commitment = account_witness_record
713            .commitment
714            .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))?
715            .try_into()?;
716        let path: SparseMerklePath = account_witness_record
717            .path
718            .as_ref()
719            .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))?
720            .clone()
721            .try_into()?;
722
723        let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| {
724            ConversionError::deserialization_error(
725                "AccountWitness",
726                DeserializationError::InvalidValue(err.to_string()),
727            )
728        })?;
729
730        Ok(Self {
731            account_id: account_witness_record
732                .account_id
733                .ok_or(proto::account::AccountWitness::missing_field(stringify!(account_id)))?
734                .try_into()?,
735            witness,
736        })
737    }
738}
739
740impl From<AccountWitnessRecord> for proto::account::AccountWitness {
741    fn from(from: AccountWitnessRecord) -> Self {
742        Self {
743            account_id: Some(from.account_id.into()),
744            witness_id: Some(from.witness.id().into()),
745            commitment: Some(from.witness.state_commitment().into()),
746            path: Some(from.witness.path().clone().into()),
747        }
748    }
749}
750
751// ACCOUNT STATE
752// ================================================================================================
753
754/// Information needed from the store to verify account in transaction.
755#[derive(Debug)]
756pub struct AccountState {
757    /// Account ID
758    pub account_id: AccountId,
759    /// The account commitment in the store corresponding to tx's account ID
760    pub account_commitment: Option<Word>,
761}
762
763impl Display for AccountState {
764    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
765        f.write_fmt(format_args!(
766            "{{ account_id: {}, account_commitment: {} }}",
767            self.account_id,
768            format_opt(self.account_commitment.as_ref()),
769        ))
770    }
771}
772
773impl TryFrom<proto::block_producer_store::transaction_inputs::AccountTransactionInputRecord>
774    for AccountState
775{
776    type Error = ConversionError;
777
778    fn try_from(
779        from: proto::block_producer_store::transaction_inputs::AccountTransactionInputRecord,
780    ) -> Result<Self, Self::Error> {
781        let account_id = from
782            .account_id
783            .ok_or(proto::block_producer_store::transaction_inputs::AccountTransactionInputRecord::missing_field(
784                stringify!(account_id),
785            ))?
786            .try_into()?;
787
788        let account_commitment = from
789            .account_commitment
790            .ok_or(proto::block_producer_store::transaction_inputs::AccountTransactionInputRecord::missing_field(
791                stringify!(account_commitment),
792            ))?
793            .try_into()?;
794
795        // If the commitment is equal to `Word::empty()`, it signifies that this is a new
796        // account which is not yet present in the Store.
797        let account_commitment = if account_commitment == Word::empty() {
798            None
799        } else {
800            Some(account_commitment)
801        };
802
803        Ok(Self { account_id, account_commitment })
804    }
805}
806
807impl From<AccountState>
808    for proto::block_producer_store::transaction_inputs::AccountTransactionInputRecord
809{
810    fn from(from: AccountState) -> Self {
811        Self {
812            account_id: Some(from.account_id.into()),
813            account_commitment: from.account_commitment.map(Into::into),
814        }
815    }
816}
817
818// ASSET
819// ================================================================================================
820
821impl TryFrom<proto::primitives::Asset> for Asset {
822    type Error = ConversionError;
823
824    fn try_from(value: proto::primitives::Asset) -> Result<Self, Self::Error> {
825        let inner = value.asset.ok_or(proto::primitives::Asset::missing_field("asset"))?;
826        let word = Word::try_from(inner)?;
827
828        Asset::try_from(word).map_err(ConversionError::AssetError)
829    }
830}
831
832impl From<Asset> for proto::primitives::Asset {
833    fn from(asset_from: Asset) -> Self {
834        proto::primitives::Asset {
835            asset: Some(Word::from(asset_from).into()),
836        }
837    }
838}
839
840// NETWORK ACCOUNT PREFIX
841// ================================================================================================
842
843pub type AccountPrefix = u32;
844
845/// Newtype wrapper for network account prefix.
846/// Provides type safety for accounts that are meant for network execution.
847#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
848pub struct NetworkAccountPrefix(u32);
849
850impl std::fmt::Display for NetworkAccountPrefix {
851    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
852        std::fmt::Display::fmt(&self.0, f)
853    }
854}
855
856impl NetworkAccountPrefix {
857    pub fn inner(&self) -> u32 {
858        self.0
859    }
860}
861
862impl TryFrom<u32> for NetworkAccountPrefix {
863    type Error = NetworkAccountError;
864
865    fn try_from(value: u32) -> Result<Self, Self::Error> {
866        if value >> 30 != 0 {
867            return Err(NetworkAccountError::InvalidPrefix(value));
868        }
869        Ok(NetworkAccountPrefix(value))
870    }
871}
872
873impl TryFrom<AccountId> for NetworkAccountPrefix {
874    type Error = NetworkAccountError;
875
876    fn try_from(id: AccountId) -> Result<Self, Self::Error> {
877        if !id.is_network() {
878            return Err(NetworkAccountError::NotNetworkAccount(id));
879        }
880        let prefix = get_account_id_tag_prefix(id);
881        Ok(NetworkAccountPrefix(prefix))
882    }
883}
884
885impl TryFrom<NoteTag> for NetworkAccountPrefix {
886    type Error = NetworkAccountError;
887
888    fn try_from(tag: NoteTag) -> Result<Self, Self::Error> {
889        if tag.execution_mode() != NoteExecutionMode::Network || !tag.is_single_target() {
890            return Err(NetworkAccountError::InvalidExecutionMode(tag));
891        }
892
893        let tag_inner: u32 = tag.into();
894        assert!(tag_inner >> 30 == 0, "first 2 bits have to be 0");
895        Ok(NetworkAccountPrefix(tag_inner))
896    }
897}
898
899impl From<NetworkAccountPrefix> for u32 {
900    fn from(value: NetworkAccountPrefix) -> Self {
901        value.inner()
902    }
903}
904
905#[derive(Debug, Error)]
906pub enum NetworkAccountError {
907    #[error("account ID {0} is not a valid network account ID")]
908    NotNetworkAccount(AccountId),
909    #[error("note tag {0} is not valid for network account execution")]
910    InvalidExecutionMode(NoteTag),
911    #[error("note prefix should be 30-bit long ({0} has non-zero in the 2 most significant bits)")]
912    InvalidPrefix(u32),
913}
914
915/// Gets the 30-bit prefix of the account ID.
916fn get_account_id_tag_prefix(id: AccountId) -> AccountPrefix {
917    (id.prefix().as_u64() >> 34) as AccountPrefix
918}