1use miden_node_proto::domain::account::{
2 AccountDetailRequest,
3 AccountDetails,
4 AccountRequest,
5 AccountResponse,
6 AccountStorageDetails,
7 AccountStorageMapDetails,
8 AccountStorageRequest,
9 AccountVaultDetails,
10 SlotData,
11 StorageMapEntries,
12 StorageMapRequest,
13};
14use miden_node_proto::generated as proto;
15use miden_node_proto::prost::Message as _;
16use miden_node_proto::prost::encoding::{encoded_len_varint, key_len};
17use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES;
18use miden_node_utils::tracing::miden_instrument;
19use miden_protocol::account::{
20 AccountHeader,
21 AccountId,
22 AccountStorageHeader,
23 StorageSlotName,
24 StorageSlotType,
25};
26use miden_protocol::block::BlockNumber;
27use miden_protocol::block::account_tree::AccountWitness;
28use tracing::Instrument;
29
30use super::State;
31use crate::COMPONENT;
32use crate::account_state_forest::AccountStorageMapResult;
33use crate::errors::{DatabaseError, GetAccountError};
34
35impl State {
36 #[miden_instrument(
46 target = COMPONENT,
47 skip_all,
48 )]
49 pub async fn get_account(
50 &self,
51 account_request: AccountRequest,
52 ) -> Result<AccountResponse, GetAccountError> {
53 let AccountRequest { block_num, account_id, details } = account_request;
54
55 if details.is_some() && !account_id.is_public() {
56 return Err(GetAccountError::AccountNotPublic(account_id));
57 }
58
59 let (block_num, witness) = self.get_account_witness(block_num, account_id).await?;
60
61 let details = if let Some(request) = details {
62 Some(
63 self.fetch_public_account_details(account_id, block_num, &witness, request)
64 .await?,
65 )
66 } else {
67 None
68 };
69
70 Ok(AccountResponse { block_num, witness, details })
71 }
72
73 #[miden_instrument(
78 target = COMPONENT,
79 skip_all,
80 )]
81 async fn get_account_witness(
82 &self,
83 block_num: Option<BlockNumber>,
84 account_id: AccountId,
85 ) -> Result<(BlockNumber, AccountWitness), GetAccountError> {
86 self.with_inner_read_blocking(|inner_state| {
87 let (block_num, witness) = if let Some(requested_block) = block_num {
89 let witness = inner_state
91 .account_tree
92 .open_at(account_id, requested_block)
93 .ok_or_else(|| {
94 let latest_block = inner_state.account_tree.block_number_latest();
95 if requested_block > latest_block {
96 GetAccountError::UnknownBlock(requested_block)
97 } else {
98 GetAccountError::BlockPruned(requested_block)
99 }
100 })?;
101 (requested_block, witness)
102 } else {
103 let block_num = inner_state.account_tree.block_number_latest();
105 let witness = inner_state.account_tree.open_latest(account_id);
106 (block_num, witness)
107 };
108
109 Ok((block_num, witness))
110 })
111 }
112
113 #[miden_instrument(
120 target = COMPONENT,
121 skip_all,
122 )]
123 fn get_storage_map_details_from_forest(
124 &self,
125 account_id: AccountId,
126 slot_name: &StorageSlotName,
127 block_num: BlockNumber,
128 ) -> Result<Option<AccountStorageMapDetails>, DatabaseError> {
129 self.with_forest_read_blocking(|forest| {
130 match forest
131 .get_storage_map_details_for_all_entries(account_id, slot_name.clone(), block_num)
132 .map_err(DatabaseError::MerkleError)?
133 {
134 AccountStorageMapResult::NotFound => Err(DatabaseError::StorageRootNotFound {
135 account_id,
136 slot_name: slot_name.to_string(),
137 block_num,
138 }),
139 AccountStorageMapResult::Details(details) => Ok(Some(details)),
140 AccountStorageMapResult::CannotReconstructKeysFromCache => Ok(None),
141 }
142 })
143 }
144
145 async fn reconstruct_vault_details_from_db(
147 &self,
148 account_id: AccountId,
149 block_num: BlockNumber,
150 ) -> Result<AccountVaultDetails, DatabaseError> {
151 let assets = self.db.select_account_vault_at_block(account_id, block_num).await?;
152
153 if assets.len() > AccountVaultDetails::MAX_RETURN_ENTRIES {
154 return Ok(AccountVaultDetails::LimitExceeded);
155 }
156
157 let keys = assets.iter().map(miden_protocol::asset::Asset::id);
158
159 let forest = self.forest.write().await;
160
161 forest
162 .vault_key_cache
163 .put_many(keys.into_iter().map(|raw_key| (raw_key.hash(), raw_key)));
164
165 Ok(AccountVaultDetails::from_assets(assets))
166 }
167
168 async fn reconstruct_storage_map_details_from_db(
170 &self,
171 account_id: AccountId,
172 slot_name: StorageSlotName,
173 block_num: BlockNumber,
174 ) -> Result<AccountStorageMapDetails, DatabaseError> {
175 let details = self
176 .db
177 .reconstruct_storage_map_from_db(
178 account_id,
179 slot_name,
180 block_num,
181 Some(AccountStorageMapDetails::MAX_RETURN_ENTRIES),
182 )
183 .await?;
184
185 if let StorageMapEntries::AllEntries(entries) = &details.entries {
186 self.forest
187 .write()
188 .await
189 .cache_storage_map_keys(entries.iter().map(|(raw_key, _)| *raw_key));
190 }
191
192 Ok(details)
193 }
194
195 #[miden_instrument(
206 target = COMPONENT,
207 skip_all,
208 )]
209 async fn fetch_public_account_details(
210 &self,
211 account_id: AccountId,
212 block_num: BlockNumber,
213 witness: &AccountWitness,
214 detail_request: AccountDetailRequest,
215 ) -> Result<AccountDetails, GetAccountError> {
216 let AccountDetailRequest {
217 code_commitment,
218 asset_vault_commitment,
219 storage_request,
220 } = detail_request;
221
222 if !account_id.is_public() {
223 return Err(GetAccountError::AccountNotPublic(account_id));
224 }
225
226 {
228 let inner = self.inner.read().instrument(tracing::info_span!("acquire_inner")).await;
229 let latest_block_num = inner.latest_block_num();
230
231 if block_num > latest_block_num {
232 return Err(GetAccountError::UnknownBlock(block_num));
233 }
234 }
235
236 let (account_header, storage_header) = self
238 .db
239 .select_account_header_with_storage_header_at_block(account_id, block_num)
240 .await?
241 .ok_or(GetAccountError::AccountNotFound(account_id, block_num))?;
242
243 let should_apply_response_budget =
244 matches!(&storage_request, AccountStorageRequest::AllStorageMaps);
245 let storage_requests = expand_account_storage_request(storage_request, &storage_header);
246
247 let account_code = match code_commitment {
248 Some(commitment) if commitment == account_header.code_commitment() => None,
249 Some(_) => {
250 self.db
251 .select_account_code_by_commitment(account_header.code_commitment())
252 .await?
253 },
254 None => None,
255 };
256
257 let vault_details = match asset_vault_commitment {
263 Some(commitment) if commitment == account_header.vault_root() => {
264 AccountVaultDetails::empty()
265 },
266 Some(_) => {
267 let forest_details = self.with_forest_read_blocking(|forest| {
268 forest.get_vault_details(account_id, block_num).map_err(|err| {
269 DatabaseError::DataCorrupted(format!(
270 "failed to reconstruct vault for account {account_id} at block {block_num}: {err}"
271 ))
272 })
273 })?;
274
275 match forest_details {
276 Some(details) => details,
277 None => self.reconstruct_vault_details_from_db(account_id, block_num).await?,
278 }
279 },
280 None => AccountVaultDetails::empty(),
281 };
282
283 let mut storage_map_details =
287 Vec::<AccountStorageMapDetails>::with_capacity(storage_requests.len());
288 let mut map_keys_requests = Vec::new();
289 let mut all_entries_requests = Vec::new();
290 let mut storage_request_slots = Vec::with_capacity(storage_requests.len());
291
292 for (index, StorageMapRequest { slot_name, slot_data }) in
293 storage_requests.into_iter().enumerate()
294 {
295 storage_request_slots.push(slot_name.clone());
296 match slot_data {
297 SlotData::MapKeys(keys) => {
298 map_keys_requests.push((index, slot_name, keys));
299 },
300 SlotData::All => {
301 all_entries_requests.push((index, slot_name));
302 },
303 }
304 }
305
306 let mut storage_map_details_by_index = vec![None; storage_request_slots.len()];
307
308 if !map_keys_requests.is_empty() {
310 self.with_forest_read_blocking(|forest| {
311 for (index, slot_name, keys) in map_keys_requests {
312 let details = forest
313 .get_storage_map_details_for_keys(
314 account_id,
315 slot_name.clone(),
316 block_num,
317 &keys,
318 )
319 .ok_or_else(|| DatabaseError::StorageRootNotFound {
320 account_id,
321 slot_name: slot_name.to_string(),
322 block_num,
323 })?
324 .map_err(DatabaseError::MerkleError)?;
325 storage_map_details_by_index[index] = Some(details);
326 }
327 Ok::<(), DatabaseError>(())
328 })?;
329 }
330
331 for (index, slot_name) in all_entries_requests {
333 let details = match self
334 .get_storage_map_details_from_forest(account_id, &slot_name, block_num)?
335 {
336 Some(details) => details,
337 None => {
338 self.reconstruct_storage_map_details_from_db(account_id, slot_name, block_num)
339 .await?
340 },
341 };
342 storage_map_details_by_index[index] = Some(details);
343 }
344
345 for (details, slot_name) in
346 storage_map_details_by_index.into_iter().zip(storage_request_slots.iter())
347 {
348 let details = details.ok_or_else(|| DatabaseError::StorageRootNotFound {
349 account_id,
350 slot_name: slot_name.to_string(),
351 block_num,
352 })?;
353 storage_map_details.push(details);
354 }
355
356 if should_apply_response_budget {
361 return Ok(apply_all_storage_maps_response_budget(
362 block_num,
363 witness,
364 account_header,
365 account_code,
366 vault_details,
367 storage_header,
368 storage_map_details,
369 storage_request_slots,
370 MAX_ALL_STORAGE_MAPS_RESPONSE_PAYLOAD_WITH_BUDGET_RESERVED_FOR_LIMIT_EXCEEDED_SLOTS,
371 ));
372 }
373
374 Ok(AccountDetails {
375 account_header,
376 account_code,
377 vault_details,
378 storage_details: AccountStorageDetails {
379 header: storage_header,
380 map_details: storage_map_details,
381 },
382 })
383 }
384}
385
386fn expand_account_storage_request(
391 storage_request: AccountStorageRequest,
392 storage_header: &AccountStorageHeader,
393) -> Vec<StorageMapRequest> {
394 match storage_request {
395 AccountStorageRequest::None => Vec::new(),
396 AccountStorageRequest::Explicit(requests) => requests,
397 AccountStorageRequest::AllStorageMaps => storage_header
398 .slots()
399 .filter(|slot| slot.slot_type() == StorageSlotType::Map)
400 .map(|slot| StorageMapRequest {
401 slot_name: slot.name().clone(),
402 slot_data: SlotData::All,
403 })
404 .collect(),
405 }
406}
407
408const STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN: usize = 263;
411
412const MAX_ALL_STORAGE_MAPS_RESPONSE_PAYLOAD_WITH_BUDGET_RESERVED_FOR_LIMIT_EXCEEDED_SLOTS: usize =
415 MAX_RESPONSE_PAYLOAD_BYTES - 256 * STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN - 8192;
416
417const STORAGE_MAP_ENTRY_MAX_LEN: usize = 78;
420
421fn protobuf_bytes_field_len(field_number: u32, len: usize) -> usize {
422 key_len(field_number) + encoded_len_varint(len as u64) + len
423}
424
425fn estimate_storage_map_details_field_len(details: &AccountStorageMapDetails) -> usize {
427 match &details.entries {
428 StorageMapEntries::LimitExceeded => STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN,
429 StorageMapEntries::AllEntries(entries) => {
430 let slot_name_len = details.slot_name.as_str().len();
431 let slot_name_field_len = protobuf_bytes_field_len(1, slot_name_len);
432 let all_entries_payload_len = entries.len() * STORAGE_MAP_ENTRY_MAX_LEN;
433 let all_entries_field_len = protobuf_bytes_field_len(3, all_entries_payload_len);
434 let details_len = slot_name_field_len + all_entries_field_len;
435
436 protobuf_bytes_field_len(2, details_len)
437 },
438 StorageMapEntries::EntriesWithProofs(_) => usize::MAX,
441 }
442}
443
444#[expect(clippy::too_many_arguments)]
454fn apply_all_storage_maps_response_budget(
455 block_num: BlockNumber,
456 witness: &AccountWitness,
457 account_header: AccountHeader,
458 account_code: Option<Vec<u8>>,
459 vault_details: AccountVaultDetails,
460 storage_header: AccountStorageHeader,
461 ordered_map_details: Vec<AccountStorageMapDetails>,
462 ordered_map_slot_names: Vec<StorageSlotName>,
463 max_response_payload_bytes: usize,
464) -> AccountDetails {
465 let mut accepted_map_details = Vec::with_capacity(ordered_map_details.len());
466 let base_response_size_without_map_details =
467 proto::rpc::AccountResponse::from(AccountResponse {
468 block_num,
469 witness: witness.clone(),
470 details: Some(AccountDetails {
471 account_header: account_header.clone(),
472 account_code: account_code.clone(),
473 vault_details: vault_details.clone(),
474 storage_details: AccountStorageDetails {
475 header: storage_header.clone(),
476 map_details: vec![],
477 },
478 }),
479 })
480 .encoded_len();
481 let available_map_details_budget =
482 max_response_payload_bytes.saturating_sub(base_response_size_without_map_details);
483 let reserved_limit_exceeded_budget =
484 ordered_map_slot_names.len() * STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN;
485 let mut extra_budget_for_full_maps =
486 available_map_details_budget.saturating_sub(reserved_limit_exceeded_budget);
487
488 for (details, slot_name) in ordered_map_details.into_iter().zip(ordered_map_slot_names) {
489 let estimated_details_len = estimate_storage_map_details_field_len(&details);
490 let extra_cost_over_limit_exceeded =
491 estimated_details_len.saturating_sub(STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN);
492
493 if extra_cost_over_limit_exceeded <= extra_budget_for_full_maps {
494 extra_budget_for_full_maps -= extra_cost_over_limit_exceeded;
495 accepted_map_details.push(details);
496 } else {
497 accepted_map_details.push(AccountStorageMapDetails::limit_exceeded(slot_name));
498 }
499 }
500
501 AccountDetails {
502 account_header,
503 account_code,
504 vault_details,
505 storage_details: AccountStorageDetails {
506 header: storage_header,
507 map_details: accepted_map_details,
508 },
509 }
510}
511
512#[cfg(test)]
516mod tests {
517 use miden_node_proto::domain::account::{
518 AccountDetails,
519 AccountResponse,
520 AccountStorageDetails,
521 AccountStorageMapDetails,
522 AccountStorageRequest,
523 AccountVaultDetails,
524 SlotData,
525 StorageMapEntries,
526 StorageMapRequest,
527 };
528 use miden_protocol::account::{
529 AccountHeader,
530 AccountId,
531 AccountStorageHeader,
532 StorageMapKey,
533 StorageSlotHeader,
534 StorageSlotName,
535 StorageSlotType,
536 };
537 use miden_protocol::block::BlockNumber;
538 use miden_protocol::block::account_tree::{AccountIdKey, AccountTree, AccountWitness};
539 use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage};
540 use miden_protocol::testing::account_id::AccountIdBuilder;
541 use miden_protocol::{EMPTY_WORD, Felt, Word};
542
543 use super::{apply_all_storage_maps_response_budget, expand_account_storage_request};
544
545 fn storage_header() -> AccountStorageHeader {
546 AccountStorageHeader::new(vec![
547 StorageSlotHeader::new(StorageSlotName::mock(0), StorageSlotType::Value, EMPTY_WORD),
548 StorageSlotHeader::new(StorageSlotName::mock(1), StorageSlotType::Map, EMPTY_WORD),
549 StorageSlotHeader::new(StorageSlotName::mock(2), StorageSlotType::Map, EMPTY_WORD),
550 ])
551 .unwrap()
552 }
553
554 fn account_id() -> AccountId {
555 AccountIdBuilder::new().build_with_seed([42; 32])
556 }
557
558 fn account_header(account_id: AccountId) -> AccountHeader {
559 AccountHeader::new(account_id, Felt::ZERO, EMPTY_WORD, EMPTY_WORD, EMPTY_WORD)
560 }
561
562 fn account_witness(account_id: AccountId) -> AccountWitness {
563 let smt = LargeSmt::with_entries(
564 MemoryStorage::default(),
565 [(AccountIdKey::from(account_id).as_word(), EMPTY_WORD)],
566 )
567 .unwrap();
568 AccountTree::new(smt).unwrap().open(account_id)
569 }
570
571 fn map_details(slot_name: StorageSlotName, value: Word) -> AccountStorageMapDetails {
572 AccountStorageMapDetails {
573 slot_name,
574 entries: StorageMapEntries::AllEntries(vec![(StorageMapKey::from_index(1), value)]),
575 }
576 }
577
578 fn map_details_with_entries(
579 slot_name: StorageSlotName,
580 entry_count: u8,
581 ) -> AccountStorageMapDetails {
582 AccountStorageMapDetails {
583 slot_name,
584 entries: StorageMapEntries::AllEntries(
585 (1..=entry_count)
586 .map(|index| {
587 (
588 StorageMapKey::from_index(u32::from(index)),
589 Word::from([u32::from(index), 0, 0, 0]),
590 )
591 })
592 .collect(),
593 ),
594 }
595 }
596
597 #[test]
598 fn all_storage_maps_expands_only_map_slots() {
599 let requests = expand_account_storage_request(
600 AccountStorageRequest::AllStorageMaps,
601 &storage_header(),
602 );
603
604 assert_eq!(requests.len(), 2);
605 assert_eq!(requests[0].slot_name, StorageSlotName::mock(1));
606 assert_eq!(requests[1].slot_name, StorageSlotName::mock(2));
607 assert!(requests.iter().all(|request| request.slot_data == SlotData::All));
608 }
609
610 #[test]
611 fn explicit_storage_maps_are_preserved() {
612 let slot_name = StorageSlotName::mock(2);
613 let explicit = vec![StorageMapRequest {
614 slot_name: slot_name.clone(),
615 slot_data: SlotData::All,
616 }];
617
618 let requests = expand_account_storage_request(
619 AccountStorageRequest::Explicit(explicit.clone()),
620 &storage_header(),
621 );
622
623 assert_eq!(requests, explicit);
624 assert_eq!(requests[0].slot_name, slot_name);
625 }
626
627 #[test]
628 fn absent_storage_slot_data_expands_to_no_requests() {
629 let requests =
630 expand_account_storage_request(AccountStorageRequest::None, &storage_header());
631
632 assert!(requests.is_empty());
633 }
634
635 #[test]
636 fn limit_exceeded_max_size_covers_max_slot_name() {
637 use miden_node_proto::prost::Message;
638
639 let max_slot_name = StorageSlotName::new(format!("a::{}", "a".repeat(252))).unwrap();
640
641 let details = super::proto::rpc::account_storage_details::AccountStorageMapDetails::from(
642 AccountStorageMapDetails::limit_exceeded(max_slot_name),
643 );
644
645 assert!(super::STORAGE_MAP_LIMIT_EXCEEDED_FIELD_MAX_LEN >= details.encoded_len());
646 }
647
648 #[test]
649 fn all_entries_size_estimate_covers_actual_protobuf_size() {
650 use miden_node_proto::prost::Message;
651
652 let details = map_details(StorageSlotName::mock(1), Word::from([1u32, 0, 0, 0]));
653 let actual = super::proto::rpc::account_storage_details::AccountStorageMapDetails::from(
654 details.clone(),
655 )
656 .encoded_len();
657
658 assert!(super::estimate_storage_map_details_field_len(&details) >= actual);
659 }
660
661 #[test]
662 fn all_storage_maps_budget_marks_maps_as_limit_exceeded_when_budget_is_exhausted() {
663 use miden_node_proto::prost::Message;
664
665 let account_id = account_id();
666 let witness = account_witness(account_id);
667 let header = account_header(account_id);
668 let storage_header = storage_header();
669 let slot_1 = StorageSlotName::mock(1);
670 let slot_2 = StorageSlotName::mock(2);
671 let marker_only_budget = super::proto::rpc::AccountResponse::from(AccountResponse {
672 block_num: BlockNumber::GENESIS,
673 witness: witness.clone(),
674 details: Some(AccountDetails {
675 account_header: header.clone(),
676 account_code: None,
677 vault_details: AccountVaultDetails::empty(),
678 storage_details: AccountStorageDetails {
679 header: storage_header.clone(),
680 map_details: vec![
681 AccountStorageMapDetails::limit_exceeded(slot_1.clone()),
682 AccountStorageMapDetails::limit_exceeded(slot_2.clone()),
683 ],
684 },
685 }),
686 })
687 .encoded_len();
688 let details = apply_all_storage_maps_response_budget(
689 BlockNumber::GENESIS,
690 &witness,
691 header,
692 None,
693 AccountVaultDetails::empty(),
694 storage_header,
695 vec![
696 map_details_with_entries(slot_1.clone(), 8),
697 map_details_with_entries(slot_2.clone(), 8),
698 ],
699 vec![slot_1.clone(), slot_2.clone()],
700 marker_only_budget,
701 );
702
703 assert_eq!(details.storage_details.map_details.len(), 2);
704 assert_eq!(details.storage_details.map_details[0].slot_name, slot_1);
705 assert_eq!(
706 details.storage_details.map_details[0].entries,
707 StorageMapEntries::LimitExceeded
708 );
709 assert_eq!(details.storage_details.map_details[1].slot_name, slot_2);
710 assert_eq!(
711 details.storage_details.map_details[1].entries,
712 StorageMapEntries::LimitExceeded
713 );
714 }
715
716 #[test]
717 fn all_storage_maps_budget_stays_under_hard_cap_with_many_limit_exceeded_maps() {
718 use miden_node_proto::prost::Message;
719
720 let account_id = account_id();
721 let witness = account_witness(account_id);
722 let header = account_header(account_id);
723 let mut slot_names: Vec<_> = (1..10).map(StorageSlotName::mock).collect();
724 slot_names.sort();
725 let storage_header = AccountStorageHeader::new(
726 slot_names
727 .iter()
728 .cloned()
729 .map(|slot_name| {
730 StorageSlotHeader::new(slot_name, StorageSlotType::Map, EMPTY_WORD)
731 })
732 .collect(),
733 )
734 .unwrap();
735 let marker_only_hard_cap = super::proto::rpc::AccountResponse::from(AccountResponse {
736 block_num: BlockNumber::GENESIS,
737 witness: witness.clone(),
738 details: Some(AccountDetails {
739 account_header: header.clone(),
740 account_code: None,
741 vault_details: AccountVaultDetails::empty(),
742 storage_details: AccountStorageDetails {
743 header: storage_header.clone(),
744 map_details: slot_names
745 .iter()
746 .cloned()
747 .map(AccountStorageMapDetails::limit_exceeded)
748 .collect(),
749 },
750 }),
751 })
752 .encoded_len();
753
754 let details = apply_all_storage_maps_response_budget(
755 BlockNumber::GENESIS,
756 &witness,
757 header,
758 None,
759 AccountVaultDetails::empty(),
760 storage_header,
761 slot_names
762 .iter()
763 .cloned()
764 .map(|slot_name| map_details_with_entries(slot_name, 8))
765 .collect(),
766 slot_names.clone(),
767 marker_only_hard_cap,
768 );
769
770 assert_eq!(details.storage_details.map_details.len(), slot_names.len());
771 assert!(
772 details
773 .storage_details
774 .map_details
775 .iter()
776 .all(|details| details.entries == StorageMapEntries::LimitExceeded)
777 );
778 assert!(
779 super::proto::rpc::AccountResponse::from(AccountResponse {
780 block_num: BlockNumber::GENESIS,
781 witness,
782 details: Some(details),
783 })
784 .encoded_len()
785 <= marker_only_hard_cap
786 );
787 }
788
789 #[test]
790 fn all_storage_maps_budget_keeps_entries_that_fit() {
791 let account_id = account_id();
792 let slot_1 = StorageSlotName::mock(1);
793 let details = apply_all_storage_maps_response_budget(
794 BlockNumber::GENESIS,
795 &account_witness(account_id),
796 account_header(account_id),
797 None,
798 AccountVaultDetails::empty(),
799 storage_header(),
800 vec![map_details(slot_1.clone(), Word::from([1u32, 0, 0, 0]))],
801 vec![slot_1.clone()],
802 usize::MAX,
803 );
804
805 assert_eq!(details.storage_details.map_details.len(), 1);
806 assert_eq!(details.storage_details.map_details[0].slot_name, slot_1);
807 assert!(matches!(
808 details.storage_details.map_details[0].entries,
809 StorageMapEntries::AllEntries(_)
810 ));
811 }
812}