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 account::{Account, AccountCode, AccountHeader, AccountId, AccountStorageHeader},
10 block::BlockNumber,
11 crypto::merkle::{MerklePath, SmtProof},
12 Digest, Felt,
13};
14use miden_tx::utils::{Deserializable, Serializable, ToHex};
15use thiserror::Error;
16
17use crate::rpc::{
18 errors::RpcConversionError,
19 generated::{
20 account::{AccountHeader as ProtoAccountHeader, AccountId as ProtoAccountId},
21 requests::get_account_proofs_request,
22 responses::{AccountStateHeader as ProtoAccountStateHeader, StorageSlotMapProof},
23 },
24 RpcError,
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 hash(&self) -> Digest {
50 match self {
51 Self::Private(_, summary) | Self::Public(_, summary) => summary.hash,
52 }
53 }
54}
55
56pub struct AccountUpdateSummary {
61 pub hash: Digest,
63 pub last_block_num: u32,
65}
66
67impl AccountUpdateSummary {
68 pub fn new(hash: Digest, last_block_num: u32) -> Self {
70 Self { hash, last_block_num }
71 }
72}
73
74impl Display for ProtoAccountId {
78 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
80 }
81}
82
83impl Debug for ProtoAccountId {
84 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
85 Display::fmt(self, f)
86 }
87}
88
89impl From<AccountId> for ProtoAccountId {
93 fn from(account_id: AccountId) -> Self {
94 Self { id: account_id.to_bytes() }
95 }
96}
97
98impl TryFrom<ProtoAccountId> for AccountId {
102 type Error = RpcConversionError;
103
104 fn try_from(account_id: ProtoAccountId) -> Result<Self, Self::Error> {
105 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
106 }
107}
108
109impl ProtoAccountHeader {
113 pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, RpcError> {
114 let ProtoAccountHeader {
115 nonce,
116 vault_root,
117 storage_commitment,
118 code_commitment,
119 } = self;
120 let vault_root = vault_root
121 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.VaultRoot")))?
122 .try_into()?;
123 let storage_commitment = storage_commitment
124 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.StorageCommitment")))?
125 .try_into()?;
126 let code_commitment = code_commitment
127 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.CodeCommitment")))?
128 .try_into()?;
129
130 Ok(AccountHeader::new(
131 account_id,
132 Felt::new(nonce),
133 vault_root,
134 storage_commitment,
135 code_commitment,
136 ))
137 }
138}
139
140impl ProtoAccountStateHeader {
144 pub fn into_domain(
154 self,
155 account_id: AccountId,
156 known_account_codes: &BTreeMap<Digest, AccountCode>,
157 ) -> Result<StateHeaders, RpcError> {
158 let ProtoAccountStateHeader {
159 header,
160 storage_header,
161 account_code,
162 storage_maps,
163 } = self;
164 let account_header = header
165 .ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".to_string()))?
166 .into_domain(account_id)?;
167
168 let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;
169
170 let code = {
174 let received_code =
175 account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
176 match received_code {
177 Some(code) => code,
178 None => known_account_codes
179 .get(&account_header.code_commitment())
180 .ok_or(RpcError::InvalidResponse(
181 "Account code was not provided, but the response did not contain it either"
182 .to_string(),
183 ))?
184 .clone(),
185 }
186 };
187
188 let mut storage_slot_proofs: BTreeMap<u8, Vec<SmtProof>> = BTreeMap::new();
190 for StorageSlotMapProof { storage_slot, smt_proof } in storage_maps {
191 let proof = SmtProof::read_from_bytes(&smt_proof)?;
192 match storage_slot_proofs.get_mut(&(storage_slot as u8)) {
193 Some(list) => list.push(proof),
194 None => _ = storage_slot_proofs.insert(storage_slot as u8, vec![proof]),
195 }
196 }
197
198 Ok(StateHeaders {
199 account_header,
200 storage_header,
201 code,
202 storage_slots: storage_slot_proofs,
203 })
204 }
205}
206
207pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
212
213pub struct StateHeaders {
215 pub account_header: AccountHeader,
216 pub storage_header: AccountStorageHeader,
217 pub code: AccountCode,
218 pub storage_slots: BTreeMap<StorageSlotIndex, Vec<SmtProof>>,
219}
220
221pub struct AccountProof {
223 account_id: AccountId,
225 merkle_proof: MerklePath,
227 account_hash: Digest,
229 state_headers: Option<StateHeaders>,
231}
232
233impl AccountProof {
234 pub fn new(
235 account_id: AccountId,
236 merkle_proof: MerklePath,
237 account_hash: Digest,
238 state_headers: Option<StateHeaders>,
239 ) -> Result<Self, AccountProofError> {
240 if let Some(StateHeaders {
241 account_header, storage_header: _, code, ..
242 }) = &state_headers
243 {
244 if account_header.hash() != account_hash {
245 return Err(AccountProofError::InconsistentAccountHash);
246 }
247 if account_id != account_header.id() {
248 return Err(AccountProofError::InconsistentAccountId);
249 }
250 if code.commitment() != account_header.code_commitment() {
251 return Err(AccountProofError::InconsistentCodeCommitment);
252 }
253 }
254
255 Ok(Self {
256 account_id,
257 merkle_proof,
258 account_hash,
259 state_headers,
260 })
261 }
262
263 pub fn account_id(&self) -> AccountId {
264 self.account_id
265 }
266
267 pub fn account_header(&self) -> Option<&AccountHeader> {
268 self.state_headers.as_ref().map(|headers| &headers.account_header)
269 }
270
271 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
272 self.state_headers.as_ref().map(|headers| &headers.storage_header)
273 }
274
275 pub fn account_code(&self) -> Option<&AccountCode> {
276 self.state_headers.as_ref().map(|headers| &headers.code)
277 }
278
279 pub fn state_headers(&self) -> Option<&StateHeaders> {
280 self.state_headers.as_ref()
281 }
282
283 pub fn code_commitment(&self) -> Option<Digest> {
284 self.account_code().map(|c| c.commitment())
285 }
286
287 pub fn account_hash(&self) -> Digest {
288 self.account_hash
289 }
290
291 pub fn merkle_proof(&self) -> &MerklePath {
292 &self.merkle_proof
293 }
294
295 pub fn into_parts(self) -> (AccountId, MerklePath, Digest, Option<StateHeaders>) {
297 (self.account_id, self.merkle_proof, self.account_hash, self.state_headers)
298 }
299}
300
301pub type StorageSlotIndex = u8;
305pub type StorageMapKey = Digest;
306
307#[derive(Clone, Debug, Default, Eq, PartialEq)]
310pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
311
312impl AccountStorageRequirements {
313 pub fn new<'a>(
314 slots_and_keys: impl IntoIterator<
315 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
316 >,
317 ) -> Self {
318 let map = slots_and_keys
319 .into_iter()
320 .map(|(slot_index, keys_iter)| {
321 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().cloned().collect();
322 (slot_index, keys_vec)
323 })
324 .collect();
325
326 AccountStorageRequirements(map)
327 }
328
329 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
330 &self.0
331 }
332}
333
334impl From<AccountStorageRequirements> for Vec<get_account_proofs_request::StorageRequest> {
335 fn from(value: AccountStorageRequirements) -> Vec<get_account_proofs_request::StorageRequest> {
336 let mut requests = Vec::with_capacity(value.0.len());
337 for (slot_index, map_keys) in value.0.into_iter() {
338 requests.push(get_account_proofs_request::StorageRequest {
339 storage_slot_index: slot_index as u32,
340 map_keys: map_keys
341 .into_iter()
342 .map(crate::rpc::generated::digest::Digest::from)
343 .collect(),
344 });
345 }
346 requests
347 }
348}
349
350impl Serializable for AccountStorageRequirements {
351 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
352 target.write(&self.0);
353 }
354}
355
356impl Deserializable for AccountStorageRequirements {
357 fn read_from<R: miden_tx::utils::ByteReader>(
358 source: &mut R,
359 ) -> Result<Self, miden_tx::utils::DeserializationError> {
360 Ok(AccountStorageRequirements(source.read()?))
361 }
362}
363
364#[derive(Debug, Error)]
368pub enum AccountProofError {
369 #[error("the received account hash doesn't match the received account header's hash")]
370 InconsistentAccountHash,
371 #[error("the received account id doesn't match the received account header's id")]
372 InconsistentAccountId,
373 #[error(
374 "the received code commitment doesn't match the received account header's code commitment"
375 )]
376 InconsistentCodeCommitment,
377}