1use crate::{ParamsWithBlock, Provider, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock};
2use alloy_eips::BlockId;
3use alloy_json_rpc::{RpcError, RpcSend};
4use alloy_network::Network;
5use alloy_primitives::{keccak256, Address, Bytes, StorageKey, StorageValue, TxHash, B256, U256};
6use alloy_rpc_types_eth::{BlockNumberOrTag, EIP1186AccountProofResponse, Filter, Log};
7use alloy_transport::{TransportErrorKind, TransportResult};
8use lru::LruCache;
9use parking_lot::RwLock;
10use serde::{Deserialize, Serialize};
11use std::{io::BufReader, marker::PhantomData, num::NonZero, path::PathBuf, sync::Arc};
12#[derive(Debug, Clone)]
20pub struct CacheLayer {
21 cache: SharedCache,
23}
24
25impl CacheLayer {
26 pub fn new(max_items: u32) -> Self {
29 Self { cache: SharedCache::new(max_items) }
30 }
31
32 pub const fn max_items(&self) -> u32 {
34 self.cache.max_items()
35 }
36
37 pub fn cache(&self) -> SharedCache {
39 self.cache.clone()
40 }
41}
42
43impl<P, N> ProviderLayer<P, N> for CacheLayer
44where
45 P: Provider<N>,
46 N: Network,
47{
48 type Provider = CacheProvider<P, N>;
49
50 fn layer(&self, inner: P) -> Self::Provider {
51 CacheProvider::new(inner, self.cache())
52 }
53}
54
55#[derive(Debug, Clone)]
63pub struct CacheProvider<P, N> {
64 inner: P,
66 cache: SharedCache,
68 _pd: PhantomData<N>,
70}
71
72impl<P, N> CacheProvider<P, N>
73where
74 P: Provider<N>,
75 N: Network,
76{
77 pub const fn new(inner: P, cache: SharedCache) -> Self {
79 Self { inner, cache, _pd: PhantomData }
80 }
81}
82
83macro_rules! rpc_call_with_block {
91 ($cache:expr, $client:expr, $req:expr) => {{
92 let client =
93 $client.upgrade().ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"));
94 let cache = $cache.clone();
95 ProviderCall::BoxedFuture(Box::pin(async move {
96 let client = client?;
97
98 let result = client.request($req.method(), $req.params()).map_params(|params| {
99 ParamsWithBlock::new(params, $req.block_id.unwrap_or(BlockId::latest()))
100 });
101
102 let res = result.await?;
103 if !$req.has_block_tag() {
106 let json_str = serde_json::to_string(&res).map_err(TransportErrorKind::custom)?;
107 let hash = $req.params_hash()?;
108 let _ = cache.put(hash, json_str);
109 }
110
111 Ok(res)
112 }))
113 }};
114}
115
116macro_rules! cache_rpc_call_with_block {
122 ($cache:expr, $client:expr, $req:expr) => {{
123 if $req.has_block_tag() {
124 return rpc_call_with_block!($cache, $client, $req);
125 }
126
127 let hash = $req.params_hash().ok();
128
129 if let Some(hash) = hash {
130 if let Ok(Some(cached)) = $cache.get_deserialized(&hash) {
131 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
132 }
133 }
134
135 rpc_call_with_block!($cache, $client, $req)
136 }};
137}
138
139#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
140#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
141impl<P, N> Provider<N> for CacheProvider<P, N>
142where
143 P: Provider<N>,
144 N: Network,
145{
146 #[inline(always)]
147 fn root(&self) -> &RootProvider<N> {
148 self.inner.root()
149 }
150
151 fn get_block_receipts(
152 &self,
153 block: BlockId,
154 ) -> ProviderCall<(BlockId,), Option<Vec<N::ReceiptResponse>>> {
155 let req = RequestType::new("eth_getBlockReceipts", (block,)).with_block_id(block);
156
157 let redirect = req.has_block_tag();
158
159 if !redirect {
160 let params_hash = req.params_hash().ok();
161
162 if let Some(hash) = params_hash {
163 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
164 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
165 }
166 }
167 }
168
169 let client = self.inner.weak_client();
170 let cache = self.cache.clone();
171
172 ProviderCall::BoxedFuture(Box::pin(async move {
173 let client = client
174 .upgrade()
175 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
176
177 let result = client.request(req.method(), req.params()).await?;
178
179 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
180
181 if !redirect {
182 let hash = req.params_hash()?;
183 let _ = cache.put(hash, json_str);
184 }
185
186 Ok(result)
187 }))
188 }
189
190 fn get_code_at(&self, address: Address) -> RpcWithBlock<Address, Bytes> {
191 let client = self.inner.weak_client();
192 let cache = self.cache.clone();
193 RpcWithBlock::new_provider(move |block_id| {
194 let req = RequestType::new("eth_getCode", address).with_block_id(block_id);
195 cache_rpc_call_with_block!(cache, client, req)
196 })
197 }
198
199 async fn get_logs(&self, filter: &Filter) -> TransportResult<Vec<Log>> {
200 let req = RequestType::new("eth_getLogs", filter.clone());
201
202 let params_hash = req.params_hash().ok();
203
204 if let Some(hash) = params_hash {
205 if let Some(cached) = self.cache.get_deserialized(&hash)? {
206 return Ok(cached);
207 }
208 }
209
210 let result = self.inner.get_logs(filter).await?;
211
212 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
213
214 let hash = req.params_hash()?;
215 let _ = self.cache.put(hash, json_str);
216
217 Ok(result)
218 }
219
220 fn get_proof(
221 &self,
222 address: Address,
223 keys: Vec<StorageKey>,
224 ) -> RpcWithBlock<(Address, Vec<StorageKey>), EIP1186AccountProofResponse> {
225 let client = self.inner.weak_client();
226 let cache = self.cache.clone();
227 RpcWithBlock::new_provider(move |block_id| {
228 let req =
229 RequestType::new("eth_getProof", (address, keys.clone())).with_block_id(block_id);
230 cache_rpc_call_with_block!(cache, client, req)
231 })
232 }
233
234 fn get_storage_at(
235 &self,
236 address: Address,
237 key: U256,
238 ) -> RpcWithBlock<(Address, U256), StorageValue> {
239 let client = self.inner.weak_client();
240 let cache = self.cache.clone();
241 RpcWithBlock::new_provider(move |block_id| {
242 let req = RequestType::new("eth_getStorageAt", (address, key)).with_block_id(block_id);
243 cache_rpc_call_with_block!(cache, client, req)
244 })
245 }
246
247 fn get_transaction_by_hash(
248 &self,
249 hash: TxHash,
250 ) -> ProviderCall<(TxHash,), Option<N::TransactionResponse>> {
251 let req = RequestType::new("eth_getTransactionByHash", (hash,));
252
253 let params_hash = req.params_hash().ok();
254
255 if let Some(hash) = params_hash {
256 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
257 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
258 }
259 }
260 let client = self.inner.weak_client();
261 let cache = self.cache.clone();
262 ProviderCall::BoxedFuture(Box::pin(async move {
263 let client = client
264 .upgrade()
265 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
266 let result = client.request(req.method(), req.params()).await?;
267
268 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
269 let hash = req.params_hash()?;
270 let _ = cache.put(hash, json_str);
271
272 Ok(result)
273 }))
274 }
275
276 fn get_raw_transaction_by_hash(&self, hash: TxHash) -> ProviderCall<(TxHash,), Option<Bytes>> {
277 let req = RequestType::new("eth_getRawTransactionByHash", (hash,));
278
279 let params_hash = req.params_hash().ok();
280
281 if let Some(hash) = params_hash {
282 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
283 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
284 }
285 }
286
287 let client = self.inner.weak_client();
288 let cache = self.cache.clone();
289 ProviderCall::BoxedFuture(Box::pin(async move {
290 let client = client
291 .upgrade()
292 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
293
294 let result = client.request(req.method(), req.params()).await?;
295
296 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
297 let hash = req.params_hash()?;
298 let _ = cache.put(hash, json_str);
299
300 Ok(result)
301 }))
302 }
303
304 fn get_transaction_receipt(
305 &self,
306 hash: TxHash,
307 ) -> ProviderCall<(TxHash,), Option<N::ReceiptResponse>> {
308 let req = RequestType::new("eth_getTransactionReceipt", (hash,));
309
310 let params_hash = req.params_hash().ok();
311
312 if let Some(hash) = params_hash {
313 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
314 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
315 }
316 }
317
318 let client = self.inner.weak_client();
319 let cache = self.cache.clone();
320 ProviderCall::BoxedFuture(Box::pin(async move {
321 let client = client
322 .upgrade()
323 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
324
325 let result = client.request(req.method(), req.params()).await?;
326
327 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
328 let hash = req.params_hash()?;
329 let _ = cache.put(hash, json_str);
330
331 Ok(result)
332 }))
333 }
334}
335
336struct RequestType<Params: RpcSend> {
338 method: &'static str,
339 params: Params,
340 block_id: Option<BlockId>,
341}
342
343impl<Params: RpcSend> RequestType<Params> {
344 const fn new(method: &'static str, params: Params) -> Self {
345 Self { method, params, block_id: None }
346 }
347
348 const fn with_block_id(mut self, block_id: BlockId) -> Self {
349 self.block_id = Some(block_id);
350 self
351 }
352
353 fn params_hash(&self) -> TransportResult<B256> {
354 let hash = serde_json::to_string(&self.params())
358 .map(|p| {
359 keccak256(
360 match self.block_id {
361 Some(BlockId::Hash(rpc_block_hash)) => {
362 format!("{}{}{}", rpc_block_hash, self.method(), p)
363 }
364 Some(BlockId::Number(BlockNumberOrTag::Number(number))) => {
365 format!("{}{}{}", number, self.method(), p)
366 }
367 _ => format!("{}{}", self.method(), p),
368 }
369 .as_bytes(),
370 )
371 })
372 .map_err(RpcError::ser_err)?;
373
374 Ok(hash)
375 }
376
377 const fn method(&self) -> &'static str {
378 self.method
379 }
380
381 fn params(&self) -> Params {
382 self.params.clone()
383 }
384
385 const fn has_block_tag(&self) -> bool {
388 if let Some(block_id) = self.block_id {
389 return !matches!(
390 block_id,
391 BlockId::Hash(_) | BlockId::Number(BlockNumberOrTag::Number(_))
392 );
393 }
394 true
397 }
398}
399
400#[derive(Debug, Serialize, Deserialize)]
401struct FsCacheEntry {
402 key: B256,
404 value: String,
406}
407
408#[derive(Debug, Clone)]
410pub struct SharedCache {
411 inner: Arc<RwLock<LruCache<B256, String, alloy_primitives::map::FbBuildHasher<32>>>>,
412 max_items: NonZero<usize>,
413}
414
415impl SharedCache {
416 pub fn new(max_items: u32) -> Self {
418 let max_items = NonZero::new(max_items as usize).unwrap_or(NonZero::<usize>::MIN);
419 let inner = Arc::new(RwLock::new(LruCache::with_hasher(max_items, Default::default())));
420 Self { inner, max_items }
421 }
422
423 pub const fn max_items(&self) -> u32 {
425 self.max_items.get() as u32
426 }
427
428 pub fn put(&self, key: B256, value: String) -> TransportResult<bool> {
430 Ok(self.inner.write().put(key, value).is_some())
431 }
432
433 pub fn get(&self, key: &B256) -> Option<String> {
435 self.inner.write().get(key).cloned()
437 }
438
439 pub fn get_deserialized<T>(&self, key: &B256) -> TransportResult<Option<T>>
441 where
442 T: for<'de> Deserialize<'de>,
443 {
444 let Some(cached) = self.get(key) else { return Ok(None) };
445 let result = serde_json::from_str(&cached).map_err(TransportErrorKind::custom)?;
446 Ok(Some(result))
447 }
448
449 pub fn save_cache(&self, path: PathBuf) -> TransportResult<()> {
453 let entries: Vec<FsCacheEntry> = {
454 self.inner
455 .read()
456 .iter()
457 .map(|(key, value)| FsCacheEntry { key: *key, value: value.clone() })
458 .collect()
459 };
460 let file = std::fs::File::create(path).map_err(TransportErrorKind::custom)?;
461 serde_json::to_writer(file, &entries).map_err(TransportErrorKind::custom)?;
462 Ok(())
463 }
464
465 pub fn load_cache(&self, path: PathBuf) -> TransportResult<()> {
468 if !path.exists() {
469 return Ok(());
470 };
471 let file = std::fs::File::open(path).map_err(TransportErrorKind::custom)?;
472 let file = BufReader::new(file);
473 let entries: Vec<FsCacheEntry> =
474 serde_json::from_reader(file).map_err(TransportErrorKind::custom)?;
475 let mut cache = self.inner.write();
476 for entry in entries {
477 cache.put(entry.key, entry.value);
478 }
479
480 Ok(())
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::ProviderBuilder;
488 use alloy_network::TransactionBuilder;
489 use alloy_node_bindings::{utils::run_with_tempdir, Anvil};
490 use alloy_primitives::{bytes, hex, Bytes, FixedBytes};
491 use alloy_rpc_types_eth::{BlockId, TransactionRequest};
492
493 #[tokio::test]
494 async fn test_get_proof() {
495 run_with_tempdir("get-proof", |dir| async move {
496 let cache_layer = CacheLayer::new(100);
497 let shared_cache = cache_layer.cache();
498 let anvil = Anvil::new().block_time_f64(0.3).spawn();
499 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
500
501 let from = anvil.addresses()[0];
502 let path = dir.join("rpc-cache-proof.txt");
503
504 shared_cache.load_cache(path.clone()).unwrap();
505
506 let calldata: Bytes = "0x6080604052348015600f57600080fd5b506101f28061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633fb5c1cb146100465780638381f58a14610062578063d09de08a14610080575b600080fd5b610060600480360381019061005b91906100ee565b61008a565b005b61006a610094565b604051610077919061012a565b60405180910390f35b61008861009a565b005b8060008190555050565b60005481565b6000808154809291906100ac90610174565b9190505550565b600080fd5b6000819050919050565b6100cb816100b8565b81146100d657600080fd5b50565b6000813590506100e8816100c2565b92915050565b600060208284031215610104576101036100b3565b5b6000610112848285016100d9565b91505092915050565b610124816100b8565b82525050565b600060208201905061013f600083018461011b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061017f826100b8565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101b1576101b0610145565b5b60018201905091905056fea264697066735822122067ac0f21f648b0cacd1b7260772852ad4a0f63e2cc174168c51a6887fd5197a964736f6c634300081a0033".parse().unwrap();
507
508 let tx = TransactionRequest::default()
509 .with_from(from)
510 .with_input(calldata)
511 .with_max_fee_per_gas(1_000_000_000)
512 .with_max_priority_fee_per_gas(1_000_000)
513 .with_gas_limit(1_000_000)
514 .with_nonce(0);
515
516 let tx_receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
517
518 let counter_addr = tx_receipt.contract_address.unwrap();
519
520 let keys = vec![
521 FixedBytes::with_last_byte(0),
522 FixedBytes::with_last_byte(0x1),
523 FixedBytes::with_last_byte(0x2),
524 FixedBytes::with_last_byte(0x3),
525 FixedBytes::with_last_byte(0x4),
526 ];
527
528 let proof =
529 provider.get_proof(counter_addr, keys.clone()).block_id(1.into()).await.unwrap();
530 let proof2 = provider.get_proof(counter_addr, keys).block_id(1.into()).await.unwrap();
531
532 assert_eq!(proof, proof2);
533
534 shared_cache.save_cache(path).unwrap();
535 }).await;
536 }
537
538 #[tokio::test]
539 async fn test_get_tx_by_hash_and_receipt() {
540 run_with_tempdir("get-tx-by-hash", |dir| async move {
541 let cache_layer = CacheLayer::new(100);
542 let shared_cache = cache_layer.cache();
543 let anvil = Anvil::new().block_time_f64(0.3).spawn();
544 let provider = ProviderBuilder::new()
545 .disable_recommended_fillers()
546 .layer(cache_layer)
547 .connect_http(anvil.endpoint_url());
548
549 let path = dir.join("rpc-cache-tx.txt");
550 shared_cache.load_cache(path.clone()).unwrap();
551
552 let req = TransactionRequest::default()
553 .from(anvil.addresses()[0])
554 .to(Address::repeat_byte(5))
555 .value(U256::ZERO)
556 .input(bytes!("deadbeef").into());
557
558 let tx_hash =
559 *provider.send_transaction(req).await.expect("failed to send tx").tx_hash();
560
561 let tx = provider.get_transaction_by_hash(tx_hash).await.unwrap(); let tx2 = provider.get_transaction_by_hash(tx_hash).await.unwrap(); assert_eq!(tx, tx2);
564
565 let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap(); let receipt2 = provider.get_transaction_receipt(tx_hash).await.unwrap(); assert_eq!(receipt, receipt2);
569
570 shared_cache.save_cache(path).unwrap();
571 })
572 .await;
573 }
574
575 #[tokio::test]
576 async fn test_block_receipts() {
577 run_with_tempdir("get-block-receipts", |dir| async move {
578 let cache_layer = CacheLayer::new(100);
579 let shared_cache = cache_layer.cache();
580 let anvil = Anvil::new().spawn();
581 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
582
583 let path = dir.join("rpc-cache-block-receipts.txt");
584 shared_cache.load_cache(path.clone()).unwrap();
585
586 let receipt = provider
589 .send_raw_transaction(
590 bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b").as_ref()
592 )
593 .await.unwrap().get_receipt().await.unwrap();
594
595 let block_number = receipt.block_number.unwrap();
596
597 let receipts =
598 provider.get_block_receipts(block_number.into()).await.unwrap(); let receipts2 =
600 provider.get_block_receipts(block_number.into()).await.unwrap(); assert_eq!(receipts, receipts2);
602
603 assert!(receipts.is_some_and(|r| r[0] == receipt));
604
605 shared_cache.save_cache(path).unwrap();
606 })
607 .await
608 }
609
610 #[tokio::test]
611 async fn test_get_code() {
612 run_with_tempdir("get-code", |dir| async move {
613 let cache_layer = CacheLayer::new(100);
614 let shared_cache = cache_layer.cache();
615 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
616
617 let path = dir.join("rpc-cache-code.txt");
618 shared_cache.load_cache(path.clone()).unwrap();
619
620 let bytecode = hex::decode(
621 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
623 ).unwrap();
624 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
625
626 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
627
628 let counter_addr = receipt.contract_address.unwrap();
629
630 let block_id = BlockId::number(receipt.block_number.unwrap());
631
632 let code = provider.get_code_at(counter_addr).block_id(block_id).await.unwrap(); let code2 = provider.get_code_at(counter_addr).block_id(block_id).await.unwrap(); assert_eq!(code, code2);
635
636 shared_cache.save_cache(path).unwrap();
637 })
638 .await;
639 }
640
641 #[cfg(all(test, feature = "anvil-api"))]
642 #[tokio::test]
643 async fn test_get_storage_at_different_block_ids() {
644 use crate::ext::AnvilApi;
645
646 run_with_tempdir("get-code-different-block-id", |dir| async move {
647 let cache_layer = CacheLayer::new(100);
648 let shared_cache = cache_layer.cache();
649 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
650
651 let path = dir.join("rpc-cache-code.txt");
652 shared_cache.load_cache(path.clone()).unwrap();
653
654 let bytecode = hex::decode(
655 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
657 ).unwrap();
658
659 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
660 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
661 let counter_addr = receipt.contract_address.unwrap();
662 let block_id = BlockId::number(receipt.block_number.unwrap());
663
664 let counter = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, U256::ZERO);
666 let counter_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, counter_cached);
668
669 provider.anvil_mine(Some(1), None).await.unwrap();
670
671 let tx2 = TransactionRequest::default().with_nonce(1).to(counter_addr).input(hex::decode("d09de08a").unwrap().into()).with_chain_id(31337);
673 let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap();
674 let block_id2 = BlockId::number(receipt2.block_number.unwrap());
675
676 let counter2 = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, U256::from(1));
678 let counter2_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, counter2_cached);
680
681 shared_cache.save_cache(path).unwrap();
682 })
683 .await;
684 }
685}