starknet_devnet_core/starknet/
mod.rs

1use std::num::NonZeroU128;
2use std::sync::Arc;
3
4use alloy::primitives::B256;
5use blockifier::context::{BlockContext, ChainInfo, TransactionContext};
6use blockifier::execution::common_hints::ExecutionMode;
7use blockifier::state::cached_state::CachedState;
8use blockifier::state::state_api::StateReader;
9use blockifier::transaction::account_transaction::{AccountTransaction, ExecutionFlags};
10use blockifier::transaction::errors::TransactionExecutionError;
11use blockifier::transaction::objects::TransactionExecutionInfo;
12use blockifier::transaction::transactions::ExecutableTransaction;
13use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
14use parking_lot::RwLock;
15use starknet_api::block::{
16    BlockInfo, BlockNumber, BlockTimestamp, FeeType, GasPrice, GasPricePerToken, GasPriceVector,
17    GasPrices,
18};
19use starknet_api::block_hash::block_hash_calculator::calculate_block_commitments;
20use starknet_api::core::SequencerContractAddress;
21use starknet_api::data_availability::DataAvailabilityMode;
22use starknet_api::transaction::fields::{GasVectorComputationMode, Tip};
23use starknet_api::transaction::{TransactionHasher, TransactionVersion};
24use starknet_rs_core::types::{Felt, Hash256, MsgFromL1};
25use starknet_rs_core::utils::get_selector_from_name;
26use starknet_rs_signers::{LocalWallet, Signer, SigningKey};
27use starknet_types::chain_id::ChainId;
28use starknet_types::contract_address::ContractAddress;
29use starknet_types::contract_class::ContractClass;
30use starknet_types::emitted_event::EmittedEvent;
31use starknet_types::felt::{
32    BlockHash, ClassHash, TransactionHash, felt_from_prefixed_hex, split_biguint,
33};
34use starknet_types::num_bigint::BigUint;
35use starknet_types::patricia_key::PatriciaKey;
36use starknet_types::rpc::block::{
37    Block, BlockHeader, BlockId as CustomBlockId, BlockResult, BlockStatus,
38    BlockTag as CustomBlockTag, PreConfirmedBlock, PreConfirmedBlockHeader,
39};
40use starknet_types::rpc::estimate_message_fee::FeeEstimateWrapper;
41use starknet_types::rpc::gas_modification::{GasModification, GasModificationRequest};
42use starknet_types::rpc::state::{
43    PreConfirmedStateUpdate, StateUpdate, StateUpdateResult, ThinStateDiff,
44};
45use starknet_types::rpc::transaction_receipt::TransactionReceipt;
46use starknet_types::rpc::transactions::broadcasted_invoke_transaction_v3::BroadcastedInvokeTransactionV3;
47use starknet_types::rpc::transactions::l1_handler_transaction::L1HandlerTransaction;
48use starknet_types::rpc::transactions::{
49    BlockTransactionTrace, BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction,
50    BroadcastedInvokeTransaction, BroadcastedTransaction, BroadcastedTransactionCommonV3,
51    L1HandlerTransactionStatus, ResourceBoundsWrapper, SimulatedTransaction, SimulationFlag,
52    TransactionFinalityStatus, TransactionStatus, TransactionTrace, TransactionType,
53    TransactionWithHash, TransactionWithReceipt, Transactions,
54};
55use starknet_types::traits::HashProducer;
56use tracing::{error, info};
57
58use self::cheats::Cheats;
59use self::defaulter::StarknetDefaulter;
60use self::predeployed::initialize_erc20_at_address;
61use self::starknet_config::{StarknetConfig, StateArchiveCapacity};
62use self::transaction_trace::create_trace;
63use crate::account::Account;
64use crate::blocks::{StarknetBlock, StarknetBlocks};
65use crate::constants::{
66    ARGENT_CONTRACT_CLASS_HASH, ARGENT_CONTRACT_SIERRA, ARGENT_MULTISIG_CONTRACT_CLASS_HASH,
67    ARGENT_MULTISIG_CONTRACT_SIERRA, CHARGEABLE_ACCOUNT_ADDRESS, CHARGEABLE_ACCOUNT_PRIVATE_KEY,
68    DEVNET_DEFAULT_CHAIN_ID, DEVNET_DEFAULT_L1_DATA_GAS_PRICE, DEVNET_DEFAULT_L1_GAS_PRICE,
69    DEVNET_DEFAULT_L2_GAS_PRICE, DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
70    ENTRYPOINT_NOT_FOUND_ERROR_ENCODED, ETH_ERC20_CONTRACT_ADDRESS, ETH_ERC20_NAME,
71    ETH_ERC20_SYMBOL, STRK_ERC20_CONTRACT_ADDRESS, STRK_ERC20_NAME, STRK_ERC20_SYMBOL, USE_KZG_DA,
72};
73use crate::contract_class_choice::AccountContractClassChoice;
74use crate::error::{ContractExecutionError, DevnetResult, Error, TransactionValidationError};
75use crate::messaging::MessagingBroker;
76use crate::nonzero_gas_price;
77use crate::predeployed_accounts::PredeployedAccounts;
78use crate::state::state_diff::StateDiff;
79use crate::state::{CommittedClassStorage, CustomState, CustomStateReader, StarknetState};
80use crate::traits::{AccountGenerator, Deployed, HashIdentified, HashIdentifiedMut};
81use crate::transactions::{StarknetTransaction, StarknetTransactions};
82use crate::utils::{custom_bouncer_config, get_versioned_constants, maybe_extract_failure_reason};
83
84mod add_declare_transaction;
85mod add_deploy_account_transaction;
86mod add_invoke_transaction;
87mod add_l1_handler_transaction;
88mod cheats;
89pub(crate) mod defaulter;
90mod estimations;
91pub mod events;
92mod get_class_impls;
93mod predeployed;
94pub mod starknet_config;
95mod state_update;
96pub(crate) mod transaction_trace;
97
98pub struct Starknet {
99    pub latest_state: StarknetState,
100    pub pre_confirmed_state: StarknetState,
101    /// Contains the diff since the last block
102    pre_confirmed_state_diff: StateDiff,
103    predeployed_accounts: PredeployedAccounts,
104    pub(in crate::starknet) block_context: BlockContext,
105    // To avoid repeating some logic related to blocks,
106    // having `blocks` public allows to re-use functions like `get_blocks()`.
107    pub(crate) blocks: StarknetBlocks,
108    pub transactions: StarknetTransactions,
109    pub config: StarknetConfig,
110    pub pre_confirmed_block_timestamp_shift: i64,
111    pub next_block_timestamp: Option<u64>,
112    pub next_block_gas: GasModification,
113    pub(crate) messaging: MessagingBroker,
114    rpc_contract_classes: Arc<RwLock<CommittedClassStorage>>,
115    cheats: Cheats,
116}
117
118impl Default for Starknet {
119    fn default() -> Self {
120        Self {
121            block_context: Self::init_block_context(
122                DEVNET_DEFAULT_L1_GAS_PRICE,
123                DEVNET_DEFAULT_L1_GAS_PRICE,
124                DEVNET_DEFAULT_L1_DATA_GAS_PRICE,
125                DEVNET_DEFAULT_L1_DATA_GAS_PRICE,
126                DEVNET_DEFAULT_L2_GAS_PRICE,
127                DEVNET_DEFAULT_L2_GAS_PRICE,
128                ETH_ERC20_CONTRACT_ADDRESS,
129                STRK_ERC20_CONTRACT_ADDRESS,
130                DEVNET_DEFAULT_CHAIN_ID,
131                DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
132            ),
133            latest_state: Default::default(),
134            pre_confirmed_state: Default::default(),
135            pre_confirmed_state_diff: Default::default(),
136            predeployed_accounts: Default::default(),
137            blocks: Default::default(),
138            transactions: Default::default(),
139            config: Default::default(),
140            pre_confirmed_block_timestamp_shift: 0,
141            next_block_timestamp: None,
142            next_block_gas: GasModification {
143                gas_price_wei: DEVNET_DEFAULT_L1_GAS_PRICE,
144                data_gas_price_wei: DEVNET_DEFAULT_L1_DATA_GAS_PRICE,
145                gas_price_fri: DEVNET_DEFAULT_L1_GAS_PRICE,
146                data_gas_price_fri: DEVNET_DEFAULT_L1_DATA_GAS_PRICE,
147                l2_gas_price_fri: DEVNET_DEFAULT_L2_GAS_PRICE,
148                l2_gas_price_wei: DEVNET_DEFAULT_L2_GAS_PRICE,
149            },
150            messaging: Default::default(),
151            rpc_contract_classes: Default::default(),
152            cheats: Default::default(),
153        }
154    }
155}
156
157impl Starknet {
158    pub fn new(config: &StarknetConfig) -> DevnetResult<Self> {
159        let defaulter = StarknetDefaulter::new(config.fork_config.clone());
160        let rpc_contract_classes = Arc::new(RwLock::new(CommittedClassStorage::default()));
161        let mut state = StarknetState::new(defaulter, rpc_contract_classes.clone());
162
163        // predeclare account classes eligible for predeployment
164        for account_class_choice in
165            [AccountContractClassChoice::Cairo0, AccountContractClassChoice::Cairo1]
166        {
167            let class_wrapper = account_class_choice.get_class_wrapper()?;
168            state.predeclare_contract_class(
169                class_wrapper.class_hash,
170                class_wrapper.contract_class,
171            )?;
172        }
173
174        // predeclare argent account classes (not predeployable)
175        if config.predeclare_argent {
176            for (class_hash, raw_sierra) in [
177                (ARGENT_CONTRACT_CLASS_HASH, ARGENT_CONTRACT_SIERRA),
178                (ARGENT_MULTISIG_CONTRACT_CLASS_HASH, ARGENT_MULTISIG_CONTRACT_SIERRA),
179            ] {
180                let contract_class =
181                    ContractClass::Cairo1(ContractClass::cairo_1_from_sierra_json_str(raw_sierra)?);
182                state.predeclare_contract_class(class_hash, contract_class)?;
183            }
184        }
185
186        // deploy udc, eth erc20 and strk erc20 contracts
187        let eth_erc20_fee_contract = predeployed::create_erc20_at_address_extended(
188            ETH_ERC20_CONTRACT_ADDRESS,
189            config.eth_erc20_class_hash,
190            &config.eth_erc20_contract_class,
191        )?;
192        let strk_erc20_fee_contract = predeployed::create_erc20_at_address_extended(
193            STRK_ERC20_CONTRACT_ADDRESS,
194            config.strk_erc20_class_hash,
195            &config.strk_erc20_contract_class,
196        )?;
197
198        for udc in [predeployed::create_legacy_udc()?, predeployed::create_udc()?] {
199            udc.deploy(&mut state)?;
200        }
201
202        eth_erc20_fee_contract.deploy(&mut state)?;
203        initialize_erc20_at_address(
204            &mut state,
205            ETH_ERC20_CONTRACT_ADDRESS,
206            ETH_ERC20_NAME,
207            ETH_ERC20_SYMBOL,
208        )?;
209
210        strk_erc20_fee_contract.deploy(&mut state)?;
211        initialize_erc20_at_address(
212            &mut state,
213            STRK_ERC20_CONTRACT_ADDRESS,
214            STRK_ERC20_NAME,
215            STRK_ERC20_SYMBOL,
216        )?;
217
218        let mut predeployed_accounts = PredeployedAccounts::new(
219            config.seed,
220            config.predeployed_accounts_initial_balance.clone(),
221            eth_erc20_fee_contract.get_address(),
222            strk_erc20_fee_contract.get_address(),
223        );
224
225        let accounts = predeployed_accounts.generate_accounts(
226            config.total_accounts,
227            config.account_contract_class_hash,
228            &config.account_contract_class,
229        )?;
230        for account in accounts {
231            account.deploy(&mut state)?;
232        }
233
234        let chargeable_account = Account::new_chargeable(
235            eth_erc20_fee_contract.get_address(),
236            strk_erc20_fee_contract.get_address(),
237        )?;
238
239        if state
240            .state
241            .get_class_hash_at(chargeable_account.account_address.into())
242            .is_ok_and(|h| h.0 == Felt::ZERO)
243        {
244            chargeable_account.deploy(&mut state)?;
245        }
246
247        // when forking, the number of the first new block to be mined is equal to the last origin
248        // block (the one specified by the user) plus one.
249        // The parent hash of the first new block is equal to the last origin block hash.
250        let starting_block_number =
251            config.fork_config.block_number.map_or(DEVNET_DEFAULT_STARTING_BLOCK_NUMBER, |n| n + 1);
252        let last_block_hash = config.fork_config.block_hash;
253
254        let pre_confirmed_state_diff = state.commit_diff(starting_block_number)?;
255
256        let mut this = Self {
257            latest_state: Default::default(), // temporary - overwritten on genesis block creation
258            pre_confirmed_state: state,
259            pre_confirmed_state_diff,
260            predeployed_accounts,
261            block_context: Self::init_block_context(
262                config.gas_price_wei,
263                config.gas_price_fri,
264                config.data_gas_price_wei,
265                config.data_gas_price_fri,
266                config.l2_gas_price_wei,
267                config.l2_gas_price_fri,
268                ETH_ERC20_CONTRACT_ADDRESS,
269                STRK_ERC20_CONTRACT_ADDRESS,
270                config.chain_id,
271                starting_block_number,
272            ),
273            blocks: StarknetBlocks::new(starting_block_number, last_block_hash),
274            transactions: StarknetTransactions::default(),
275            config: config.clone(),
276            pre_confirmed_block_timestamp_shift: 0,
277            next_block_timestamp: None,
278            next_block_gas: GasModification {
279                gas_price_wei: config.gas_price_wei,
280                data_gas_price_wei: config.data_gas_price_wei,
281                gas_price_fri: config.gas_price_fri,
282                data_gas_price_fri: config.data_gas_price_fri,
283                l2_gas_price_wei: config.l2_gas_price_wei,
284                l2_gas_price_fri: config.l2_gas_price_fri,
285            },
286            messaging: Default::default(),
287            rpc_contract_classes,
288            cheats: Default::default(),
289        };
290
291        this.restart_pre_confirmed_block()?;
292
293        // Create an empty genesis block, set start_time before if it's set
294        if let Some(start_time) = config.start_time {
295            this.set_next_block_timestamp(start_time);
296        };
297        this.create_block()?;
298
299        Ok(this)
300    }
301
302    pub fn get_state(&mut self) -> &mut StarknetState {
303        &mut self.pre_confirmed_state
304    }
305
306    pub fn restart(&mut self, restart_l1_to_l2_messaging: bool) -> DevnetResult<()> {
307        let new_messsaging_ethereum =
308            if restart_l1_to_l2_messaging { None } else { self.messaging.ethereum.clone() };
309
310        *self = Starknet::new(&self.config)?;
311        self.messaging.ethereum = new_messsaging_ethereum;
312
313        info!("Starknet Devnet restarted");
314        Ok(())
315    }
316
317    pub fn get_predeployed_accounts(&self) -> Vec<Account> {
318        self.predeployed_accounts.get_accounts().to_vec()
319    }
320
321    // Update block context
322    // Initialize values for new pre_confirmed block
323    pub(crate) fn generate_pre_confirmed_block(&mut self) -> DevnetResult<()> {
324        Self::advance_block_context_block_number(&mut self.block_context);
325
326        Self::set_block_context_gas(&mut self.block_context, &self.next_block_gas);
327
328        // Pre_confirmed block header gas data needs to be set
329        let header = &mut self.blocks.pre_confirmed_block.header.block_header_without_hash;
330
331        // Set L1 gas prices
332        header.l1_gas_price = GasPricePerToken {
333            price_in_wei: GasPrice(self.next_block_gas.gas_price_wei.get()),
334            price_in_fri: GasPrice(self.next_block_gas.gas_price_fri.get()),
335        };
336
337        // Set L1 data gas prices
338        header.l1_data_gas_price = GasPricePerToken {
339            price_in_wei: GasPrice(self.next_block_gas.data_gas_price_wei.get()),
340            price_in_fri: GasPrice(self.next_block_gas.data_gas_price_fri.get()),
341        };
342
343        // Set L2 gas prices
344        header.l2_gas_price = GasPricePerToken {
345            price_in_wei: GasPrice(self.next_block_gas.l2_gas_price_wei.get()),
346            price_in_fri: GasPrice(self.next_block_gas.l2_gas_price_fri.get()),
347        };
348
349        self.restart_pre_confirmed_block()?;
350
351        Ok(())
352    }
353
354    fn next_block_timestamp(&mut self) -> BlockTimestamp {
355        match self.next_block_timestamp {
356            Some(timestamp) => {
357                self.next_block_timestamp = None;
358                BlockTimestamp(timestamp)
359            }
360            None => BlockTimestamp(
361                (Starknet::get_unix_timestamp_as_seconds() as i64
362                    + self.pre_confirmed_block_timestamp_shift) as u64,
363            ),
364        }
365    }
366
367    /// Transfer data from pre_confirmed block into new block and save it to blocks collection.
368    /// Generates new pre_confirmed block. Same for pre_confirmed state. Returns the new block hash.
369    pub(crate) fn generate_new_block_and_state(&mut self) -> DevnetResult<Felt> {
370        let timer = std::time::Instant::now();
371
372        let mut new_block = self.pre_confirmed_block().clone();
373
374        // Set new block header
375        // Update gas prices in the block header
376        let header = &mut new_block.header.block_header_without_hash;
377
378        let starknet_version = header.starknet_version;
379        let l1_da_mode = header.l1_da_mode;
380
381        // Set L1 gas prices
382        header.l1_gas_price = GasPricePerToken {
383            price_in_fri: GasPrice(self.next_block_gas.gas_price_fri.get()),
384            price_in_wei: GasPrice(self.next_block_gas.gas_price_wei.get()),
385        };
386
387        // Set L1 data gas prices
388        header.l1_data_gas_price = GasPricePerToken {
389            price_in_fri: GasPrice(self.next_block_gas.data_gas_price_fri.get()),
390            price_in_wei: GasPrice(self.next_block_gas.data_gas_price_wei.get()),
391        };
392
393        // Set L2 gas prices
394        header.l2_gas_price = GasPricePerToken {
395            price_in_fri: GasPrice(self.next_block_gas.l2_gas_price_fri.get()),
396            price_in_wei: GasPrice(self.next_block_gas.l2_gas_price_wei.get()),
397        };
398
399        let new_block_number = self.blocks.next_latest_block_number();
400        new_block.set_block_hash(if self.config.lite_mode {
401            BlockHash::from(new_block_number.0)
402        } else {
403            new_block.generate_hash()?
404        });
405        new_block.status = BlockStatus::AcceptedOnL2;
406        new_block.set_block_number(new_block_number.0);
407
408        // set block timestamp and context block timestamp for contract execution
409        let block_timestamp = self.next_block_timestamp();
410        new_block.set_timestamp(block_timestamp);
411        Self::update_block_context_block_timestamp(&mut self.block_context, block_timestamp);
412
413        let new_block_hash = new_block.header.block_hash.0;
414
415        // update block hash for each pre_confirmed tx; block number should already be set
416        new_block.get_transactions().iter().for_each(|tx_hash| {
417            if let Some(tx) = self.transactions.get_by_hash_mut(tx_hash) {
418                debug_assert_eq!(tx.block_number, Some(new_block_number));
419                tx.block_hash = Some(new_block_hash);
420                tx.finality_status = TransactionFinalityStatus::AcceptedOnL2;
421            } else {
422                error!("Transaction is not present in the transactions collection");
423            }
424        });
425
426        let transaction_data: Vec<
427            starknet_api::block_hash::block_hash_calculator::TransactionHashingData,
428        > = new_block
429            .get_transactions()
430            .iter()
431            // filter map is used here, although in normal conditions unwrap should be safe. Every transaction hash that has been added to preconfirmed block should be present in transactions collection
432            // changes should be done later so this is not even possible
433            .filter_map(|tx_hash| self.transactions.get_by_hash(*tx_hash))
434            .map(|tx| tx.into())
435            .collect();
436
437        let thin_state_diff = self.pre_confirmed_state_diff.clone().into();
438
439        if !self.config.lite_mode {
440            let commitments = calculate_block_commitments(
441                &transaction_data,
442                &thin_state_diff,
443                l1_da_mode,
444                &starknet_version,
445            );
446            new_block.set_commitments(commitments);
447        }
448
449        new_block.set_counts(
450            transaction_data.len(),
451            transaction_data.iter().map(|tx| tx.transaction_output.events.len()).sum(),
452            thin_state_diff.len(),
453        );
454
455        // insert pre_confirmed block in the blocks collection and connect it to the state diff
456        self.blocks.insert(new_block, self.pre_confirmed_state_diff.clone());
457        self.pre_confirmed_state_diff = StateDiff::default();
458
459        // save into blocks state archive
460        if self.config.state_archive == StateArchiveCapacity::Full {
461            let clone = self.pre_confirmed_state.clone_historic();
462            self.blocks.save_state_at(new_block_hash, clone);
463        }
464
465        self.generate_pre_confirmed_block()?;
466
467        // for every new block we need to clone pre_confirmed state into state
468        self.latest_state = self.pre_confirmed_state.clone_historic();
469
470        // Record metrics
471        let duration = timer.elapsed().as_secs_f64();
472        crate::metrics::BLOCK_CREATION_DURATION.observe(duration);
473        crate::metrics::BLOCK_COUNT.inc();
474
475        Ok(new_block_hash)
476    }
477
478    /// Commits the changes since the last commit. Use it to commit the changes generated by the
479    /// last tx. Updates the `pre_confirmed_state_diff` to accumulate the changes since the last
480    /// block. Check `StarknetState::commit_diff` for more info.
481    pub fn commit_diff(&mut self) -> DevnetResult<StateDiff> {
482        let next_block_number = self.blocks.next_latest_block_number();
483        let state_diff = self.pre_confirmed_state.commit_diff(next_block_number.0)?;
484        self.pre_confirmed_state_diff.extend(&state_diff);
485
486        Ok(state_diff)
487    }
488
489    /// Handles succeeded and reverted transactions. The tx is stored and potentially dumped. A new
490    /// block is generated in block-generation-on-transaction mode.
491    pub(crate) fn handle_accepted_transaction(
492        &mut self,
493        transaction: TransactionWithHash,
494        tx_info: TransactionExecutionInfo,
495    ) -> DevnetResult<()> {
496        let state_diff = self.commit_diff()?;
497        let transaction_hash = transaction.get_transaction_hash();
498        let gas_vector_computation_mode = transaction.transaction.gas_vector_computation_mode();
499
500        let trace = create_trace(
501            &mut self.pre_confirmed_state.state,
502            transaction.get_type(),
503            &tx_info,
504            state_diff.into(),
505            self.block_context.versioned_constants(),
506            &gas_vector_computation_mode,
507        )?;
508
509        let transaction_to_add = StarknetTransaction::pre_confirm(
510            &transaction,
511            tx_info,
512            trace,
513            self.blocks.pre_confirmed_block.block_number(),
514        );
515
516        // add accepted transaction to pre_confirmed block
517        self.blocks.pre_confirmed_block.add_transaction(*transaction_hash);
518
519        self.transactions.insert(transaction_hash, transaction_to_add);
520
521        // Update transaction count metric
522        crate::metrics::TRANSACTION_COUNT.inc();
523
524        // create new block from pre_confirmed one, only in block-generation-on-transaction mode
525        if !self.config.uses_pre_confirmed_block() {
526            self.generate_new_block_and_state()?;
527        }
528
529        Ok(())
530    }
531
532    #[allow(clippy::too_many_arguments)]
533    /// Create a BlockContext based on BlockContext::create_for_testing()
534    fn init_block_context(
535        gas_price_wei: NonZeroU128,
536        gas_price_fri: NonZeroU128,
537        data_gas_price_wei: NonZeroU128,
538        data_gas_price_fri: NonZeroU128,
539        l2_gas_price_wei: NonZeroU128,
540        l2_gas_price_fri: NonZeroU128,
541        eth_fee_token_address: Felt,
542        strk_fee_token_address: Felt,
543        chain_id: ChainId,
544        block_number: u64,
545    ) -> BlockContext {
546        let block_info = BlockInfo {
547            block_number: BlockNumber(block_number),
548            block_timestamp: BlockTimestamp(0),
549            sequencer_address: starknet_api::contract_address!("0x1000"),
550            gas_prices: GasPrices {
551                eth_gas_prices: GasPriceVector {
552                    l1_gas_price: nonzero_gas_price!(gas_price_wei),
553                    l1_data_gas_price: nonzero_gas_price!(data_gas_price_wei),
554                    l2_gas_price: nonzero_gas_price!(l2_gas_price_wei),
555                },
556                strk_gas_prices: GasPriceVector {
557                    l1_gas_price: nonzero_gas_price!(gas_price_fri),
558                    l1_data_gas_price: nonzero_gas_price!(data_gas_price_fri),
559                    l2_gas_price: nonzero_gas_price!(l2_gas_price_fri),
560                },
561            },
562            use_kzg_da: USE_KZG_DA,
563        };
564
565        let chain_info = ChainInfo {
566            chain_id: chain_id.into(),
567            fee_token_addresses: blockifier::context::FeeTokenAddresses {
568                eth_fee_token_address: starknet_api::contract_address!(
569                    eth_fee_token_address.to_hex_string().as_str()
570                ),
571                strk_fee_token_address: starknet_api::contract_address!(
572                    strk_fee_token_address.to_hex_string().as_str()
573                ),
574            },
575            is_l3: false,
576        };
577
578        BlockContext::new(
579            block_info,
580            chain_info,
581            get_versioned_constants(),
582            custom_bouncer_config(),
583        )
584    }
585
586    /// Update block context block_number with the next one
587    /// # Arguments
588    /// * `block_context` - BlockContext to be updated
589    fn advance_block_context_block_number(block_context: &mut BlockContext) {
590        let mut block_info = block_context.block_info().clone();
591        block_info.block_number = block_info.block_number.next().unwrap_or_default();
592        // TODO: update block_context via preferred method in the documentation
593        *block_context = BlockContext::new(
594            block_info,
595            block_context.chain_info().clone(),
596            get_versioned_constants(),
597            custom_bouncer_config(),
598        );
599    }
600
601    fn set_block_number(&mut self, block_number: u64) {
602        self.blocks.pre_confirmed_block.set_block_number(block_number);
603
604        let mut block_info = self.block_context.block_info().clone();
605        block_info.block_number.0 = block_number;
606
607        // TODO: update block_context via preferred method in the documentation
608        self.block_context = BlockContext::new(
609            block_info,
610            self.block_context.chain_info().clone(),
611            get_versioned_constants(),
612            custom_bouncer_config(),
613        );
614    }
615
616    fn set_block_context_gas(block_context: &mut BlockContext, gas_modification: &GasModification) {
617        let mut block_info = block_context.block_info().clone();
618
619        // Block info gas needs to be set here
620        block_info.gas_prices = GasPrices {
621            eth_gas_prices: GasPriceVector {
622                l1_gas_price: nonzero_gas_price!(gas_modification.gas_price_wei),
623                l1_data_gas_price: nonzero_gas_price!(gas_modification.data_gas_price_wei),
624                l2_gas_price: nonzero_gas_price!(gas_modification.l2_gas_price_wei),
625            },
626            strk_gas_prices: GasPriceVector {
627                l1_gas_price: nonzero_gas_price!(gas_modification.gas_price_fri),
628                l1_data_gas_price: nonzero_gas_price!(gas_modification.data_gas_price_fri),
629                l2_gas_price: nonzero_gas_price!(gas_modification.l2_gas_price_fri),
630            },
631        };
632
633        // TODO: update block_context via preferred method in the documentation
634        *block_context = BlockContext::new(
635            block_info,
636            block_context.chain_info().clone(),
637            get_versioned_constants(),
638            custom_bouncer_config(),
639        );
640    }
641
642    fn update_block_context_block_timestamp(
643        block_context: &mut BlockContext,
644        block_timestamp: BlockTimestamp,
645    ) {
646        let mut block_info = block_context.block_info().clone();
647        block_info.block_timestamp = block_timestamp;
648
649        // TODO: update block_context via preferred method in the documentation
650        *block_context = BlockContext::new(
651            block_info,
652            block_context.chain_info().clone(),
653            get_versioned_constants(),
654            custom_bouncer_config(),
655        );
656    }
657
658    fn pre_confirmed_block(&self) -> &StarknetBlock {
659        &self.blocks.pre_confirmed_block
660    }
661
662    /// Restarts pre-confirmed block with information from block_context
663    pub(crate) fn restart_pre_confirmed_block(&mut self) -> DevnetResult<()> {
664        let mut block = StarknetBlock::create_pre_confirmed_block();
665        let header = &mut block.header.block_header_without_hash;
666
667        header.block_number = self.block_context.block_info().block_number;
668
669        let gas_prices = &self.block_context.block_info().gas_prices;
670
671        header.l1_gas_price = GasPricePerToken {
672            price_in_fri: gas_prices.l1_gas_price(&FeeType::Strk).get(),
673            price_in_wei: gas_prices.l1_gas_price(&FeeType::Eth).get(),
674        };
675        header.l1_data_gas_price = GasPricePerToken {
676            price_in_fri: gas_prices.l1_data_gas_price(&FeeType::Strk).get(),
677            price_in_wei: gas_prices.l1_data_gas_price(&FeeType::Eth).get(),
678        };
679        header.l2_gas_price = GasPricePerToken {
680            price_in_fri: gas_prices.l2_gas_price(&FeeType::Strk).get(),
681            price_in_wei: gas_prices.l2_gas_price(&FeeType::Eth).get(),
682        };
683
684        header.sequencer =
685            SequencerContractAddress(self.block_context.block_info().sequencer_address);
686
687        block.set_timestamp(self.block_context.block_info().block_timestamp);
688
689        self.blocks.pre_confirmed_block = block;
690
691        Ok(())
692    }
693
694    fn get_mut_state_at(&mut self, block_id: &CustomBlockId) -> DevnetResult<&mut StarknetState> {
695        match block_id {
696            CustomBlockId::Tag(CustomBlockTag::Latest) => Ok(&mut self.latest_state),
697            CustomBlockId::Tag(CustomBlockTag::PreConfirmed) => Ok(&mut self.pre_confirmed_state),
698            _ => {
699                let block = self.get_block(block_id)?;
700                let block_hash = block.block_hash();
701
702                if self.blocks.last_block_hash == Some(block_hash) {
703                    return Ok(&mut self.latest_state);
704                }
705
706                if self.config.state_archive == StateArchiveCapacity::None {
707                    return Err(Error::NoStateAtBlock { block_id: *block_id });
708                }
709
710                let state = self
711                    .blocks
712                    .hash_to_state
713                    .get_mut(&block_hash)
714                    .ok_or(Error::NoStateAtBlock { block_id: *block_id })?;
715                Ok(state)
716            }
717        }
718    }
719
720    pub fn get_class_hash_at(
721        &mut self,
722        block_id: &CustomBlockId,
723        contract_address: ContractAddress,
724    ) -> DevnetResult<ClassHash> {
725        get_class_impls::get_class_hash_at_impl(self, block_id, contract_address)
726    }
727
728    pub fn get_class(
729        &self,
730        block_id: &CustomBlockId,
731        class_hash: ClassHash,
732    ) -> DevnetResult<ContractClass> {
733        get_class_impls::get_class_impl(self, block_id, class_hash)
734    }
735
736    pub fn get_class_at(
737        &mut self,
738        block_id: &CustomBlockId,
739        contract_address: ContractAddress,
740    ) -> DevnetResult<ContractClass> {
741        get_class_impls::get_class_at_impl(self, block_id, contract_address)
742    }
743
744    pub fn get_compiled_casm(&self, class_hash: ClassHash) -> DevnetResult<CasmContractClass> {
745        get_class_impls::get_compiled_casm_impl(self, class_hash)
746    }
747
748    pub fn call(
749        &mut self,
750        block_id: &CustomBlockId,
751        contract_address: Felt,
752        entrypoint_selector: Felt,
753        calldata: Vec<Felt>,
754    ) -> DevnetResult<Vec<Felt>> {
755        let block_context = Arc::new(self.block_context.clone());
756        let state = self.get_mut_state_at(block_id)?;
757
758        state.assert_contract_deployed(ContractAddress::new(contract_address)?)?;
759        let storage_address = contract_address.try_into()?;
760        let class_hash = state.get_class_hash_at(storage_address)?;
761
762        let mut initial_gas =
763            block_context.versioned_constants().sierra_gas_limit(&ExecutionMode::Execute);
764        let call = blockifier::execution::entry_point::CallEntryPoint {
765            calldata: starknet_api::transaction::fields::Calldata(Arc::new(calldata.clone())),
766            storage_address: contract_address.try_into()?,
767            entry_point_selector: starknet_api::core::EntryPointSelector(entrypoint_selector),
768            initial_gas: initial_gas.0,
769            class_hash: Some(class_hash),
770            ..Default::default()
771        };
772
773        let mut execution_context =
774            blockifier::execution::entry_point::EntryPointExecutionContext::new(
775                Arc::new(TransactionContext {
776                    block_context: block_context.clone(),
777                    tx_info: blockifier::transaction::objects::TransactionInfo::Deprecated(
778                        blockifier::transaction::objects::DeprecatedTransactionInfo::default(),
779                    ),
780                }),
781                blockifier::execution::common_hints::ExecutionMode::Execute,
782                false,
783                blockifier::execution::entry_point::SierraGasRevertTracker::new(initial_gas),
784            );
785
786        let mut transactional_state = CachedState::create_transactional(&mut state.state);
787        let res = call
788            .execute(&mut transactional_state, &mut execution_context, &mut initial_gas.0)
789            .map_err(|error| {
790                Error::ContractExecutionError(
791                    TransactionExecutionError::ExecutionError {
792                        error: Box::new(error),
793                        class_hash,
794                        storage_address,
795                        selector: starknet_api::core::EntryPointSelector(entrypoint_selector),
796                    }
797                    .into(),
798                )
799            })?;
800
801        if res.execution.failed {
802            if res.execution.retdata.0.first() == Some(&ENTRYPOINT_NOT_FOUND_ERROR_ENCODED) {
803                return Err(Error::EntrypointNotFound);
804            } else {
805                return Err(Error::ContractExecutionError(ContractExecutionError::from(&res)));
806            }
807        }
808
809        Ok(res.execution.retdata.0)
810    }
811
812    pub fn estimate_fee(
813        &mut self,
814        block_id: &CustomBlockId,
815        transactions: &[BroadcastedTransaction],
816        simulation_flags: &[SimulationFlag],
817    ) -> DevnetResult<Vec<FeeEstimateWrapper>> {
818        let mut skip_validate = false;
819        for flag in simulation_flags.iter() {
820            if *flag == SimulationFlag::SkipValidate {
821                skip_validate = true;
822            }
823        }
824        estimations::estimate_fee(self, block_id, transactions, None, Some(!skip_validate), true)
825    }
826
827    pub fn estimate_message_fee(
828        &mut self,
829        block_id: &CustomBlockId,
830        message: MsgFromL1,
831    ) -> DevnetResult<FeeEstimateWrapper> {
832        estimations::estimate_message_fee(self, block_id, message)
833    }
834
835    pub fn add_declare_transaction(
836        &mut self,
837        declare_transaction: BroadcastedDeclareTransaction,
838    ) -> DevnetResult<(TransactionHash, ClassHash)> {
839        add_declare_transaction::add_declare_transaction(self, declare_transaction)
840    }
841
842    /// returning the chain id as object
843    pub fn chain_id(&self) -> ChainId {
844        self.config.chain_id
845    }
846
847    pub fn add_deploy_account_transaction(
848        &mut self,
849        deploy_account_transaction: BroadcastedDeployAccountTransaction,
850    ) -> DevnetResult<(TransactionHash, ContractAddress)> {
851        add_deploy_account_transaction::add_deploy_account_transaction(
852            self,
853            deploy_account_transaction,
854        )
855    }
856
857    pub fn add_invoke_transaction(
858        &mut self,
859        invoke_transaction: BroadcastedInvokeTransaction,
860    ) -> DevnetResult<TransactionHash> {
861        add_invoke_transaction::add_invoke_transaction(self, invoke_transaction)
862    }
863
864    pub fn add_l1_handler_transaction(
865        &mut self,
866        l1_handler_transaction: L1HandlerTransaction,
867    ) -> DevnetResult<TransactionHash> {
868        add_l1_handler_transaction::add_l1_handler_transaction(self, l1_handler_transaction)
869    }
870
871    fn minting_calldata(
872        fundable_address: ContractAddress,
873        amount: BigUint,
874        erc20_address: ContractAddress,
875    ) -> DevnetResult<Vec<Felt>> {
876        let (high, low) = split_biguint(amount);
877
878        let mut calldata = vec![
879            Felt::ONE,            // number of calls
880            erc20_address.into(), // target address
881            get_selector_from_name("permissioned_mint")
882                .map_err(|e| Error::UnexpectedInternalError { msg: e.to_string() })?,
883        ];
884
885        let raw_calldata = vec![Felt::from(fundable_address), low, high];
886        calldata.push(raw_calldata.len().into());
887        for el in raw_calldata {
888            calldata.push(el);
889        }
890
891        Ok(calldata)
892    }
893
894    /// Creates an invoke tx for minting, using the chargeable account.
895    /// Uses permissioned_mint function of the ERC20 contract
896    pub async fn mint(
897        &mut self,
898        fundable_address: ContractAddress,
899        amount: BigUint,
900        erc20_address: ContractAddress,
901    ) -> DevnetResult<Felt> {
902        let chargeable_address = felt_from_prefixed_hex(CHARGEABLE_ACCOUNT_ADDRESS)?;
903        let state = self.get_state();
904        let nonce = state
905            .get_nonce_at(starknet_api::core::ContractAddress::try_from(chargeable_address)?)?;
906
907        let unsigned_tx = BroadcastedInvokeTransactionV3 {
908            sender_address: ContractAddress::new(chargeable_address)?,
909            calldata: Self::minting_calldata(fundable_address, amount, erc20_address)?,
910            common: BroadcastedTransactionCommonV3 {
911                version: Felt::THREE,
912                signature: vec![],
913                nonce: nonce.0,
914                resource_bounds: ResourceBoundsWrapper::new(
915                    1_000_000,
916                    self.config.gas_price_fri.get(),
917                    1_000_000,
918                    self.config.data_gas_price_fri.get(),
919                    1_000_000_000,
920                    self.config.l2_gas_price_fri.get(),
921                ),
922                tip: Tip(0),
923                paymaster_data: vec![],
924                nonce_data_availability_mode: DataAvailabilityMode::L1,
925                fee_data_availability_mode: DataAvailabilityMode::L1,
926            },
927            account_deployment_data: vec![],
928        };
929
930        // generate signature by signing the tx hash
931        let signer = LocalWallet::from(SigningKey::from_secret_scalar(felt_from_prefixed_hex(
932            CHARGEABLE_ACCOUNT_PRIVATE_KEY,
933        )?));
934        let tx_hash = unsigned_tx
935            .create_sn_api_invoke()?
936            .calculate_transaction_hash(&self.config.chain_id.into(), &TransactionVersion::THREE)?;
937        let signature = signer.sign_hash(&tx_hash).await?;
938
939        let mut invoke_tx = unsigned_tx;
940        invoke_tx.common.signature = vec![signature.r, signature.s];
941
942        // apply the invoke tx
943        add_invoke_transaction::add_invoke_transaction(
944            self,
945            BroadcastedInvokeTransaction::V3(invoke_tx),
946        )
947    }
948
949    pub fn block_state_update(&self, block_id: &CustomBlockId) -> DevnetResult<StateUpdateResult> {
950        let state_update = state_update::state_update_by_block_id(self, block_id)?;
951
952        // StateUpdate needs to be mapped to PreConfirmedStateUpdate when block_id is pre_confirmed
953        if let CustomBlockId::Tag(CustomBlockTag::PreConfirmed) = block_id {
954            Ok(StateUpdateResult::PreConfirmedStateUpdate(PreConfirmedStateUpdate {
955                old_root: Some(state_update.old_root),
956                state_diff: state_update.state_diff,
957            }))
958        } else {
959            Ok(StateUpdateResult::StateUpdate(StateUpdate {
960                block_hash: state_update.block_hash,
961                new_root: state_update.new_root,
962                old_root: state_update.old_root,
963                state_diff: state_update.state_diff,
964            }))
965        }
966    }
967
968    pub fn set_next_block_gas(
969        &mut self,
970        gas_prices: GasModificationRequest,
971    ) -> DevnetResult<GasModification> {
972        self.next_block_gas.update(gas_prices.clone());
973
974        // If generate_block is true, generate new block, for now custom dump_event is None but in
975        // future it will change to GasSetEvent with self.next_block_gas data
976        if let Some(true) = gas_prices.generate_block {
977            self.create_block()?
978        }
979
980        Ok(self.next_block_gas.clone())
981    }
982
983    pub fn abort_blocks(
984        &mut self,
985        mut starting_block_id: CustomBlockId,
986    ) -> DevnetResult<Vec<TransactionHash>> {
987        if self.config.state_archive != StateArchiveCapacity::Full {
988            let msg = "The abort blocks feature requires state-archive-capacity set to full.";
989            return Err(Error::UnsupportedAction { msg: msg.into() });
990        }
991
992        if let CustomBlockId::Tag(CustomBlockTag::PreConfirmed) = starting_block_id {
993            self.create_block()?;
994            starting_block_id = CustomBlockId::Tag(CustomBlockTag::Latest);
995        }
996
997        let starting_block_hash = match self.blocks.get_by_block_id(&starting_block_id) {
998            Some(block) => block.block_hash(),
999            None => return Err(Error::NoBlock),
1000        };
1001
1002        let genesis_block = self
1003            .blocks
1004            .get_by_block_id(&CustomBlockId::Number(self.blocks.starting_block_number))
1005            .ok_or(Error::UnsupportedAction { msg: "Cannot abort - no genesis block".into() })?;
1006
1007        if starting_block_hash == genesis_block.block_hash() {
1008            return Err(Error::UnsupportedAction { msg: "Genesis block can't be aborted".into() });
1009        }
1010
1011        let mut next_block_to_abort_hash = self
1012            .blocks
1013            .last_block_hash
1014            .ok_or(Error::UnsupportedAction { msg: "No blocks to abort".into() })?;
1015        let mut reached_starting_block = false;
1016        let mut aborted: Vec<Felt> = Vec::new();
1017
1018        let mut rpc_contract_classes = self.rpc_contract_classes.write();
1019
1020        // Abort blocks from latest to starting (iterating backwards) and revert transactions.
1021        let mut reverted_tx = 0;
1022        while !reached_starting_block {
1023            reached_starting_block = next_block_to_abort_hash == starting_block_hash;
1024
1025            let aborted_block = self.blocks.remove(&next_block_to_abort_hash).ok_or(
1026                Error::UnexpectedInternalError {
1027                    msg: format!("Cannot find block for abortion: {next_block_to_abort_hash:#x}"),
1028                },
1029            )?;
1030
1031            // Revert transactions
1032            for tx_hash in aborted_block.get_transactions() {
1033                self.transactions.remove(tx_hash).ok_or(Error::UnexpectedInternalError {
1034                    msg: format!("No tx of abortable block: {tx_hash:#x}"),
1035                })?;
1036                reverted_tx += 1;
1037            }
1038
1039            rpc_contract_classes.remove_classes_at(aborted_block.block_number().0);
1040            aborted.push(aborted_block.block_hash());
1041
1042            // Update next block hash to abort
1043            next_block_to_abort_hash = aborted_block.parent_hash();
1044        }
1045        let last_unaborted_block_hash = next_block_to_abort_hash;
1046
1047        // Update last_block_hash based on last reached block and revert state only if
1048        // starting block is reached in while loop.
1049        if reached_starting_block {
1050            self.blocks.last_block_hash = Some(last_unaborted_block_hash);
1051
1052            let reverted_state = self.blocks.hash_to_state.get(&last_unaborted_block_hash).ok_or(
1053                Error::NoStateAtBlock { block_id: CustomBlockId::Hash(last_unaborted_block_hash) },
1054            )?;
1055
1056            // In the abort block scenario, we need to revert state and pre_confirmed_state to be
1057            // able to use the calls properly.
1058            self.latest_state = reverted_state.clone_historic();
1059            self.pre_confirmed_state = reverted_state.clone_historic();
1060        }
1061
1062        self.pre_confirmed_state_diff = StateDiff::default();
1063        rpc_contract_classes.empty_staging();
1064        drop(rpc_contract_classes); // to later allow set_block_number
1065        self.blocks.aborted_blocks.extend_from_slice(&aborted);
1066
1067        // Pre-confirmed block is empty, but block number needs to be modified.
1068        let old_pre_confirmed_block_number = self.blocks.pre_confirmed_block.block_number().0;
1069        let new_pre_confirmed_block_number = old_pre_confirmed_block_number - aborted.len() as u64;
1070
1071        self.set_block_number(new_pre_confirmed_block_number);
1072
1073        // Reset metrics
1074        let old_tx_count = crate::metrics::TRANSACTION_COUNT.get();
1075        crate::metrics::TRANSACTION_COUNT.reset();
1076        crate::metrics::TRANSACTION_COUNT.inc_by(old_tx_count - reverted_tx);
1077
1078        let old_block_count = crate::metrics::BLOCK_COUNT.get();
1079        crate::metrics::BLOCK_COUNT.reset();
1080        crate::metrics::BLOCK_COUNT.inc_by(old_block_count - aborted.len() as u64);
1081
1082        Ok(aborted)
1083    }
1084
1085    pub fn last_aborted_block_hash(&self) -> Option<&BlockHash> {
1086        self.blocks.aborted_blocks.last()
1087    }
1088
1089    fn validate_acceptability_on_l1(&self, block_status: BlockStatus) -> DevnetResult<()> {
1090        let err_msg = match block_status {
1091            BlockStatus::AcceptedOnL2 => return Ok(()),
1092            BlockStatus::PreConfirmed => "Pre-confirmed block cannot be accepted on L1",
1093            BlockStatus::AcceptedOnL1 => "Block already accepted on L1",
1094            BlockStatus::Rejected => {
1095                "Rejected blocks should no longer exist; if you see this message, please report a bug at https://github.com/0xSpaceShard/starknet-devnet/issues/new"
1096            }
1097        };
1098
1099        Err(Error::UnsupportedAction { msg: err_msg.into() })
1100    }
1101
1102    pub fn accept_on_l1(&mut self, block_id: CustomBlockId) -> DevnetResult<Vec<BlockHash>> {
1103        let block_status = self.get_block(&block_id)?.status;
1104        let block_hash = self.get_block(&block_id)?.block_hash();
1105        // Only the starting block is validated; all ancestors are guaranteed to be ACCEPTED_ON_L2
1106        self.validate_acceptability_on_l1(block_status)?;
1107
1108        let mut acceptable_block =
1109            self.blocks.hash_to_block.get_mut(&block_hash).ok_or(Error::NoBlock)?;
1110        let mut accepted = vec![];
1111        while acceptable_block.status != BlockStatus::AcceptedOnL1 {
1112            // Accept block on L1
1113            acceptable_block.status = BlockStatus::AcceptedOnL1;
1114            accepted.push(acceptable_block.block_hash());
1115
1116            // Mark the transactions from the block as accepted on L1
1117            for tx_hash in acceptable_block.get_transactions() {
1118                let tx = self.transactions.get_by_hash_mut(tx_hash).ok_or(Error::NoTransaction)?;
1119                tx.finality_status = TransactionFinalityStatus::AcceptedOnL1;
1120            }
1121
1122            // Get next block for accepting: parent
1123            let parent_block_hash = acceptable_block.parent_hash();
1124            match self.blocks.hash_to_block.get_mut(&parent_block_hash) {
1125                Some(parent_block) => acceptable_block = parent_block,
1126                None => break, // Means genesis block is encountered
1127            }
1128        }
1129
1130        self.blocks.last_accepted_on_l1 = Some(block_hash);
1131        Ok(accepted)
1132    }
1133
1134    pub fn get_block_txs_count(&self, block_id: &CustomBlockId) -> DevnetResult<u64> {
1135        let block = self.get_block(block_id)?;
1136        Ok(block.get_transactions().len() as u64)
1137    }
1138
1139    pub fn contract_nonce_at_block(
1140        &mut self,
1141        block_id: &CustomBlockId,
1142        contract_address: ContractAddress,
1143    ) -> DevnetResult<Felt> {
1144        let state = self.get_mut_state_at(block_id)?;
1145        state.assert_contract_deployed(contract_address)?;
1146        let nonce = state.get_nonce_at(contract_address.into())?;
1147        Ok(nonce.0)
1148    }
1149
1150    pub fn contract_storage_at_block(
1151        &mut self,
1152        block_id: &CustomBlockId,
1153        contract_address: ContractAddress,
1154        storage_key: PatriciaKey,
1155    ) -> DevnetResult<Felt> {
1156        let state = self.get_mut_state_at(block_id)?;
1157        state.assert_contract_deployed(contract_address)?;
1158        Ok(state.get_storage_at(contract_address.into(), storage_key.into())?)
1159    }
1160
1161    pub fn get_block(&self, block_id: &CustomBlockId) -> DevnetResult<&StarknetBlock> {
1162        self.blocks.get_by_block_id(block_id).ok_or(Error::NoBlock)
1163    }
1164
1165    pub fn get_block_with_transactions(
1166        &self,
1167        block_id: &CustomBlockId,
1168    ) -> DevnetResult<BlockResult> {
1169        let block = self.get_block(block_id)?;
1170        let transactions = block
1171            .get_transactions()
1172            .iter()
1173            .map(|transaction_hash| {
1174                self.transactions
1175                    .get_by_hash(*transaction_hash)
1176                    .ok_or(Error::NoTransaction)
1177                    .map(|transaction| transaction.inner.clone())
1178            })
1179            .collect::<DevnetResult<Vec<TransactionWithHash>>>()?;
1180
1181        if block.status() == &BlockStatus::PreConfirmed {
1182            Ok(BlockResult::PreConfirmedBlock(PreConfirmedBlock {
1183                header: PreConfirmedBlockHeader::from(block),
1184                transactions: Transactions::Full(transactions),
1185            }))
1186        } else {
1187            Ok(BlockResult::Block(Block {
1188                status: *block.status(),
1189                header: BlockHeader::from(block),
1190                transactions: Transactions::Full(transactions),
1191            }))
1192        }
1193    }
1194
1195    pub fn get_block_with_receipts(&self, block_id: &CustomBlockId) -> DevnetResult<BlockResult> {
1196        let block = self.get_block(block_id)?;
1197        let mut transaction_receipts: Vec<TransactionWithReceipt> = vec![];
1198
1199        for transaction_hash in block.get_transactions() {
1200            let sn_transaction =
1201                self.transactions.get_by_hash(*transaction_hash).ok_or(Error::NoTransaction)?;
1202
1203            let transaction = sn_transaction.inner.clone();
1204            let mut receipt = sn_transaction.get_receipt()?;
1205            receipt.clear_block_properties(); // Already contained in block header
1206
1207            transaction_receipts
1208                .push(TransactionWithReceipt { receipt, transaction: transaction.transaction });
1209        }
1210
1211        if block.status() == &BlockStatus::PreConfirmed {
1212            Ok(BlockResult::PreConfirmedBlock(PreConfirmedBlock {
1213                header: PreConfirmedBlockHeader::from(block),
1214                transactions: Transactions::FullWithReceipts(transaction_receipts),
1215            }))
1216        } else {
1217            Ok(BlockResult::Block(Block {
1218                status: *block.status(),
1219                header: BlockHeader::from(block),
1220                transactions: Transactions::FullWithReceipts(transaction_receipts),
1221            }))
1222        }
1223    }
1224
1225    pub fn get_transaction_by_block_id_and_index(
1226        &self,
1227        block_id: &CustomBlockId,
1228        index: u64,
1229    ) -> DevnetResult<&TransactionWithHash> {
1230        let block = self.get_block(block_id)?;
1231        let transaction_hash = block
1232            .get_transactions()
1233            .get(index as usize)
1234            .ok_or(Error::InvalidTransactionIndexInBlock)?;
1235
1236        self.get_transaction_by_hash(*transaction_hash)
1237    }
1238
1239    pub fn get_latest_block(&self) -> DevnetResult<StarknetBlock> {
1240        let block = self
1241            .blocks
1242            .get_by_block_id(&CustomBlockId::Tag(CustomBlockTag::Latest))
1243            .ok_or(crate::error::Error::NoBlock)?;
1244
1245        Ok(block.clone())
1246    }
1247
1248    pub fn get_transaction_by_hash(
1249        &self,
1250        transaction_hash: Felt,
1251    ) -> DevnetResult<&TransactionWithHash> {
1252        self.transactions
1253            .get_by_hash(transaction_hash)
1254            .map(|starknet_transaction| &starknet_transaction.inner)
1255            .ok_or(Error::NoTransaction)
1256    }
1257
1258    pub fn get_unlimited_events(
1259        &self,
1260        from_block: Option<CustomBlockId>,
1261        to_block: Option<CustomBlockId>,
1262        address: Option<ContractAddress>,
1263        keys: Option<Vec<Vec<Felt>>>,
1264        finality_status_filter: Option<TransactionFinalityStatus>,
1265    ) -> DevnetResult<Vec<EmittedEvent>> {
1266        events::get_events(
1267            self,
1268            from_block,
1269            to_block,
1270            address,
1271            keys,
1272            finality_status_filter,
1273            0,
1274            None,
1275        )
1276        .map(|(emitted_events, _)| emitted_events)
1277    }
1278
1279    #[allow(clippy::too_many_arguments)]
1280    pub fn get_events(
1281        &self,
1282        from_block: Option<CustomBlockId>,
1283        to_block: Option<CustomBlockId>,
1284        address: Option<ContractAddress>,
1285        keys: Option<Vec<Vec<Felt>>>,
1286        finality_status_filter: Option<TransactionFinalityStatus>,
1287        skip: u64,
1288        limit: Option<u64>,
1289    ) -> DevnetResult<(Vec<EmittedEvent>, bool)> {
1290        events::get_events(
1291            self,
1292            from_block,
1293            to_block,
1294            address,
1295            keys,
1296            finality_status_filter,
1297            skip,
1298            limit,
1299        )
1300    }
1301
1302    pub fn get_transaction_receipt_by_hash(
1303        &self,
1304        transaction_hash: &TransactionHash,
1305    ) -> DevnetResult<TransactionReceipt> {
1306        let transaction_to_map =
1307            self.transactions.get(transaction_hash).ok_or(Error::NoTransaction)?;
1308
1309        transaction_to_map.get_receipt()
1310    }
1311
1312    pub fn get_transaction_trace_by_hash(
1313        &self,
1314        transaction_hash: TransactionHash,
1315    ) -> DevnetResult<TransactionTrace> {
1316        let tx = self.transactions.get(&transaction_hash).ok_or(Error::NoTransaction)?;
1317        tx.get_trace().ok_or(Error::NoTransactionTrace)
1318    }
1319
1320    pub fn get_transaction_traces_from_block(
1321        &self,
1322        block_id: &CustomBlockId,
1323    ) -> DevnetResult<Vec<BlockTransactionTrace>> {
1324        let transactions = match self.get_block_with_transactions(block_id)? {
1325            BlockResult::Block(b) => b.transactions,
1326            BlockResult::PreConfirmedBlock(b) => b.transactions,
1327        };
1328
1329        let mut traces = Vec::new();
1330        if let Transactions::Full(txs) = transactions {
1331            for tx in txs {
1332                let tx_hash = *tx.get_transaction_hash();
1333                let trace = self.get_transaction_trace_by_hash(tx_hash)?;
1334                let block_trace =
1335                    BlockTransactionTrace { transaction_hash: tx_hash, trace_root: trace };
1336
1337                traces.push(block_trace);
1338            }
1339        }
1340
1341        Ok(traces)
1342    }
1343
1344    pub fn get_transaction_execution_and_finality_status(
1345        &self,
1346        transaction_hash: TransactionHash,
1347    ) -> DevnetResult<TransactionStatus> {
1348        let transaction = self.transactions.get(&transaction_hash).ok_or(Error::NoTransaction)?;
1349        Ok(transaction.get_status())
1350    }
1351
1352    pub fn simulate_transactions(
1353        &mut self,
1354        block_id: &CustomBlockId,
1355        transactions: &[BroadcastedTransaction],
1356        simulation_flags: Vec<SimulationFlag>,
1357    ) -> DevnetResult<Vec<SimulatedTransaction>> {
1358        let chain_id = self.chain_id().to_felt();
1359        let block_context = self.block_context.clone();
1360
1361        let mut skip_validate = false;
1362        let mut skip_fee_charge = false;
1363        for flag in simulation_flags.iter() {
1364            match flag {
1365                SimulationFlag::SkipValidate => {
1366                    skip_validate = true;
1367                }
1368                SimulationFlag::SkipFeeCharge => skip_fee_charge = true,
1369            }
1370        }
1371        let using_pre_confirmed_block = self.config.uses_pre_confirmed_block();
1372
1373        let mut transactions_traces: Vec<TransactionTrace> = vec![];
1374        let cheats = self.cheats.clone();
1375        let state = self.get_mut_state_at(block_id)?;
1376
1377        let executable_txs = {
1378            transactions
1379                .iter()
1380                .enumerate()
1381                .map(|(tx_idx, txn)| {
1382                    // According to this conversation https://spaceshard.slack.com/archives/C03HL8DH52N/p1710683496750409, simulating a transaction will:
1383                    // fail if the fee provided is 0
1384                    // succeed if the fee provided is 0 and SKIP_FEE_CHARGE is set
1385                    // succeed if the fee provided is > 0
1386                    if !skip_fee_charge && !txn.are_gas_bounds_valid()  {
1387                        return Err(Error::ContractExecutionErrorInSimulation {
1388                            failure_index: tx_idx,
1389                            execution_error: ContractExecutionError::from(TransactionValidationError::InsufficientResourcesForValidate
1390                                .to_string()),
1391                        });
1392                    }
1393
1394                    let skip_validate_due_to_impersonation =
1395                        Starknet::should_transaction_skip_validation_if_sender_is_impersonated(
1396                            state, &cheats, txn,
1397                        )?;
1398
1399                    Ok((
1400                        txn.to_blockifier_account_transaction(&chain_id, ExecutionFlags {
1401                            only_query: true,
1402                            charge_fee: !skip_fee_charge,
1403                            validate: !(skip_validate || skip_validate_due_to_impersonation),
1404                            strict_nonce_check: txn.requires_strict_nonce_check(using_pre_confirmed_block),
1405                        })?,
1406                        txn.get_type(),
1407                        txn.gas_vector_computation_mode(),
1408                    ))
1409                })
1410                .collect::<DevnetResult<Vec<(AccountTransaction, TransactionType, GasVectorComputationMode)>>>()?
1411        };
1412
1413        let transactional_rpc_contract_classes =
1414            Arc::new(RwLock::new(state.clone_rpc_contract_classes()));
1415        let mut transactional_state =
1416            CachedState::new(CachedState::create_transactional(&mut state.state));
1417
1418        for (tx_idx, (blockifier_transaction, transaction_type, gas_vector_computation_mode)) in
1419            executable_txs.into_iter().enumerate()
1420        {
1421            let tx_execution_info = blockifier_transaction
1422                .execute(&mut transactional_state, &block_context)
1423                .map_err(|err| Error::ContractExecutionErrorInSimulation {
1424                    failure_index: tx_idx,
1425                    execution_error: ContractExecutionError::from(err),
1426                })?;
1427
1428            let block_number = block_context.block_info().block_number.0;
1429            let new_classes = transactional_rpc_contract_classes.write().commit(block_number);
1430            let state_diff: ThinStateDiff =
1431                StateDiff::generate(&mut transactional_state, new_classes)?.into();
1432            let trace = create_trace(
1433                &mut transactional_state,
1434                transaction_type,
1435                &tx_execution_info,
1436                state_diff,
1437                block_context.versioned_constants(),
1438                &gas_vector_computation_mode,
1439            )?;
1440            transactions_traces.push(trace);
1441        }
1442
1443        let estimated = estimations::estimate_fee(
1444            self,
1445            block_id,
1446            transactions,
1447            Some(!skip_fee_charge),
1448            Some(!skip_validate),
1449            false,
1450        )?;
1451
1452        // if the underlying simulation is correct, this should never be the case
1453        // in alignment with always avoiding assertions in production code, this has to be done
1454        if transactions_traces.len() != estimated.len() {
1455            return Err(Error::UnexpectedInternalError {
1456                msg: format!(
1457                    "Non-matching number of simulations ({}) and estimations ({})",
1458                    transactions_traces.len(),
1459                    estimated.len()
1460                ),
1461            });
1462        }
1463
1464        let simulation_results = transactions_traces
1465            .into_iter()
1466            .zip(estimated)
1467            .map(|(trace, fee_estimation)| SimulatedTransaction {
1468                transaction_trace: trace,
1469                fee_estimation,
1470            })
1471            .collect();
1472
1473        Ok(simulation_results)
1474    }
1475
1476    /// create new block from pre_confirmed one
1477    pub fn create_block(&mut self) -> DevnetResult<(), Error> {
1478        self.generate_new_block_and_state()?;
1479        Ok(())
1480    }
1481
1482    // Set time and optionally create a new block
1483    pub fn set_time(&mut self, timestamp: u64, create_block: bool) -> DevnetResult<(), Error> {
1484        self.set_block_timestamp_shift(
1485            timestamp as i64 - Starknet::get_unix_timestamp_as_seconds() as i64,
1486        );
1487
1488        self.set_next_block_timestamp(timestamp);
1489        if create_block {
1490            self.create_block()?;
1491        }
1492
1493        Ok(())
1494    }
1495
1496    // Set timestamp shift and create empty block
1497    pub fn increase_time(&mut self, time_shift: u64) -> DevnetResult<(), Error> {
1498        self.set_block_timestamp_shift(
1499            self.pre_confirmed_block_timestamp_shift + time_shift as i64,
1500        );
1501        self.create_block()
1502    }
1503
1504    // Set timestamp shift for next blocks
1505    pub fn set_block_timestamp_shift(&mut self, timestamp: i64) {
1506        self.pre_confirmed_block_timestamp_shift = timestamp;
1507    }
1508
1509    // Set next block timestamp
1510    pub fn set_next_block_timestamp(&mut self, timestamp: u64) {
1511        self.next_block_timestamp = Some(timestamp);
1512    }
1513
1514    #[allow(clippy::expect_used)]
1515    pub fn get_unix_timestamp_as_seconds() -> u64 {
1516        std::time::SystemTime::now()
1517            .duration_since(std::time::UNIX_EPOCH)
1518            .expect("should get current UNIX timestamp")
1519            .as_secs()
1520    }
1521
1522    /// Impersonates account, allowing to send transactions on behalf of the account, without its
1523    /// private key
1524    ///
1525    /// # Arguments
1526    /// * `account` - Account to impersonate
1527    pub fn impersonate_account(&mut self, account: ContractAddress) -> DevnetResult<(), Error> {
1528        if self.config.fork_config.url.is_none() {
1529            return Err(Error::UnsupportedAction {
1530                msg: "Account impersonation is supported when forking mode is enabled.".to_string(),
1531            });
1532        }
1533        if self.pre_confirmed_state.is_contract_deployed_locally(account)? {
1534            return Err(Error::UnsupportedAction {
1535                msg: "Account is in local state, cannot be impersonated".to_string(),
1536            });
1537        }
1538        self.cheats.impersonate_account(account);
1539        Ok(())
1540    }
1541
1542    /// Stops impersonating account.
1543    /// After this call, the account, previously impersonated can't be used to send transactions
1544    /// without its private key
1545    ///
1546    /// # Arguments
1547    /// * `account` - Account to stop impersonating
1548    pub fn stop_impersonating_account(&mut self, account: &ContractAddress) {
1549        self.cheats.stop_impersonating_account(account);
1550    }
1551
1552    /// Turn on/off auto impersonation of accounts that are not part of the state
1553    ///
1554    /// # Arguments
1555    /// * `auto_impersonation` - If true, auto impersonate every account that is not part of the
1556    ///   state, otherwise dont auto impersonate
1557    pub fn set_auto_impersonate_account(
1558        &mut self,
1559        auto_impersonation: bool,
1560    ) -> DevnetResult<(), Error> {
1561        if self.config.fork_config.url.is_none() {
1562            return Err(Error::UnsupportedAction {
1563                msg: "Account impersonation is supported when forking mode is enabled.".to_string(),
1564            });
1565        }
1566        self.cheats.set_auto_impersonate(auto_impersonation);
1567
1568        Ok(())
1569    }
1570
1571    /// Returns true if the account is not part of the state and is impersonated
1572    ///
1573    /// # Arguments
1574    /// * `account` - Account to check
1575    fn is_account_impersonated(
1576        state: &mut StarknetState,
1577        cheats: &Cheats,
1578        account: &ContractAddress,
1579    ) -> DevnetResult<bool> {
1580        let is_contract_already_in_state = state.is_contract_deployed_locally(*account)?;
1581        if is_contract_already_in_state {
1582            return Ok(false);
1583        }
1584
1585        Ok(cheats.is_impersonated(account))
1586    }
1587
1588    /// Returns true if the transaction should skip validation if the sender is impersonated
1589    ///
1590    /// # Arguments
1591    /// * `transaction` - Transaction to check
1592    fn should_transaction_skip_validation_if_sender_is_impersonated(
1593        state: &mut StarknetState,
1594        cheats: &Cheats,
1595        transaction: &BroadcastedTransaction,
1596    ) -> DevnetResult<bool> {
1597        let sender_address = match transaction {
1598            BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3(v3)) => {
1599                Some(&v3.sender_address)
1600            }
1601            BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V3(v3)) => {
1602                Some(&v3.sender_address)
1603            }
1604            BroadcastedTransaction::DeployAccount(_) => None,
1605        };
1606
1607        if let Some(sender_address) = sender_address {
1608            Starknet::is_account_impersonated(state, cheats, sender_address)
1609        } else {
1610            Ok(false)
1611        }
1612    }
1613
1614    pub fn get_messages_status(
1615        &self,
1616        l1_tx_hash: Hash256,
1617    ) -> Option<Vec<L1HandlerTransactionStatus>> {
1618        match self.messaging.l1_to_l2_tx_hashes.get(&B256::new(*l1_tx_hash.as_bytes())) {
1619            Some(l2_tx_hashes) => {
1620                let mut statuses = vec![];
1621                for l2_tx_hash in l2_tx_hashes {
1622                    match self.transactions.get(l2_tx_hash) {
1623                        Some(l2_tx) => statuses.push(L1HandlerTransactionStatus {
1624                            transaction_hash: *l2_tx_hash,
1625                            finality_status: l2_tx.finality_status,
1626                            execution_status: l2_tx.execution_result.status(),
1627                            failure_reason: maybe_extract_failure_reason(&l2_tx.execution_info),
1628                        }),
1629                        // should never happen due to handling in add_l1_handler_transaction
1630                        None => return None,
1631                    }
1632                }
1633                Some(statuses)
1634            }
1635            None => None,
1636        }
1637    }
1638}
1639
1640#[cfg(test)]
1641mod tests {
1642    use std::thread;
1643    use std::time::Duration;
1644
1645    use blockifier::state::state_api::{State, StateReader};
1646    use nonzero_ext::nonzero;
1647    use starknet_api::block::{BlockHash, BlockNumber, BlockTimestamp, FeeType};
1648    use starknet_rs_core::types::Felt;
1649    use starknet_rs_core::utils::get_selector_from_name;
1650    use starknet_types::contract_address::ContractAddress;
1651    use starknet_types::felt::felt_from_prefixed_hex;
1652    use starknet_types::rpc::block::{
1653        BlockId as CustomBlockId, BlockStatus, BlockTag as CustomBlockTag,
1654    };
1655    use starknet_types::rpc::state::Balance;
1656    use starknet_types::rpc::transaction_receipt::TransactionReceipt;
1657    use starknet_types::traits::HashProducer;
1658
1659    use super::Starknet;
1660    use super::starknet_config::BlockGenerationOn;
1661    use crate::account::{Account, FeeToken};
1662    use crate::blocks::StarknetBlock;
1663    use crate::constants::{
1664        ARGENT_CONTRACT_CLASS_HASH, ARGENT_MULTISIG_CONTRACT_CLASS_HASH,
1665        CAIRO_0_ACCOUNT_CONTRACT_HASH, CAIRO_1_ACCOUNT_CONTRACT_SIERRA_HASH,
1666        DEVNET_DEFAULT_CHAIN_ID, DEVNET_DEFAULT_INITIAL_BALANCE,
1667        DEVNET_DEFAULT_STARTING_BLOCK_NUMBER, ETH_ERC20_CONTRACT_ADDRESS,
1668        STRK_ERC20_CONTRACT_ADDRESS,
1669    };
1670    use crate::error::{DevnetResult, Error};
1671    use crate::starknet::starknet_config::{StarknetConfig, StateArchiveCapacity};
1672    use crate::traits::{Accounted, Deployed, HashIdentified};
1673    use crate::utils::test_utils::{
1674        broadcasted_declare_tx_v3_of_dummy_class, cairo_0_account_without_validations,
1675        dummy_contract_address, dummy_declare_tx_v3_with_hash, dummy_felt, dummy_key_pair,
1676        resource_bounds_with_price_1,
1677    };
1678
1679    /// Initializes starknet with 1 account that doesn't perform actual tx signature validation.
1680    /// Allows specifying the state archive capacity.
1681    pub(crate) fn setup_starknet_with_no_signature_check_account_and_state_capacity(
1682        acc_balance: u128,
1683        state_archive: StateArchiveCapacity,
1684    ) -> (Starknet, Account) {
1685        let mut starknet = Starknet::new(&StarknetConfig {
1686            gas_price_wei: nonzero!(1u128),
1687            gas_price_fri: nonzero!(1u128),
1688            data_gas_price_wei: nonzero!(1u128),
1689            data_gas_price_fri: nonzero!(1u128),
1690            l2_gas_price_wei: nonzero!(1u128),
1691            l2_gas_price_fri: nonzero!(1u128),
1692            state_archive,
1693            ..Default::default()
1694        })
1695        .unwrap();
1696
1697        let account_class = cairo_0_account_without_validations();
1698        let acc = Account::new(
1699            Balance::from(acc_balance),
1700            dummy_key_pair(),
1701            account_class.generate_hash().unwrap(),
1702            "Custom",
1703            account_class.into(),
1704            starknet.block_context.chain_info().fee_token_addresses.eth_fee_token_address.into(),
1705            starknet.block_context.chain_info().fee_token_addresses.strk_fee_token_address.into(),
1706        )
1707        .unwrap();
1708        acc.deploy(&mut starknet.pre_confirmed_state).unwrap();
1709
1710        starknet.commit_diff().unwrap();
1711        starknet.generate_new_block_and_state().unwrap();
1712        starknet.restart_pre_confirmed_block().unwrap();
1713
1714        (starknet, acc)
1715    }
1716
1717    /// Initializes starknet with 1 account that doesn't perform actual tx signature validation.
1718    pub(crate) fn setup_starknet_with_no_signature_check_account(
1719        acc_balance: u128,
1720    ) -> (Starknet, Account) {
1721        setup_starknet_with_no_signature_check_account_and_state_capacity(
1722            acc_balance,
1723            StateArchiveCapacity::None,
1724        )
1725    }
1726
1727    #[test]
1728    fn correct_initial_state_with_test_config() {
1729        let config = StarknetConfig::default();
1730        let mut starknet = Starknet::new(&config).unwrap();
1731        let predeployed_accounts = starknet.predeployed_accounts.get_accounts();
1732        let expected_balance = config.predeployed_accounts_initial_balance;
1733
1734        for account in predeployed_accounts {
1735            let account_balance =
1736                account.get_balance(&mut starknet.pre_confirmed_state, FeeToken::ETH).unwrap();
1737            assert_eq!(expected_balance, account_balance);
1738
1739            let account_balance =
1740                account.get_balance(&mut starknet.pre_confirmed_state, FeeToken::STRK).unwrap();
1741            assert_eq!(expected_balance, account_balance);
1742        }
1743    }
1744
1745    #[test]
1746    fn correct_block_context_creation() {
1747        let fee_token_address =
1748            ContractAddress::new(felt_from_prefixed_hex("0xAA").unwrap()).unwrap();
1749        let block_ctx = Starknet::init_block_context(
1750            nonzero!(10u128),
1751            nonzero!(10u128),
1752            nonzero!(10u128),
1753            nonzero!(10u128),
1754            nonzero!(10u128),
1755            nonzero!(10u128),
1756            felt_from_prefixed_hex("0xAA").unwrap(),
1757            STRK_ERC20_CONTRACT_ADDRESS,
1758            DEVNET_DEFAULT_CHAIN_ID,
1759            DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
1760        );
1761        assert_eq!(block_ctx.block_info().block_number, BlockNumber(0));
1762        assert_eq!(block_ctx.block_info().block_timestamp, BlockTimestamp(0));
1763        assert_eq!(block_ctx.block_info().gas_prices.l1_gas_price(&FeeType::Eth).get().0, 10);
1764        assert_eq!(
1765            ContractAddress::from(block_ctx.chain_info().fee_token_addresses.eth_fee_token_address),
1766            fee_token_address
1767        );
1768    }
1769
1770    #[test]
1771    fn pre_confirmed_block_is_correct() {
1772        let config = StarknetConfig::default();
1773        let mut starknet = Starknet::new(&config).unwrap();
1774        let initial_block_number = starknet.block_context.block_info().block_number;
1775        starknet.generate_pre_confirmed_block().unwrap();
1776
1777        assert_eq!(
1778            starknet.pre_confirmed_block().header.block_header_without_hash.block_number,
1779            initial_block_number.next().unwrap()
1780        );
1781    }
1782
1783    #[test]
1784    fn correct_new_block_creation() {
1785        let config = StarknetConfig::default();
1786        let mut starknet = Starknet::new(&config).unwrap();
1787
1788        let tx = dummy_declare_tx_v3_with_hash();
1789
1790        // add transaction hash to pre_confirmed block
1791        starknet.blocks.pre_confirmed_block.add_transaction(*tx.get_transaction_hash());
1792
1793        // pre_confirmed block has some transactions
1794        assert!(!starknet.pre_confirmed_block().get_transactions().is_empty());
1795        // blocks collection should not be empty
1796        assert_eq!(starknet.blocks.hash_to_block.len(), 1);
1797
1798        starknet.generate_new_block_and_state().unwrap();
1799        // blocks collection should not be empty
1800        assert_eq!(starknet.blocks.hash_to_block.len(), 2);
1801
1802        // get latest block and check that the transactions in the block are correct
1803        let added_block =
1804            starknet.blocks.get_by_hash(starknet.blocks.last_block_hash.unwrap()).unwrap();
1805
1806        assert!(added_block.get_transactions().len() == 1);
1807        assert_eq!(*added_block.get_transactions().first().unwrap(), *tx.get_transaction_hash());
1808    }
1809
1810    #[test]
1811    fn successful_emptying_of_pre_confirmed_block() {
1812        let config = StarknetConfig { start_time: Some(0), ..Default::default() };
1813        let mut starknet = Starknet::new(&config).unwrap();
1814
1815        let initial_block_number = starknet.block_context.block_info().block_number;
1816        let initial_gas_price_wei =
1817            starknet.block_context.block_info().gas_prices.l1_gas_price(&FeeType::Eth);
1818        let initial_gas_price_fri =
1819            starknet.block_context.block_info().gas_prices.l1_gas_price(&FeeType::Strk);
1820        let initial_data_gas_price_wei =
1821            starknet.block_context.block_info().gas_prices.l1_gas_price(&FeeType::Eth);
1822        let initial_data_gas_price_fri =
1823            starknet.block_context.block_info().gas_prices.l1_data_gas_price(&FeeType::Strk);
1824        let initial_block_timestamp = starknet.block_context.block_info().block_timestamp;
1825        let initial_sequencer = starknet.block_context.block_info().sequencer_address;
1826
1827        // create pre_confirmed block with some information in it
1828        let mut pre_confirmed_block = StarknetBlock::create_pre_confirmed_block();
1829        pre_confirmed_block.add_transaction(dummy_felt());
1830        pre_confirmed_block.status = BlockStatus::AcceptedOnL2;
1831
1832        // assign the pre_confirmed block
1833        starknet.blocks.pre_confirmed_block = pre_confirmed_block.clone();
1834        assert!(*starknet.pre_confirmed_block() == pre_confirmed_block);
1835
1836        // empty the pre_confirmed to block and check if it is in starting state
1837        starknet.restart_pre_confirmed_block().unwrap();
1838
1839        assert!(*starknet.pre_confirmed_block() != pre_confirmed_block);
1840        assert_eq!(starknet.pre_confirmed_block().status, BlockStatus::PreConfirmed);
1841        assert!(starknet.pre_confirmed_block().get_transactions().is_empty());
1842
1843        let header = &starknet.pre_confirmed_block().header.block_header_without_hash;
1844        assert_eq!(header.timestamp, initial_block_timestamp);
1845        assert_eq!(header.block_number, initial_block_number);
1846        assert_eq!(header.parent_hash, BlockHash::default());
1847        assert_eq!(header.l1_gas_price.price_in_wei, initial_gas_price_wei.get());
1848        assert_eq!(header.l1_gas_price.price_in_fri, initial_gas_price_fri.get());
1849        assert_eq!(header.l1_data_gas_price.price_in_wei, initial_data_gas_price_wei.get());
1850        assert_eq!(header.l1_data_gas_price.price_in_fri, initial_data_gas_price_fri.get());
1851        assert_eq!(header.l2_gas_price.price_in_fri, initial_gas_price_fri.get());
1852        assert_eq!(header.sequencer.0, initial_sequencer);
1853    }
1854
1855    #[test]
1856    fn correct_block_context_update() {
1857        let mut block_ctx = Starknet::init_block_context(
1858            nonzero!(1u128),
1859            nonzero!(1u128),
1860            nonzero!(1u128),
1861            nonzero!(1u128),
1862            nonzero!(1u128),
1863            nonzero!(1u128),
1864            ETH_ERC20_CONTRACT_ADDRESS,
1865            STRK_ERC20_CONTRACT_ADDRESS,
1866            DEVNET_DEFAULT_CHAIN_ID,
1867            DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
1868        );
1869        let initial_block_number = block_ctx.block_info().block_number;
1870        Starknet::advance_block_context_block_number(&mut block_ctx);
1871
1872        assert_eq!(block_ctx.block_info().block_number, initial_block_number.next().unwrap());
1873    }
1874
1875    #[test]
1876    fn getting_state_of_latest_block() {
1877        let config = StarknetConfig::default();
1878        let mut starknet = Starknet::new(&config).unwrap();
1879        starknet
1880            .get_mut_state_at(&CustomBlockId::Tag(CustomBlockTag::Latest))
1881            .expect("Should be OK");
1882    }
1883
1884    #[test]
1885    fn getting_state_of_pre_confirmed_block() {
1886        let config = StarknetConfig::default();
1887        let mut starknet = Starknet::new(&config).unwrap();
1888        starknet
1889            .get_mut_state_at(&CustomBlockId::Tag(CustomBlockTag::PreConfirmed))
1890            .expect("Should be OK");
1891    }
1892
1893    #[test]
1894    fn getting_state_at_block_by_nonexistent_hash_with_full_state_archive() {
1895        let config =
1896            StarknetConfig { state_archive: StateArchiveCapacity::Full, ..Default::default() };
1897        let mut starknet = Starknet::new(&config).unwrap();
1898        starknet.generate_new_block_and_state().unwrap();
1899
1900        match starknet.get_mut_state_at(&CustomBlockId::Hash(Felt::ZERO)) {
1901            Err(Error::NoBlock) => (),
1902            _ => panic!("Should fail with NoBlock"),
1903        }
1904    }
1905
1906    #[test]
1907    fn getting_nonexistent_state_at_block_by_number_with_full_state_archive() {
1908        let config =
1909            StarknetConfig { state_archive: StateArchiveCapacity::Full, ..Default::default() };
1910        let mut starknet = Starknet::new(&config).unwrap();
1911        let genesis_block_hash = starknet.get_latest_block().unwrap();
1912        let block_hash = starknet.generate_new_block_and_state().unwrap();
1913        starknet.blocks.hash_to_state.remove(&block_hash);
1914        starknet.blocks.last_block_hash = Some(genesis_block_hash.block_hash());
1915
1916        match starknet.get_mut_state_at(&CustomBlockId::Number(1)) {
1917            Err(Error::NoStateAtBlock { block_id: _ }) => (),
1918            _ => panic!("Should fail with NoStateAtBlock"),
1919        }
1920    }
1921
1922    #[test]
1923    fn getting_state_at_without_state_archive() {
1924        let config = StarknetConfig::default();
1925        let mut starknet = Starknet::new(&config).unwrap();
1926        starknet.generate_new_block_and_state().unwrap();
1927
1928        match starknet.get_mut_state_at(&CustomBlockId::Number(0)) {
1929            Err(Error::NoStateAtBlock { .. }) => (),
1930            _ => panic!("Should fail with NoStateAtBlock."),
1931        }
1932    }
1933
1934    #[test]
1935    fn assert_expected_predeclared_account_classes() {
1936        let config = StarknetConfig { predeclare_argent: true, ..Default::default() };
1937        let starknet = Starknet::new(&config).unwrap();
1938        for class_hash in [
1939            ARGENT_CONTRACT_CLASS_HASH,
1940            ARGENT_MULTISIG_CONTRACT_CLASS_HASH,
1941            Felt::from_hex_unchecked(CAIRO_0_ACCOUNT_CONTRACT_HASH),
1942            Felt::from_hex_unchecked(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_HASH),
1943        ] {
1944            let contract = starknet
1945                .get_class(&CustomBlockId::Tag(CustomBlockTag::Latest), class_hash)
1946                .unwrap();
1947            assert_eq!(contract.generate_hash().unwrap(), class_hash);
1948        }
1949    }
1950
1951    #[test]
1952    fn calling_method_of_undeployed_contract() {
1953        let config = StarknetConfig::default();
1954        let mut starknet = Starknet::new(&config).unwrap();
1955
1956        let undeployed_address = Felt::from_hex_unchecked("0x1234");
1957        let entry_point_selector = get_selector_from_name("balanceOf").unwrap();
1958
1959        match starknet.call(
1960            &CustomBlockId::Tag(CustomBlockTag::Latest),
1961            undeployed_address,
1962            entry_point_selector,
1963            vec![],
1964        ) {
1965            Err(Error::ContractNotFound) => (),
1966            unexpected => panic!("Should have failed; got {unexpected:?}"),
1967        }
1968    }
1969
1970    #[test]
1971    fn calling_nonexistent_contract_method() {
1972        let config = StarknetConfig::default();
1973        let mut starknet = Starknet::new(&config).unwrap();
1974
1975        let predeployed_account = &starknet.predeployed_accounts.get_accounts()[0];
1976        let entry_point_selector = get_selector_from_name("nonExistentMethod").unwrap();
1977
1978        match starknet.call(
1979            &CustomBlockId::Tag(CustomBlockTag::Latest),
1980            ETH_ERC20_CONTRACT_ADDRESS,
1981            entry_point_selector,
1982            vec![Felt::from(predeployed_account.account_address)],
1983        ) {
1984            Err(Error::EntrypointNotFound) => (),
1985            unexpected => panic!("Should have failed; got {unexpected:?}"),
1986        }
1987    }
1988
1989    /// utility method for happy path balance retrieval
1990    fn get_balance_at(
1991        starknet: &mut Starknet,
1992        contract_address: ContractAddress,
1993    ) -> DevnetResult<Vec<Felt>> {
1994        let entry_point_selector = get_selector_from_name("balanceOf").unwrap();
1995        starknet.call(
1996            &CustomBlockId::Tag(CustomBlockTag::Latest),
1997            ETH_ERC20_CONTRACT_ADDRESS,
1998            entry_point_selector,
1999            vec![Felt::from(contract_address)],
2000        )
2001    }
2002
2003    #[test]
2004    fn getting_balance_of_predeployed_contract() {
2005        let config = StarknetConfig::default();
2006        let mut starknet = Starknet::new(&config).unwrap();
2007
2008        let predeployed_account = &starknet.predeployed_accounts.get_accounts()[0].clone();
2009        let result = get_balance_at(&mut starknet, predeployed_account.account_address).unwrap();
2010
2011        let balance_uint256 = vec![Felt::from(DEVNET_DEFAULT_INITIAL_BALANCE), Felt::ZERO];
2012        assert_eq!(result, balance_uint256);
2013    }
2014
2015    #[test]
2016    fn getting_balance_of_undeployed_contract() {
2017        let config = StarknetConfig::default();
2018        let mut starknet = Starknet::new(&config).unwrap();
2019
2020        let undeployed_address = ContractAddress::new(Felt::from_hex_unchecked("0x1234")).unwrap();
2021        let result = get_balance_at(&mut starknet, undeployed_address).unwrap();
2022
2023        let expected_balance_uint256 = vec![Felt::ZERO, Felt::ZERO];
2024        assert_eq!(result, expected_balance_uint256);
2025    }
2026
2027    #[test]
2028    fn correct_latest_block() {
2029        let config = StarknetConfig::default();
2030        let mut starknet = Starknet::new(&config).unwrap();
2031
2032        // last added block number -> 0
2033        let added_block =
2034            starknet.blocks.get_by_hash(starknet.blocks.last_block_hash.unwrap()).unwrap();
2035        // number of the accepted block -> 1
2036        let block_number = starknet.get_latest_block().unwrap().block_number();
2037
2038        assert_eq!(block_number.0, added_block.header.block_header_without_hash.block_number.0);
2039
2040        starknet.generate_new_block_and_state().unwrap();
2041
2042        let added_block2 =
2043            starknet.blocks.get_by_hash(starknet.blocks.last_block_hash.unwrap()).unwrap();
2044        let block_number2 = starknet.get_latest_block().unwrap().block_number();
2045
2046        assert_eq!(block_number2.0, added_block2.header.block_header_without_hash.block_number.0);
2047    }
2048
2049    #[test]
2050    fn returns_chain_id() {
2051        let config = StarknetConfig::default();
2052        let starknet = Starknet::new(&config).unwrap();
2053        let chain_id = starknet.chain_id();
2054
2055        assert_eq!(chain_id.to_string(), DEVNET_DEFAULT_CHAIN_ID.to_string());
2056    }
2057
2058    #[test]
2059    fn correct_state_at_specific_block() {
2060        let mut starknet = Starknet::new(&StarknetConfig {
2061            state_archive: StateArchiveCapacity::Full,
2062            ..Default::default()
2063        })
2064        .expect("Could not start Devnet");
2065
2066        // generate initial block with empty state
2067        starknet.generate_new_block_and_state().unwrap();
2068
2069        // **generate second block**
2070        // add data to state
2071        starknet
2072            .pre_confirmed_state
2073            .state
2074            .increment_nonce(dummy_contract_address().into())
2075            .unwrap();
2076
2077        // generate new block and save the state
2078        starknet.commit_diff().unwrap();
2079        let second_block = starknet.generate_new_block_and_state().unwrap();
2080
2081        // **generate third block**
2082        // add data to state
2083        starknet
2084            .pre_confirmed_state
2085            .state
2086            .increment_nonce(dummy_contract_address().into())
2087            .unwrap();
2088
2089        // generate new block and save the state
2090        starknet.commit_diff().unwrap();
2091        let third_block = starknet.generate_new_block_and_state().unwrap();
2092
2093        // check modified state at block 1 and 2 to contain the correct value for the nonce
2094        let second_block_address_nonce = starknet
2095            .blocks
2096            .hash_to_state
2097            .get_mut(&second_block)
2098            .unwrap()
2099            .get_nonce_at(dummy_contract_address().into())
2100            .unwrap();
2101        let second_block_expected_address_nonce = Felt::ONE;
2102        assert_eq!(second_block_expected_address_nonce, second_block_address_nonce.0);
2103
2104        let third_block_address_nonce = starknet
2105            .blocks
2106            .hash_to_state
2107            .get_mut(&third_block)
2108            .unwrap()
2109            .get_nonce_at(dummy_contract_address().into())
2110            .unwrap();
2111        let third_block_expected_address_nonce = Felt::TWO;
2112        assert_eq!(third_block_expected_address_nonce, third_block_address_nonce.0);
2113    }
2114
2115    #[test]
2116    fn gets_latest_block() {
2117        let config = StarknetConfig::default();
2118        let mut starknet = Starknet::new(&config).unwrap();
2119
2120        starknet.generate_new_block_and_state().unwrap();
2121        starknet.generate_new_block_and_state().unwrap();
2122        starknet.generate_new_block_and_state().unwrap();
2123
2124        let latest_block = starknet.get_latest_block();
2125
2126        assert_eq!(latest_block.unwrap().block_number(), BlockNumber(3));
2127    }
2128    #[test]
2129    fn check_timestamp_of_newly_generated_block() {
2130        let config = StarknetConfig::default();
2131        let mut starknet = Starknet::new(&config).unwrap();
2132
2133        starknet.generate_new_block_and_state().unwrap();
2134        starknet
2135            .blocks
2136            .pre_confirmed_block
2137            .set_timestamp(BlockTimestamp(Starknet::get_unix_timestamp_as_seconds()));
2138        let pre_confirmed_block_timestamp =
2139            starknet.pre_confirmed_block().header.block_header_without_hash.timestamp;
2140
2141        let sleep_duration_secs = 5;
2142        thread::sleep(Duration::from_secs(sleep_duration_secs));
2143        starknet.generate_new_block_and_state().unwrap();
2144
2145        let block_timestamp =
2146            starknet.get_latest_block().unwrap().header.block_header_without_hash.timestamp;
2147        // check if the pre_confirmed_block_timestamp is less than the block_timestamp,
2148        // by number of sleep seconds because the timeline of events is this:
2149        // ----(pre_confirmed block timestamp)----(sleep)----(new block timestamp)
2150        assert!(pre_confirmed_block_timestamp.0 + sleep_duration_secs <= block_timestamp.0);
2151    }
2152
2153    #[test]
2154    fn test_block_abortion_when_state_archive_capacity_not_full() {
2155        let mut starknet = Starknet::new(&StarknetConfig {
2156            state_archive: StateArchiveCapacity::None,
2157            ..Default::default()
2158        })
2159        .unwrap();
2160
2161        let dummy_hash = felt_from_prefixed_hex("0x42").unwrap();
2162        match starknet.abort_blocks(CustomBlockId::Hash(dummy_hash)) {
2163            Err(Error::UnsupportedAction { msg }) => {
2164                assert!(msg.contains("state-archive-capacity"))
2165            }
2166            unexpected => panic!("Got unexpected response: {unexpected:?}"),
2167        }
2168    }
2169
2170    #[test]
2171    fn test_abortion_of_non_existent_block() {
2172        let mut starknet = Starknet::new(&StarknetConfig {
2173            state_archive: StateArchiveCapacity::Full,
2174            ..Default::default()
2175        })
2176        .unwrap();
2177
2178        let dummy_hash = felt_from_prefixed_hex("0x42").unwrap();
2179        match starknet.abort_blocks(CustomBlockId::Hash(dummy_hash)) {
2180            Err(Error::NoBlock) => (),
2181            unexpected => panic!("Got unexpected response: {unexpected:?}"),
2182        }
2183    }
2184
2185    #[test]
2186    fn receipt_should_have_block_properties_after_tx_is_accepted() {
2187        let (mut starknet, sender) = setup_starknet_with_no_signature_check_account(1e18 as u128);
2188        starknet.config.block_generation_on = BlockGenerationOn::Demand;
2189
2190        let tx = broadcasted_declare_tx_v3_of_dummy_class(
2191            sender.account_address,
2192            Felt::ZERO,
2193            resource_bounds_with_price_1(0, 1000, 1e9 as u64),
2194        );
2195
2196        let (tx_hash, _) = starknet.add_declare_transaction(tx.into()).unwrap();
2197
2198        let receipt = starknet.get_transaction_receipt_by_hash(&tx_hash).unwrap();
2199        match receipt {
2200            TransactionReceipt::Common(receipt) => {
2201                assert!(receipt.block_hash.is_none());
2202                assert_eq!(receipt.block_number, Some(BlockNumber(2)));
2203            }
2204            other => panic!("Unexpected receipt response: {other:?}"),
2205        }
2206
2207        // receipt should have block params after accepting the tx by triggering block creation
2208        starknet.generate_new_block_and_state().unwrap();
2209        let latest_block = starknet.get_block(&CustomBlockId::Tag(CustomBlockTag::Latest)).unwrap();
2210
2211        let receipt = starknet.get_transaction_receipt_by_hash(&tx_hash).unwrap();
2212        match receipt {
2213            TransactionReceipt::Common(receipt) => {
2214                assert_eq!(receipt.block_hash, Some(latest_block.block_hash()));
2215                assert_eq!(receipt.block_number, Some(BlockNumber(2))); // setup created block 1
2216            }
2217            other => panic!("Unexpected receipt response: {other:?}"),
2218        }
2219    }
2220}