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