1use anvil_zksync_common::{
2 cache::{Cache, CacheConfig},
3 sh_err,
4};
5use anvil_zksync_config::constants::{
6 DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
7 DEFAULT_FAIR_PUBDATA_PRICE,
8};
9use anyhow::Context;
10use async_trait::async_trait;
11use futures::TryFutureExt;
12use itertools::Itertools;
13use std::fmt;
14use std::future::Future;
15use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
16use tracing::Instrument;
17use url::Url;
18use zksync_types::fee_model::FeeParams;
19use zksync_types::url::SensitiveUrl;
20use zksync_types::web3::Index;
21use zksync_types::{
22 api, Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, Transaction, H256,
23 U256,
24};
25use zksync_web3_decl::client::{DynClient, L2};
26use zksync_web3_decl::error::Web3Error;
27use zksync_web3_decl::namespaces::{EthNamespaceClient, ZksNamespaceClient};
28
29#[async_trait]
35pub trait ForkSource: fmt::Debug + Send + Sync {
36 fn dyn_cloned(&self) -> Box<dyn ForkSource>;
38
39 fn url(&self) -> Option<Url>;
41
42 fn details(&self) -> Option<ForkDetails>;
44
45 async fn get_storage_at(
47 &self,
48 address: Address,
49 idx: U256,
50 block: Option<api::BlockIdVariant>,
51 ) -> anyhow::Result<H256>;
52
53 async fn get_storage_at_forked(&self, address: Address, idx: U256) -> anyhow::Result<H256>;
55
56 async fn get_bytecode_by_hash(&self, hash: H256) -> anyhow::Result<Option<Vec<u8>>>;
58
59 async fn get_transaction_by_hash(&self, hash: H256)
61 -> anyhow::Result<Option<api::Transaction>>;
62
63 async fn get_transaction_details(
65 &self,
66 hash: H256,
67 ) -> anyhow::Result<Option<api::TransactionDetails>>;
68
69 async fn get_raw_block_transactions(
71 &self,
72 block_number: L2BlockNumber,
73 ) -> anyhow::Result<Vec<zksync_types::Transaction>>;
74
75 async fn get_block_by_hash(
77 &self,
78 hash: H256,
79 ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>>;
80
81 async fn get_block_by_number(
83 &self,
84 block_number: api::BlockNumber,
85 ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>>;
86
87 async fn get_block_by_id(
89 &self,
90 block_id: api::BlockId,
91 ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
92 match block_id {
93 api::BlockId::Hash(hash) => self.get_block_by_hash(hash).await,
94 api::BlockId::Number(number) => self.get_block_by_number(number).await,
95 }
96 }
97
98 async fn get_block_details(
100 &self,
101 block_number: L2BlockNumber,
102 ) -> anyhow::Result<Option<api::BlockDetails>>;
103
104 async fn get_block_transaction_count_by_hash(
106 &self,
107 block_hash: H256,
108 ) -> anyhow::Result<Option<U256>>;
109
110 async fn get_block_transaction_count_by_number(
112 &self,
113 block_number: api::BlockNumber,
114 ) -> anyhow::Result<Option<U256>>;
115
116 async fn get_block_transaction_count_by_id(
118 &self,
119 block_id: api::BlockId,
120 ) -> anyhow::Result<Option<U256>> {
121 match block_id {
122 api::BlockId::Hash(hash) => self.get_block_transaction_count_by_hash(hash).await,
123 api::BlockId::Number(number) => {
124 self.get_block_transaction_count_by_number(number).await
125 }
126 }
127 }
128
129 async fn get_transaction_by_block_hash_and_index(
131 &self,
132 block_hash: H256,
133 index: Index,
134 ) -> anyhow::Result<Option<api::Transaction>>;
135
136 async fn get_transaction_by_block_number_and_index(
138 &self,
139 block_number: api::BlockNumber,
140 index: Index,
141 ) -> anyhow::Result<Option<api::Transaction>>;
142
143 async fn get_transaction_by_block_id_and_index(
145 &self,
146 block_id: api::BlockId,
147 index: Index,
148 ) -> anyhow::Result<Option<api::Transaction>> {
149 match block_id {
150 api::BlockId::Hash(hash) => {
151 self.get_transaction_by_block_hash_and_index(hash, index)
152 .await
153 }
154 api::BlockId::Number(number) => {
155 self.get_transaction_by_block_number_and_index(number, index)
156 .await
157 }
158 }
159 }
160
161 async fn get_bridge_contracts(&self) -> anyhow::Result<Option<api::BridgeAddresses>>;
163
164 async fn get_confirmed_tokens(
166 &self,
167 from: u32,
168 limit: u8,
169 ) -> anyhow::Result<Option<Vec<zksync_web3_decl::types::Token>>>;
170}
171
172impl Clone for Box<dyn ForkSource> {
173 fn clone(&self) -> Self {
174 self.dyn_cloned()
175 }
176}
177
178#[derive(Debug, Clone)]
179pub struct ForkDetails {
180 pub chain_id: L2ChainId,
182 pub protocol_version: ProtocolVersionId,
184 pub batch_number: L1BatchNumber,
186 pub block_number: L2BlockNumber,
188 pub block_hash: H256,
190 pub block_timestamp: u64,
192 pub api_block: api::Block<api::TransactionVariant>,
194 pub l1_gas_price: u64,
195 pub l2_fair_gas_price: u64,
196 pub fair_pubdata_price: u64,
198 pub estimate_gas_price_scale_factor: f64,
200 pub estimate_gas_scale_factor: f32,
202 pub fee_params: FeeParams,
203}
204
205pub struct ForkConfig {
206 pub url: Url,
207 pub estimate_gas_price_scale_factor: f64,
208 pub estimate_gas_scale_factor: f32,
209}
210
211impl ForkConfig {
212 pub fn unknown(url: Url) -> Self {
214 let (estimate_gas_price_scale_factor, estimate_gas_scale_factor) = (
218 DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
219 DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
220 );
221 Self {
222 url,
223 estimate_gas_price_scale_factor,
224 estimate_gas_scale_factor,
225 }
226 }
227}
228
229#[derive(Debug, Clone)]
231pub struct ForkClient {
232 pub url: Url,
233 pub details: ForkDetails,
234 l2_client: Box<DynClient<L2>>,
235}
236
237impl ForkClient {
238 async fn new(
239 config: ForkConfig,
240 l2_client: Box<DynClient<L2>>,
241 block_number: L2BlockNumber,
242 ) -> anyhow::Result<Self> {
243 let ForkConfig {
244 url,
245 estimate_gas_price_scale_factor,
246 estimate_gas_scale_factor,
247 } = config;
248 let chain_id = l2_client
249 .chain_id()
250 .await
251 .with_context(|| format!("failed to get chain id from fork={url}"))?;
252 let chain_id = L2ChainId::try_from(chain_id.as_u64())
253 .map_err(|e| anyhow::anyhow!("fork has malformed chain id: {e}"))?;
254 let block_details = l2_client
255 .get_block_details(block_number)
256 .await?
257 .ok_or_else(
258 || anyhow::anyhow!("could not find block #{block_number} at fork={url}",),
259 )?;
260 let root_hash = block_details
261 .base
262 .root_hash
263 .ok_or_else(|| anyhow::anyhow!("fork block #{block_number} missing root hash"))?;
264 let mut block = l2_client
265 .get_block_by_hash(root_hash, true)
266 .await?
267 .ok_or_else(|| {
268 anyhow::anyhow!(
269 "could not find API block #{block_number} at fork={url} despite finding its details \
270 through `zks_getBlockDetails` previously; this is likely a bug, please report this",
271 )
272 })?;
273 let batch_number = block_details.l1_batch_number;
274 block.l1_batch_number = Some(batch_number.0.into());
277
278 let Some(protocol_version) = block_details.protocol_version else {
279 anyhow::bail!(
282 "Block #{block_number} from fork={url} does not have protocol version set. \
283 Likely you are using an external node with a block for protocol version below 9 which are unsupported in anvil-zksync. \
284 Please report this as a bug if that's not the case."
285 )
286 };
287 if !SupportedProtocolVersions::is_supported(protocol_version) {
288 anyhow::bail!(
289 "Block #{block_number} from fork={url} is using unsupported protocol version `{protocol_version}`. \
290 anvil-zksync only supports the following versions: {SupportedProtocolVersions}."
291 )
292 }
293
294 let fee_params = l2_client.get_fee_params().await?;
295 let details = ForkDetails {
296 chain_id,
297 protocol_version,
298 batch_number,
299 block_number,
300 block_hash: root_hash,
301 block_timestamp: block_details.base.timestamp,
302 api_block: block,
303 l1_gas_price: block_details.base.l1_gas_price,
304 l2_fair_gas_price: block_details.base.l2_fair_gas_price,
305 fair_pubdata_price: block_details
306 .base
307 .fair_pubdata_price
308 .unwrap_or(DEFAULT_FAIR_PUBDATA_PRICE),
309 estimate_gas_price_scale_factor,
310 estimate_gas_scale_factor,
311 fee_params,
312 };
313 let fork = ForkClient {
314 url,
315 details,
316 l2_client,
317 };
318 Ok(fork)
319 }
320
321 pub async fn at_block_number(
323 config: ForkConfig,
324 block_number: Option<L2BlockNumber>,
325 ) -> anyhow::Result<Self> {
326 let l2_client =
327 zksync_web3_decl::client::Client::http(SensitiveUrl::from(config.url.clone()))?.build();
328 let block_number = if let Some(block_number) = block_number {
329 block_number
330 } else {
331 let block_number = l2_client
332 .get_block_number()
333 .await
334 .with_context(|| format!("failed to get block number from fork={}", config.url))?;
335 L2BlockNumber(block_number.as_u32())
336 };
337
338 Self::new(config, Box::new(l2_client), block_number).await
339 }
340
341 pub async fn at_before_tx(
344 config: ForkConfig,
345 tx_hash: H256,
346 ) -> anyhow::Result<(Self, Vec<Transaction>)> {
347 let l2_client =
348 zksync_web3_decl::client::Client::http(SensitiveUrl::from(config.url.clone()))?.build();
349 let tx_details = l2_client
350 .get_transaction_by_hash(tx_hash)
351 .await?
352 .ok_or_else(|| {
353 anyhow::anyhow!(
354 "could not find tx with hash={tx_hash:?} at fork={}",
355 config.url
356 )
357 })?;
358 let block_number = tx_details.block_number.ok_or_else(|| {
359 anyhow::anyhow!(
360 "could not initialize fork from tx with hash={tx_hash:?} as it is still pending"
361 )
362 })?;
363 let block_number = L2BlockNumber(block_number.as_u32());
364 if block_number == L2BlockNumber(0) {
365 anyhow::bail!(
366 "could not initialize fork from tx with hash={tx_hash:?} as it belongs to genesis"
367 );
368 }
369 let mut earlier_txs = Vec::new();
370 for tx in l2_client.get_raw_block_transactions(block_number).await? {
371 let hash = tx.hash();
372 earlier_txs.push(tx);
373
374 if hash == tx_hash {
375 break;
376 }
377 }
378
379 Ok((
381 Self::new(config, Box::new(l2_client), block_number - 1).await?,
382 earlier_txs,
383 ))
384 }
385}
386
387impl ForkClient {
388 pub async fn get_fee_params(&self) -> anyhow::Result<FeeParams> {
389 self.l2_client
390 .get_fee_params()
391 .await
392 .with_context(|| format!("failed to get fee parameters from fork={}", self.url))
393 }
394}
395
396#[cfg(test)]
397impl ForkClient {
398 pub fn mock(details: ForkDetails, storage: crate::deps::InMemoryStorage) -> Self {
399 use zksync_types::{u256_to_h256, AccountTreeId, StorageKey, H160};
400
401 let storage = Arc::new(RwLock::new(storage));
402 let storage_clone = storage.clone();
403 let l2_client = Box::new(
404 zksync_web3_decl::client::MockClient::builder(L2::default())
405 .method(
406 "eth_getStorageAt",
407 move |address: Address, idx: U256, _block: Option<api::BlockIdVariant>| {
408 let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(idx));
409 Ok(storage
410 .read()
411 .unwrap()
412 .state
413 .get(&key)
414 .cloned()
415 .unwrap_or_default())
416 },
417 )
418 .method("zks_getBytecodeByHash", move |hash: H256| {
419 Ok(storage_clone
420 .read()
421 .unwrap()
422 .factory_deps
423 .get(&hash)
424 .cloned())
425 })
426 .method("zks_getBlockDetails", move |block_number: L2BlockNumber| {
427 Ok(Some(api::BlockDetails {
428 number: block_number,
429 l1_batch_number: L1BatchNumber(123),
430 base: api::BlockDetailsBase {
431 timestamp: 0,
432 l1_tx_count: 0,
433 l2_tx_count: 0,
434 root_hash: None,
435 status: api::BlockStatus::Sealed,
436 commit_tx_hash: None,
437 committed_at: None,
438 commit_chain_id: None,
439 prove_tx_hash: None,
440 proven_at: None,
441 prove_chain_id: None,
442 execute_tx_hash: None,
443 executed_at: None,
444 execute_chain_id: None,
445 l1_gas_price: 123,
446 l2_fair_gas_price: 234,
447 fair_pubdata_price: Some(345),
448 base_system_contracts_hashes: Default::default(),
449 commit_tx_finality: None,
450 prove_tx_finality: None,
451 execute_tx_finality: None,
452 },
453 operator_address: H160::zero(),
454 protocol_version: None,
455 }))
456 })
457 .build(),
458 );
459 ForkClient {
460 url: Url::parse("http://test-fork-in-memory-storage.local").unwrap(),
461 details,
462 l2_client,
463 }
464 }
465}
466
467#[derive(Debug, Clone)]
468pub(super) struct Fork {
469 state: Arc<RwLock<ForkState>>,
470}
471
472#[derive(Debug)]
473struct ForkState {
474 client: Option<ForkClient>,
475 cache: Cache,
476}
477
478impl Fork {
479 pub(super) fn new(client: Option<ForkClient>, cache_config: CacheConfig) -> Self {
480 let cache = Cache::new(cache_config);
481 Self {
482 state: Arc::new(RwLock::new(ForkState { client, cache })),
483 }
484 }
485
486 pub(super) fn reset_fork_client(&self, client: Option<ForkClient>) {
487 self.write().client = client;
490 }
491
492 pub(super) fn set_fork_url(&self, new_url: Url) -> Option<Url> {
493 let mut writer = self.write();
496 if let Some(client) = writer.client.as_mut() {
497 Some(std::mem::replace(&mut client.url, new_url))
498 } else {
499 None
500 }
501 }
502
503 fn read(&self) -> RwLockReadGuard<ForkState> {
504 self.state.read().expect("Fork lock is poisoned")
505 }
506
507 fn write(&self) -> RwLockWriteGuard<ForkState> {
508 self.state.write().expect("Fork lock is poisoned")
509 }
510
511 async fn make_call<T, F: Future<Output = anyhow::Result<T>>>(
512 &self,
513 method: &str,
514 call_body: impl FnOnce(Box<DynClient<L2>>) -> F,
515 ) -> Option<anyhow::Result<T>> {
516 let (client, span) = if let Some(client) = self.read().client.as_ref() {
517 let span = tracing::info_span!("fork_rpc_call", method, url = %client.url);
518 (client.l2_client.clone(), span)
519 } else {
520 return None;
521 };
522 Some(
523 call_body(client)
524 .map_err(|error| {
525 sh_err!("call failed: {}", error);
526 error
527 })
528 .instrument(span)
529 .await,
530 )
531 }
532}
533
534#[async_trait]
535impl ForkSource for Fork {
536 fn dyn_cloned(&self) -> Box<dyn ForkSource> {
537 Box::new(self.clone())
538 }
539
540 fn url(&self) -> Option<Url> {
541 self.read().client.as_ref().map(|client| client.url.clone())
542 }
543
544 fn details(&self) -> Option<ForkDetails> {
545 self.read()
546 .client
547 .as_ref()
548 .map(|client| client.details.clone())
549 }
550
551 async fn get_storage_at(
552 &self,
553 address: Address,
554 idx: U256,
555 block: Option<api::BlockIdVariant>,
556 ) -> anyhow::Result<H256> {
557 self.make_call("get_storage_at", |client| async move {
560 client
561 .get_storage_at(address, idx, block)
562 .await
563 .with_context(|| format!("(address={address:?}, idx={idx:?})"))
564 })
565 .await
566 .unwrap_or(Ok(H256::zero()))
567 }
568
569 async fn get_storage_at_forked(&self, address: Address, idx: U256) -> anyhow::Result<H256> {
570 let Some(block_number) = self
571 .read()
572 .client
573 .as_ref()
574 .map(|client| client.details.block_number)
575 else {
576 return Ok(H256::zero());
577 };
578 self.get_storage_at(
579 address,
580 idx,
581 Some(api::BlockIdVariant::BlockNumber(api::BlockNumber::Number(
582 block_number.0.into(),
583 ))),
584 )
585 .await
586 }
587
588 async fn get_bytecode_by_hash(&self, hash: H256) -> anyhow::Result<Option<Vec<u8>>> {
589 self.make_call("get_bytecode_by_hash", |client| async move {
592 client
593 .get_bytecode_by_hash(hash)
594 .await
595 .with_context(|| format!("(hash={hash:?})"))
596 })
597 .await
598 .unwrap_or(Ok(None))
599 }
600
601 async fn get_transaction_by_hash(
602 &self,
603 hash: H256,
604 ) -> anyhow::Result<Option<api::Transaction>> {
605 if let Some(tx) = self.read().cache.get_transaction(&hash).cloned() {
606 tracing::debug!(?hash, "using cached transaction");
607 return Ok(Some(tx));
608 }
609
610 let tx = self
611 .make_call("get_transaction_by_hash", |client| async move {
612 client
613 .get_transaction_by_hash(hash)
614 .await
615 .with_context(|| format!("(hash={hash:?})"))
616 })
617 .await
618 .unwrap_or(Ok(None))?;
619
620 if let Some(tx) = tx {
621 self.write().cache.insert_transaction(hash, tx.clone());
622 Ok(Some(tx))
623 } else {
624 Ok(None)
625 }
626 }
627
628 async fn get_transaction_details(
629 &self,
630 hash: H256,
631 ) -> anyhow::Result<Option<api::TransactionDetails>> {
632 self.make_call("get_transaction_details", |client| async move {
636 client
637 .get_transaction_details(hash)
638 .await
639 .with_context(|| format!("(hash={hash:?})"))
640 })
641 .await
642 .unwrap_or(Ok(None))
643 }
644
645 async fn get_raw_block_transactions(
646 &self,
647 block_number: L2BlockNumber,
648 ) -> anyhow::Result<Vec<zksync_types::Transaction>> {
649 if let Some(txs) = self
650 .read()
651 .cache
652 .get_block_raw_transactions(&(block_number.0 as u64))
653 .cloned()
654 {
655 tracing::debug!(%block_number, "using cached block raw transactions");
656 return Ok(txs);
657 }
658
659 let txs = self
660 .make_call("get_raw_block_transactions", |client| async move {
661 client
662 .get_raw_block_transactions(block_number)
663 .await
664 .with_context(|| format!("(block_number={block_number})"))
665 })
666 .await
667 .unwrap_or(Err(Web3Error::NoBlock.into()))?;
668
669 self.write()
670 .cache
671 .insert_block_raw_transactions(block_number.0 as u64, txs.clone());
672 Ok(txs)
673 }
674
675 async fn get_block_by_hash(
676 &self,
677 hash: H256,
678 ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
679 if let Some(block) = self.read().cache.get_block(&hash, true).cloned() {
680 tracing::debug!(?hash, "using cached block");
681 return Ok(Some(block));
682 }
683
684 let block = self
685 .make_call("get_block_by_hash", |client| async move {
686 client
687 .get_block_by_hash(hash, true)
688 .await
689 .with_context(|| format!("(hash={hash:?}, full_transactions=true)"))
690 })
691 .await
692 .unwrap_or(Ok(None))?;
693
694 if let Some(block) = block {
695 self.write().cache.insert_block(hash, true, block.clone());
696 Ok(Some(block))
697 } else {
698 Ok(None)
699 }
700 }
701
702 async fn get_block_by_number(
703 &self,
704 block_number: api::BlockNumber,
705 ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
706 match block_number {
707 api::BlockNumber::Number(block_number) => {
708 {
709 let guard = self.read();
710 let cache = &guard.cache;
711 if let Some(block) = cache
712 .get_block_hash(&block_number.as_u64())
713 .and_then(|hash| cache.get_block(hash, true))
714 .cloned()
715 {
716 tracing::debug!(%block_number, "using cached block");
717 return Ok(Some(block));
718 }
719 }
720
721 let block = self
722 .make_call("get_block_by_number", |client| async move {
723 client
724 .get_block_by_number(api::BlockNumber::Number(block_number), true)
725 .await
726 .with_context(|| {
727 format!("(block_number={block_number}, full_transactions=true)")
728 })
729 })
730 .await
731 .unwrap_or(Ok(None))?;
732
733 if let Some(block) = block {
734 self.write()
735 .cache
736 .insert_block(block.hash, true, block.clone());
737 Ok(Some(block))
738 } else {
739 Ok(None)
740 }
741 }
742 _ => self
743 .make_call("get_block_by_number", |client| async move {
744 client
745 .get_block_by_number(block_number, true)
746 .await
747 .with_context(|| {
748 format!("(block_number={block_number}, full_transactions=true)")
749 })
750 })
751 .await
752 .unwrap_or(Ok(None)),
753 }
754 }
755
756 async fn get_block_details(
757 &self,
758 block_number: L2BlockNumber,
759 ) -> anyhow::Result<Option<api::BlockDetails>> {
760 self.make_call("get_block_details", |client| async move {
764 client
765 .get_block_details(block_number)
766 .await
767 .with_context(|| format!("(block_number={block_number})"))
768 })
769 .await
770 .unwrap_or(Ok(None))
771 }
772
773 async fn get_block_transaction_count_by_hash(
774 &self,
775 block_hash: H256,
776 ) -> anyhow::Result<Option<U256>> {
777 self.make_call("get_block_transaction_count_by_hash", |client| async move {
779 client
780 .get_block_transaction_count_by_hash(block_hash)
781 .await
782 .with_context(|| format!("(block_hash={block_hash:?})"))
783 })
784 .await
785 .unwrap_or(Ok(None))
786 }
787
788 async fn get_block_transaction_count_by_number(
789 &self,
790 block_number: api::BlockNumber,
791 ) -> anyhow::Result<Option<U256>> {
792 self.make_call(
794 "get_block_transaction_count_by_number",
795 |client| async move {
796 client
797 .get_block_transaction_count_by_number(block_number)
798 .await
799 .with_context(|| format!("(block_number={block_number})"))
800 },
801 )
802 .await
803 .unwrap_or(Ok(None))
804 }
805
806 async fn get_transaction_by_block_hash_and_index(
807 &self,
808 block_hash: H256,
809 index: Index,
810 ) -> anyhow::Result<Option<api::Transaction>> {
811 self.make_call(
813 "get_transaction_by_block_hash_and_index",
814 |client| async move {
815 client
816 .get_transaction_by_block_hash_and_index(block_hash, index)
817 .await
818 .with_context(|| format!("(block_hash={block_hash:?}, index={index})"))
819 },
820 )
821 .await
822 .unwrap_or(Ok(None))
823 }
824
825 async fn get_transaction_by_block_number_and_index(
826 &self,
827 block_number: api::BlockNumber,
828 index: Index,
829 ) -> anyhow::Result<Option<api::Transaction>> {
830 self.make_call(
832 "get_transaction_by_block_number_and_index",
833 |client| async move {
834 client
835 .get_transaction_by_block_number_and_index(block_number, index)
836 .await
837 .with_context(|| format!("(block_number={block_number}, index={index})"))
838 },
839 )
840 .await
841 .unwrap_or(Ok(None))
842 }
843
844 async fn get_bridge_contracts(&self) -> anyhow::Result<Option<api::BridgeAddresses>> {
845 if let Some(bridge_contracts) = self.read().cache.get_bridge_addresses().cloned() {
846 tracing::debug!("using cached bridge contracts");
847 return Ok(Some(bridge_contracts));
848 }
849
850 let bridge_contracts = self
851 .make_call("get_bridge_contracts", |client| async move {
852 Ok(Some(client.get_bridge_contracts().await?))
853 })
854 .await
855 .unwrap_or(Ok(None))?;
856
857 if let Some(bridge_contracts) = bridge_contracts {
858 self.write()
859 .cache
860 .set_bridge_addresses(bridge_contracts.clone());
861 Ok(Some(bridge_contracts))
862 } else {
863 Ok(None)
864 }
865 }
866
867 async fn get_confirmed_tokens(
868 &self,
869 from: u32,
870 limit: u8,
871 ) -> anyhow::Result<Option<Vec<zksync_web3_decl::types::Token>>> {
872 if let Some(confirmed_tokens) = self.read().cache.get_confirmed_tokens(from, limit).cloned()
873 {
874 tracing::debug!(from, limit, "using cached confirmed tokens");
875 return Ok(Some(confirmed_tokens));
876 }
877
878 let confirmed_tokens = self
879 .make_call("get_block_details", |client| async move {
880 Ok(Some(
881 client
882 .get_confirmed_tokens(from, limit)
883 .await
884 .with_context(|| format!("(from={from}, limit={limit})"))?,
885 ))
886 })
887 .await
888 .unwrap_or(Ok(None))?;
889
890 if let Some(confirmed_tokens) = confirmed_tokens {
891 self.write()
892 .cache
893 .set_confirmed_tokens(from, limit, confirmed_tokens.clone());
894 Ok(Some(confirmed_tokens))
895 } else {
896 Ok(None)
897 }
898 }
899}
900
901struct SupportedProtocolVersions;
902
903impl SupportedProtocolVersions {
904 const SUPPORTED_VERSIONS: [ProtocolVersionId; 3] = [
905 ProtocolVersionId::Version26,
906 ProtocolVersionId::Version27,
907 ProtocolVersionId::Version28,
908 ];
909
910 fn is_supported(version: ProtocolVersionId) -> bool {
911 Self::SUPPORTED_VERSIONS.contains(&version)
912 }
913}
914
915impl fmt::Display for SupportedProtocolVersions {
916 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
917 f.write_str(
918 &Self::SUPPORTED_VERSIONS
919 .iter()
920 .map(|v| v.to_string())
921 .join(", "),
922 )
923 }
924}
925
926#[cfg(test)]
927mod test {
928 use super::*;
929 use crate::deps::InMemoryStorage;
930 use maplit::hashmap;
931 use zksync_types::block::{pack_block_info, unpack_block_info};
932 use zksync_types::fee_model::{BaseTokenConversionRatio, FeeModelConfigV2, FeeParamsV2};
933 use zksync_types::{h256_to_u256, u256_to_h256, AccountTreeId, StorageKey};
934
935 impl Default for ForkDetails {
936 fn default() -> Self {
937 let config = FeeModelConfigV2 {
938 minimal_l2_gas_price: 10_000_000_000,
939 compute_overhead_part: 0.0,
940 pubdata_overhead_part: 1.0,
941 batch_overhead_l1_gas: 800_000,
942 max_gas_per_batch: 200_000_000,
943 max_pubdata_per_batch: 500_000,
944 };
945 Self {
946 chain_id: Default::default(),
947 protocol_version: ProtocolVersionId::latest(),
948 batch_number: Default::default(),
949 block_number: Default::default(),
950 block_hash: Default::default(),
951 block_timestamp: 0,
952 api_block: Default::default(),
953 l1_gas_price: 0,
954 l2_fair_gas_price: 0,
955 fair_pubdata_price: 0,
956 estimate_gas_price_scale_factor: 0.0,
957 estimate_gas_scale_factor: 0.0,
958 fee_params: FeeParams::V2(FeeParamsV2::new(
959 config,
960 10_000_000_000,
961 5_000_000_000,
962 BaseTokenConversionRatio::default(),
963 )),
964 }
965 }
966 }
967
968 #[tokio::test]
969 async fn test_mock_client() {
970 let input_batch = 1;
971 let input_l2_block = 2;
972 let input_timestamp = 3;
973 let input_bytecode = vec![0x4];
974 let batch_key = StorageKey::new(
975 AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
976 zksync_types::SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
977 );
978 let l2_block_key = StorageKey::new(
979 AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
980 zksync_types::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION,
981 );
982
983 let client = ForkClient::mock(
984 ForkDetails::default(),
985 InMemoryStorage {
986 state: hashmap! {
987 batch_key => u256_to_h256(U256::from(input_batch)),
988 l2_block_key => u256_to_h256(pack_block_info(
989 input_l2_block,
990 input_timestamp,
991 ))
992 },
993 factory_deps: hashmap! {
994 H256::repeat_byte(0x1) => input_bytecode.clone(),
995 },
996 },
997 );
998 let fork = Fork::new(Some(client), CacheConfig::None);
999
1000 let actual_batch = fork
1001 .get_storage_at(
1002 zksync_types::SYSTEM_CONTEXT_ADDRESS,
1003 h256_to_u256(zksync_types::SYSTEM_CONTEXT_BLOCK_INFO_POSITION),
1004 None,
1005 )
1006 .await
1007 .map(|value| h256_to_u256(value).as_u64())
1008 .expect("failed getting batch number");
1009 assert_eq!(input_batch, actual_batch);
1010
1011 let (actual_l2_block, actual_timestamp) = fork
1012 .get_storage_at(
1013 zksync_types::SYSTEM_CONTEXT_ADDRESS,
1014 h256_to_u256(zksync_types::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION),
1015 None,
1016 )
1017 .await
1018 .map(|value| unpack_block_info(h256_to_u256(value)))
1019 .expect("failed getting l2 block info");
1020 assert_eq!(input_l2_block, actual_l2_block);
1021 assert_eq!(input_timestamp, actual_timestamp);
1022
1023 let zero_missing_value = fork
1024 .get_storage_at(
1025 zksync_types::SYSTEM_CONTEXT_ADDRESS,
1026 h256_to_u256(H256::repeat_byte(0x1e)),
1027 None,
1028 )
1029 .await
1030 .map(|value| h256_to_u256(value).as_u64())
1031 .expect("failed missing value");
1032 assert_eq!(0, zero_missing_value);
1033
1034 let actual_bytecode = fork
1035 .get_bytecode_by_hash(H256::repeat_byte(0x1))
1036 .await
1037 .expect("failed getting bytecode")
1038 .expect("missing bytecode");
1039 assert_eq!(input_bytecode, actual_bytecode);
1040 }
1041}