1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5
6use miden_objects::Word;
7use miden_objects::account::{
8 Account,
9 AccountCode,
10 AccountHeader,
11 AccountId,
12 AccountStorageHeader,
13 StorageSlotType,
14};
15use miden_objects::asset::Asset;
16use miden_objects::block::{AccountWitness, BlockNumber};
17use miden_objects::crypto::merkle::SparseMerklePath;
18use miden_tx::utils::{Deserializable, Serializable, ToHex};
19use thiserror::Error;
20
21use crate::alloc::borrow::ToOwned;
22use crate::alloc::string::ToString;
23use crate::rpc::RpcError;
24use crate::rpc::domain::MissingFieldHelper;
25use crate::rpc::errors::RpcConversionError;
26use crate::rpc::generated::{self as proto};
27
28pub enum FetchedAccount {
33 Private(AccountId, AccountUpdateSummary),
36 Public(Box<Account>, AccountUpdateSummary),
39}
40
41impl FetchedAccount {
42 pub fn new_private(account_id: AccountId, summary: AccountUpdateSummary) -> Self {
45 Self::Private(account_id, summary)
46 }
47
48 pub fn new_public(account: Account, summary: AccountUpdateSummary) -> Self {
50 Self::Public(Box::new(account), summary)
51 }
52
53 pub fn account_id(&self) -> AccountId {
55 match self {
56 Self::Private(account_id, _) => *account_id,
57 Self::Public(account, _) => account.id(),
58 }
59 }
60
61 pub fn commitment(&self) -> Word {
63 match self {
64 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
65 }
66 }
67
68 pub fn account(&self) -> Option<&Account> {
70 match self {
71 Self::Private(..) => None,
72 Self::Public(account, _) => Some(account.as_ref()),
73 }
74 }
75}
76
77impl From<FetchedAccount> for Option<Account> {
78 fn from(acc: FetchedAccount) -> Self {
79 match acc {
80 FetchedAccount::Private(..) => None,
81 FetchedAccount::Public(account, _) => Some(*account),
82 }
83 }
84}
85
86pub struct AccountUpdateSummary {
91 pub commitment: Word,
93 pub last_block_num: u32,
95}
96
97impl AccountUpdateSummary {
98 pub fn new(commitment: Word, last_block_num: u32) -> Self {
100 Self { commitment, last_block_num }
101 }
102}
103
104impl Display for proto::account::AccountId {
108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
110 }
111}
112
113impl Debug for proto::account::AccountId {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 Display::fmt(self, f)
116 }
117}
118
119impl From<AccountId> for proto::account::AccountId {
123 fn from(account_id: AccountId) -> Self {
124 Self { id: account_id.to_bytes() }
125 }
126}
127
128impl TryFrom<proto::account::AccountId> for AccountId {
132 type Error = RpcConversionError;
133
134 fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
135 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
136 }
137}
138
139pub(crate) struct SlotTypeProto(pub u32);
143
144impl TryInto<StorageSlotType> for SlotTypeProto {
145 type Error = crate::rpc::RpcError;
146
147 fn try_into(self) -> Result<StorageSlotType, Self::Error> {
148 match self.0 {
149 0 => Ok(StorageSlotType::Map),
150 1 => Ok(StorageSlotType::Value),
151 _ => Err(RpcError::InvalidResponse("Invalid storage slot type".into())),
152 }
153 }
154}
155
156impl TryInto<AccountHeader> for proto::account::AccountHeader {
157 type Error = crate::rpc::RpcError;
158
159 fn try_into(self) -> Result<AccountHeader, Self::Error> {
160 use miden_objects::Felt;
161
162 use crate::rpc::domain::MissingFieldHelper;
163
164 let proto::account::AccountHeader {
165 account_id,
166 nonce,
167 vault_root,
168 storage_commitment,
169 code_commitment,
170 } = self;
171
172 let account_id: AccountId = account_id
173 .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
174 .try_into()?;
175 let vault_root = vault_root
176 .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
177 .try_into()?;
178 let storage_commitment = storage_commitment
179 .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
180 .try_into()?;
181 let code_commitment = code_commitment
182 .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
183 .try_into()?;
184
185 Ok(AccountHeader::new(
186 account_id,
187 Felt::new(nonce),
188 vault_root,
189 storage_commitment,
190 code_commitment,
191 ))
192 }
193}
194
195impl TryInto<AccountStorageHeader> for proto::account::AccountStorageHeader {
199 type Error = crate::rpc::RpcError;
200
201 fn try_into(self) -> Result<AccountStorageHeader, Self::Error> {
202 use crate::rpc::domain::MissingFieldHelper;
203
204 let mut header_slots: Vec<(StorageSlotType, Word)> = Vec::with_capacity(self.slots.len());
205
206 for slot in self.slots {
207 let commitment: Word = slot
208 .commitment
209 .ok_or(proto::account::account_storage_header::StorageSlot::missing_field(
210 stringify!(commitment),
211 ))?
212 .try_into()?;
213
214 let slot_type: StorageSlotType = SlotTypeProto(slot.slot_type).try_into()?;
215
216 header_slots.push((slot_type, commitment));
217 }
218
219 Ok(AccountStorageHeader::new(header_slots))
220 }
221}
222
223#[cfg(feature = "tonic")]
227impl proto::rpc_store::account_proof_response::AccountDetails {
228 pub fn into_domain(
238 self,
239 known_account_codes: &BTreeMap<Word, AccountCode>,
240 ) -> Result<AccountDetails, crate::rpc::RpcError> {
241 use crate::rpc::RpcError;
242 use crate::rpc::domain::MissingFieldHelper;
243
244 let proto::rpc_store::account_proof_response::AccountDetails {
245 header,
246 storage_details,
247 code,
248 vault_details,
249 } = self;
250 let header: AccountHeader = header
251 .ok_or(proto::rpc_store::account_proof_response::AccountDetails::missing_field(
252 stringify!(header),
253 ))?
254 .try_into()?;
255
256 let storage_details = storage_details
257 .ok_or(proto::rpc_store::account_proof_response::AccountDetails::missing_field(
258 stringify!(storage_details),
259 ))?
260 .try_into()?;
261
262 let code = {
266 let received_code = code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
267 match received_code {
268 Some(code) => code,
269 None => known_account_codes
270 .get(&header.code_commitment())
271 .ok_or(RpcError::InvalidResponse(
272 "Account code was not provided, but the response did not contain it either"
273 .into(),
274 ))?
275 .clone(),
276 }
277 };
278
279 let vault_details = vault_details
280 .ok_or(proto::rpc_store::AccountVaultDetails::missing_field(stringify!(vault_details)))?
281 .try_into()?;
282
283 Ok(AccountDetails {
284 header,
285 storage_details,
286 code,
287 vault_details,
288 })
289 }
290}
291
292pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
297
298#[derive(Clone, Debug)]
303pub struct AccountDetails {
304 pub header: AccountHeader,
305 pub storage_details: AccountStorageDetails,
306 pub code: AccountCode,
307 pub vault_details: AccountVaultDetails,
308}
309
310#[derive(Clone, Debug)]
315pub struct AccountStorageDetails {
316 pub header: AccountStorageHeader,
318 pub map_details: Vec<AccountStorageMapDetails>,
320}
321
322impl TryFrom<proto::rpc_store::AccountStorageDetails> for AccountStorageDetails {
323 type Error = RpcError;
324
325 fn try_from(value: proto::rpc_store::AccountStorageDetails) -> Result<Self, Self::Error> {
326 let header = value
327 .header
328 .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
329 .try_into()?;
330 let map_details = value
331 .map_details
332 .iter()
333 .map(|entry| entry.to_owned().try_into())
334 .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
335
336 Ok(Self { header, map_details })
337 }
338}
339
340#[derive(Clone, Debug)]
344pub struct AccountStorageMapDetails {
345 pub slot_index: u32,
347 pub too_many_entries: bool,
351 pub entries: Vec<StorageMapEntry>,
353}
354
355impl TryFrom<proto::rpc_store::account_storage_details::AccountStorageMapDetails>
356 for AccountStorageMapDetails
357{
358 type Error = RpcError;
359
360 fn try_from(
361 value: proto::rpc_store::account_storage_details::AccountStorageMapDetails,
362 ) -> Result<Self, Self::Error> {
363 let slot_index = value.slot_index;
364 let too_many_entries = value.too_many_entries;
365
366 let entries = value
367 .entries
368 .ok_or(
369 proto::rpc_store::account_storage_details::AccountStorageMapDetails::missing_field(
370 stringify!(entries),
371 ),
372 )?
373 .entries
374 .iter_mut()
375 .map(|entry| entry.to_owned().try_into())
376 .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
377
378 Ok(Self { slot_index, too_many_entries, entries })
379 }
380}
381
382#[derive(Clone, Debug)]
386pub struct StorageMapEntry {
387 pub key: Word,
388 pub value: Word,
389}
390
391impl TryFrom<proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry>
392 for StorageMapEntry
393{
394 type Error = RpcError;
395
396 fn try_from(value: proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
397 let key = value.key.ok_or(
398 proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(
399 stringify!(key),
400 ))?.try_into()?;
401
402 let value = value.value.ok_or(
403 proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(
404 stringify!(value),
405 ))?.try_into()?;
406
407 Ok(Self {
408 key,
409 value
410 })
411 }
412}
413
414#[derive(Clone, Debug)]
418pub struct AccountVaultDetails {
419 pub too_many_assets: bool,
423 pub assets: Vec<Asset>,
426}
427
428impl TryFrom<proto::rpc_store::AccountVaultDetails> for AccountVaultDetails {
429 type Error = RpcError;
430
431 fn try_from(value: proto::rpc_store::AccountVaultDetails) -> Result<Self, Self::Error> {
432 let too_many_assets = value.too_many_assets;
433 let assets = value
434 .assets
435 .into_iter()
436 .map(|asset| {
437 let native_digest: Word = asset
438 .asset
439 .ok_or(proto::rpc_store::AccountVaultDetails::missing_field(stringify!(
440 assets
441 )))?
442 .try_into()?;
443 native_digest
444 .try_into()
445 .map_err(|_| RpcError::DeserializationError("asset".to_string()))
446 })
447 .collect::<Result<Vec<Asset>, RpcError>>()?;
448
449 Ok(Self { too_many_assets, assets })
450 }
451}
452
453#[derive(Clone, Debug)]
458pub struct AccountProof {
459 account_witness: AccountWitness,
461 state_headers: Option<AccountDetails>,
463}
464
465impl AccountProof {
466 pub fn new(
468 account_witness: AccountWitness,
469 account_details: Option<AccountDetails>,
470 ) -> Result<Self, AccountProofError> {
471 if let Some(AccountDetails {
472 header: account_header,
473 storage_details: _,
474 code,
475 ..
476 }) = &account_details
477 {
478 if account_header.commitment() != account_witness.state_commitment() {
479 return Err(AccountProofError::InconsistentAccountCommitment);
480 }
481 if account_header.id() != account_witness.id() {
482 return Err(AccountProofError::InconsistentAccountId);
483 }
484 if code.commitment() != account_header.code_commitment() {
485 return Err(AccountProofError::InconsistentCodeCommitment);
486 }
487 }
488
489 Ok(Self {
490 account_witness,
491 state_headers: account_details,
492 })
493 }
494
495 pub fn account_id(&self) -> AccountId {
497 self.account_witness.id()
498 }
499
500 pub fn account_header(&self) -> Option<&AccountHeader> {
502 self.state_headers.as_ref().map(|account_details| &account_details.header)
503 }
504
505 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
507 self.state_headers
508 .as_ref()
509 .map(|account_details| &account_details.storage_details.header)
510 }
511
512 pub fn account_code(&self) -> Option<&AccountCode> {
514 self.state_headers.as_ref().map(|headers| &headers.code)
515 }
516
517 pub fn code_commitment(&self) -> Option<Word> {
519 self.account_code().map(AccountCode::commitment)
520 }
521
522 pub fn account_commitment(&self) -> Word {
524 self.account_witness.state_commitment()
525 }
526
527 pub fn account_witness(&self) -> &AccountWitness {
528 &self.account_witness
529 }
530
531 pub fn merkle_proof(&self) -> &SparseMerklePath {
533 self.account_witness.path()
534 }
535
536 pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
538 (self.account_witness, self.state_headers)
539 }
540}
541
542impl TryFrom<proto::account::AccountWitness> for AccountWitness {
546 type Error = RpcError;
547
548 fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
549 let state_commitment = account_witness
550 .commitment
551 .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
552 .try_into()?;
553 let merkle_path = account_witness
554 .path
555 .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
556 .try_into()?;
557 let account_id = account_witness
558 .witness_id
559 .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
560 .try_into()?;
561
562 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
563 Ok(witness)
564 }
565}
566
567pub type StorageSlotIndex = u8;
571pub type StorageMapKey = Word;
572
573#[derive(Clone, Debug, Default, Eq, PartialEq)]
576pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
577
578impl AccountStorageRequirements {
579 pub fn new<'a>(
580 slots_and_keys: impl IntoIterator<
581 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
582 >,
583 ) -> Self {
584 let map = slots_and_keys
585 .into_iter()
586 .map(|(slot_index, keys_iter)| {
587 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
588 (slot_index, keys_vec)
589 })
590 .collect();
591
592 AccountStorageRequirements(map)
593 }
594
595 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
596 &self.0
597 }
598}
599
600impl From<AccountStorageRequirements>
601 for Vec<
602 proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest,
603 >
604{
605 fn from(
606 value: AccountStorageRequirements,
607 ) -> Vec<proto::rpc_store::account_proof_request::account_detail_request::StorageMapDetailRequest>
608 {
609 use proto::rpc_store::account_proof_request::account_detail_request;
610 use proto::rpc_store::account_proof_request::account_detail_request::storage_map_detail_request;
611 let request_map = value.0;
612 let mut requests = Vec::with_capacity(request_map.len());
613 for (slot_index, map_keys) in request_map {
614 let map_keys = storage_map_detail_request::MapKeys {
615 map_keys: map_keys
616 .into_iter()
617 .map(crate::rpc::generated::primitives::Digest::from)
618 .collect(),
619 };
620 let slot_data = Some(storage_map_detail_request::SlotData::MapKeys(map_keys));
621 requests.push(account_detail_request::StorageMapDetailRequest {
622 slot_index: u32::from(slot_index),
623 slot_data,
624 });
625 }
626 requests
627 }
628}
629
630impl Serializable for AccountStorageRequirements {
631 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
632 target.write(&self.0);
633 }
634}
635
636impl Deserializable for AccountStorageRequirements {
637 fn read_from<R: miden_tx::utils::ByteReader>(
638 source: &mut R,
639 ) -> Result<Self, miden_tx::utils::DeserializationError> {
640 Ok(AccountStorageRequirements(source.read()?))
641 }
642}
643
644#[derive(Debug, Error)]
648pub enum AccountProofError {
649 #[error(
650 "the received account commitment doesn't match the received account header's commitment"
651 )]
652 InconsistentAccountCommitment,
653 #[error("the received account id doesn't match the received account header's id")]
654 InconsistentAccountId,
655 #[error(
656 "the received code commitment doesn't match the received account header's code commitment"
657 )]
658 InconsistentCodeCommitment,
659}