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