1use std::collections::HashMap;
104use std::sync::{Arc, OnceLock};
105
106use alloy_eips::BlockId;
107use alloy_primitives::{Address, B256, Bytes, U256, hex};
108use alloy_provider::Provider;
109use alloy_provider::network::AnyNetwork;
110use alloy_rpc_types_eth::TransactionRequest;
111use alloy_rpc_types_eth::state::{AccountOverride, StateOverride};
112use alloy_sol_types::SolCall;
113use futures::stream::{self, StreamExt};
114use tracing::{debug, warn};
115
116use crate::cache::{StorageBatchFetchFn, block_in_place_handle};
117use crate::errors::{StorageFetchError, StorageFetchResult};
118use crate::multicall::{IMulticall3, MULTICALL3_ADDRESS};
119
120pub const STORAGE_EXTRACTOR_CODE: &[u8] = &hex!("5f5b80361460135780355481526020016001565b365ff3");
127
128pub const STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI: &[u8] =
131 &hex!("60005b80361460145780355481526020016002565b366000f3");
132
133pub fn multicall3_runtime_code() -> &'static Bytes {
141 static CODE: OnceLock<Bytes> = OnceLock::new();
142 CODE.get_or_init(|| {
143 let raw = include_str!("../fixtures/multicall3_runtime.hex");
144 Bytes::from(hex::decode(raw.trim()).expect("valid multicall3 runtime hex fixture"))
145 })
146}
147
148#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
150pub enum CallDispatch {
151 #[default]
155 PerCall,
156 CallMany,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub struct BulkCallConfig {
176 pub max_slots_per_call: usize,
180 pub max_targets_per_call: usize,
183 pub max_concurrent_calls: usize,
186 pub point_read_threshold: usize,
191 pub pre_shanghai_extractor: bool,
193 pub dispatch: CallDispatch,
196 pub max_slots_per_request: usize,
202}
203
204impl Default for BulkCallConfig {
205 fn default() -> Self {
206 Self {
207 max_slots_per_call: 10_000,
208 max_targets_per_call: 250,
209 max_concurrent_calls: 4,
210 point_read_threshold: 2,
211 pre_shanghai_extractor: false,
212 dispatch: CallDispatch::PerCall,
213 max_slots_per_request: 25_000,
214 }
215 }
216}
217
218impl BulkCallConfig {
219 fn normalized(self) -> Self {
220 Self {
221 max_slots_per_call: self.max_slots_per_call.max(1),
222 max_targets_per_call: self.max_targets_per_call.max(1),
223 max_concurrent_calls: self.max_concurrent_calls.max(1),
224 max_slots_per_request: self.max_slots_per_request.max(1),
225 ..self
226 }
227 }
228
229 fn extractor(&self) -> Bytes {
230 if self.pre_shanghai_extractor {
231 Bytes::from_static(STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI)
232 } else {
233 Bytes::from_static(STORAGE_EXTRACTOR_CODE)
234 }
235 }
236}
237
238pub fn pack_slots_calldata(slots: &[U256]) -> Bytes {
241 let mut out = Vec::with_capacity(slots.len() * 32);
242 for slot in slots {
243 out.extend_from_slice(&slot.to_be_bytes::<32>());
244 }
245 out.into()
246}
247
248pub fn decode_packed_values(data: &[u8], expected: usize) -> Option<Vec<U256>> {
254 if data.len() != expected * 32 {
255 return None;
256 }
257 Some(data.chunks_exact(32).map(U256::from_be_slice).collect())
258}
259
260pub fn encode_multi_target_calldata(targets: &[(Address, Vec<U256>)]) -> Bytes {
265 let calls: Vec<IMulticall3::Call3> = targets
266 .iter()
267 .map(|(target, slots)| IMulticall3::Call3 {
268 target: *target,
269 allowFailure: true,
270 callData: pack_slots_calldata(slots),
271 })
272 .collect();
273 IMulticall3::aggregate3Call { calls }.abi_encode().into()
274}
275
276pub fn decode_multi_target_response(
279 targets: &[(Address, Vec<U256>)],
280 response: &[u8],
281) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
282 let decoded = match IMulticall3::aggregate3Call::abi_decode_returns(response) {
283 Ok(results) if results.len() == targets.len() => results,
284 Ok(results) => {
285 return per_target_errors(targets, || {
286 StorageFetchError::custom(format!(
287 "aggregate3 returned {} results for {} extraction targets",
288 results.len(),
289 targets.len()
290 ))
291 });
292 }
293 Err(e) => {
294 return per_target_errors(targets, || {
295 StorageFetchError::custom(format!("failed to decode aggregate3 response: {e}"))
296 });
297 }
298 };
299
300 let mut out = Vec::with_capacity(targets.iter().map(|(_, s)| s.len()).sum());
301 for ((target, slots), result) in targets.iter().zip(decoded) {
302 if !result.success {
303 out.extend(slots.iter().map(|slot| {
304 (
305 *target,
306 *slot,
307 Err(StorageFetchError::custom(
308 "extractor subcall failed (allowFailure=true); the target may be a precompile",
309 )),
310 )
311 }));
312 continue;
313 }
314 match decode_packed_values(&result.returnData, slots.len()) {
315 Some(values) => out.extend(
316 slots
317 .iter()
318 .zip(values)
319 .map(|(slot, value)| (*target, *slot, Ok(value))),
320 ),
321 None => out.extend(slots.iter().map(|slot| {
322 (
323 *target,
324 *slot,
325 Err(StorageFetchError::custom(format!(
326 "extractor at {target} returned {} bytes, expected {}",
327 result.returnData.len(),
328 slots.len() * 32
329 ))),
330 )
331 })),
332 }
333 }
334 out
335}
336
337fn per_target_errors(
338 targets: &[(Address, Vec<U256>)],
339 make: impl Fn() -> StorageFetchError,
340) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
341 targets
342 .iter()
343 .flat_map(|(target, slots)| slots.iter().map(|slot| (*target, *slot, Err(make()))))
344 .collect()
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
349enum CallPlan {
350 Single { target: Address, slots: Vec<U256> },
352 Multi { targets: Vec<(Address, Vec<U256>)> },
354}
355
356impl CallPlan {
357 fn request_slot_count(&self) -> usize {
358 match self {
359 Self::Single { slots, .. } => slots.len(),
360 Self::Multi { targets } => targets.iter().map(|(_, s)| s.len()).sum(),
361 }
362 }
363}
364
365fn plan_calls(requests: &[(Address, U256)], config: &BulkCallConfig) -> Vec<CallPlan> {
374 let mut order: Vec<Address> = Vec::new();
375 let mut groups: HashMap<Address, Vec<U256>> = HashMap::new();
376 for (address, slot) in requests {
377 groups
378 .entry(*address)
379 .or_insert_with(|| {
380 order.push(*address);
381 Vec::new()
382 })
383 .push(*slot);
384 }
385
386 let mut plans = Vec::new();
387 let mut packable: Vec<(Address, Vec<U256>)> = Vec::new();
388 for address in order {
389 let slots = groups.remove(&address).expect("grouped above");
390 for chunk in slots.chunks(config.max_slots_per_call) {
391 let full = chunk.len() == config.max_slots_per_call;
392 if full || address == MULTICALL3_ADDRESS {
397 plans.push(CallPlan::Single {
398 target: address,
399 slots: chunk.to_vec(),
400 });
401 } else {
402 packable.push((address, chunk.to_vec()));
403 }
404 }
405 }
406
407 let mut current: Vec<(Address, Vec<U256>)> = Vec::new();
409 let mut current_slots = 0usize;
410 let flush =
411 |current: &mut Vec<(Address, Vec<U256>)>, plans: &mut Vec<CallPlan>| match current.len() {
412 0 => {}
413 1 => {
414 let (target, slots) = current.pop().expect("len checked");
415 plans.push(CallPlan::Single { target, slots });
416 }
417 _ => plans.push(CallPlan::Multi {
418 targets: std::mem::take(current),
419 }),
420 };
421 for (address, slots) in packable {
422 let would_overflow = current_slots + slots.len() > config.max_slots_per_call
423 || current.len() >= config.max_targets_per_call;
424 if !current.is_empty() && would_overflow {
425 flush(&mut current, &mut plans);
426 current_slots = 0;
427 }
428 current_slots += slots.len();
429 current.push((address, slots));
430 }
431 flush(&mut current, &mut plans);
432
433 plans
434}
435
436fn overrides_for_plan(plan: &CallPlan, extractor: &Bytes) -> StateOverride {
439 let mut overrides = StateOverride::default();
440 match plan {
441 CallPlan::Single { target, .. } => {
442 overrides.insert(
443 *target,
444 AccountOverride::default().with_code(extractor.clone()),
445 );
446 }
447 CallPlan::Multi { targets } => {
448 overrides.insert(
449 MULTICALL3_ADDRESS,
450 AccountOverride::default().with_code(multicall3_runtime_code().clone()),
451 );
452 for (target, _) in targets {
453 overrides.insert(
454 *target,
455 AccountOverride::default().with_code(extractor.clone()),
456 );
457 }
458 }
459 }
460 overrides
461}
462
463fn plan_call_parts(plan: &CallPlan) -> (Address, Bytes) {
465 match plan {
466 CallPlan::Single { target, slots } => (*target, pack_slots_calldata(slots)),
467 CallPlan::Multi { targets } => (MULTICALL3_ADDRESS, encode_multi_target_calldata(targets)),
468 }
469}
470
471fn decode_plan_response(
473 plan: &CallPlan,
474 bytes: &[u8],
475) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
476 match plan {
477 CallPlan::Single { target, slots } => match decode_packed_values(bytes, slots.len()) {
478 Some(values) => slots
479 .iter()
480 .zip(values)
481 .map(|(slot, value)| (*target, *slot, Ok(value)))
482 .collect(),
483 None => slots
484 .iter()
485 .map(|slot| {
486 (
487 *target,
488 *slot,
489 Err(StorageFetchError::custom(format!(
490 "extractor at {target} returned {} bytes, expected {} — the \
491 provider may not support eth_call state overrides, or the \
492 target is a precompile",
493 bytes.len(),
494 slots.len() * 32
495 ))),
496 )
497 })
498 .collect(),
499 },
500 CallPlan::Multi { targets } => decode_multi_target_response(targets, bytes),
501 }
502}
503
504fn plan_error_results(
506 plan: &CallPlan,
507 err: StorageFetchError,
508) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
509 match plan {
510 CallPlan::Single { target, slots } => slots
511 .iter()
512 .map(|slot| (*target, *slot, Err(err.clone())))
513 .collect(),
514 CallPlan::Multi { targets } => targets
515 .iter()
516 .flat_map(|(target, slots)| {
517 slots.iter().map({
518 let err = err.clone();
519 move |slot| (*target, *slot, Err(err.clone()))
520 })
521 })
522 .collect(),
523 }
524}
525
526async fn execute_plan<P: Provider<AnyNetwork>>(
527 provider: &P,
528 block: BlockId,
529 plan: CallPlan,
530 extractor: &Bytes,
531) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
532 let overrides = overrides_for_plan(&plan, extractor);
533 let (to, data) = plan_call_parts(&plan);
534 let tx = TransactionRequest::default().to(to).input(data.into());
535
536 let response: Result<Bytes, _> = provider
537 .client()
538 .request("eth_call", (tx, block, overrides))
539 .await;
540
541 match response {
542 Ok(bytes) => decode_plan_response(&plan, &bytes),
543 Err(e) => plan_error_results(&plan, StorageFetchError::provider("eth_call", &e)),
544 }
545}
546
547#[derive(Debug, serde::Deserialize)]
550struct CallManyEntry {
551 value: Option<Bytes>,
552 error: Option<serde_json::Value>,
553}
554
555async fn execute_plans_call_many<P: Provider<AnyNetwork>>(
562 provider: &P,
563 number: alloy_eips::BlockNumberOrTag,
564 plans: &[CallPlan],
565 extractor: &Bytes,
566) -> Result<Vec<(Address, U256, StorageFetchResult<U256>)>, StorageFetchError> {
567 let mut overrides = StateOverride::default();
572 let mut transactions = Vec::with_capacity(plans.len());
573 for plan in plans {
574 for (address, account) in overrides_for_plan(plan, extractor) {
575 overrides.insert(address, account);
576 }
577 let (to, data) = plan_call_parts(plan);
578 transactions.push(serde_json::json!({ "to": to, "data": data }));
579 }
580
581 let bundles = serde_json::json!([{ "transactions": transactions }]);
582 let context = serde_json::json!({ "blockNumber": number, "transactionIndex": -1 });
583 let response: Vec<Vec<CallManyEntry>> = provider
584 .client()
585 .request("eth_callMany", (bundles, context, overrides))
586 .await
587 .map_err(|e| StorageFetchError::provider("eth_callMany", &e))?;
588
589 let entries: Vec<CallManyEntry> = response.into_iter().flatten().collect();
590 if entries.len() != plans.len() {
591 return Err(StorageFetchError::custom(format!(
592 "eth_callMany returned {} results for {} bundled calls",
593 entries.len(),
594 plans.len()
595 )));
596 }
597
598 let mut out = Vec::new();
599 for (plan, entry) in plans.iter().zip(entries) {
600 match entry.value {
601 Some(bytes) => out.extend(decode_plan_response(plan, &bytes)),
602 None => {
603 let detail = entry
604 .error
605 .map(|e| e.to_string())
606 .unwrap_or_else(|| "no value returned".to_string());
607 out.extend(plan_error_results(
608 plan,
609 StorageFetchError::custom(format!("eth_callMany transaction failed: {detail}")),
610 ));
611 }
612 }
613 }
614 Ok(out)
615}
616
617fn group_plans_for_call_many(
620 plans: Vec<CallPlan>,
621 max_slots_per_request: usize,
622) -> Vec<Vec<CallPlan>> {
623 let mut requests: Vec<Vec<CallPlan>> = Vec::new();
624 let mut current: Vec<CallPlan> = Vec::new();
625 let mut current_slots = 0usize;
626 for plan in plans {
627 let slots = plan.request_slot_count();
628 if !current.is_empty() && current_slots + slots > max_slots_per_request {
629 requests.push(std::mem::take(&mut current));
630 current_slots = 0;
631 }
632 current_slots += slots;
633 current.push(plan);
634 }
635 if !current.is_empty() {
636 requests.push(current);
637 }
638 requests
639}
640
641pub async fn fetch_slots_bulk<P: Provider<AnyNetwork>>(
653 provider: &P,
654 requests: Vec<(Address, U256)>,
655 block: BlockId,
656 config: BulkCallConfig,
657) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
658 let config = config.normalized();
659 if requests.is_empty() {
660 return Vec::new();
661 }
662 let extractor = config.extractor();
663 let plans = plan_calls(&requests, &config);
664 debug!(
665 slots = requests.len(),
666 calls = plans.len(),
667 dispatch = ?config.dispatch,
668 "bulk storage extraction dispatch"
669 );
670
671 let extractor = &extractor;
672 let call_many_number = match (config.dispatch, block) {
675 (CallDispatch::CallMany, BlockId::Number(number)) => Some(number),
676 _ => None,
677 };
678
679 let Some(number) = call_many_number else {
680 let results: Vec<Vec<_>> = stream::iter(
681 plans
682 .into_iter()
683 .map(|plan| execute_plan(provider, block, plan, extractor)),
684 )
685 .buffer_unordered(config.max_concurrent_calls)
686 .collect()
687 .await;
688 return results.into_iter().flatten().collect();
689 };
690
691 let (conflicting, bundleable): (Vec<_>, Vec<_>) = plans.into_iter().partition(
695 |plan| matches!(plan, CallPlan::Single { target, .. } if *target == MULTICALL3_ADDRESS),
696 );
697 let groups = group_plans_for_call_many(bundleable, config.max_slots_per_request);
698 let group_futs = groups.into_iter().map(|group| async move {
699 match execute_plans_call_many(provider, number, &group, extractor).await {
700 Ok(results) => results,
701 Err(e) => {
702 warn!(
706 error = %e,
707 chunks = group.len(),
708 "eth_callMany dispatch failed; re-dispatching per-call"
709 );
710 let mut results = Vec::new();
711 for plan in group {
712 results.extend(execute_plan(provider, block, plan, extractor).await);
713 }
714 results
715 }
716 }
717 });
718 let mut results: Vec<_> = stream::iter(group_futs)
719 .buffer_unordered(config.max_concurrent_calls)
720 .collect::<Vec<Vec<_>>>()
721 .await
722 .into_iter()
723 .flatten()
724 .collect();
725 for plan in conflicting {
726 results.extend(execute_plan(provider, block, plan, extractor).await);
727 }
728 results
729}
730
731pub fn planned_call_count(requests: &[(Address, U256)], config: &BulkCallConfig) -> usize {
737 plan_calls(requests, &config.normalized()).len()
738}
739
740pub fn bulk_call_storage_fetcher<P: Provider<AnyNetwork> + 'static>(
752 provider: Arc<P>,
753 config: BulkCallConfig,
754) -> StorageBatchFetchFn {
755 make_fetcher(provider, config, None)
756}
757
758pub fn bulk_call_storage_fetcher_with_fallback<P: Provider<AnyNetwork> + 'static>(
768 provider: Arc<P>,
769 config: BulkCallConfig,
770 fallback: StorageBatchFetchFn,
771) -> StorageBatchFetchFn {
772 make_fetcher(provider, config, Some(fallback))
773}
774
775fn make_fetcher<P: Provider<AnyNetwork> + 'static>(
776 provider: Arc<P>,
777 config: BulkCallConfig,
778 fallback: Option<StorageBatchFetchFn>,
779) -> StorageBatchFetchFn {
780 let config = config.normalized();
781 const OVERRIDE_FAILURE_LATCH: usize = 2;
788 let consecutive_failures = Arc::new(std::sync::atomic::AtomicUsize::new(0));
789 Arc::new(move |requests: Vec<(Address, U256)>, block: BlockId| {
790 use std::sync::atomic::Ordering;
791 if requests.is_empty() {
792 return Vec::new();
793 }
794 if let Some(fallback) = &fallback
795 && (requests.len() < config.point_read_threshold
796 || consecutive_failures.load(Ordering::Relaxed) >= OVERRIDE_FAILURE_LATCH)
797 {
798 return fallback(requests, block);
799 }
800
801 let bulk_results = match block_in_place_handle() {
808 Ok(handle) => tokio::task::block_in_place(|| {
809 handle.block_on(fetch_slots_bulk(provider.as_ref(), requests, block, config))
810 }),
811 Err(e) => requests
812 .into_iter()
813 .map(|(addr, slot)| (addr, slot, Err(StorageFetchError::Runtime(e.clone()))))
814 .collect(),
815 };
816
817 let Some(fallback) = &fallback else {
818 return bulk_results;
819 };
820
821 if bulk_results.iter().any(|(_, _, r)| r.is_ok()) {
824 consecutive_failures.store(0, Ordering::Relaxed);
825 } else if bulk_results
826 .iter()
827 .any(|(_, _, r)| matches!(r, Err(StorageFetchError::Provider { .. })))
828 {
829 let streak = consecutive_failures.fetch_add(1, Ordering::Relaxed) + 1;
830 if streak == OVERRIDE_FAILURE_LATCH {
831 warn!(
832 streak,
833 "bulk storage extraction failed consecutive batches with provider errors; \
834 latching this fetcher to the point-read fallback (install a fresh fetcher \
835 to retry bulk extraction)"
836 );
837 }
838 }
839
840 let mut repaired = Vec::with_capacity(bulk_results.len());
843 let mut failed: Vec<(Address, U256)> = Vec::new();
844 for (addr, slot, result) in bulk_results {
845 match result {
846 Ok(value) => repaired.push((addr, slot, Ok(value))),
847 Err(_) => failed.push((addr, slot)),
848 }
849 }
850 if !failed.is_empty() {
851 warn!(
852 failed = failed.len(),
853 "bulk storage extraction failed for some slots; repairing via fallback fetcher"
854 );
855 repaired.extend(fallback(failed, block));
856 }
857 repaired
858 })
859}
860
861#[derive(Debug, Clone, PartialEq, Eq)]
883pub struct StorageProgram {
884 pub target: Address,
887 pub code: Bytes,
889 pub calldata: Bytes,
891}
892
893pub async fn run_storage_program<P: Provider<AnyNetwork>>(
895 provider: &P,
896 block: BlockId,
897 program: &StorageProgram,
898) -> StorageFetchResult<Bytes> {
899 let mut overrides = StateOverride::default();
900 overrides.insert(
901 program.target,
902 AccountOverride::default().with_code(program.code.clone()),
903 );
904 let tx = TransactionRequest::default()
905 .to(program.target)
906 .input(program.calldata.clone().into());
907 provider
908 .client()
909 .request("eth_call", (tx, block, overrides))
910 .await
911 .map_err(|e| StorageFetchError::provider("eth_call", &e))
912}
913
914pub async fn run_storage_programs<P: Provider<AnyNetwork>>(
921 provider: &P,
922 block: BlockId,
923 programs: &[StorageProgram],
924) -> Vec<StorageFetchResult<Bytes>> {
925 let mut seen = std::collections::HashSet::new();
926 let mut bundle: Vec<usize> = Vec::new();
927 let mut individual: Vec<usize> = Vec::new();
928 for (index, program) in programs.iter().enumerate() {
929 if program.target != MULTICALL3_ADDRESS && seen.insert(program.target) {
930 bundle.push(index);
931 } else {
932 individual.push(index);
933 }
934 }
935 if bundle.len() == 1 {
937 individual.append(&mut bundle);
938 }
939
940 let mut out: Vec<Option<StorageFetchResult<Bytes>>> = vec![None; programs.len()];
941
942 if !bundle.is_empty() {
943 let mut overrides = StateOverride::default();
944 overrides.insert(
945 MULTICALL3_ADDRESS,
946 AccountOverride::default().with_code(multicall3_runtime_code().clone()),
947 );
948 let calls: Vec<IMulticall3::Call3> = bundle
949 .iter()
950 .map(|&index| {
951 let program = &programs[index];
952 overrides.insert(
953 program.target,
954 AccountOverride::default().with_code(program.code.clone()),
955 );
956 IMulticall3::Call3 {
957 target: program.target,
958 allowFailure: true,
959 callData: program.calldata.clone(),
960 }
961 })
962 .collect();
963 let data: Bytes = IMulticall3::aggregate3Call { calls }.abi_encode().into();
964 let tx = TransactionRequest::default()
965 .to(MULTICALL3_ADDRESS)
966 .input(data.into());
967 let response: Result<Bytes, _> = provider
968 .client()
969 .request("eth_call", (tx, block, overrides))
970 .await;
971 match response
972 .map_err(|e| StorageFetchError::provider("eth_call", &e))
973 .and_then(|bytes| {
974 IMulticall3::aggregate3Call::abi_decode_returns(&bytes).map_err(|e| {
975 StorageFetchError::custom(format!("failed to decode aggregate3 response: {e}"))
976 })
977 }) {
978 Ok(results) if results.len() == bundle.len() => {
979 for (&index, result) in bundle.iter().zip(results) {
980 out[index] = Some(if result.success {
981 Ok(result.returnData)
982 } else {
983 Err(StorageFetchError::custom(
984 "storage program subcall failed (allowFailure=true)",
985 ))
986 });
987 }
988 }
989 Ok(results) => {
990 let err = StorageFetchError::custom(format!(
991 "aggregate3 returned {} results for {} programs",
992 results.len(),
993 bundle.len()
994 ));
995 for &index in &bundle {
996 out[index] = Some(Err(err.clone()));
997 }
998 }
999 Err(err) => {
1000 for &index in &bundle {
1001 out[index] = Some(Err(err.clone()));
1002 }
1003 }
1004 }
1005 }
1006
1007 for index in individual {
1008 out[index] = Some(run_storage_program(provider, block, &programs[index]).await);
1009 }
1010
1011 out.into_iter()
1012 .map(|entry| entry.expect("every program resolved"))
1013 .collect()
1014}
1015
1016pub const ACCOUNT_FIELDS_EXTRACTOR_CODE: &[u8] =
1036 &hex!("5f5b803614602057803580318260011b523f8160011b602001526020016001565b3660011b5ff3");
1037
1038#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1041pub struct AccountFieldsSample {
1042 pub balance: U256,
1044 pub code_hash: B256,
1047}
1048
1049pub async fn fetch_account_fields_bulk<P: Provider<AnyNetwork>>(
1058 provider: &P,
1059 addresses: &[Address],
1060 block: BlockId,
1061) -> StorageFetchResult<Vec<(Address, AccountFieldsSample)>> {
1062 if addresses.is_empty() {
1063 return Ok(Vec::new());
1064 }
1065 let mut calldata = Vec::with_capacity(addresses.len() * 32);
1066 for address in addresses {
1067 calldata.extend_from_slice(&[0u8; 12]);
1068 calldata.extend_from_slice(address.as_slice());
1069 }
1070 let program = StorageProgram {
1071 target: MULTICALL3_ADDRESS,
1072 code: Bytes::from_static(ACCOUNT_FIELDS_EXTRACTOR_CODE),
1073 calldata: calldata.into(),
1074 };
1075 let bytes = run_storage_program(provider, block, &program).await?;
1076 if bytes.len() != addresses.len() * 64 {
1077 return Err(StorageFetchError::custom(format!(
1078 "account-fields extractor returned {} bytes, expected {}",
1079 bytes.len(),
1080 addresses.len() * 64
1081 )));
1082 }
1083 Ok(addresses
1084 .iter()
1085 .enumerate()
1086 .map(|(i, address)| {
1087 (
1088 *address,
1089 AccountFieldsSample {
1090 balance: U256::from_be_slice(&bytes[i * 64..i * 64 + 32]),
1091 code_hash: B256::from_slice(&bytes[i * 64 + 32..i * 64 + 64]),
1092 },
1093 )
1094 })
1095 .collect())
1096}
1097
1098pub const BLOCK_CONTEXT_EXTRACTOR_CODE: &[u8] =
1104 &hex!("435f52426020524860405241606052446080524560a0524660c05260e05ff3");
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1109pub struct BlockContextSample {
1110 pub number: u64,
1112 pub timestamp: u64,
1114 pub basefee: U256,
1116 pub coinbase: Address,
1118 pub prevrandao: B256,
1120 pub gas_limit: u64,
1122 pub chain_id: u64,
1124}
1125
1126pub async fn fetch_block_context<P: Provider<AnyNetwork>>(
1129 provider: &P,
1130 block: BlockId,
1131) -> StorageFetchResult<BlockContextSample> {
1132 let program = StorageProgram {
1133 target: MULTICALL3_ADDRESS,
1134 code: Bytes::from_static(BLOCK_CONTEXT_EXTRACTOR_CODE),
1135 calldata: Bytes::new(),
1136 };
1137 let bytes = run_storage_program(provider, block, &program).await?;
1138 if bytes.len() != 7 * 32 {
1139 return Err(StorageFetchError::custom(format!(
1140 "block-context extractor returned {} bytes, expected 224",
1141 bytes.len()
1142 )));
1143 }
1144 let word = |i: usize| U256::from_be_slice(&bytes[i * 32..(i + 1) * 32]);
1145 let to_u64 = |v: U256| u64::try_from(v).unwrap_or(u64::MAX);
1146 Ok(BlockContextSample {
1147 number: to_u64(word(0)),
1148 timestamp: to_u64(word(1)),
1149 basefee: word(2),
1150 coinbase: Address::from_slice(&bytes[3 * 32 + 12..4 * 32]),
1151 prevrandao: B256::from_slice(&bytes[4 * 32..5 * 32]),
1152 gas_limit: to_u64(word(5)),
1153 chain_id: to_u64(word(6)),
1154 })
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160
1161 fn addr(byte: u8) -> Address {
1162 Address::repeat_byte(byte)
1163 }
1164
1165 fn cfg(max_slots: usize, max_targets: usize) -> BulkCallConfig {
1166 BulkCallConfig {
1167 max_slots_per_call: max_slots,
1168 max_targets_per_call: max_targets,
1169 ..BulkCallConfig::default()
1170 }
1171 }
1172
1173 #[test]
1174 fn pack_and_decode_roundtrip() {
1175 let slots = vec![U256::ZERO, U256::from(1u64), U256::MAX];
1176 let packed = pack_slots_calldata(&slots);
1177 assert_eq!(packed.len(), 96);
1178 assert_eq!(&packed[32..64], &U256::from(1u64).to_be_bytes::<32>());
1179 let decoded = decode_packed_values(&packed, 3).expect("exact length");
1180 assert_eq!(decoded, slots);
1181 assert!(decode_packed_values(&packed, 2).is_none());
1182 assert!(decode_packed_values(&packed[..95], 3).is_none());
1183 }
1184
1185 #[test]
1186 fn extractor_constants_are_wellformed() {
1187 assert_eq!(STORAGE_EXTRACTOR_CODE.len(), 23);
1190 assert_eq!(STORAGE_EXTRACTOR_CODE[0], 0x5f, "PUSH0 entry");
1191 assert_eq!(STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI.len(), 25);
1192 assert!(
1193 !STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI.contains(&0x5f),
1194 "pre-Shanghai variant must not use PUSH0"
1195 );
1196 assert!(multicall3_runtime_code().len() > 1_000);
1197 }
1198
1199 #[test]
1200 fn planning_single_small_group() {
1201 let requests = vec![
1202 (addr(0xaa), U256::from(1u64)),
1203 (addr(0xaa), U256::from(2u64)),
1204 ];
1205 let plans = plan_calls(&requests, &cfg(100, 10));
1206 assert_eq!(
1207 plans,
1208 vec![CallPlan::Single {
1209 target: addr(0xaa),
1210 slots: vec![U256::from(1u64), U256::from(2u64)],
1211 }]
1212 );
1213 }
1214
1215 #[test]
1216 fn planning_splits_oversized_target_and_packs_remainder() {
1217 let mut requests: Vec<_> = (0..7u64).map(|i| (addr(0x01), U256::from(i))).collect();
1220 requests.push((addr(0x02), U256::from(99u64)));
1221 let plans = plan_calls(&requests, &cfg(3, 10));
1222 assert_eq!(plans.len(), 3);
1223 assert_eq!(
1224 plans[0],
1225 CallPlan::Single {
1226 target: addr(0x01),
1227 slots: (0..3u64).map(U256::from).collect(),
1228 }
1229 );
1230 assert_eq!(
1231 plans[1],
1232 CallPlan::Single {
1233 target: addr(0x01),
1234 slots: (3..6u64).map(U256::from).collect(),
1235 }
1236 );
1237 assert_eq!(
1238 plans[2],
1239 CallPlan::Multi {
1240 targets: vec![
1241 (addr(0x01), vec![U256::from(6u64)]),
1242 (addr(0x02), vec![U256::from(99u64)]),
1243 ],
1244 }
1245 );
1246 let planned: usize = plans.iter().map(CallPlan::request_slot_count).sum();
1247 assert_eq!(planned, requests.len());
1248 }
1249
1250 #[test]
1251 fn planning_respects_target_budget() {
1252 let requests: Vec<_> = (0..5u8)
1253 .map(|i| (addr(i + 1), U256::from(i as u64)))
1254 .collect();
1255 let plans = plan_calls(&requests, &cfg(100, 2));
1256 assert_eq!(plans.len(), 3);
1258 assert!(matches!(&plans[0], CallPlan::Multi { targets } if targets.len() == 2));
1259 assert!(matches!(&plans[1], CallPlan::Multi { targets } if targets.len() == 2));
1260 assert!(matches!(&plans[2], CallPlan::Single { .. }));
1261 }
1262
1263 #[test]
1264 fn planning_lone_remainder_degrades_to_single_call() {
1265 let requests: Vec<_> = (0..4u64).map(|i| (addr(0x01), U256::from(i))).collect();
1266 let plans = plan_calls(&requests, &cfg(3, 10));
1267 assert_eq!(plans.len(), 2);
1268 assert!(matches!(&plans[1], CallPlan::Single { slots, .. } if slots.len() == 1));
1269 }
1270
1271 #[test]
1272 fn planning_isolates_dispatcher_address_collision() {
1273 let requests = vec![
1274 (MULTICALL3_ADDRESS, U256::from(1u64)),
1275 (addr(0x02), U256::from(2u64)),
1276 (addr(0x03), U256::from(3u64)),
1277 ];
1278 let plans = plan_calls(&requests, &cfg(100, 10));
1279 assert_eq!(
1280 plans[0],
1281 CallPlan::Single {
1282 target: MULTICALL3_ADDRESS,
1283 slots: vec![U256::from(1u64)],
1284 }
1285 );
1286 assert!(matches!(&plans[1], CallPlan::Multi { targets } if targets.len() == 2));
1287 }
1288
1289 #[test]
1290 fn multi_target_overrides_include_dispatcher_and_extractors() {
1291 let plan = CallPlan::Multi {
1292 targets: vec![
1293 (addr(0x02), vec![U256::from(1u64)]),
1294 (addr(0x03), vec![U256::from(2u64)]),
1295 ],
1296 };
1297 let extractor = Bytes::from_static(STORAGE_EXTRACTOR_CODE);
1298 let overrides = overrides_for_plan(&plan, &extractor);
1299 assert_eq!(overrides.len(), 3);
1300 assert_eq!(
1301 overrides[&MULTICALL3_ADDRESS].code.as_ref(),
1302 Some(multicall3_runtime_code())
1303 );
1304 assert_eq!(overrides[&addr(0x02)].code.as_ref(), Some(&extractor));
1305 assert_eq!(overrides[&addr(0x03)].code.as_ref(), Some(&extractor));
1306 }
1307
1308 #[test]
1309 fn call_many_grouping_respects_request_budget() {
1310 let plans = vec![
1311 CallPlan::Single {
1312 target: addr(0x01),
1313 slots: (0..6u64).map(U256::from).collect(),
1314 },
1315 CallPlan::Single {
1316 target: addr(0x02),
1317 slots: (0..6u64).map(U256::from).collect(),
1318 },
1319 CallPlan::Single {
1320 target: addr(0x03),
1321 slots: (0..2u64).map(U256::from).collect(),
1322 },
1323 ];
1324 let groups = group_plans_for_call_many(plans, 10);
1325 assert_eq!(groups.len(), 2);
1327 assert_eq!(groups[0].len(), 1);
1328 assert_eq!(groups[1].len(), 2);
1329 let total: usize = groups
1330 .iter()
1331 .flatten()
1332 .map(CallPlan::request_slot_count)
1333 .sum();
1334 assert_eq!(total, 14);
1335 }
1336
1337 #[test]
1338 fn multi_target_response_decodes_per_target_failures() {
1339 let targets = vec![
1340 (addr(0x02), vec![U256::from(1u64), U256::from(2u64)]),
1341 (addr(0x03), vec![U256::from(3u64)]),
1342 ];
1343 let response = IMulticall3::aggregate3Call::abi_encode_returns(&vec![
1344 IMulticall3::Result {
1345 success: true,
1346 returnData: pack_slots_calldata(&[U256::from(11u64), U256::from(22u64)]),
1347 },
1348 IMulticall3::Result {
1349 success: false,
1350 returnData: Bytes::new(),
1351 },
1352 ]);
1353 let results = decode_multi_target_response(&targets, &response);
1354 assert_eq!(results.len(), 3);
1355 assert!(matches!(results[0], (_, _, Ok(v)) if v == U256::from(11u64)));
1356 assert!(matches!(results[1], (_, _, Ok(v)) if v == U256::from(22u64)));
1357 assert!(results[2].2.is_err());
1358 }
1359}