1use crate::{
2 utils, ParamsWithBlock, Provider, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
3};
4use alloy_eips::BlockId;
5use alloy_json_rpc::{RpcError, RpcSend};
6use alloy_network::Network;
7use alloy_primitives::{
8 keccak256, Address, Bytes, StorageKey, StorageValue, TxHash, B256, U256, U64,
9};
10use alloy_rpc_types_eth::{
11 BlockNumberOrTag, EIP1186AccountProofResponse, Filter, Log, StorageValuesRequest,
12 StorageValuesResponse,
13};
14use alloy_transport::{TransportErrorKind, TransportResult};
15use lru::LruCache;
16use parking_lot::RwLock;
17use serde::{Deserialize, Serialize};
18use std::{io::BufReader, marker::PhantomData, num::NonZero, path::PathBuf, sync::Arc};
19#[derive(Debug, Clone)]
27pub struct CacheLayer {
28 cache: SharedCache,
30}
31
32impl CacheLayer {
33 pub fn new(max_items: u32) -> Self {
36 Self { cache: SharedCache::new(max_items) }
37 }
38
39 pub const fn max_items(&self) -> u32 {
41 self.cache.max_items()
42 }
43
44 pub fn cache(&self) -> SharedCache {
46 self.cache.clone()
47 }
48}
49
50impl<P, N> ProviderLayer<P, N> for CacheLayer
51where
52 P: Provider<N>,
53 N: Network,
54{
55 type Provider = CacheProvider<P, N>;
56
57 fn layer(&self, inner: P) -> Self::Provider {
58 CacheProvider::new(inner, self.cache())
59 }
60}
61
62#[derive(Debug, Clone)]
70pub struct CacheProvider<P, N> {
71 inner: P,
73 cache: SharedCache,
75 _pd: PhantomData<N>,
77}
78
79impl<P, N> CacheProvider<P, N>
80where
81 P: Provider<N>,
82 N: Network,
83{
84 pub const fn new(inner: P, cache: SharedCache) -> Self {
86 Self { inner, cache, _pd: PhantomData }
87 }
88}
89
90macro_rules! rpc_call_with_block {
98 ($cache:expr, $client:expr, $req:expr) => {{
99 let client =
100 $client.upgrade().ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"));
101 let cache = $cache.clone();
102 ProviderCall::BoxedFuture(Box::pin(async move {
103 let client = client?;
104
105 let result = client.request($req.method(), $req.params()).map_params(|params| {
106 ParamsWithBlock::new(params, $req.block_id.unwrap_or(BlockId::latest()))
107 });
108
109 let res = result.await?;
110 if !$req.has_block_tag() {
113 let json_str = serde_json::to_string(&res).map_err(TransportErrorKind::custom)?;
114 let hash = $req.params_hash()?;
115 let _ = cache.put(hash, json_str);
116 }
117
118 Ok(res)
119 }))
120 }};
121}
122
123macro_rules! cache_rpc_call_with_block {
129 ($cache:expr, $client:expr, $req:expr) => {{
130 if $req.has_block_tag() {
131 return rpc_call_with_block!($cache, $client, $req);
132 }
133
134 let hash = $req.params_hash().ok();
135
136 if let Some(hash) = hash {
137 if let Ok(Some(cached)) = $cache.get_deserialized(&hash) {
138 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
139 }
140 }
141
142 rpc_call_with_block!($cache, $client, $req)
143 }};
144}
145
146#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
147#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
148impl<P, N> Provider<N> for CacheProvider<P, N>
149where
150 P: Provider<N>,
151 N: Network,
152{
153 #[inline(always)]
154 fn root(&self) -> &RootProvider<N> {
155 self.inner.root()
156 }
157
158 fn get_block_receipts(
159 &self,
160 block: BlockId,
161 ) -> ProviderCall<(BlockId,), Option<Vec<N::ReceiptResponse>>> {
162 let req = RequestType::new("eth_getBlockReceipts", (block,)).with_block_id(block);
163
164 let redirect = req.has_block_tag();
165
166 if !redirect {
167 let params_hash = req.params_hash().ok();
168
169 if let Some(hash) = params_hash {
170 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
171 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
172 }
173 }
174 }
175
176 let client = self.inner.weak_client();
177 let cache = self.cache.clone();
178
179 ProviderCall::BoxedFuture(Box::pin(async move {
180 let client = client
181 .upgrade()
182 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
183
184 let result = client.request(req.method(), req.params()).await?;
185
186 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
187
188 if !redirect {
189 let hash = req.params_hash()?;
190 let _ = cache.put(hash, json_str);
191 }
192
193 Ok(result)
194 }))
195 }
196
197 fn get_balance(&self, address: Address) -> RpcWithBlock<Address, U256> {
198 let client = self.inner.weak_client();
199 let cache = self.cache.clone();
200 RpcWithBlock::new_provider(move |block_id| {
201 let req = RequestType::new("eth_getBalance", address).with_block_id(block_id);
202 cache_rpc_call_with_block!(cache, client, req)
203 })
204 }
205
206 fn get_code_at(&self, address: Address) -> RpcWithBlock<Address, Bytes> {
207 let client = self.inner.weak_client();
208 let cache = self.cache.clone();
209 RpcWithBlock::new_provider(move |block_id| {
210 let req = RequestType::new("eth_getCode", address).with_block_id(block_id);
211 cache_rpc_call_with_block!(cache, client, req)
212 })
213 }
214
215 async fn get_logs(&self, filter: &Filter) -> TransportResult<Vec<Log>> {
216 if filter.block_option.as_block_hash().is_none() {
217 let from_is_number = filter
219 .block_option
220 .get_from_block()
221 .as_ref()
222 .is_some_and(|block| block.is_number());
223 let to_is_number =
224 filter.block_option.get_to_block().as_ref().is_some_and(|block| block.is_number());
225
226 if !from_is_number || !to_is_number {
227 return self.inner.get_logs(filter).await;
228 }
229 }
230
231 let req = RequestType::new("eth_getLogs", (filter,));
232
233 let params_hash = req.params_hash().ok();
234
235 if let Some(hash) = params_hash {
236 if let Some(cached) = self.cache.get_deserialized(&hash)? {
237 return Ok(cached);
238 }
239 }
240
241 let result = self.inner.get_logs(filter).await?;
242
243 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
244
245 let hash = req.params_hash()?;
246 let _ = self.cache.put(hash, json_str);
247
248 Ok(result)
249 }
250
251 fn get_proof(
252 &self,
253 address: Address,
254 keys: Vec<StorageKey>,
255 ) -> RpcWithBlock<(Address, Vec<StorageKey>), EIP1186AccountProofResponse> {
256 let client = self.inner.weak_client();
257 let cache = self.cache.clone();
258 RpcWithBlock::new_provider(move |block_id| {
259 let req =
260 RequestType::new("eth_getProof", (address, keys.clone())).with_block_id(block_id);
261 cache_rpc_call_with_block!(cache, client, req)
262 })
263 }
264
265 fn get_storage_at(
266 &self,
267 address: Address,
268 key: U256,
269 ) -> RpcWithBlock<(Address, U256), StorageValue> {
270 let client = self.inner.weak_client();
271 let cache = self.cache.clone();
272 RpcWithBlock::new_provider(move |block_id| {
273 let req = RequestType::new("eth_getStorageAt", (address, key)).with_block_id(block_id);
274 cache_rpc_call_with_block!(cache, client, req)
275 })
276 }
277
278 fn get_storage_values(
279 &self,
280 requests: StorageValuesRequest,
281 ) -> RpcWithBlock<(StorageValuesRequest,), StorageValuesResponse> {
282 let client = self.inner.weak_client();
283 let cache = self.cache.clone();
284 RpcWithBlock::new_provider(move |block_id| {
285 let req = RequestType::new("eth_getStorageValues", (requests.clone(),))
286 .with_block_id(block_id);
287 cache_rpc_call_with_block!(cache, client, req)
288 })
289 }
290
291 fn get_transaction_by_hash(
292 &self,
293 hash: TxHash,
294 ) -> ProviderCall<(TxHash,), Option<N::TransactionResponse>> {
295 let req = RequestType::new("eth_getTransactionByHash", (hash,));
296
297 let params_hash = req.params_hash().ok();
298
299 if let Some(hash) = params_hash {
300 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
301 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
302 }
303 }
304 let client = self.inner.weak_client();
305 let cache = self.cache.clone();
306 ProviderCall::BoxedFuture(Box::pin(async move {
307 let client = client
308 .upgrade()
309 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
310 let result = client.request(req.method(), req.params()).await?;
311
312 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
313 let hash = req.params_hash()?;
314 let _ = cache.put(hash, json_str);
315
316 Ok(result)
317 }))
318 }
319
320 fn get_raw_transaction_by_hash(&self, hash: TxHash) -> ProviderCall<(TxHash,), Option<Bytes>> {
321 let req = RequestType::new("eth_getRawTransactionByHash", (hash,));
322
323 let params_hash = req.params_hash().ok();
324
325 if let Some(hash) = params_hash {
326 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
327 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
328 }
329 }
330
331 let client = self.inner.weak_client();
332 let cache = self.cache.clone();
333 ProviderCall::BoxedFuture(Box::pin(async move {
334 let client = client
335 .upgrade()
336 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
337
338 let result = client.request(req.method(), req.params()).await?;
339
340 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
341 let hash = req.params_hash()?;
342 let _ = cache.put(hash, json_str);
343
344 Ok(result)
345 }))
346 }
347
348 fn get_transaction_receipt(
349 &self,
350 hash: TxHash,
351 ) -> ProviderCall<(TxHash,), Option<N::ReceiptResponse>> {
352 let req = RequestType::new("eth_getTransactionReceipt", (hash,));
353
354 let params_hash = req.params_hash().ok();
355
356 if let Some(hash) = params_hash {
357 if let Ok(Some(cached)) = self.cache.get_deserialized(&hash) {
358 return ProviderCall::BoxedFuture(Box::pin(async move { Ok(cached) }));
359 }
360 }
361
362 let client = self.inner.weak_client();
363 let cache = self.cache.clone();
364 ProviderCall::BoxedFuture(Box::pin(async move {
365 let client = client
366 .upgrade()
367 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
368
369 let result = client.request(req.method(), req.params()).await?;
370
371 let json_str = serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
372 let hash = req.params_hash()?;
373 let _ = cache.put(hash, json_str);
374
375 Ok(result)
376 }))
377 }
378
379 fn get_transaction_count(
380 &self,
381 address: Address,
382 ) -> RpcWithBlock<Address, U64, u64, fn(U64) -> u64> {
383 let client = self.inner.weak_client();
384 let cache = self.cache.clone();
385 RpcWithBlock::new_provider(move |block_id| {
386 let req = RequestType::new("eth_getTransactionCount", address).with_block_id(block_id);
387
388 let redirect = req.has_block_tag();
389
390 if !redirect {
391 let params_hash = req.params_hash().ok();
392
393 if let Some(hash) = params_hash {
394 if let Ok(Some(cached)) = cache.get_deserialized::<U64>(&hash) {
395 return ProviderCall::BoxedFuture(Box::pin(async move {
396 Ok(utils::convert_u64(cached))
397 }));
398 }
399 }
400 }
401
402 let client = client.clone();
403 let cache = cache.clone();
404
405 ProviderCall::BoxedFuture(Box::pin(async move {
406 let client = client
407 .upgrade()
408 .ok_or_else(|| TransportErrorKind::custom_str("RPC client dropped"))?;
409
410 let result: U64 = client
411 .request(req.method(), req.params())
412 .map_params(|params| ParamsWithBlock::new(params, block_id))
413 .await?;
414
415 if !redirect {
416 let json_str =
417 serde_json::to_string(&result).map_err(TransportErrorKind::custom)?;
418 let hash = req.params_hash()?;
419 let _ = cache.put(hash, json_str);
420 }
421
422 Ok(utils::convert_u64(result))
423 }))
424 })
425 }
426}
427
428struct RequestType<Params: RpcSend> {
430 method: &'static str,
431 params: Params,
432 block_id: Option<BlockId>,
433}
434
435impl<Params: RpcSend> RequestType<Params> {
436 const fn new(method: &'static str, params: Params) -> Self {
437 Self { method, params, block_id: None }
438 }
439
440 const fn with_block_id(mut self, block_id: BlockId) -> Self {
441 self.block_id = Some(block_id);
442 self
443 }
444
445 fn params_hash(&self) -> TransportResult<B256> {
446 let hash = serde_json::to_string(&self.params())
450 .map(|p| {
451 keccak256(
452 match self.block_id {
453 Some(BlockId::Hash(rpc_block_hash)) => {
454 format!("{}{}{}", rpc_block_hash, self.method(), p)
455 }
456 Some(BlockId::Number(BlockNumberOrTag::Number(number))) => {
457 format!("{}{}{}", number, self.method(), p)
458 }
459 _ => format!("{}{}", self.method(), p),
460 }
461 .as_bytes(),
462 )
463 })
464 .map_err(RpcError::ser_err)?;
465
466 Ok(hash)
467 }
468
469 const fn method(&self) -> &'static str {
470 self.method
471 }
472
473 fn params(&self) -> Params {
474 self.params.clone()
475 }
476
477 const fn has_block_tag(&self) -> bool {
480 if let Some(block_id) = self.block_id {
481 return !matches!(
482 block_id,
483 BlockId::Hash(_) | BlockId::Number(BlockNumberOrTag::Number(_))
484 );
485 }
486 true
489 }
490}
491
492#[derive(Debug, Serialize, Deserialize)]
493struct FsCacheEntry {
494 key: B256,
496 value: String,
498}
499
500#[derive(Debug, Clone)]
502pub struct SharedCache {
503 inner: Arc<RwLock<LruCache<B256, String, alloy_primitives::map::FbBuildHasher<32>>>>,
504 max_items: NonZero<usize>,
505}
506
507impl SharedCache {
508 pub fn new(max_items: u32) -> Self {
510 let max_items = NonZero::new(max_items as usize).unwrap_or(NonZero::<usize>::MIN);
511 let inner = Arc::new(RwLock::new(LruCache::with_hasher(max_items, Default::default())));
512 Self { inner, max_items }
513 }
514
515 pub const fn max_items(&self) -> u32 {
517 self.max_items.get() as u32
518 }
519
520 pub fn put(&self, key: B256, value: String) -> TransportResult<bool> {
522 Ok(self.inner.write().put(key, value).is_some())
523 }
524
525 pub fn get(&self, key: &B256) -> Option<String> {
527 self.inner.write().get(key).cloned()
529 }
530
531 pub fn get_deserialized<T>(&self, key: &B256) -> TransportResult<Option<T>>
533 where
534 T: for<'de> Deserialize<'de>,
535 {
536 let Some(cached) = self.get(key) else { return Ok(None) };
537 let result = serde_json::from_str(&cached).map_err(TransportErrorKind::custom)?;
538 Ok(Some(result))
539 }
540
541 pub fn save_cache(&self, path: PathBuf) -> TransportResult<()> {
545 let entries: Vec<FsCacheEntry> = {
546 self.inner
547 .read()
548 .iter()
549 .map(|(key, value)| FsCacheEntry { key: *key, value: value.clone() })
550 .collect()
551 };
552 let file = std::fs::File::create(path).map_err(TransportErrorKind::custom)?;
553 serde_json::to_writer(file, &entries).map_err(TransportErrorKind::custom)?;
554 Ok(())
555 }
556
557 pub fn load_cache(&self, path: PathBuf) -> TransportResult<()> {
560 if !path.exists() {
561 return Ok(());
562 };
563 let file = std::fs::File::open(path).map_err(TransportErrorKind::custom)?;
564 let file = BufReader::new(file);
565 let entries: Vec<FsCacheEntry> =
566 serde_json::from_reader(file).map_err(TransportErrorKind::custom)?;
567 let mut cache = self.inner.write();
568 for entry in entries {
569 cache.put(entry.key, entry.value);
570 }
571
572 Ok(())
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::ProviderBuilder;
580 use alloy_network::TransactionBuilder;
581 use alloy_node_bindings::{utils::run_with_tempdir, Anvil};
582 use alloy_primitives::{bytes, hex, utils::Unit, Bytes, FixedBytes};
583 use alloy_rpc_types_eth::{BlockId, TransactionRequest};
584
585 #[tokio::test]
586 async fn test_get_proof() {
587 run_with_tempdir("get-proof", |dir| async move {
588 let cache_layer = CacheLayer::new(100);
589 let shared_cache = cache_layer.cache();
590 let anvil = Anvil::new().block_time_f64(0.3).spawn();
591 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
592
593 let from = anvil.addresses()[0];
594 let path = dir.join("rpc-cache-proof.txt");
595
596 shared_cache.load_cache(path.clone()).unwrap();
597
598 let calldata: Bytes = "0x6080604052348015600f57600080fd5b506101f28061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633fb5c1cb146100465780638381f58a14610062578063d09de08a14610080575b600080fd5b610060600480360381019061005b91906100ee565b61008a565b005b61006a610094565b604051610077919061012a565b60405180910390f35b61008861009a565b005b8060008190555050565b60005481565b6000808154809291906100ac90610174565b9190505550565b600080fd5b6000819050919050565b6100cb816100b8565b81146100d657600080fd5b50565b6000813590506100e8816100c2565b92915050565b600060208284031215610104576101036100b3565b5b6000610112848285016100d9565b91505092915050565b610124816100b8565b82525050565b600060208201905061013f600083018461011b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061017f826100b8565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101b1576101b0610145565b5b60018201905091905056fea264697066735822122067ac0f21f648b0cacd1b7260772852ad4a0f63e2cc174168c51a6887fd5197a964736f6c634300081a0033".parse().unwrap();
599
600 let tx = TransactionRequest::default()
601 .with_from(from)
602 .with_input(calldata)
603 .with_max_fee_per_gas(1_000_000_000)
604 .with_max_priority_fee_per_gas(1_000_000)
605 .with_gas_limit(1_000_000)
606 .with_nonce(0);
607
608 let tx_receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
609
610 let counter_addr = tx_receipt.contract_address.unwrap();
611
612 let keys = vec![
613 FixedBytes::with_last_byte(0),
614 FixedBytes::with_last_byte(0x1),
615 FixedBytes::with_last_byte(0x2),
616 FixedBytes::with_last_byte(0x3),
617 FixedBytes::with_last_byte(0x4),
618 ];
619
620 let proof =
621 provider.get_proof(counter_addr, keys.clone()).block_id(1.into()).await.unwrap();
622 let proof2 = provider.get_proof(counter_addr, keys).block_id(1.into()).await.unwrap();
623
624 assert_eq!(proof, proof2);
625
626 shared_cache.save_cache(path).unwrap();
627 }).await;
628 }
629
630 #[tokio::test]
631 async fn test_get_tx_by_hash_and_receipt() {
632 run_with_tempdir("get-tx-by-hash", |dir| async move {
633 let cache_layer = CacheLayer::new(100);
634 let shared_cache = cache_layer.cache();
635 let anvil = Anvil::new().block_time_f64(0.3).spawn();
636 let provider = ProviderBuilder::new()
637 .disable_recommended_fillers()
638 .layer(cache_layer)
639 .connect_http(anvil.endpoint_url());
640
641 let path = dir.join("rpc-cache-tx.txt");
642 shared_cache.load_cache(path.clone()).unwrap();
643
644 let req = TransactionRequest::default()
645 .from(anvil.addresses()[0])
646 .to(Address::repeat_byte(5))
647 .value(U256::ZERO)
648 .input(bytes!("deadbeef").into());
649
650 let tx_hash =
651 *provider.send_transaction(req).await.expect("failed to send tx").tx_hash();
652
653 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);
656
657 let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap(); let receipt2 = provider.get_transaction_receipt(tx_hash).await.unwrap(); assert_eq!(receipt, receipt2);
661
662 shared_cache.save_cache(path).unwrap();
663 })
664 .await;
665 }
666
667 #[tokio::test]
668 async fn test_block_receipts() {
669 run_with_tempdir("get-block-receipts", |dir| async move {
670 let cache_layer = CacheLayer::new(100);
671 let shared_cache = cache_layer.cache();
672 let anvil = Anvil::new().spawn();
673 let provider = ProviderBuilder::new().layer(cache_layer).connect_http(anvil.endpoint_url());
674
675 let path = dir.join("rpc-cache-block-receipts.txt");
676 shared_cache.load_cache(path.clone()).unwrap();
677
678 let receipt = provider
681 .send_raw_transaction(
682 bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b").as_ref()
684 )
685 .await.unwrap().get_receipt().await.unwrap();
686
687 let block_number = receipt.block_number.unwrap();
688
689 let receipts =
690 provider.get_block_receipts(block_number.into()).await.unwrap(); let receipts2 =
692 provider.get_block_receipts(block_number.into()).await.unwrap(); assert_eq!(receipts, receipts2);
694
695 assert!(receipts.is_some_and(|r| r[0] == receipt));
696
697 shared_cache.save_cache(path).unwrap();
698 })
699 .await
700 }
701
702 #[tokio::test]
703 async fn test_get_balance() {
704 run_with_tempdir("get-balance", |dir| async move {
705 let cache_layer = CacheLayer::new(100);
706 let cache_layer2 = cache_layer.clone();
707 let shared_cache = cache_layer.cache();
708 let anvil = Anvil::new().spawn();
709 let provider = ProviderBuilder::new()
710 .disable_recommended_fillers()
711 .layer(cache_layer)
712 .connect_http(anvil.endpoint_url());
713
714 let path = dir.join("rpc-cache-balance.txt");
715 shared_cache.load_cache(path.clone()).unwrap();
716
717 let to = Address::repeat_byte(5);
718
719 let req = TransactionRequest::default()
721 .from(anvil.addresses()[0])
722 .to(to)
723 .value(Unit::ETHER.wei());
724
725 let receipt = provider
726 .send_transaction(req)
727 .await
728 .expect("failed to send tx")
729 .get_receipt()
730 .await
731 .unwrap();
732 let block_number = receipt.block_number.unwrap();
733
734 let balance = provider.get_balance(to).block_id(block_number.into()).await.unwrap();
736 assert_eq!(balance, Unit::ETHER.wei());
737
738 drop(anvil);
740
741 let provider2 = ProviderBuilder::new()
743 .disable_recommended_fillers()
744 .layer(cache_layer2)
745 .connect_http("http://localhost:1".parse().unwrap());
746
747 let balance2 = provider2.get_balance(to).block_id(block_number.into()).await.unwrap();
749 assert_eq!(balance, balance2);
750
751 shared_cache.save_cache(path).unwrap();
752 })
753 .await;
754 }
755
756 #[tokio::test]
757 async fn test_get_code() {
758 run_with_tempdir("get-code", |dir| async move {
759 let cache_layer = CacheLayer::new(100);
760 let shared_cache = cache_layer.cache();
761 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
762
763 let path = dir.join("rpc-cache-code.txt");
764 shared_cache.load_cache(path.clone()).unwrap();
765
766 let bytecode = hex::decode(
767 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
769 ).unwrap();
770 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
771
772 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
773
774 let counter_addr = receipt.contract_address.unwrap();
775
776 let block_id = BlockId::number(receipt.block_number.unwrap());
777
778 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);
781
782 shared_cache.save_cache(path).unwrap();
783 })
784 .await;
785 }
786
787 #[cfg(all(test, feature = "anvil-api"))]
788 #[tokio::test]
789 async fn test_get_storage_at_different_block_ids() {
790 use crate::ext::AnvilApi;
791
792 run_with_tempdir("get-code-different-block-id", |dir| async move {
793 let cache_layer = CacheLayer::new(100);
794 let shared_cache = cache_layer.cache();
795 let provider = ProviderBuilder::new().disable_recommended_fillers().with_gas_estimation().layer(cache_layer).connect_anvil_with_wallet();
796
797 let path = dir.join("rpc-cache-code.txt");
798 shared_cache.load_cache(path.clone()).unwrap();
799
800 let bytecode = hex::decode(
801 "6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
803 ).unwrap();
804
805 let tx = TransactionRequest::default().with_nonce(0).with_deploy_code(bytecode).with_chain_id(31337);
806 let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
807 let counter_addr = receipt.contract_address.unwrap();
808 let block_id = BlockId::number(receipt.block_number.unwrap());
809
810 let counter = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, U256::ZERO);
812 let counter_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id).await.unwrap(); assert_eq!(counter, counter_cached);
814
815 provider.anvil_mine(Some(1), None).await.unwrap();
816
817 let tx2 = TransactionRequest::default().with_nonce(1).to(counter_addr).input(hex::decode("d09de08a").unwrap().into()).with_chain_id(31337);
819 let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap();
820 let block_id2 = BlockId::number(receipt2.block_number.unwrap());
821
822 let counter2 = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, U256::from(1));
824 let counter2_cached = provider.get_storage_at(counter_addr, U256::ZERO).block_id(block_id2).await.unwrap(); assert_eq!(counter2, counter2_cached);
826
827 shared_cache.save_cache(path).unwrap();
828 })
829 .await;
830 }
831
832 #[tokio::test]
833 async fn test_get_transaction_count() {
834 run_with_tempdir("get-tx-count", |dir| async move {
835 let cache_layer = CacheLayer::new(100);
836 let cache_layer2 = cache_layer.clone();
838 let shared_cache = cache_layer.cache();
839 let anvil = Anvil::new().spawn();
840 let provider = ProviderBuilder::new()
841 .disable_recommended_fillers()
842 .layer(cache_layer)
843 .connect_http(anvil.endpoint_url());
844
845 let path = dir.join("rpc-cache-tx-count.txt");
846 shared_cache.load_cache(path.clone()).unwrap();
847
848 let address = anvil.addresses()[0];
849
850 let req = TransactionRequest::default()
852 .from(address)
853 .to(Address::repeat_byte(5))
854 .value(U256::ZERO)
855 .input(bytes!("deadbeef").into());
856
857 let receipt = provider
858 .send_transaction(req)
859 .await
860 .expect("failed to send tx")
861 .get_receipt()
862 .await
863 .unwrap();
864 let block_number = receipt.block_number.unwrap();
865
866 let count = provider
868 .get_transaction_count(address)
869 .block_id(block_number.into())
870 .await
871 .unwrap();
872 assert_eq!(count, 1);
873
874 drop(anvil);
876
877 let provider2 = ProviderBuilder::new()
879 .disable_recommended_fillers()
880 .layer(cache_layer2)
881 .connect_http("http://localhost:1".parse().unwrap());
882
883 let count2 = provider2
885 .get_transaction_count(address)
886 .block_id(block_number.into())
887 .await
888 .unwrap();
889 assert_eq!(count, count2);
890
891 shared_cache.save_cache(path).unwrap();
892 })
893 .await;
894 }
895}