1use alloc::{
2 collections::BTreeMap,
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::fmt::{self, Debug, Display, Formatter};
7
8use miden_objects::{
9 Digest, Felt,
10 account::{Account, AccountCode, AccountHeader, AccountId, AccountStorageHeader},
11 block::BlockNumber,
12 crypto::merkle::{MerklePath, SmtProof},
13};
14use miden_tx::utils::{Deserializable, Serializable, ToHex};
15use thiserror::Error;
16
17use crate::rpc::{
18 RpcError,
19 errors::RpcConversionError,
20 generated::{
21 account::{AccountHeader as ProtoAccountHeader, AccountId as ProtoAccountId},
22 requests::get_account_proofs_request,
23 responses::{AccountStateHeader as ProtoAccountStateHeader, StorageSlotMapProof},
24 },
25};
26
27pub enum AccountDetails {
32 Private(AccountId, AccountUpdateSummary),
35 Public(Account, AccountUpdateSummary),
38}
39
40impl AccountDetails {
41 pub fn account_id(&self) -> AccountId {
43 match self {
44 Self::Private(account_id, _) => *account_id,
45 Self::Public(account, _) => account.id(),
46 }
47 }
48
49 pub fn commitment(&self) -> Digest {
51 match self {
52 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
53 }
54 }
55
56 pub fn account(&self) -> Option<&Account> {
58 match self {
59 Self::Private(..) => None,
60 Self::Public(account, _) => Some(account),
61 }
62 }
63}
64
65pub struct AccountUpdateSummary {
70 pub commitment: Digest,
72 pub last_block_num: u32,
74}
75
76impl AccountUpdateSummary {
77 pub fn new(commitment: Digest, last_block_num: u32) -> Self {
79 Self { commitment, last_block_num }
80 }
81}
82
83impl Display for ProtoAccountId {
87 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
88 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
89 }
90}
91
92impl Debug for ProtoAccountId {
93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94 Display::fmt(self, f)
95 }
96}
97
98impl From<AccountId> for ProtoAccountId {
102 fn from(account_id: AccountId) -> Self {
103 Self { id: account_id.to_bytes() }
104 }
105}
106
107impl TryFrom<ProtoAccountId> for AccountId {
111 type Error = RpcConversionError;
112
113 fn try_from(account_id: ProtoAccountId) -> Result<Self, Self::Error> {
114 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
115 }
116}
117
118impl ProtoAccountHeader {
122 #[allow(dead_code)]
123 pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, RpcError> {
124 let ProtoAccountHeader {
125 nonce,
126 vault_root,
127 storage_commitment,
128 code_commitment,
129 } = self;
130 let vault_root = vault_root
131 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.VaultRoot")))?
132 .try_into()?;
133 let storage_commitment = storage_commitment
134 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.StorageCommitment")))?
135 .try_into()?;
136 let code_commitment = code_commitment
137 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.CodeCommitment")))?
138 .try_into()?;
139
140 Ok(AccountHeader::new(
141 account_id,
142 Felt::new(nonce),
143 vault_root,
144 storage_commitment,
145 code_commitment,
146 ))
147 }
148}
149
150impl ProtoAccountStateHeader {
154 #[allow(dead_code)]
164 pub fn into_domain(
165 self,
166 account_id: AccountId,
167 known_account_codes: &BTreeMap<Digest, AccountCode>,
168 ) -> Result<StateHeaders, RpcError> {
169 let ProtoAccountStateHeader {
170 header,
171 storage_header,
172 account_code,
173 storage_maps,
174 } = self;
175 let account_header = header
176 .ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".to_string()))?
177 .into_domain(account_id)?;
178
179 let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;
180
181 let code = {
185 let received_code =
186 account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
187 match received_code {
188 Some(code) => code,
189 None => known_account_codes
190 .get(&account_header.code_commitment())
191 .ok_or(RpcError::InvalidResponse(
192 "Account code was not provided, but the response did not contain it either"
193 .to_string(),
194 ))?
195 .clone(),
196 }
197 };
198
199 let mut storage_slot_proofs: BTreeMap<u8, Vec<SmtProof>> = BTreeMap::new();
201 for StorageSlotMapProof { storage_slot, smt_proof } in storage_maps {
202 let proof = SmtProof::read_from_bytes(&smt_proof)?;
203 match storage_slot_proofs
204 .get_mut(&(u8::try_from(storage_slot).expect("there are no more than 256 slots")))
205 {
206 Some(list) => list.push(proof),
207 None => {
208 _ = storage_slot_proofs.insert(
209 u8::try_from(storage_slot).expect("only 256 storage slots"),
210 vec![proof],
211 );
212 },
213 }
214 }
215
216 Ok(StateHeaders {
217 account_header,
218 storage_header,
219 code,
220 storage_slots: storage_slot_proofs,
221 })
222 }
223}
224
225pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
230
231pub struct StateHeaders {
233 pub account_header: AccountHeader,
234 pub storage_header: AccountStorageHeader,
235 pub code: AccountCode,
236 pub storage_slots: BTreeMap<StorageSlotIndex, Vec<SmtProof>>,
237}
238
239pub struct AccountProof {
241 account_id: AccountId,
243 merkle_proof: MerklePath,
245 account_commitment: Digest,
247 state_headers: Option<StateHeaders>,
249}
250
251impl AccountProof {
252 pub fn new(
253 account_id: AccountId,
254 merkle_proof: MerklePath,
255 account_commitment: Digest,
256 state_headers: Option<StateHeaders>,
257 ) -> Result<Self, AccountProofError> {
258 if let Some(StateHeaders {
259 account_header, storage_header: _, code, ..
260 }) = &state_headers
261 {
262 if account_header.commitment() != account_commitment {
263 return Err(AccountProofError::InconsistentAccountCommitment);
264 }
265 if account_id != account_header.id() {
266 return Err(AccountProofError::InconsistentAccountId);
267 }
268 if code.commitment() != account_header.code_commitment() {
269 return Err(AccountProofError::InconsistentCodeCommitment);
270 }
271 }
272
273 Ok(Self {
274 account_id,
275 merkle_proof,
276 account_commitment,
277 state_headers,
278 })
279 }
280
281 pub fn account_id(&self) -> AccountId {
282 self.account_id
283 }
284
285 pub fn account_header(&self) -> Option<&AccountHeader> {
286 self.state_headers.as_ref().map(|headers| &headers.account_header)
287 }
288
289 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
290 self.state_headers.as_ref().map(|headers| &headers.storage_header)
291 }
292
293 pub fn account_code(&self) -> Option<&AccountCode> {
294 self.state_headers.as_ref().map(|headers| &headers.code)
295 }
296
297 pub fn state_headers(&self) -> Option<&StateHeaders> {
298 self.state_headers.as_ref()
299 }
300
301 pub fn code_commitment(&self) -> Option<Digest> {
302 self.account_code().map(AccountCode::commitment)
303 }
304
305 pub fn account_commitment(&self) -> Digest {
306 self.account_commitment
307 }
308
309 pub fn merkle_proof(&self) -> &MerklePath {
310 &self.merkle_proof
311 }
312
313 pub fn into_parts(self) -> (AccountId, MerklePath, Digest, Option<StateHeaders>) {
315 (self.account_id, self.merkle_proof, self.account_commitment, self.state_headers)
316 }
317}
318
319pub type StorageSlotIndex = u8;
323pub type StorageMapKey = Digest;
324
325#[derive(Clone, Debug, Default, Eq, PartialEq)]
328pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
329
330impl AccountStorageRequirements {
331 pub fn new<'a>(
332 slots_and_keys: impl IntoIterator<
333 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
334 >,
335 ) -> Self {
336 let map = slots_and_keys
337 .into_iter()
338 .map(|(slot_index, keys_iter)| {
339 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
340 (slot_index, keys_vec)
341 })
342 .collect();
343
344 AccountStorageRequirements(map)
345 }
346
347 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
348 &self.0
349 }
350}
351
352impl From<AccountStorageRequirements> for Vec<get_account_proofs_request::StorageRequest> {
353 fn from(value: AccountStorageRequirements) -> Vec<get_account_proofs_request::StorageRequest> {
354 let mut requests = Vec::with_capacity(value.0.len());
355 for (slot_index, map_keys) in value.0 {
356 requests.push(get_account_proofs_request::StorageRequest {
357 storage_slot_index: u32::from(slot_index),
358 map_keys: map_keys
359 .into_iter()
360 .map(crate::rpc::generated::digest::Digest::from)
361 .collect(),
362 });
363 }
364 requests
365 }
366}
367
368impl Serializable for AccountStorageRequirements {
369 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
370 target.write(&self.0);
371 }
372}
373
374impl Deserializable for AccountStorageRequirements {
375 fn read_from<R: miden_tx::utils::ByteReader>(
376 source: &mut R,
377 ) -> Result<Self, miden_tx::utils::DeserializationError> {
378 Ok(AccountStorageRequirements(source.read()?))
379 }
380}
381
382#[derive(Debug, Error)]
386pub enum AccountProofError {
387 #[error(
388 "the received account commitment doesn't match the received account header's commitment"
389 )]
390 InconsistentAccountCommitment,
391 #[error("the received account id doesn't match the received account header's id")]
392 InconsistentAccountId,
393 #[error(
394 "the received code commitment doesn't match the received account header's code commitment"
395 )]
396 InconsistentCodeCommitment,
397}