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 pub max_request_bytes: usize,
209}
210
211impl Default for BulkCallConfig {
212 fn default() -> Self {
213 Self {
214 max_slots_per_call: 10_000,
215 max_targets_per_call: 250,
216 max_concurrent_calls: 4,
217 point_read_threshold: 2,
218 pre_shanghai_extractor: false,
219 dispatch: CallDispatch::PerCall,
220 max_slots_per_request: 25_000,
221 max_request_bytes: 2_400_000,
222 }
223 }
224}
225
226impl BulkCallConfig {
227 fn normalized(self) -> Self {
228 const JSON_RPC_ENVELOPE_RESERVE: usize = 512;
229 const JSON_HEX_BYTES_PER_SLOT: usize = 64;
230 let byte_limited_slots = self
231 .max_request_bytes
232 .saturating_sub(JSON_RPC_ENVELOPE_RESERVE)
233 .checked_div(JSON_HEX_BYTES_PER_SLOT)
234 .unwrap_or(0)
235 .max(1);
236 Self {
237 max_slots_per_call: self.max_slots_per_call.max(1).min(byte_limited_slots),
238 max_targets_per_call: self.max_targets_per_call.max(1),
239 max_concurrent_calls: self.max_concurrent_calls.max(1),
240 max_slots_per_request: self.max_slots_per_request.max(1).min(byte_limited_slots),
241 max_request_bytes: self
242 .max_request_bytes
243 .max(JSON_RPC_ENVELOPE_RESERVE.saturating_add(JSON_HEX_BYTES_PER_SLOT)),
244 ..self
245 }
246 }
247
248 fn extractor(&self) -> Bytes {
249 if self.pre_shanghai_extractor {
250 Bytes::from_static(STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI)
251 } else {
252 Bytes::from_static(STORAGE_EXTRACTOR_CODE)
253 }
254 }
255}
256
257pub fn pack_slots_calldata(slots: &[U256]) -> Bytes {
260 let mut out = Vec::with_capacity(slots.len() * 32);
261 for slot in slots {
262 out.extend_from_slice(&slot.to_be_bytes::<32>());
263 }
264 out.into()
265}
266
267pub fn decode_packed_values(data: &[u8], expected: usize) -> Option<Vec<U256>> {
273 if data.len() != expected * 32 {
274 return None;
275 }
276 Some(data.chunks_exact(32).map(U256::from_be_slice).collect())
277}
278
279pub fn encode_multi_target_calldata(targets: &[(Address, Vec<U256>)]) -> Bytes {
284 let calls: Vec<IMulticall3::Call3> = targets
285 .iter()
286 .map(|(target, slots)| IMulticall3::Call3 {
287 target: *target,
288 allowFailure: true,
289 callData: pack_slots_calldata(slots),
290 })
291 .collect();
292 IMulticall3::aggregate3Call { calls }.abi_encode().into()
293}
294
295pub fn decode_multi_target_response(
298 targets: &[(Address, Vec<U256>)],
299 response: &[u8],
300) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
301 let decoded = match IMulticall3::aggregate3Call::abi_decode_returns(response) {
302 Ok(results) if results.len() == targets.len() => results,
303 Ok(results) => {
304 return per_target_errors(targets, || {
305 StorageFetchError::custom(format!(
306 "aggregate3 returned {} results for {} extraction targets",
307 results.len(),
308 targets.len()
309 ))
310 });
311 }
312 Err(e) => {
313 return per_target_errors(targets, || {
314 StorageFetchError::custom(format!("failed to decode aggregate3 response: {e}"))
315 });
316 }
317 };
318
319 let mut out = Vec::with_capacity(targets.iter().map(|(_, s)| s.len()).sum());
320 for ((target, slots), result) in targets.iter().zip(decoded) {
321 if !result.success {
322 out.extend(slots.iter().map(|slot| {
323 (
324 *target,
325 *slot,
326 Err(StorageFetchError::custom(
327 "extractor subcall failed (allowFailure=true); the target may be a precompile",
328 )),
329 )
330 }));
331 continue;
332 }
333 match decode_packed_values(&result.returnData, slots.len()) {
334 Some(values) => out.extend(
335 slots
336 .iter()
337 .zip(values)
338 .map(|(slot, value)| (*target, *slot, Ok(value))),
339 ),
340 None => out.extend(slots.iter().map(|slot| {
341 (
342 *target,
343 *slot,
344 Err(StorageFetchError::custom(format!(
345 "extractor at {target} returned {} bytes, expected {}",
346 result.returnData.len(),
347 slots.len() * 32
348 ))),
349 )
350 })),
351 }
352 }
353 out
354}
355
356fn per_target_errors(
357 targets: &[(Address, Vec<U256>)],
358 make: impl Fn() -> StorageFetchError,
359) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
360 targets
361 .iter()
362 .flat_map(|(target, slots)| slots.iter().map(|slot| (*target, *slot, Err(make()))))
363 .collect()
364}
365
366#[derive(Debug, Clone, PartialEq, Eq)]
368enum CallPlan {
369 Single { target: Address, slots: Vec<U256> },
371 Multi { targets: Vec<(Address, Vec<U256>)> },
373}
374
375impl CallPlan {
376 fn request_slot_count(&self) -> usize {
377 match self {
378 Self::Single { slots, .. } => slots.len(),
379 Self::Multi { targets } => targets.iter().map(|(_, s)| s.len()).sum(),
380 }
381 }
382}
383
384fn plan_calls(requests: &[(Address, U256)], config: &BulkCallConfig) -> Vec<CallPlan> {
393 let mut order: Vec<Address> = Vec::new();
394 let mut groups: HashMap<Address, Vec<U256>> = HashMap::new();
395 for (address, slot) in requests {
396 groups
397 .entry(*address)
398 .or_insert_with(|| {
399 order.push(*address);
400 Vec::new()
401 })
402 .push(*slot);
403 }
404
405 let mut plans = Vec::new();
406 let mut packable: Vec<(Address, Vec<U256>)> = Vec::new();
407 for address in order {
408 let slots = groups.remove(&address).expect("grouped above");
409 for chunk in slots.chunks(config.max_slots_per_call) {
410 let full = chunk.len() == config.max_slots_per_call;
411 if full || address == MULTICALL3_ADDRESS {
416 plans.push(CallPlan::Single {
417 target: address,
418 slots: chunk.to_vec(),
419 });
420 } else {
421 packable.push((address, chunk.to_vec()));
422 }
423 }
424 }
425
426 let mut current: Vec<(Address, Vec<U256>)> = Vec::new();
428 let mut current_slots = 0usize;
429 let flush =
430 |current: &mut Vec<(Address, Vec<U256>)>, plans: &mut Vec<CallPlan>| match current.len() {
431 0 => {}
432 1 => {
433 let (target, slots) = current.pop().expect("len checked");
434 plans.push(CallPlan::Single { target, slots });
435 }
436 _ => plans.push(CallPlan::Multi {
437 targets: std::mem::take(current),
438 }),
439 };
440 for (address, slots) in packable {
441 let would_overflow = current_slots + slots.len() > config.max_slots_per_call
442 || current.len() >= config.max_targets_per_call;
443 if !current.is_empty() && would_overflow {
444 flush(&mut current, &mut plans);
445 current_slots = 0;
446 }
447 current_slots += slots.len();
448 current.push((address, slots));
449 }
450 flush(&mut current, &mut plans);
451
452 plans
453}
454
455fn overrides_for_plan(plan: &CallPlan, extractor: &Bytes) -> StateOverride {
458 let mut overrides = StateOverride::default();
459 match plan {
460 CallPlan::Single { target, .. } => {
461 overrides.insert(
462 *target,
463 AccountOverride::default().with_code(extractor.clone()),
464 );
465 }
466 CallPlan::Multi { targets } => {
467 overrides.insert(
468 MULTICALL3_ADDRESS,
469 AccountOverride::default().with_code(multicall3_runtime_code().clone()),
470 );
471 for (target, _) in targets {
472 overrides.insert(
473 *target,
474 AccountOverride::default().with_code(extractor.clone()),
475 );
476 }
477 }
478 }
479 overrides
480}
481
482fn plan_call_parts(plan: &CallPlan) -> (Address, Bytes) {
484 match plan {
485 CallPlan::Single { target, slots } => (*target, pack_slots_calldata(slots)),
486 CallPlan::Multi { targets } => (MULTICALL3_ADDRESS, encode_multi_target_calldata(targets)),
487 }
488}
489
490fn decode_plan_response(
492 plan: &CallPlan,
493 bytes: &[u8],
494) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
495 match plan {
496 CallPlan::Single { target, slots } => match decode_packed_values(bytes, slots.len()) {
497 Some(values) => slots
498 .iter()
499 .zip(values)
500 .map(|(slot, value)| (*target, *slot, Ok(value)))
501 .collect(),
502 None => slots
503 .iter()
504 .map(|slot| {
505 (
506 *target,
507 *slot,
508 Err(StorageFetchError::custom(format!(
509 "extractor at {target} returned {} bytes, expected {} — the \
510 provider may not support eth_call state overrides, or the \
511 target is a precompile",
512 bytes.len(),
513 slots.len() * 32
514 ))),
515 )
516 })
517 .collect(),
518 },
519 CallPlan::Multi { targets } => decode_multi_target_response(targets, bytes),
520 }
521}
522
523fn plan_error_results(
525 plan: &CallPlan,
526 err: StorageFetchError,
527) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
528 match plan {
529 CallPlan::Single { target, slots } => slots
530 .iter()
531 .map(|slot| (*target, *slot, Err(err.clone())))
532 .collect(),
533 CallPlan::Multi { targets } => targets
534 .iter()
535 .flat_map(|(target, slots)| {
536 slots.iter().map({
537 let err = err.clone();
538 move |slot| (*target, *slot, Err(err.clone()))
539 })
540 })
541 .collect(),
542 }
543}
544
545async fn execute_plan<P: Provider<AnyNetwork>>(
546 provider: &P,
547 block: BlockId,
548 plan: CallPlan,
549 extractor: &Bytes,
550) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
551 let overrides = overrides_for_plan(&plan, extractor);
552 let (to, data) = plan_call_parts(&plan);
553 let tx = TransactionRequest::default().to(to).input(data.into());
554
555 let response: Result<Bytes, _> = provider
556 .client()
557 .request("eth_call", (tx, block, overrides))
558 .await;
559
560 match response {
561 Ok(bytes) => decode_plan_response(&plan, &bytes),
562 Err(e) => plan_error_results(&plan, StorageFetchError::provider("eth_call", &e)),
563 }
564}
565
566#[derive(Debug, serde::Deserialize)]
569struct CallManyEntry {
570 value: Option<Bytes>,
571 error: Option<serde_json::Value>,
572}
573
574async fn execute_plans_call_many<P: Provider<AnyNetwork>>(
581 provider: &P,
582 number: alloy_eips::BlockNumberOrTag,
583 plans: &[CallPlan],
584 extractor: &Bytes,
585) -> Result<Vec<(Address, U256, StorageFetchResult<U256>)>, StorageFetchError> {
586 let mut overrides = StateOverride::default();
591 let mut transactions = Vec::with_capacity(plans.len());
592 for plan in plans {
593 for (address, account) in overrides_for_plan(plan, extractor) {
594 overrides.insert(address, account);
595 }
596 let (to, data) = plan_call_parts(plan);
597 transactions.push(serde_json::json!({ "to": to, "data": data }));
598 }
599
600 let bundles = serde_json::json!([{ "transactions": transactions }]);
601 let context = serde_json::json!({ "blockNumber": number, "transactionIndex": -1 });
602 let response: Vec<Vec<CallManyEntry>> = provider
603 .client()
604 .request("eth_callMany", (bundles, context, overrides))
605 .await
606 .map_err(|e| StorageFetchError::provider("eth_callMany", &e))?;
607
608 let entries: Vec<CallManyEntry> = response.into_iter().flatten().collect();
609 if entries.len() != plans.len() {
610 return Err(StorageFetchError::custom(format!(
611 "eth_callMany returned {} results for {} bundled calls",
612 entries.len(),
613 plans.len()
614 )));
615 }
616
617 let mut out = Vec::new();
618 for (plan, entry) in plans.iter().zip(entries) {
619 match entry.value {
620 Some(bytes) => out.extend(decode_plan_response(plan, &bytes)),
621 None => {
622 let detail = entry
623 .error
624 .map(|e| e.to_string())
625 .unwrap_or_else(|| "no value returned".to_string());
626 out.extend(plan_error_results(
627 plan,
628 StorageFetchError::custom(format!("eth_callMany transaction failed: {detail}")),
629 ));
630 }
631 }
632 }
633 Ok(out)
634}
635
636fn group_plans_for_call_many(
639 plans: Vec<CallPlan>,
640 max_slots_per_request: usize,
641) -> Vec<Vec<CallPlan>> {
642 let mut requests: Vec<Vec<CallPlan>> = Vec::new();
643 let mut current: Vec<CallPlan> = Vec::new();
644 let mut current_slots = 0usize;
645 for plan in plans {
646 let slots = plan.request_slot_count();
647 if !current.is_empty() && current_slots + slots > max_slots_per_request {
648 requests.push(std::mem::take(&mut current));
649 current_slots = 0;
650 }
651 current_slots += slots;
652 current.push(plan);
653 }
654 if !current.is_empty() {
655 requests.push(current);
656 }
657 requests
658}
659
660pub async fn fetch_slots_bulk<P: Provider<AnyNetwork>>(
672 provider: &P,
673 requests: Vec<(Address, U256)>,
674 block: BlockId,
675 config: BulkCallConfig,
676) -> Vec<(Address, U256, StorageFetchResult<U256>)> {
677 let config = config.normalized();
678 if requests.is_empty() {
679 return Vec::new();
680 }
681 let extractor = config.extractor();
682 let plans = plan_calls(&requests, &config);
683 debug!(
684 slots = requests.len(),
685 calls = plans.len(),
686 dispatch = ?config.dispatch,
687 "bulk storage extraction dispatch"
688 );
689
690 let extractor = &extractor;
691 let call_many_number = match (config.dispatch, block) {
694 (CallDispatch::CallMany, BlockId::Number(number)) => Some(number),
695 _ => None,
696 };
697
698 let Some(number) = call_many_number else {
699 let results: Vec<Vec<_>> = stream::iter(
700 plans
701 .into_iter()
702 .map(|plan| execute_plan(provider, block, plan, extractor)),
703 )
704 .buffer_unordered(config.max_concurrent_calls)
705 .collect()
706 .await;
707 return results.into_iter().flatten().collect();
708 };
709
710 let (conflicting, bundleable): (Vec<_>, Vec<_>) = plans.into_iter().partition(
714 |plan| matches!(plan, CallPlan::Single { target, .. } if *target == MULTICALL3_ADDRESS),
715 );
716 let groups = group_plans_for_call_many(bundleable, config.max_slots_per_request);
717 let group_futs = groups.into_iter().map(|group| async move {
718 match execute_plans_call_many(provider, number, &group, extractor).await {
719 Ok(results) => results,
720 Err(e) => {
721 warn!(
725 error = %e,
726 chunks = group.len(),
727 "eth_callMany dispatch failed; re-dispatching per-call"
728 );
729 let mut results = Vec::new();
730 for plan in group {
731 results.extend(execute_plan(provider, block, plan, extractor).await);
732 }
733 results
734 }
735 }
736 });
737 let mut results: Vec<_> = stream::iter(group_futs)
738 .buffer_unordered(config.max_concurrent_calls)
739 .collect::<Vec<Vec<_>>>()
740 .await
741 .into_iter()
742 .flatten()
743 .collect();
744 for plan in conflicting {
745 results.extend(execute_plan(provider, block, plan, extractor).await);
746 }
747 results
748}
749
750pub fn planned_call_count(requests: &[(Address, U256)], config: &BulkCallConfig) -> usize {
756 plan_calls(requests, &config.normalized()).len()
757}
758
759#[derive(Clone, Debug)]
775pub struct BulkFetcherStatus {
776 consecutive_failures: Arc<std::sync::atomic::AtomicUsize>,
777 latch_threshold: usize,
778}
779
780impl BulkFetcherStatus {
781 pub fn consecutive_override_failures(&self) -> usize {
787 self.consecutive_failures
788 .load(std::sync::atomic::Ordering::Relaxed)
789 }
790
791 pub fn latch_threshold(&self) -> usize {
793 self.latch_threshold
794 }
795
796 pub fn fallback_latched(&self) -> bool {
802 self.consecutive_override_failures() >= self.latch_threshold
803 }
804}
805
806pub fn bulk_call_storage_fetcher<P: Provider<AnyNetwork> + 'static>(
818 provider: Arc<P>,
819 config: BulkCallConfig,
820) -> StorageBatchFetchFn {
821 make_fetcher(provider, config, None).0
822}
823
824pub fn bulk_call_storage_fetcher_with_fallback<P: Provider<AnyNetwork> + 'static>(
834 provider: Arc<P>,
835 config: BulkCallConfig,
836 fallback: StorageBatchFetchFn,
837) -> StorageBatchFetchFn {
838 make_fetcher(provider, config, Some(fallback)).0
839}
840
841pub fn bulk_call_storage_fetcher_with_status<P: Provider<AnyNetwork> + 'static>(
851 provider: Arc<P>,
852 config: BulkCallConfig,
853 fallback: StorageBatchFetchFn,
854) -> (StorageBatchFetchFn, BulkFetcherStatus) {
855 make_fetcher(provider, config, Some(fallback))
856}
857
858fn make_fetcher<P: Provider<AnyNetwork> + 'static>(
859 provider: Arc<P>,
860 config: BulkCallConfig,
861 fallback: Option<StorageBatchFetchFn>,
862) -> (StorageBatchFetchFn, BulkFetcherStatus) {
863 let config = config.normalized();
864 const OVERRIDE_FAILURE_LATCH: usize = 2;
871 let consecutive_failures = Arc::new(std::sync::atomic::AtomicUsize::new(0));
872 let status = BulkFetcherStatus {
873 consecutive_failures: Arc::clone(&consecutive_failures),
874 latch_threshold: OVERRIDE_FAILURE_LATCH,
875 };
876 let fetcher: StorageBatchFetchFn =
877 Arc::new(move |requests: Vec<(Address, U256)>, block: BlockId| {
878 use std::sync::atomic::Ordering;
879 if requests.is_empty() {
880 return Vec::new();
881 }
882 if let Some(fallback) = &fallback
883 && (requests.len() < config.point_read_threshold
884 || consecutive_failures.load(Ordering::Relaxed) >= OVERRIDE_FAILURE_LATCH)
885 {
886 return fallback(requests, block);
887 }
888
889 let bulk_results = match block_in_place_handle() {
896 Ok(handle) => tokio::task::block_in_place(|| {
897 handle.block_on(fetch_slots_bulk(provider.as_ref(), requests, block, config))
898 }),
899 Err(e) => requests
900 .into_iter()
901 .map(|(addr, slot)| (addr, slot, Err(StorageFetchError::Runtime(e.clone()))))
902 .collect(),
903 };
904
905 let Some(fallback) = &fallback else {
906 return bulk_results;
907 };
908
909 if bulk_results.iter().any(|(_, _, r)| r.is_ok()) {
912 consecutive_failures.store(0, Ordering::Relaxed);
913 } else if bulk_results
914 .iter()
915 .any(|(_, _, r)| matches!(r, Err(StorageFetchError::Provider { .. })))
916 {
917 let streak = consecutive_failures.fetch_add(1, Ordering::Relaxed) + 1;
918 if streak == OVERRIDE_FAILURE_LATCH {
919 warn!(
920 streak,
921 "bulk storage extraction failed consecutive batches with provider errors; \
922 latching this fetcher to the point-read fallback (install a fresh fetcher \
923 to retry bulk extraction)"
924 );
925 }
926 }
927
928 let mut repaired = Vec::with_capacity(bulk_results.len());
931 let mut failed: Vec<(Address, U256)> = Vec::new();
932 for (addr, slot, result) in bulk_results {
933 match result {
934 Ok(value) => repaired.push((addr, slot, Ok(value))),
935 Err(_) => failed.push((addr, slot)),
936 }
937 }
938 if !failed.is_empty() {
939 warn!(
940 failed = failed.len(),
941 "bulk storage extraction failed for some slots; repairing via fallback fetcher"
942 );
943 repaired.extend(fallback(failed, block));
944 }
945 repaired
946 });
947 (fetcher, status)
948}
949
950#[derive(Debug, Clone, PartialEq, Eq)]
972pub struct StorageProgram {
973 pub target: Address,
976 pub code: Bytes,
978 pub calldata: Bytes,
980}
981
982pub async fn run_storage_program<P: Provider<AnyNetwork>>(
984 provider: &P,
985 block: BlockId,
986 program: &StorageProgram,
987) -> StorageFetchResult<Bytes> {
988 let mut overrides = StateOverride::default();
989 overrides.insert(
990 program.target,
991 AccountOverride::default().with_code(program.code.clone()),
992 );
993 let tx = TransactionRequest::default()
994 .to(program.target)
995 .input(program.calldata.clone().into());
996 provider
997 .client()
998 .request("eth_call", (tx, block, overrides))
999 .await
1000 .map_err(|e| StorageFetchError::provider("eth_call", &e))
1001}
1002
1003pub async fn run_storage_programs<P: Provider<AnyNetwork>>(
1010 provider: &P,
1011 block: BlockId,
1012 programs: &[StorageProgram],
1013) -> Vec<StorageFetchResult<Bytes>> {
1014 let mut seen = std::collections::HashSet::new();
1015 let mut bundle: Vec<usize> = Vec::new();
1016 let mut individual: Vec<usize> = Vec::new();
1017 for (index, program) in programs.iter().enumerate() {
1018 if program.target != MULTICALL3_ADDRESS && seen.insert(program.target) {
1019 bundle.push(index);
1020 } else {
1021 individual.push(index);
1022 }
1023 }
1024 if bundle.len() == 1 {
1026 individual.append(&mut bundle);
1027 }
1028
1029 let mut out: Vec<Option<StorageFetchResult<Bytes>>> = vec![None; programs.len()];
1030
1031 if !bundle.is_empty() {
1032 let mut overrides = StateOverride::default();
1033 overrides.insert(
1034 MULTICALL3_ADDRESS,
1035 AccountOverride::default().with_code(multicall3_runtime_code().clone()),
1036 );
1037 let calls: Vec<IMulticall3::Call3> = bundle
1038 .iter()
1039 .map(|&index| {
1040 let program = &programs[index];
1041 overrides.insert(
1042 program.target,
1043 AccountOverride::default().with_code(program.code.clone()),
1044 );
1045 IMulticall3::Call3 {
1046 target: program.target,
1047 allowFailure: true,
1048 callData: program.calldata.clone(),
1049 }
1050 })
1051 .collect();
1052 let data: Bytes = IMulticall3::aggregate3Call { calls }.abi_encode().into();
1053 let tx = TransactionRequest::default()
1054 .to(MULTICALL3_ADDRESS)
1055 .input(data.into());
1056 let response: Result<Bytes, _> = provider
1057 .client()
1058 .request("eth_call", (tx, block, overrides))
1059 .await;
1060 match response
1061 .map_err(|e| StorageFetchError::provider("eth_call", &e))
1062 .and_then(|bytes| {
1063 IMulticall3::aggregate3Call::abi_decode_returns(&bytes).map_err(|e| {
1064 StorageFetchError::custom(format!("failed to decode aggregate3 response: {e}"))
1065 })
1066 }) {
1067 Ok(results) if results.len() == bundle.len() => {
1068 for (&index, result) in bundle.iter().zip(results) {
1069 out[index] = Some(if result.success {
1070 Ok(result.returnData)
1071 } else {
1072 Err(StorageFetchError::custom(
1073 "storage program subcall failed (allowFailure=true)",
1074 ))
1075 });
1076 }
1077 }
1078 Ok(results) => {
1079 let err = StorageFetchError::custom(format!(
1080 "aggregate3 returned {} results for {} programs",
1081 results.len(),
1082 bundle.len()
1083 ));
1084 for &index in &bundle {
1085 out[index] = Some(Err(err.clone()));
1086 }
1087 }
1088 Err(err) => {
1089 for &index in &bundle {
1090 out[index] = Some(Err(err.clone()));
1091 }
1092 }
1093 }
1094 }
1095
1096 for index in individual {
1097 out[index] = Some(run_storage_program(provider, block, &programs[index]).await);
1098 }
1099
1100 out.into_iter()
1101 .map(|entry| entry.expect("every program resolved"))
1102 .collect()
1103}
1104
1105pub const ACCOUNT_FIELDS_EXTRACTOR_CODE: &[u8] =
1125 &hex!("5f5b803614602057803580318260011b523f8160011b602001526020016001565b3660011b5ff3");
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1130pub struct AccountFieldsSample {
1131 pub balance: U256,
1133 pub code_hash: B256,
1136}
1137
1138pub async fn fetch_account_fields_bulk<P: Provider<AnyNetwork>>(
1147 provider: &P,
1148 addresses: &[Address],
1149 block: BlockId,
1150) -> StorageFetchResult<Vec<(Address, AccountFieldsSample)>> {
1151 if addresses.is_empty() {
1152 return Ok(Vec::new());
1153 }
1154 let mut calldata = Vec::with_capacity(addresses.len() * 32);
1155 for address in addresses {
1156 calldata.extend_from_slice(&[0u8; 12]);
1157 calldata.extend_from_slice(address.as_slice());
1158 }
1159 let program = StorageProgram {
1160 target: MULTICALL3_ADDRESS,
1161 code: Bytes::from_static(ACCOUNT_FIELDS_EXTRACTOR_CODE),
1162 calldata: calldata.into(),
1163 };
1164 let bytes = run_storage_program(provider, block, &program).await?;
1165 if bytes.len() != addresses.len() * 64 {
1166 return Err(StorageFetchError::custom(format!(
1167 "account-fields extractor returned {} bytes, expected {}",
1168 bytes.len(),
1169 addresses.len() * 64
1170 )));
1171 }
1172 Ok(addresses
1173 .iter()
1174 .enumerate()
1175 .map(|(i, address)| {
1176 (
1177 *address,
1178 AccountFieldsSample {
1179 balance: U256::from_be_slice(&bytes[i * 64..i * 64 + 32]),
1180 code_hash: B256::from_slice(&bytes[i * 64 + 32..i * 64 + 64]),
1181 },
1182 )
1183 })
1184 .collect())
1185}
1186
1187pub const BLOCK_CONTEXT_EXTRACTOR_CODE: &[u8] =
1193 &hex!("435f52426020524860405241606052446080524560a0524660c05260e05ff3");
1194
1195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1198pub struct BlockContextSample {
1199 pub number: u64,
1201 pub timestamp: u64,
1203 pub basefee: U256,
1205 pub coinbase: Address,
1207 pub prevrandao: B256,
1209 pub gas_limit: u64,
1211 pub chain_id: u64,
1213}
1214
1215pub async fn fetch_block_context<P: Provider<AnyNetwork>>(
1218 provider: &P,
1219 block: BlockId,
1220) -> StorageFetchResult<BlockContextSample> {
1221 let program = StorageProgram {
1222 target: MULTICALL3_ADDRESS,
1223 code: Bytes::from_static(BLOCK_CONTEXT_EXTRACTOR_CODE),
1224 calldata: Bytes::new(),
1225 };
1226 let bytes = run_storage_program(provider, block, &program).await?;
1227 if bytes.len() != 7 * 32 {
1228 return Err(StorageFetchError::custom(format!(
1229 "block-context extractor returned {} bytes, expected 224",
1230 bytes.len()
1231 )));
1232 }
1233 let word = |i: usize| U256::from_be_slice(&bytes[i * 32..(i + 1) * 32]);
1234 let to_u64 = |v: U256| u64::try_from(v).unwrap_or(u64::MAX);
1235 Ok(BlockContextSample {
1236 number: to_u64(word(0)),
1237 timestamp: to_u64(word(1)),
1238 basefee: word(2),
1239 coinbase: Address::from_slice(&bytes[3 * 32 + 12..4 * 32]),
1240 prevrandao: B256::from_slice(&bytes[4 * 32..5 * 32]),
1241 gas_limit: to_u64(word(5)),
1242 chain_id: to_u64(word(6)),
1243 })
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248 use super::*;
1249
1250 fn addr(byte: u8) -> Address {
1251 Address::repeat_byte(byte)
1252 }
1253
1254 fn cfg(max_slots: usize, max_targets: usize) -> BulkCallConfig {
1255 BulkCallConfig {
1256 max_slots_per_call: max_slots,
1257 max_targets_per_call: max_targets,
1258 ..BulkCallConfig::default()
1259 }
1260 }
1261
1262 #[test]
1263 fn pack_and_decode_roundtrip() {
1264 let slots = vec![U256::ZERO, U256::from(1u64), U256::MAX];
1265 let packed = pack_slots_calldata(&slots);
1266 assert_eq!(packed.len(), 96);
1267 assert_eq!(&packed[32..64], &U256::from(1u64).to_be_bytes::<32>());
1268 let decoded = decode_packed_values(&packed, 3).expect("exact length");
1269 assert_eq!(decoded, slots);
1270 assert!(decode_packed_values(&packed, 2).is_none());
1271 assert!(decode_packed_values(&packed[..95], 3).is_none());
1272 }
1273
1274 #[test]
1275 fn extractor_constants_are_wellformed() {
1276 assert_eq!(STORAGE_EXTRACTOR_CODE.len(), 23);
1279 assert_eq!(STORAGE_EXTRACTOR_CODE[0], 0x5f, "PUSH0 entry");
1280 assert_eq!(STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI.len(), 25);
1281 assert!(
1282 !STORAGE_EXTRACTOR_CODE_PRE_SHANGHAI.contains(&0x5f),
1283 "pre-Shanghai variant must not use PUSH0"
1284 );
1285 assert!(multicall3_runtime_code().len() > 1_000);
1286 }
1287
1288 #[test]
1289 fn planning_single_small_group() {
1290 let requests = vec![
1291 (addr(0xaa), U256::from(1u64)),
1292 (addr(0xaa), U256::from(2u64)),
1293 ];
1294 let plans = plan_calls(&requests, &cfg(100, 10));
1295 assert_eq!(
1296 plans,
1297 vec![CallPlan::Single {
1298 target: addr(0xaa),
1299 slots: vec![U256::from(1u64), U256::from(2u64)],
1300 }]
1301 );
1302 }
1303
1304 #[test]
1305 fn planning_splits_oversized_target_and_packs_remainder() {
1306 let mut requests: Vec<_> = (0..7u64).map(|i| (addr(0x01), U256::from(i))).collect();
1309 requests.push((addr(0x02), U256::from(99u64)));
1310 let plans = plan_calls(&requests, &cfg(3, 10));
1311 assert_eq!(plans.len(), 3);
1312 assert_eq!(
1313 plans[0],
1314 CallPlan::Single {
1315 target: addr(0x01),
1316 slots: (0..3u64).map(U256::from).collect(),
1317 }
1318 );
1319 assert_eq!(
1320 plans[1],
1321 CallPlan::Single {
1322 target: addr(0x01),
1323 slots: (3..6u64).map(U256::from).collect(),
1324 }
1325 );
1326 assert_eq!(
1327 plans[2],
1328 CallPlan::Multi {
1329 targets: vec![
1330 (addr(0x01), vec![U256::from(6u64)]),
1331 (addr(0x02), vec![U256::from(99u64)]),
1332 ],
1333 }
1334 );
1335 let planned: usize = plans.iter().map(CallPlan::request_slot_count).sum();
1336 assert_eq!(planned, requests.len());
1337 }
1338
1339 #[test]
1340 fn planning_respects_target_budget() {
1341 let requests: Vec<_> = (0..5u8)
1342 .map(|i| (addr(i + 1), U256::from(i as u64)))
1343 .collect();
1344 let plans = plan_calls(&requests, &cfg(100, 2));
1345 assert_eq!(plans.len(), 3);
1347 assert!(matches!(&plans[0], CallPlan::Multi { targets } if targets.len() == 2));
1348 assert!(matches!(&plans[1], CallPlan::Multi { targets } if targets.len() == 2));
1349 assert!(matches!(&plans[2], CallPlan::Single { .. }));
1350 }
1351
1352 #[test]
1353 fn planning_lone_remainder_degrades_to_single_call() {
1354 let requests: Vec<_> = (0..4u64).map(|i| (addr(0x01), U256::from(i))).collect();
1355 let plans = plan_calls(&requests, &cfg(3, 10));
1356 assert_eq!(plans.len(), 2);
1357 assert!(matches!(&plans[1], CallPlan::Single { slots, .. } if slots.len() == 1));
1358 }
1359
1360 #[test]
1361 fn planning_isolates_dispatcher_address_collision() {
1362 let requests = vec![
1363 (MULTICALL3_ADDRESS, U256::from(1u64)),
1364 (addr(0x02), U256::from(2u64)),
1365 (addr(0x03), U256::from(3u64)),
1366 ];
1367 let plans = plan_calls(&requests, &cfg(100, 10));
1368 assert_eq!(
1369 plans[0],
1370 CallPlan::Single {
1371 target: MULTICALL3_ADDRESS,
1372 slots: vec![U256::from(1u64)],
1373 }
1374 );
1375 assert!(matches!(&plans[1], CallPlan::Multi { targets } if targets.len() == 2));
1376 }
1377
1378 #[test]
1379 fn multi_target_overrides_include_dispatcher_and_extractors() {
1380 let plan = CallPlan::Multi {
1381 targets: vec![
1382 (addr(0x02), vec![U256::from(1u64)]),
1383 (addr(0x03), vec![U256::from(2u64)]),
1384 ],
1385 };
1386 let extractor = Bytes::from_static(STORAGE_EXTRACTOR_CODE);
1387 let overrides = overrides_for_plan(&plan, &extractor);
1388 assert_eq!(overrides.len(), 3);
1389 assert_eq!(
1390 overrides[&MULTICALL3_ADDRESS].code.as_ref(),
1391 Some(multicall3_runtime_code())
1392 );
1393 assert_eq!(overrides[&addr(0x02)].code.as_ref(), Some(&extractor));
1394 assert_eq!(overrides[&addr(0x03)].code.as_ref(), Some(&extractor));
1395 }
1396
1397 #[test]
1398 fn call_many_grouping_respects_request_budget() {
1399 let plans = vec![
1400 CallPlan::Single {
1401 target: addr(0x01),
1402 slots: (0..6u64).map(U256::from).collect(),
1403 },
1404 CallPlan::Single {
1405 target: addr(0x02),
1406 slots: (0..6u64).map(U256::from).collect(),
1407 },
1408 CallPlan::Single {
1409 target: addr(0x03),
1410 slots: (0..2u64).map(U256::from).collect(),
1411 },
1412 ];
1413 let groups = group_plans_for_call_many(plans, 10);
1414 assert_eq!(groups.len(), 2);
1416 assert_eq!(groups[0].len(), 1);
1417 assert_eq!(groups[1].len(), 2);
1418 let total: usize = groups
1419 .iter()
1420 .flatten()
1421 .map(CallPlan::request_slot_count)
1422 .sum();
1423 assert_eq!(total, 14);
1424 }
1425
1426 #[test]
1427 fn request_byte_budget_clamps_slot_limits() {
1428 let config = BulkCallConfig {
1429 max_slots_per_call: 25_000,
1430 max_slots_per_request: 25_000,
1431 max_request_bytes: 640_512,
1432 ..BulkCallConfig::default()
1433 }
1434 .normalized();
1435
1436 assert_eq!(config.max_slots_per_call, 10_000);
1437 assert_eq!(config.max_slots_per_request, 10_000);
1438 }
1439
1440 #[test]
1441 fn default_call_many_plan_fits_measured_request_budget_at_target_limit() {
1442 let config = BulkCallConfig {
1443 dispatch: CallDispatch::CallMany,
1444 ..BulkCallConfig::default()
1445 }
1446 .normalized();
1447 let requests: Vec<_> = (1..=config.max_targets_per_call as u64)
1448 .flat_map(|target| {
1449 let address = Address::from_word(U256::from(target).into());
1450 (0..100_u64).map(move |slot| (address, U256::from(slot)))
1451 })
1452 .collect();
1453 let plans = plan_calls(&requests, &config);
1454 let groups = group_plans_for_call_many(plans, config.max_slots_per_request);
1455 assert_eq!(groups.len(), 1);
1456
1457 let extractor = config.extractor();
1458 let mut overrides = StateOverride::default();
1459 let mut transactions = Vec::new();
1460 for plan in &groups[0] {
1461 overrides.extend(overrides_for_plan(plan, &extractor));
1462 let (to, data) = plan_call_parts(plan);
1463 transactions.push(serde_json::json!({ "to": to, "data": data }));
1464 }
1465 let bundles = serde_json::json!([{ "transactions": transactions }]);
1466 let context =
1467 serde_json::json!({ "blockNumber": "0xffffffffffffffff", "transactionIndex": -1 });
1468 let request = serde_json::json!({
1469 "jsonrpc": "2.0",
1470 "id": u64::MAX,
1471 "method": "eth_callMany",
1472 "params": [bundles, context, overrides],
1473 });
1474 let serialized = serde_json::to_vec(&request).unwrap();
1475
1476 assert!(
1477 serialized.len() <= config.max_request_bytes,
1478 "default worst-case request was {} bytes, over the {} byte planning budget",
1479 serialized.len(),
1480 config.max_request_bytes
1481 );
1482 }
1483
1484 #[test]
1485 fn multi_target_response_decodes_per_target_failures() {
1486 let targets = vec![
1487 (addr(0x02), vec![U256::from(1u64), U256::from(2u64)]),
1488 (addr(0x03), vec![U256::from(3u64)]),
1489 ];
1490 let response = IMulticall3::aggregate3Call::abi_encode_returns(&vec![
1491 IMulticall3::Result {
1492 success: true,
1493 returnData: pack_slots_calldata(&[U256::from(11u64), U256::from(22u64)]),
1494 },
1495 IMulticall3::Result {
1496 success: false,
1497 returnData: Bytes::new(),
1498 },
1499 ]);
1500 let results = decode_multi_target_response(&targets, &response);
1501 assert_eq!(results.len(), 3);
1502 assert!(matches!(results[0], (_, _, Ok(v)) if v == U256::from(11u64)));
1503 assert!(matches!(results[1], (_, _, Ok(v)) if v == U256::from(22u64)));
1504 assert!(results[2].2.is_err());
1505 }
1506}