Expand description
Bulk storage extraction over eth_call state overrides.
The default StorageBatchFetchFn issues one eth_getStorageAt per slot
(JSON-RPC-batched, but still one billed request per slot — 20 CU each on
Alchemy). This module implements the “bulk storage extraction” technique
described by Dedaub, which packs thousands of slot reads into a single
eth_call (26 CU on Alchemy, flat):
- blog: https://dedaub.com/blog/bulk-storage-extraction/
- reference implementation: https://github.com/Dedaub/storage-extractor
§Mechanism
eth_call accepts a state-override set that can replace the code at
any address while leaving its storage intact. We override the target
contract with a 23-byte handwritten extractor (STORAGE_EXTRACTOR_CODE,
Dedaub’s bytecode, credited above) that treats calldata as a raw array of
32-byte slot keys, SLOADs each one, and returns the packed values —
no function selector, no ABI:
[00] PUSH0 counter = 0
[01] JUMPDEST loop:
[02] DUP1 CALLDATASIZE EQ
[05] PUSH1 0x13 JUMPI -> exit when counter == calldatasize
[08] DUP1 CALLDATALOAD slot key at calldata[counter]
[0a] SLOAD
[0b] DUP2 MSTORE mem[counter] = value (counter doubles as mem offset)
[0d] PUSH1 0x20 ADD counter += 32
[10] PUSH1 0x01 JUMP
[13] JUMPDEST CALLDATASIZE PUSH0 RETURNMarginal cost is ~2,664 gas per slot (cold SLOAD 2,100 + calldata ~510 +
loop ~30 + memory), so a default 50M-gas eth_call fits ~18,500 slots.
BulkCallConfig::max_slots_per_call defaults to a conservative 10,000
(~27M gas), splitting larger requests across concurrent calls.
§Multi-contract batches
SLOAD reads the storage of the executing contract, so each target must
run the extractor at its own address. To read many contracts in one round
trip we additionally override MULTICALL3_ADDRESS with the canonical
Multicall3 runtime (multicall3_runtime_code) and dispatch one
aggregate3 call whose subcalls hit each overridden target. Overriding the
dispatcher code unconditionally makes the scheme work on chains — and at
historical blocks — where Multicall3 is not deployed.
§Semantics & caveats
- Results are identical to
eth_getStorageAtat the same block: absent slots (and slots of code-less accounts) read as zero. - The provider must support the state-override parameter of
eth_call(Geth-lineage nodes, Reth, Erigon, and the major hosted providers all do). Providers that reject it surface a per-slot error; install a fallback viabulk_call_storage_fetcher_with_fallbackto repair those with classic point reads. - True precompile addresses (
0x01..=0x11on mainnet) execute the precompile regardless of a code override; slots requested there fail the response-length check and surface as errors (repaired by the fallback when configured) rather than silently returning garbage. STORAGE_EXTRACTOR_CODEusesPUSH0(Shanghai). For pre-Shanghai chains setBulkCallConfig::pre_shanghai_extractorto use the equivalentSTORAGE_EXTRACTOR_CODE_PRE_SHANGHAI.
§Wiring it into a cache
Since 0.2.0 this is every provider-backed cache’s default storage
fetcher — no wiring needed. Tune it with
EvmCacheBuilder::bulk_call_config,
opt out with
StorageFetchStrategy::PointRead,
or compose it manually as below (e.g. over a custom fallback):
let provider = Arc::new(
ProviderBuilder::new()
.network::<AnyNetwork>()
.connect_http("https://example-rpc.invalid".parse()?),
);
let mut cache = EvmCache::builder(provider.clone()).build().await;
// Keep the default point-read fetcher as a repair path, then route all
// batch storage fetches through call-override bulk extraction.
let fallback = cache
.storage_batch_fetcher()
.cloned()
.expect("provider-backed cache has a default fetcher");
cache.set_storage_batch_fetcher(bulk_call_storage_fetcher_with_fallback(
provider,
BulkCallConfig::default(),
fallback,
));Structs§
- Account
Fields Sample - Balance + code hash of one account, as sampled in-EVM by
ACCOUNT_FIELDS_EXTRACTOR_CODE. - Block
Context Sample - One block’s EVM-visible context, as sampled by
BLOCK_CONTEXT_EXTRACTOR_CODE. - Bulk
Call Config - Tuning knobs for the call-override bulk storage fetcher.
- Bulk
Fetcher Status - A cheap, cloneable handle to a running bulk fetcher’s fallback state.
- Storage
Program - A caller-supplied extraction program: arbitrary bytecode injected at
targetthrough a code override and executed by oneeth_call.
Enums§
- Call
Dispatch - How planned extraction chunks are shipped to the provider.
Constants§
- ACCOUNT_
FIELDS_ EXTRACTOR_ CODE - Account-fields extractor: calldata is a contiguous array of 32-byte
left-padded addresses; the return data is
[balance, extcodehash](two words) per address, via theBALANCEandEXTCODEHASHopcodes. - BLOCK_
CONTEXT_ EXTRACTOR_ CODE - Block-context extractor: no calldata; returns seven words —
NUMBER,TIMESTAMP,BASEFEE,COINBASE,PREVRANDAO,GASLIMIT,CHAINID— straight from the EVM environment of the queried block. Piggybacks block-header context onto the same transport as slot loads without aneth_getBlockByNumber. RequiresPUSH0(Shanghai). - STORAGE_
EXTRACTOR_ CODE - Dedaub’s 23-byte storage extractor (see the module docs for the annotated
disassembly). Calldata is a contiguous array of 32-byte slot keys; the
return data is the corresponding array of 32-byte values. Requires
PUSH0(Shanghai). - STORAGE_
EXTRACTOR_ CODE_ PRE_ SHANGHAI STORAGE_EXTRACTOR_CODEwith bothPUSH0s replaced byPUSH1 0x00(jump targets re-pointed), for chains that have not activated Shanghai.
Functions§
- bulk_
call_ storage_ fetcher - Build a
StorageBatchFetchFnbacked by call-override bulk extraction. - bulk_
call_ storage_ fetcher_ with_ fallback bulk_call_storage_fetcherwith a repair path.- bulk_
call_ storage_ fetcher_ with_ status bulk_call_storage_fetcher_with_fallbackthat also returns aBulkFetcherStatushandle for observing the fallback latch at runtime.- decode_
multi_ target_ response - Decode an
aggregate3response produced byencode_multi_target_calldataback into one result tuple per requested(target, slot)pair. - decode_
packed_ values - Decode extractor return data (packed 32-byte words) into values.
- encode_
multi_ target_ calldata - ABI-encode one
aggregate3dispatch whose subcalls run the extractor at each(target, slots)pair. Subcalls useallowFailure = trueso one failing target degrades to per-target errors instead of reverting the whole batch. - fetch_
account_ fields_ bulk - Fetch balance + code hash for many accounts in one
eth_call. - fetch_
block_ context - Sample a block’s EVM context in one
eth_call(seeBLOCK_CONTEXT_EXTRACTOR_CODE). - fetch_
slots_ bulk - Fetch storage slots in bulk via
eth_callcode overrides (async core). - multicall3_
runtime_ code - Runtime bytecode of Multicall3 (
0xcA11bde05977b3631167028862bE2a173976CA11), as deployed on Ethereum mainnet. Injected as a code override atMULTICALL3_ADDRESSfor multi-contract extraction so the dispatcher exists on every chain and at every historical block. - pack_
slots_ calldata - Pack slot keys into extractor calldata: the raw concatenation of each key’s 32-byte big-endian representation (no selector, no ABI).
- planned_
call_ count - Number of
eth_calls a request set will be split into underconfig. - run_
storage_ program - Execute one
StorageProgramviaeth_calland return its raw output. - run_
storage_ programs - Execute several
StoragePrograms, batching programs with distinct targets into a single Multicall3-dispatchedeth_call.