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