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 pre_confirmed_state_diff: StateDiff,
104 predeployed_accounts: PredeployedAccounts,
105 pub(in crate::starknet) block_context: BlockContext,
106 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 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 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 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 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(), 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 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 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 let header = &mut self.blocks.pre_confirmed_block.header.block_header_without_hash;
331
332 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 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 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 pub(crate) fn generate_new_block_and_state(&mut self) -> DevnetResult<Felt> {
371 let mut new_block = self.pre_confirmed_block().clone();
372
373 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 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 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 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 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 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(|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 self.blocks.insert(new_block, self.pre_confirmed_state_diff.clone());
454 self.pre_confirmed_state_diff = StateDiff::default();
455
456 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 self.latest_state = self.pre_confirmed_state.clone_historic();
466
467 Ok(new_block_hash)
468 }
469
470 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 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 self.blocks.pre_confirmed_block.add_transaction(*transaction_hash);
510
511 self.transactions.insert(transaction_hash, transaction_to_add);
512
513 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 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 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 *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 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_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 *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 *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 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 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, erc20_address.into(), 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 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 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 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 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 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 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 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 next_block_to_abort_hash = aborted_block.parent_hash();
1031 }
1032 let last_unaborted_block_hash = next_block_to_abort_hash;
1033
1034 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 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); self.blocks.aborted_blocks.extend_from_slice(&aborted);
1053
1054 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 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 acceptable_block.status = BlockStatus::AcceptedOnL1;
1092 accepted.push(acceptable_block.block_hash());
1093
1094 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 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, }
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(); 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 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 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 pub fn create_block(&mut self) -> DevnetResult<(), Error> {
1456 self.generate_new_block_and_state()?;
1457 Ok(())
1458 }
1459
1460 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 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 pub fn set_block_timestamp_shift(&mut self, timestamp: i64) {
1484 self.pre_confirmed_block_timestamp_shift = timestamp;
1485 }
1486
1487 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 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 pub fn stop_impersonating_account(&mut self, account: &ContractAddress) {
1527 self.cheats.stop_impersonating_account(account);
1528 }
1529
1530 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 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 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 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 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 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 starknet.blocks.pre_confirmed_block.add_transaction(*tx.get_transaction_hash());
1770
1771 assert!(!starknet.pre_confirmed_block().get_transactions().is_empty());
1773 assert_eq!(starknet.blocks.hash_to_block.len(), 1);
1775
1776 starknet.generate_new_block_and_state().unwrap();
1777 assert_eq!(starknet.blocks.hash_to_block.len(), 2);
1779
1780 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 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 starknet.blocks.pre_confirmed_block = pre_confirmed_block.clone();
1812 assert!(*starknet.pre_confirmed_block() == pre_confirmed_block);
1813
1814 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 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 let added_block =
2012 starknet.blocks.get_by_hash(starknet.blocks.last_block_hash.unwrap()).unwrap();
2013 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 starknet.generate_new_block_and_state().unwrap();
2046
2047 starknet
2050 .pre_confirmed_state
2051 .state
2052 .increment_nonce(dummy_contract_address().into())
2053 .unwrap();
2054
2055 starknet.commit_diff().unwrap();
2057 let second_block = starknet.generate_new_block_and_state().unwrap();
2058
2059 starknet
2062 .pre_confirmed_state
2063 .state
2064 .increment_nonce(dummy_contract_address().into())
2065 .unwrap();
2066
2067 starknet.commit_diff().unwrap();
2069 let third_block = starknet.generate_new_block_and_state().unwrap();
2070
2071 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 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 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))); }
2195 other => panic!("Unexpected receipt response: {other:?}"),
2196 }
2197 }
2198}