anvil/eth/backend/
fork.rs

1//! Support for forking off another client
2
3use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction};
4use alloy_consensus::Account;
5use alloy_eips::eip2930::AccessListResult;
6use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse};
7use alloy_primitives::{
8    map::{FbHashMap, HashMap},
9    Address, Bytes, StorageValue, B256, U256,
10};
11use alloy_provider::{
12    ext::{DebugApi, TraceApi},
13    Provider,
14};
15use alloy_rpc_types::{
16    request::TransactionRequest,
17    simulate::{SimulatePayload, SimulatedBlock},
18    trace::{
19        geth::{GethDebugTracingOptions, GethTrace},
20        parity::LocalizedTransactionTrace as Trace,
21    },
22    BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse,
23    FeeHistory, Filter, Log,
24};
25use alloy_serde::WithOtherFields;
26use alloy_transport::TransportError;
27use anvil_core::eth::transaction::{convert_to_anvil_receipt, ReceiptResponse};
28use foundry_common::provider::{ProviderBuilder, RetryProvider};
29use parking_lot::{
30    lock_api::{RwLockReadGuard, RwLockWriteGuard},
31    RawRwLock, RwLock,
32};
33use revm::primitives::BlobExcessGasAndPrice;
34use std::{sync::Arc, time::Duration};
35use tokio::sync::RwLock as AsyncRwLock;
36
37/// Represents a fork of a remote client
38///
39/// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively
40/// fetch the requested data from the remote client, if it wasn't already fetched.
41#[derive(Clone, Debug)]
42pub struct ClientFork {
43    /// Contains the cached data
44    pub storage: Arc<RwLock<ForkedStorage>>,
45    /// contains the info how the fork is configured
46    // Wrapping this in a lock, ensures we can update this on the fly via additional custom RPC
47    // endpoints
48    pub config: Arc<RwLock<ClientForkConfig>>,
49    /// This also holds a handle to the underlying database
50    pub database: Arc<AsyncRwLock<Box<dyn Db>>>,
51}
52
53impl ClientFork {
54    /// Creates a new instance of the fork
55    pub fn new(config: ClientForkConfig, database: Arc<AsyncRwLock<Box<dyn Db>>>) -> Self {
56        Self { storage: Default::default(), config: Arc::new(RwLock::new(config)), database }
57    }
58
59    /// Reset the fork to a fresh forked state, and optionally update the fork config
60    pub async fn reset(
61        &self,
62        url: Option<String>,
63        block_number: impl Into<BlockId>,
64    ) -> Result<(), BlockchainError> {
65        let block_number = block_number.into();
66        {
67            self.database
68                .write()
69                .await
70                .maybe_reset(url.clone(), block_number)
71                .map_err(BlockchainError::Internal)?;
72        }
73
74        if let Some(url) = url {
75            self.config.write().update_url(url)?;
76            let override_chain_id = self.config.read().override_chain_id;
77            let chain_id = if let Some(chain_id) = override_chain_id {
78                chain_id
79            } else {
80                self.provider().get_chain_id().await?
81            };
82            self.config.write().chain_id = chain_id;
83        }
84
85        let provider = self.provider();
86        let block =
87            provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?;
88        let block_hash = block.header.hash;
89        let timestamp = block.header.timestamp;
90        let base_fee = block.header.base_fee_per_gas;
91        let total_difficulty = block.header.total_difficulty.unwrap_or_default();
92
93        let number = block.header.number;
94        self.config.write().update_block(
95            number,
96            block_hash,
97            timestamp,
98            base_fee.map(|g| g as u128),
99            total_difficulty,
100        );
101
102        self.clear_cached_storage();
103
104        self.database.write().await.insert_block_hash(U256::from(number), block_hash);
105
106        Ok(())
107    }
108
109    /// Removes all data cached from previous responses
110    pub fn clear_cached_storage(&self) {
111        self.storage.write().clear()
112    }
113
114    /// Returns true whether the block predates the fork
115    pub fn predates_fork(&self, block: u64) -> bool {
116        block < self.block_number()
117    }
118
119    /// Returns true whether the block predates the fork _or_ is the same block as the fork
120    pub fn predates_fork_inclusive(&self, block: u64) -> bool {
121        block <= self.block_number()
122    }
123
124    pub fn timestamp(&self) -> u64 {
125        self.config.read().timestamp
126    }
127
128    pub fn block_number(&self) -> u64 {
129        self.config.read().block_number
130    }
131
132    /// Returns the transaction hash we forked off of, if any.
133    pub fn transaction_hash(&self) -> Option<B256> {
134        self.config.read().transaction_hash
135    }
136
137    pub fn total_difficulty(&self) -> U256 {
138        self.config.read().total_difficulty
139    }
140
141    pub fn base_fee(&self) -> Option<u128> {
142        self.config.read().base_fee
143    }
144
145    pub fn block_hash(&self) -> B256 {
146        self.config.read().block_hash
147    }
148
149    pub fn eth_rpc_url(&self) -> String {
150        self.config.read().eth_rpc_url.clone()
151    }
152
153    pub fn chain_id(&self) -> u64 {
154        self.config.read().chain_id
155    }
156
157    fn provider(&self) -> Arc<RetryProvider> {
158        self.config.read().provider.clone()
159    }
160
161    fn storage_read(&self) -> RwLockReadGuard<'_, RawRwLock, ForkedStorage> {
162        self.storage.read()
163    }
164
165    fn storage_write(&self) -> RwLockWriteGuard<'_, RawRwLock, ForkedStorage> {
166        self.storage.write()
167    }
168
169    /// Returns the fee history  `eth_feeHistory`
170    pub async fn fee_history(
171        &self,
172        block_count: u64,
173        newest_block: BlockNumber,
174        reward_percentiles: &[f64],
175    ) -> Result<FeeHistory, TransportError> {
176        self.provider().get_fee_history(block_count, newest_block, reward_percentiles).await
177    }
178
179    /// Sends `eth_getProof`
180    pub async fn get_proof(
181        &self,
182        address: Address,
183        keys: Vec<B256>,
184        block_number: Option<BlockId>,
185    ) -> Result<EIP1186AccountProofResponse, TransportError> {
186        self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await
187    }
188
189    /// Sends `eth_call`
190    pub async fn call(
191        &self,
192        request: &WithOtherFields<TransactionRequest>,
193        block: Option<BlockNumber>,
194    ) -> Result<Bytes, TransportError> {
195        let block = block.unwrap_or(BlockNumber::Latest);
196        let res = self.provider().call(request.clone()).block(block.into()).await?;
197
198        Ok(res)
199    }
200
201    /// Sends `eth_simulateV1`
202    pub async fn simulate_v1(
203        &self,
204        request: &SimulatePayload,
205        block: Option<BlockNumber>,
206    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, TransportError> {
207        let mut simulate_call = self.provider().simulate(request);
208        if let Some(n) = block {
209            simulate_call = simulate_call.number(n.as_number().unwrap());
210        }
211
212        let res = simulate_call.await?;
213
214        Ok(res)
215    }
216
217    /// Sends `eth_estimateGas`
218    pub async fn estimate_gas(
219        &self,
220        request: &WithOtherFields<TransactionRequest>,
221        block: Option<BlockNumber>,
222    ) -> Result<u128, TransportError> {
223        let block = block.unwrap_or_default();
224        let res = self.provider().estimate_gas(request.clone()).block(block.into()).await?;
225
226        Ok(res as u128)
227    }
228
229    /// Sends `eth_createAccessList`
230    pub async fn create_access_list(
231        &self,
232        request: &WithOtherFields<TransactionRequest>,
233        block: Option<BlockNumber>,
234    ) -> Result<AccessListResult, TransportError> {
235        self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await
236    }
237
238    pub async fn storage_at(
239        &self,
240        address: Address,
241        index: U256,
242        number: Option<BlockNumber>,
243    ) -> Result<StorageValue, TransportError> {
244        self.provider()
245            .get_storage_at(address, index)
246            .block_id(number.unwrap_or_default().into())
247            .await
248    }
249
250    pub async fn logs(&self, filter: &Filter) -> Result<Vec<Log>, TransportError> {
251        if let Some(logs) = self.storage_read().logs.get(filter).cloned() {
252            return Ok(logs);
253        }
254
255        let logs = self.provider().get_logs(filter).await?;
256
257        let mut storage = self.storage_write();
258        storage.logs.insert(filter.clone(), logs.clone());
259        Ok(logs)
260    }
261
262    pub async fn get_code(
263        &self,
264        address: Address,
265        blocknumber: u64,
266    ) -> Result<Bytes, TransportError> {
267        trace!(target: "backend::fork", "get_code={:?}", address);
268        if let Some(code) = self.storage_read().code_at.get(&(address, blocknumber)).cloned() {
269            return Ok(code);
270        }
271
272        let block_id = BlockId::number(blocknumber);
273
274        let code = self.provider().get_code_at(address).block_id(block_id).await?;
275
276        let mut storage = self.storage_write();
277        storage.code_at.insert((address, blocknumber), code.clone().0.into());
278
279        Ok(code)
280    }
281
282    pub async fn get_balance(
283        &self,
284        address: Address,
285        blocknumber: u64,
286    ) -> Result<U256, TransportError> {
287        trace!(target: "backend::fork", "get_balance={:?}", address);
288        self.provider().get_balance(address).block_id(blocknumber.into()).await
289    }
290
291    pub async fn get_nonce(&self, address: Address, block: u64) -> Result<u64, TransportError> {
292        trace!(target: "backend::fork", "get_nonce={:?}", address);
293        self.provider().get_transaction_count(address).block_id(block.into()).await
294    }
295
296    pub async fn get_account(
297        &self,
298        address: Address,
299        blocknumber: u64,
300    ) -> Result<Account, TransportError> {
301        trace!(target: "backend::fork", "get_account={:?}", address);
302        self.provider().get_account(address).block_id(blocknumber.into()).await
303    }
304
305    pub async fn transaction_by_block_number_and_index(
306        &self,
307        number: u64,
308        index: usize,
309    ) -> Result<Option<AnyRpcTransaction>, TransportError> {
310        if let Some(block) = self.block_by_number(number).await? {
311            match block.transactions() {
312                BlockTransactions::Full(txs) => {
313                    if let Some(tx) = txs.get(index) {
314                        return Ok(Some(tx.clone()));
315                    }
316                }
317                BlockTransactions::Hashes(hashes) => {
318                    if let Some(tx_hash) = hashes.get(index) {
319                        return self.transaction_by_hash(*tx_hash).await;
320                    }
321                }
322                // TODO(evalir): Is it possible to reach this case? Should we support it
323                BlockTransactions::Uncle => panic!("Uncles not supported"),
324            }
325        }
326        Ok(None)
327    }
328
329    pub async fn transaction_by_block_hash_and_index(
330        &self,
331        hash: B256,
332        index: usize,
333    ) -> Result<Option<AnyRpcTransaction>, TransportError> {
334        if let Some(block) = self.block_by_hash(hash).await? {
335            match block.transactions() {
336                BlockTransactions::Full(txs) => {
337                    if let Some(tx) = txs.get(index) {
338                        return Ok(Some(tx.clone()));
339                    }
340                }
341                BlockTransactions::Hashes(hashes) => {
342                    if let Some(tx_hash) = hashes.get(index) {
343                        return self.transaction_by_hash(*tx_hash).await;
344                    }
345                }
346                // TODO(evalir): Is it possible to reach this case? Should we support it
347                BlockTransactions::Uncle => panic!("Uncles not supported"),
348            }
349        }
350        Ok(None)
351    }
352
353    pub async fn transaction_by_hash(
354        &self,
355        hash: B256,
356    ) -> Result<Option<AnyRpcTransaction>, TransportError> {
357        trace!(target: "backend::fork", "transaction_by_hash={:?}", hash);
358        if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() {
359            return Ok(tx);
360        }
361
362        let tx = self.provider().get_transaction_by_hash(hash).await?;
363        if let Some(tx) = tx.clone() {
364            let mut storage = self.storage_write();
365            storage.transactions.insert(hash, tx);
366        }
367        Ok(tx)
368    }
369
370    pub async fn trace_transaction(&self, hash: B256) -> Result<Vec<Trace>, TransportError> {
371        if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() {
372            return Ok(traces);
373        }
374
375        let traces = self.provider().trace_transaction(hash).await?.into_iter().collect::<Vec<_>>();
376
377        let mut storage = self.storage_write();
378        storage.transaction_traces.insert(hash, traces.clone());
379
380        Ok(traces)
381    }
382
383    pub async fn debug_trace_transaction(
384        &self,
385        hash: B256,
386        opts: GethDebugTracingOptions,
387    ) -> Result<GethTrace, TransportError> {
388        if let Some(traces) = self.storage_read().geth_transaction_traces.get(&hash).cloned() {
389            return Ok(traces);
390        }
391
392        let trace = self.provider().debug_trace_transaction(hash, opts).await?;
393
394        let mut storage = self.storage_write();
395        storage.geth_transaction_traces.insert(hash, trace.clone());
396
397        Ok(trace)
398    }
399
400    pub async fn trace_block(&self, number: u64) -> Result<Vec<Trace>, TransportError> {
401        if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() {
402            return Ok(traces);
403        }
404
405        let traces =
406            self.provider().trace_block(number.into()).await?.into_iter().collect::<Vec<_>>();
407
408        let mut storage = self.storage_write();
409        storage.block_traces.insert(number, traces.clone());
410
411        Ok(traces)
412    }
413
414    pub async fn transaction_receipt(
415        &self,
416        hash: B256,
417    ) -> Result<Option<ReceiptResponse>, BlockchainError> {
418        if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() {
419            return Ok(Some(receipt));
420        }
421
422        if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? {
423            let receipt =
424                convert_to_anvil_receipt(receipt).ok_or(BlockchainError::FailedToDecodeReceipt)?;
425            let mut storage = self.storage_write();
426            storage.transaction_receipts.insert(hash, receipt.clone());
427            return Ok(Some(receipt));
428        }
429
430        Ok(None)
431    }
432
433    pub async fn block_receipts(
434        &self,
435        number: u64,
436    ) -> Result<Option<Vec<ReceiptResponse>>, BlockchainError> {
437        if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() {
438            return Ok(receipts);
439        }
440
441        // TODO Needs to be removed.
442        // Since alloy doesn't indicate in the result whether the block exists,
443        // this is being temporarily implemented in anvil.
444        if self.predates_fork_inclusive(number) {
445            let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?;
446            let receipts = receipts
447                .map(|r| {
448                    r.into_iter()
449                        .map(|r| {
450                            convert_to_anvil_receipt(r)
451                                .ok_or(BlockchainError::FailedToDecodeReceipt)
452                        })
453                        .collect::<Result<Vec<_>, _>>()
454                })
455                .transpose()?;
456
457            if let Some(receipts) = receipts.clone() {
458                let mut storage = self.storage_write();
459                storage.block_receipts.insert(number, receipts);
460            }
461
462            return Ok(receipts);
463        }
464
465        Ok(None)
466    }
467
468    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, TransportError> {
469        if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() {
470            block.transactions.convert_to_hashes();
471            return Ok(Some(block));
472        }
473
474        Ok(self.fetch_full_block(hash).await?.map(|mut b| {
475            b.transactions.convert_to_hashes();
476            b
477        }))
478    }
479
480    pub async fn block_by_hash_full(
481        &self,
482        hash: B256,
483    ) -> Result<Option<AnyRpcBlock>, TransportError> {
484        if let Some(block) = self.storage_read().blocks.get(&hash).cloned() {
485            return Ok(Some(self.convert_to_full_block(block)));
486        }
487        self.fetch_full_block(hash).await
488    }
489
490    pub async fn block_by_number(
491        &self,
492        block_number: u64,
493    ) -> Result<Option<AnyRpcBlock>, TransportError> {
494        if let Some(mut block) = self
495            .storage_read()
496            .hashes
497            .get(&block_number)
498            .and_then(|hash| self.storage_read().blocks.get(hash).cloned())
499        {
500            block.transactions.convert_to_hashes();
501            return Ok(Some(block));
502        }
503
504        let mut block = self.fetch_full_block(block_number).await?;
505        if let Some(block) = &mut block {
506            block.transactions.convert_to_hashes();
507        }
508        Ok(block)
509    }
510
511    pub async fn block_by_number_full(
512        &self,
513        block_number: u64,
514    ) -> Result<Option<AnyRpcBlock>, TransportError> {
515        if let Some(block) = self
516            .storage_read()
517            .hashes
518            .get(&block_number)
519            .copied()
520            .and_then(|hash| self.storage_read().blocks.get(&hash).cloned())
521        {
522            return Ok(Some(self.convert_to_full_block(block)));
523        }
524
525        self.fetch_full_block(block_number).await
526    }
527
528    async fn fetch_full_block(
529        &self,
530        block_id: impl Into<BlockId>,
531    ) -> Result<Option<AnyRpcBlock>, TransportError> {
532        if let Some(block) = self.provider().get_block(block_id.into()).full().await? {
533            let hash = block.header.hash;
534            let block_number = block.header.number;
535            let mut storage = self.storage_write();
536            // also insert all transactions
537            let block_txs = match block.transactions() {
538                BlockTransactions::Full(txs) => txs.to_owned(),
539                _ => vec![],
540            };
541            storage.transactions.extend(block_txs.iter().map(|tx| (tx.tx_hash(), tx.clone())));
542            storage.hashes.insert(block_number, hash);
543            storage.blocks.insert(hash, block.clone());
544            return Ok(Some(block));
545        }
546
547        Ok(None)
548    }
549
550    pub async fn uncle_by_block_hash_and_index(
551        &self,
552        hash: B256,
553        index: usize,
554    ) -> Result<Option<AnyRpcBlock>, TransportError> {
555        if let Some(block) = self.block_by_hash(hash).await? {
556            return self.uncles_by_block_and_index(block, index).await;
557        }
558        Ok(None)
559    }
560
561    pub async fn uncle_by_block_number_and_index(
562        &self,
563        number: u64,
564        index: usize,
565    ) -> Result<Option<AnyRpcBlock>, TransportError> {
566        if let Some(block) = self.block_by_number(number).await? {
567            return self.uncles_by_block_and_index(block, index).await;
568        }
569        Ok(None)
570    }
571
572    async fn uncles_by_block_and_index(
573        &self,
574        block: AnyRpcBlock,
575        index: usize,
576    ) -> Result<Option<AnyRpcBlock>, TransportError> {
577        let block_hash = block.header.hash;
578        let block_number = block.header.number;
579        if let Some(uncles) = self.storage_read().uncles.get(&block_hash) {
580            return Ok(uncles.get(index).cloned());
581        }
582
583        let mut uncles = Vec::with_capacity(block.uncles.len());
584        for (uncle_idx, _) in block.uncles.iter().enumerate() {
585            let uncle =
586                match self.provider().get_uncle(block_number.into(), uncle_idx as u64).await? {
587                    Some(u) => u,
588                    None => return Ok(None),
589                };
590            uncles.push(uncle);
591        }
592        self.storage_write().uncles.insert(block_hash, uncles.clone());
593        Ok(uncles.get(index).cloned())
594    }
595
596    /// Converts a block of hashes into a full block
597    fn convert_to_full_block(&self, mut block: AnyRpcBlock) -> AnyRpcBlock {
598        let storage = self.storage.read();
599        let block_txs_len = match block.transactions {
600            BlockTransactions::Full(ref txs) => txs.len(),
601            BlockTransactions::Hashes(ref hashes) => hashes.len(),
602            // TODO: Should this be supported at all?
603            BlockTransactions::Uncle => 0,
604        };
605        let mut transactions = Vec::with_capacity(block_txs_len);
606        for tx in block.transactions.hashes() {
607            if let Some(tx) = storage.transactions.get(&tx).cloned() {
608                transactions.push(tx);
609            }
610        }
611        // TODO: fix once blocks have generic transactions
612        block.inner.transactions = BlockTransactions::Full(transactions);
613
614        block
615    }
616}
617
618/// Contains all fork metadata
619#[derive(Clone, Debug)]
620pub struct ClientForkConfig {
621    pub eth_rpc_url: String,
622    /// The block number of the forked block
623    pub block_number: u64,
624    /// The hash of the forked block
625    pub block_hash: B256,
626    /// The transaction hash we forked off of, if any.
627    pub transaction_hash: Option<B256>,
628    // TODO make provider agnostic
629    pub provider: Arc<RetryProvider>,
630    pub chain_id: u64,
631    pub override_chain_id: Option<u64>,
632    /// The timestamp for the forked block
633    pub timestamp: u64,
634    /// The basefee of the forked block
635    pub base_fee: Option<u128>,
636    /// Blob gas used of the forked block
637    pub blob_gas_used: Option<u128>,
638    /// Blob excess gas and price of the forked block
639    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
640    /// request timeout
641    pub timeout: Duration,
642    /// request retries for spurious networks
643    pub retries: u32,
644    /// request retries for spurious networks
645    pub backoff: Duration,
646    /// available CUPS
647    pub compute_units_per_second: u64,
648    /// total difficulty of the chain until this block
649    pub total_difficulty: U256,
650    /// Transactions to force include in the forked chain
651    pub force_transactions: Option<Vec<PoolTransaction>>,
652}
653
654impl ClientForkConfig {
655    /// Updates the provider URL
656    ///
657    /// # Errors
658    ///
659    /// This will fail if no new provider could be established (erroneous URL)
660    fn update_url(&mut self, url: String) -> Result<(), BlockchainError> {
661        // let interval = self.provider.get_interval();
662        self.provider = Arc::new(
663            ProviderBuilder::new(url.as_str())
664                .timeout(self.timeout)
665                // .timeout_retry(self.retries)
666                .max_retry(self.retries)
667                .initial_backoff(self.backoff.as_millis() as u64)
668                .compute_units_per_second(self.compute_units_per_second)
669                .build()
670                .map_err(|_| BlockchainError::InvalidUrl(url.clone()))?, // .interval(interval),
671        );
672        trace!(target: "fork", "Updated rpc url  {}", url);
673        self.eth_rpc_url = url;
674        Ok(())
675    }
676    /// Updates the block forked off `(block number, block hash, timestamp)`
677    pub fn update_block(
678        &mut self,
679        block_number: u64,
680        block_hash: B256,
681        timestamp: u64,
682        base_fee: Option<u128>,
683        total_difficulty: U256,
684    ) {
685        self.block_number = block_number;
686        self.block_hash = block_hash;
687        self.timestamp = timestamp;
688        self.base_fee = base_fee;
689        self.total_difficulty = total_difficulty;
690        trace!(target: "fork", "Updated block number={} hash={:?}", block_number, block_hash);
691    }
692}
693
694/// Contains cached state fetched to serve EthApi requests
695///
696/// This is used as a cache so repeated requests to the same data are not sent to the remote client
697#[derive(Clone, Debug, Default)]
698pub struct ForkedStorage {
699    pub uncles: FbHashMap<32, Vec<AnyRpcBlock>>,
700    pub blocks: FbHashMap<32, AnyRpcBlock>,
701    pub hashes: HashMap<u64, B256>,
702    pub transactions: FbHashMap<32, AnyRpcTransaction>,
703    pub transaction_receipts: FbHashMap<32, ReceiptResponse>,
704    pub transaction_traces: FbHashMap<32, Vec<Trace>>,
705    pub logs: HashMap<Filter, Vec<Log>>,
706    pub geth_transaction_traces: FbHashMap<32, GethTrace>,
707    pub block_traces: HashMap<u64, Vec<Trace>>,
708    pub block_receipts: HashMap<u64, Vec<ReceiptResponse>>,
709    pub code_at: HashMap<(Address, u64), Bytes>,
710}
711
712impl ForkedStorage {
713    /// Clears all data
714    pub fn clear(&mut self) {
715        // simply replace with a completely new, empty instance
716        *self = Self::default()
717    }
718}