1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5
6use miden_protocol::Word;
7use miden_protocol::account::{
8 Account,
9 AccountCode,
10 AccountHeader,
11 AccountId,
12 AccountStorageHeader,
13 StorageMap,
14 StorageMapKey,
15 StorageMapWitness,
16 StorageSlotHeader,
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::SparseMerklePath;
24use miden_protocol::crypto::merkle::smt::SmtProof;
25use miden_tx::utils::{Deserializable, Serializable, ToHex};
26use thiserror::Error;
27
28use crate::alloc::borrow::ToOwned;
29use crate::alloc::string::ToString;
30use crate::rpc::RpcError;
31use crate::rpc::domain::MissingFieldHelper;
32use crate::rpc::errors::RpcConversionError;
33use crate::rpc::generated::rpc::account_request::account_detail_request;
34use crate::rpc::generated::{self as proto};
35
36pub enum FetchedAccount {
41 Private(AccountId, AccountUpdateSummary),
44 Public(Box<Account>, AccountUpdateSummary),
47}
48
49impl FetchedAccount {
50 pub fn new_private(account_id: AccountId, summary: AccountUpdateSummary) -> Self {
53 Self::Private(account_id, summary)
54 }
55
56 pub fn new_public(account: Account, summary: AccountUpdateSummary) -> Self {
58 Self::Public(Box::new(account), summary)
59 }
60
61 pub fn account_id(&self) -> AccountId {
63 match self {
64 Self::Private(account_id, _) => *account_id,
65 Self::Public(account, _) => account.id(),
66 }
67 }
68
69 pub fn commitment(&self) -> Word {
71 match self {
72 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
73 }
74 }
75
76 pub fn account(&self) -> Option<&Account> {
78 match self {
79 Self::Private(..) => None,
80 Self::Public(account, _) => Some(account.as_ref()),
81 }
82 }
83}
84
85impl From<FetchedAccount> for Option<Account> {
86 fn from(acc: FetchedAccount) -> Self {
87 match acc {
88 FetchedAccount::Private(..) => None,
89 FetchedAccount::Public(account, _) => Some(*account),
90 }
91 }
92}
93
94pub struct AccountUpdateSummary {
99 pub commitment: Word,
101 pub last_block_num: BlockNumber,
103}
104
105impl AccountUpdateSummary {
106 pub fn new(commitment: Word, last_block_num: BlockNumber) -> Self {
108 Self { commitment, last_block_num }
109 }
110}
111
112impl Display for proto::account::AccountId {
116 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
118 }
119}
120
121impl Debug for proto::account::AccountId {
122 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123 Display::fmt(self, f)
124 }
125}
126
127impl From<AccountId> for proto::account::AccountId {
131 fn from(account_id: AccountId) -> Self {
132 Self { id: account_id.to_bytes() }
133 }
134}
135
136impl TryFrom<proto::account::AccountId> for AccountId {
140 type Error = RpcConversionError;
141
142 fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
143 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
144 }
145}
146
147impl TryInto<AccountHeader> for proto::account::AccountHeader {
151 type Error = crate::rpc::RpcError;
152
153 fn try_into(self) -> Result<AccountHeader, Self::Error> {
154 use miden_protocol::Felt;
155
156 use crate::rpc::domain::MissingFieldHelper;
157
158 let proto::account::AccountHeader {
159 account_id,
160 nonce,
161 vault_root,
162 storage_commitment,
163 code_commitment,
164 } = self;
165
166 let account_id: AccountId = account_id
167 .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
168 .try_into()?;
169 let vault_root = vault_root
170 .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
171 .try_into()?;
172 let storage_commitment = storage_commitment
173 .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
174 .try_into()?;
175 let code_commitment = code_commitment
176 .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
177 .try_into()?;
178
179 Ok(AccountHeader::new(
180 account_id,
181 Felt::new(nonce),
182 vault_root,
183 storage_commitment,
184 code_commitment,
185 ))
186 }
187}
188
189impl TryInto<AccountStorageHeader> for proto::account::AccountStorageHeader {
193 type Error = crate::rpc::RpcError;
194
195 fn try_into(self) -> Result<AccountStorageHeader, Self::Error> {
196 use crate::rpc::RpcError;
197 use crate::rpc::domain::MissingFieldHelper;
198
199 let mut header_slots: Vec<StorageSlotHeader> = Vec::with_capacity(self.slots.len());
200
201 for slot in self.slots {
202 let slot_value: Word = slot
203 .commitment
204 .ok_or(proto::account::account_storage_header::StorageSlot::missing_field(
205 stringify!(commitment),
206 ))?
207 .try_into()?;
208
209 let slot_type = u8::try_from(slot.slot_type)
210 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
211 .and_then(|v| {
212 StorageSlotType::try_from(v)
213 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
214 })?;
215 let slot_name = StorageSlotName::new(slot.slot_name)
216 .map_err(|err| RpcError::InvalidResponse(err.to_string()))?;
217
218 header_slots.push(StorageSlotHeader::new(slot_name, slot_type, slot_value));
219 }
220
221 header_slots.sort_by_key(StorageSlotHeader::id);
222 AccountStorageHeader::new(header_slots)
223 .map_err(|err| RpcError::InvalidResponse(err.to_string()))
224 }
225}
226
227#[cfg(feature = "tonic")]
231impl proto::rpc::account_response::AccountDetails {
232 pub fn into_domain(
242 self,
243 known_account_codes: &BTreeMap<Word, AccountCode>,
244 ) -> Result<AccountDetails, crate::rpc::RpcError> {
245 use crate::rpc::RpcError;
246 use crate::rpc::domain::MissingFieldHelper;
247
248 let proto::rpc::account_response::AccountDetails {
249 header,
250 storage_details,
251 code,
252 vault_details,
253 } = self;
254 let header: AccountHeader = header
255 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))?
256 .try_into()?;
257
258 let storage_details = storage_details
259 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
260 storage_details
261 )))?
262 .try_into()?;
263
264 let code = {
268 let received_code = code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
269 match received_code {
270 Some(code) => code,
271 None => known_account_codes
272 .get(&header.code_commitment())
273 .ok_or(RpcError::InvalidResponse(
274 "Account code was not provided, but the response did not contain it either"
275 .into(),
276 ))?
277 .clone(),
278 }
279 };
280
281 let vault_details = vault_details
282 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(vault_details)))?
283 .try_into()?;
284
285 Ok(AccountDetails {
286 header,
287 storage_details,
288 code,
289 vault_details,
290 })
291 }
292}
293
294pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
299
300#[derive(Clone, Debug)]
305pub struct AccountDetails {
306 pub header: AccountHeader,
307 pub storage_details: AccountStorageDetails,
308 pub code: AccountCode,
309 pub vault_details: AccountVaultDetails,
310}
311
312#[derive(Clone, Debug)]
317pub struct AccountStorageDetails {
318 pub header: AccountStorageHeader,
320 pub map_details: Vec<AccountStorageMapDetails>,
322}
323
324impl AccountStorageDetails {
325 pub fn find_map_details(&self, target: &StorageSlotName) -> Option<&AccountStorageMapDetails> {
329 self.map_details.iter().find(|map_detail| map_detail.slot_name == *target)
330 }
331}
332
333impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
334 type Error = RpcError;
335
336 fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
337 let header = value
338 .header
339 .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
340 .try_into()?;
341 let map_details = value
342 .map_details
343 .iter()
344 .map(|entry| entry.to_owned().try_into())
345 .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
346
347 Ok(Self { header, map_details })
348 }
349}
350
351#[derive(Clone, Debug)]
355pub struct AccountStorageMapDetails {
356 pub slot_name: StorageSlotName,
358 pub too_many_entries: bool,
362 pub entries: StorageMapEntries,
365}
366
367impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
368 for AccountStorageMapDetails
369{
370 type Error = RpcError;
371
372 fn try_from(
373 value: proto::rpc::account_storage_details::AccountStorageMapDetails,
374 ) -> Result<Self, Self::Error> {
375 use proto::rpc::account_storage_details::account_storage_map_details::Entries;
376
377 let slot_name = StorageSlotName::new(value.slot_name)
378 .map_err(|err| RpcError::ExpectedDataMissing(err.to_string()))?;
379 let too_many_entries = value.too_many_entries;
380
381 let entries = match value.entries {
382 Some(Entries::AllEntries(all_entries)) => {
383 let entries = all_entries
384 .entries
385 .into_iter()
386 .map(core::convert::TryInto::try_into)
387 .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
388 StorageMapEntries::AllEntries(entries)
389 },
390 Some(Entries::EntriesWithProofs(entries_with_proofs)) => {
391 let witnesses = entries_with_proofs
392 .entries
393 .into_iter()
394 .map(|entry| {
395 let key: Word = entry
396 .key
397 .ok_or(RpcError::ExpectedDataMissing("key".into()))?
398 .try_into()?;
399 let proof: SmtProof = entry
400 .proof
401 .ok_or(RpcError::ExpectedDataMissing("proof".into()))?
402 .try_into()?;
403 StorageMapWitness::new(proof, [StorageMapKey::new(key)])
404 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
405 })
406 .collect::<Result<Vec<StorageMapWitness>, RpcError>>()?;
407 StorageMapEntries::EntriesWithProofs(witnesses)
408 },
409 None => StorageMapEntries::AllEntries(Vec::new()),
410 };
411
412 Ok(Self { slot_name, too_many_entries, entries })
413 }
414}
415
416#[derive(Clone, Debug)]
421pub struct StorageMapEntry {
422 pub key: StorageMapKey,
423 pub value: Word,
424}
425
426impl TryFrom<proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry>
427 for StorageMapEntry
428{
429 type Error = RpcError;
430
431 fn try_from(value: proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
432 let key: StorageMapKey =
433 value.key.ok_or(RpcError::ExpectedDataMissing("key".into()))?.try_into()?;
434 let value = value.value.ok_or(RpcError::ExpectedDataMissing("value".into()))?.try_into()?;
435 Ok(Self { key, value })
436 }
437}
438
439#[derive(Clone, Debug)]
445pub enum StorageMapEntries {
446 AllEntries(Vec<StorageMapEntry>),
448 EntriesWithProofs(Vec<StorageMapWitness>),
450}
451
452impl StorageMapEntries {
453 pub fn into_storage_map(self) -> Result<StorageMap, miden_protocol::errors::StorageMapError> {
455 match self {
456 StorageMapEntries::AllEntries(entries) => {
457 StorageMap::with_entries(entries.into_iter().map(|e| (e.key, e.value)))
458 },
459 StorageMapEntries::EntriesWithProofs(witnesses) => {
460 let entries: Vec<_> =
461 witnesses.iter().flat_map(|w| w.entries().map(|(k, v)| (*k, *v))).collect();
462 StorageMap::with_entries(entries)
463 },
464 }
465 }
466}
467
468#[derive(Clone, Debug)]
472pub struct AccountVaultDetails {
473 pub too_many_assets: bool,
477 pub assets: Vec<Asset>,
480}
481
482impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
483 type Error = RpcError;
484
485 fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
486 let too_many_assets = value.too_many_assets;
487 let assets = value
488 .assets
489 .into_iter()
490 .map(|asset| {
491 let native_digest: Word = asset
492 .asset
493 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(assets)))?
494 .try_into()?;
495 native_digest
496 .try_into()
497 .map_err(|_| RpcError::DeserializationError("asset".to_string()))
498 })
499 .collect::<Result<Vec<Asset>, RpcError>>()?;
500
501 Ok(Self { too_many_assets, assets })
502 }
503}
504
505#[derive(Clone, Debug)]
510pub struct AccountProof {
511 account_witness: AccountWitness,
513 state_headers: Option<AccountDetails>,
515}
516
517impl AccountProof {
518 pub fn new(
520 account_witness: AccountWitness,
521 account_details: Option<AccountDetails>,
522 ) -> Result<Self, AccountProofError> {
523 if let Some(AccountDetails {
524 header: account_header,
525 storage_details: _,
526 code,
527 ..
528 }) = &account_details
529 {
530 if account_header.to_commitment() != account_witness.state_commitment() {
531 return Err(AccountProofError::InconsistentAccountCommitment);
532 }
533 if account_header.id() != account_witness.id() {
534 return Err(AccountProofError::InconsistentAccountId);
535 }
536 if code.commitment() != account_header.code_commitment() {
537 return Err(AccountProofError::InconsistentCodeCommitment);
538 }
539 }
540
541 Ok(Self {
542 account_witness,
543 state_headers: account_details,
544 })
545 }
546
547 pub fn account_id(&self) -> AccountId {
549 self.account_witness.id()
550 }
551
552 pub fn account_header(&self) -> Option<&AccountHeader> {
554 self.state_headers.as_ref().map(|account_details| &account_details.header)
555 }
556
557 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
559 self.state_headers
560 .as_ref()
561 .map(|account_details| &account_details.storage_details.header)
562 }
563
564 pub fn account_code(&self) -> Option<&AccountCode> {
566 self.state_headers.as_ref().map(|headers| &headers.code)
567 }
568
569 pub fn code_commitment(&self) -> Option<Word> {
571 self.account_code().map(AccountCode::commitment)
572 }
573
574 pub fn account_commitment(&self) -> Word {
576 self.account_witness.state_commitment()
577 }
578
579 pub fn account_witness(&self) -> &AccountWitness {
580 &self.account_witness
581 }
582
583 pub fn merkle_proof(&self) -> &SparseMerklePath {
585 self.account_witness.path()
586 }
587
588 pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
590 (self.account_witness, self.state_headers)
591 }
592}
593
594#[cfg(feature = "tonic")]
595impl TryFrom<proto::rpc::AccountResponse> for AccountProof {
596 type Error = RpcError;
597 fn try_from(account_proof: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
598 let Some(witness) = account_proof.witness else {
599 return Err(RpcError::ExpectedDataMissing(
600 "GetAccountProof returned an account without witness".to_owned(),
601 ));
602 };
603
604 let details: Option<AccountDetails> = {
605 match account_proof.details {
606 None => None,
607 Some(details) => Some(details.into_domain(&BTreeMap::new())?),
608 }
609 };
610 AccountProof::new(witness.try_into()?, details)
611 .map_err(|err| RpcError::InvalidResponse(format!("{err}")))
612 }
613}
614
615impl TryFrom<proto::account::AccountWitness> for AccountWitness {
619 type Error = RpcError;
620
621 fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
622 let state_commitment = account_witness
623 .commitment
624 .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
625 .try_into()?;
626 let merkle_path = account_witness
627 .path
628 .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
629 .try_into()?;
630 let account_id = account_witness
631 .witness_id
632 .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
633 .try_into()?;
634
635 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
636 Ok(witness)
637 }
638}
639
640#[derive(Clone, Debug, Default, Eq, PartialEq)]
646pub struct AccountStorageRequirements(BTreeMap<StorageSlotName, Vec<StorageMapKey>>);
647
648impl AccountStorageRequirements {
649 pub fn new<'a>(
650 slots_and_keys: impl IntoIterator<
651 Item = (StorageSlotName, impl IntoIterator<Item = &'a StorageMapKey>),
652 >,
653 ) -> Self {
654 let map = slots_and_keys
655 .into_iter()
656 .map(|(slot_name, keys_iter)| {
657 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
658 (slot_name, keys_vec)
659 })
660 .collect();
661
662 AccountStorageRequirements(map)
663 }
664
665 pub fn inner(&self) -> &BTreeMap<StorageSlotName, Vec<StorageMapKey>> {
666 &self.0
667 }
668}
669
670impl From<AccountStorageRequirements> for Vec<account_detail_request::StorageMapDetailRequest> {
671 fn from(
672 value: AccountStorageRequirements,
673 ) -> Vec<account_detail_request::StorageMapDetailRequest> {
674 use account_detail_request::{self, storage_map_detail_request};
675 let request_map = value.0;
676 let mut requests = Vec::with_capacity(request_map.len());
677 for (slot_name, _map_keys) in request_map {
678 let slot_data = Some(storage_map_detail_request::SlotData::AllEntries(true));
679 requests.push(account_detail_request::StorageMapDetailRequest {
680 slot_name: slot_name.to_string(),
681 slot_data,
682 });
683 }
684 requests
685 }
686}
687
688impl Serializable for AccountStorageRequirements {
689 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
690 target.write(&self.0);
691 }
692}
693
694impl Deserializable for AccountStorageRequirements {
695 fn read_from<R: miden_tx::utils::ByteReader>(
696 source: &mut R,
697 ) -> Result<Self, miden_tx::utils::DeserializationError> {
698 Ok(AccountStorageRequirements(source.read()?))
699 }
700}
701
702#[derive(Debug, Error)]
706pub enum AccountProofError {
707 #[error(
708 "the received account commitment doesn't match the received account header's commitment"
709 )]
710 InconsistentAccountCommitment,
711 #[error("the received account id doesn't match the received account header's id")]
712 InconsistentAccountId,
713 #[error(
714 "the received code commitment doesn't match the received account header's code commitment"
715 )]
716 InconsistentCodeCommitment,
717}