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