1use std::sync::Arc;
2
3use blockifier::blockifier_versioned_constants::VersionedConstants;
4use blockifier::state::state_api::StateReader;
5use blockifier::transaction::account_transaction::ExecutionFlags;
6use blockifier::transaction::objects::TransactionExecutionInfo;
7use deploy_transaction::DeployTransaction;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use starknet_api::block::{BlockNumber, GasPrice};
10use starknet_api::contract_class::{ClassInfo, EntryPointType};
11use starknet_api::core::calculate_contract_address;
12use starknet_api::data_availability::DataAvailabilityMode;
13use starknet_api::transaction::fields::{
14 AllResourceBounds, GasVectorComputationMode, Tip, ValidResourceBounds,
15};
16use starknet_api::transaction::{TransactionHasher, TransactionOptions, signed_tx_version};
17use starknet_rs_core::types::{
18 EventsPage, ExecutionResult, Felt, ResourceBounds, ResourceBoundsMapping,
19 TransactionExecutionStatus,
20};
21use starknet_rs_core::utils::parse_cairo_short_string;
22
23use self::broadcasted_declare_transaction_v3::BroadcastedDeclareTransactionV3;
24use self::broadcasted_deploy_account_transaction_v3::BroadcastedDeployAccountTransactionV3;
25use self::broadcasted_invoke_transaction_v3::BroadcastedInvokeTransactionV3;
26use self::declare_transaction_v3::DeclareTransactionV3;
27use self::deploy_account_transaction_v3::DeployAccountTransactionV3;
28use self::invoke_transaction_v3::InvokeTransactionV3;
29use self::l1_handler_transaction::L1HandlerTransaction;
30use super::block::BlockId;
31use super::estimate_message_fee::FeeEstimateWrapper;
32use super::felt::BlockHash;
33use super::messaging::{MessageToL1, OrderedMessageToL1};
34use super::state::ThinStateDiff;
35use super::transaction_receipt::{ExecutionResources, FeeInUnits, TransactionReceipt};
36use crate::constants::QUERY_VERSION_OFFSET;
37use crate::contract_address::ContractAddress;
38use crate::contract_class::{ContractClass, compute_sierra_class_hash};
39use crate::emitted_event::{Event, OrderedEvent};
40use crate::error::{ConversionError, DevnetResult};
41use crate::felt::{
42 Calldata, EntryPointSelector, Nonce, TransactionHash, TransactionSignature, TransactionVersion,
43};
44use crate::rpc::transaction_receipt::CommonTransactionReceipt;
45use crate::{impl_wrapper_deserialize, impl_wrapper_serialize};
46
47pub mod broadcasted_declare_transaction_v3;
48pub mod broadcasted_deploy_account_transaction_v3;
49pub mod broadcasted_invoke_transaction_v3;
50
51pub mod declare_transaction_v3;
52pub mod deploy_account_transaction_v3;
53pub mod deploy_transaction;
54pub mod invoke_transaction_v3;
55
56pub mod l1_handler_transaction;
57
58#[derive(Debug, Clone, Serialize)]
59#[cfg_attr(feature = "testing", derive(Deserialize))]
60#[serde(untagged)]
61pub enum Transactions {
62 Hashes(Vec<TransactionHash>),
63 Full(Vec<TransactionWithHash>),
64 FullWithReceipts(Vec<TransactionWithReceipt>),
65}
66
67#[derive(Debug, Copy, Clone, Serialize, Default)]
68#[cfg_attr(feature = "testing", derive(Deserialize))]
69pub enum TransactionType {
70 #[serde(rename(deserialize = "DECLARE", serialize = "DECLARE"))]
71 Declare,
72 #[serde(rename(deserialize = "DEPLOY", serialize = "DEPLOY"))]
73 Deploy,
74 #[serde(rename(deserialize = "DEPLOY_ACCOUNT", serialize = "DEPLOY_ACCOUNT"))]
75 DeployAccount,
76 #[serde(rename(deserialize = "INVOKE", serialize = "INVOKE"))]
77 #[default]
78 Invoke,
79 #[serde(rename(deserialize = "L1_HANDLER", serialize = "L1_HANDLER"))]
80 L1Handler,
81}
82
83#[derive(Debug, Clone, Serialize)]
84#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
85#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq), serde(deny_unknown_fields))]
86pub enum Transaction {
87 Declare(DeclareTransaction),
88 DeployAccount(DeployAccountTransaction),
89 Deploy(DeployTransaction),
90 Invoke(InvokeTransaction),
91 L1Handler(L1HandlerTransaction),
92}
93
94impl Transaction {
95 pub fn gas_vector_computation_mode(&self) -> GasVectorComputationMode {
96 match self {
97 Transaction::Declare(DeclareTransaction::V3(v3)) => v3.get_resource_bounds().into(),
98 Transaction::DeployAccount(DeployAccountTransaction::V3(v3)) => {
99 v3.get_resource_bounds().into()
100 }
101 Transaction::Invoke(InvokeTransaction::V3(v3)) => v3.get_resource_bounds().into(),
102 _ => GasVectorComputationMode::NoL2Gas,
103 }
104 }
105
106 pub fn get_sender_address(&self) -> Option<ContractAddress> {
107 match self {
108 Self::Declare(tx) => Some(tx.get_sender_address()),
109 Self::DeployAccount(tx) => Some(*tx.get_contract_address()),
110 Self::Deploy(_) => None,
111 Self::Invoke(tx) => Some(tx.get_sender_address()),
112 Self::L1Handler(tx) => Some(tx.contract_address),
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize)]
118#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
119pub struct TransactionWithHash {
120 transaction_hash: TransactionHash,
121 #[serde(flatten)]
122 pub transaction: Transaction,
123}
124
125impl TransactionWithHash {
126 pub fn new(transaction_hash: TransactionHash, transaction: Transaction) -> Self {
127 Self { transaction_hash, transaction }
128 }
129
130 pub fn get_transaction_hash(&self) -> &TransactionHash {
131 &self.transaction_hash
132 }
133
134 pub fn get_type(&self) -> TransactionType {
135 match self.transaction {
136 Transaction::Declare(_) => TransactionType::Declare,
137 Transaction::DeployAccount(_) => TransactionType::DeployAccount,
138 Transaction::Deploy(_) => TransactionType::Deploy,
139 Transaction::Invoke(_) => TransactionType::Invoke,
140 Transaction::L1Handler(_) => TransactionType::L1Handler,
141 }
142 }
143
144 pub fn get_signature(&self) -> TransactionSignature {
145 match &self.transaction {
146 Transaction::Declare(DeclareTransaction::V3(tx)) => tx.signature.clone(),
147 Transaction::DeployAccount(DeployAccountTransaction::V3(tx)) => tx.signature.clone(),
148 Transaction::Invoke(InvokeTransaction::V3(tx)) => tx.signature.clone(),
149 _ => TransactionSignature::default(),
150 }
151 }
152
153 #[allow(clippy::too_many_arguments)]
154 pub fn create_common_receipt(
155 &self,
156 transaction_events: &[Event],
157 transaction_messages_sent: &[MessageToL1],
158 block_hash: Option<&BlockHash>,
159 block_number: Option<BlockNumber>,
160 execution_result: &ExecutionResult,
161 finality_status: TransactionFinalityStatus,
162 actual_fee: FeeInUnits,
163 execution_info: &TransactionExecutionInfo,
164 ) -> CommonTransactionReceipt {
165 let r#type = self.get_type();
166 let execution_resources = ExecutionResources::from(execution_info);
167
168 CommonTransactionReceipt {
169 r#type,
170 transaction_hash: *self.get_transaction_hash(),
171 actual_fee,
172 messages_sent: transaction_messages_sent.to_vec(),
173 events: transaction_events.to_vec(),
174 execution_status: execution_result.clone(),
175 finality_status,
176 block_hash: block_hash.cloned(),
177 block_number,
178 execution_resources,
179 }
180 }
181
182 pub fn get_sender_address(&self) -> Option<ContractAddress> {
183 self.transaction.get_sender_address()
184 }
185}
186
187#[derive(Debug, Clone, Serialize)]
188#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
189pub struct TransactionWithReceipt {
190 pub receipt: TransactionReceipt,
191 pub transaction: Transaction,
192}
193
194#[derive(Debug, Clone, Serialize)]
195#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
196#[serde(deny_unknown_fields)]
197pub struct TransactionStatus {
198 pub finality_status: TransactionFinalityStatus,
199 pub failure_reason: Option<String>,
200 pub execution_status: TransactionExecutionStatus,
201}
202
203#[derive(Debug, Clone, Serialize)]
204#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
205#[serde(untagged)]
206pub enum DeclareTransaction {
207 V3(DeclareTransactionV3),
208}
209
210impl DeclareTransaction {
211 pub fn get_sender_address(&self) -> ContractAddress {
212 match self {
213 DeclareTransaction::V3(tx) => tx.sender_address,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Serialize)]
219#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
220#[serde(untagged)]
221pub enum InvokeTransaction {
222 V3(InvokeTransactionV3),
223}
224
225impl InvokeTransaction {
226 pub fn get_sender_address(&self) -> ContractAddress {
227 match self {
228 InvokeTransaction::V3(tx) => tx.sender_address,
229 }
230 }
231}
232
233#[derive(Debug, Clone, Serialize)]
234#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
235#[serde(untagged)]
236pub enum DeployAccountTransaction {
237 V3(Box<DeployAccountTransactionV3>),
238}
239
240impl DeployAccountTransaction {
241 pub fn get_contract_address(&self) -> &ContractAddress {
242 match self {
243 DeployAccountTransaction::V3(tx) => tx.get_contract_address(),
244 }
245 }
246}
247
248pub fn deserialize_paid_fee_on_l1<'de, D>(deserializer: D) -> Result<u128, D::Error>
249where
250 D: Deserializer<'de>,
251{
252 let buf = String::deserialize(deserializer)?;
253 let err_msg = format!("paid_fee_on_l1: expected 0x-prefixed hex string, got: {buf}");
254 if !buf.starts_with("0x") {
255 return Err(serde::de::Error::custom(err_msg));
256 }
257 u128::from_str_radix(&buf[2..], 16).map_err(|_| serde::de::Error::custom(err_msg))
258}
259
260fn serialize_paid_fee_on_l1<S>(paid_fee_on_l1: &u128, s: S) -> Result<S::Ok, S::Error>
261where
262 S: Serializer,
263{
264 s.serialize_str(&format!("{paid_fee_on_l1:#x}"))
265}
266
267#[derive(Debug, Clone, Deserialize)]
268pub struct EventFilter {
269 pub from_block: Option<BlockId>,
270 pub to_block: Option<BlockId>,
271 pub address: Option<ContractAddress>,
272 pub keys: Option<Vec<Vec<Felt>>>,
273 pub continuation_token: Option<String>,
274 pub chunk_size: u64,
275}
276
277#[derive(Debug, Clone, Serialize)]
278#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
279pub struct EventsChunk {
280 pub events: Vec<crate::emitted_event::EmittedEvent>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub continuation_token: Option<String>,
283}
284
285impl From<EventsPage> for EventsChunk {
286 fn from(events_page: EventsPage) -> Self {
287 Self {
288 events: events_page.events.into_iter().map(|e| e.into()).collect(),
289 continuation_token: events_page.continuation_token,
290 }
291 }
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[cfg_attr(feature = "testing", derive(PartialEq, Eq), serde(deny_unknown_fields))]
296pub struct FunctionCall {
297 pub contract_address: ContractAddress,
298 pub entry_point_selector: EntryPointSelector,
299 pub calldata: Calldata,
300}
301
302fn is_only_query_common(version: &Felt) -> bool {
303 version >= &QUERY_VERSION_OFFSET
304}
305
306fn felt_to_sn_api_chain_id(f: &Felt) -> DevnetResult<starknet_api::core::ChainId> {
307 Ok(starknet_api::core::ChainId::Other(
308 parse_cairo_short_string(f).map_err(|e| ConversionError::OutOfRangeError(e.to_string()))?,
309 ))
310}
311
312#[derive(Debug, Clone, Deserialize, Serialize)]
314pub struct BroadcastedTransactionCommonV3 {
315 pub version: TransactionVersion,
316 pub signature: TransactionSignature,
317 pub nonce: Nonce,
318 pub resource_bounds: ResourceBoundsWrapper,
319 pub tip: Tip,
320 pub paymaster_data: Vec<Felt>,
321 pub nonce_data_availability_mode: DataAvailabilityMode,
322 pub fee_data_availability_mode: DataAvailabilityMode,
323}
324
325#[derive(Debug, Clone)]
326#[cfg_attr(feature = "testing", derive(PartialEq, Eq))]
327pub struct ResourceBoundsWrapper {
328 inner: ResourceBoundsMapping,
329}
330
331impl From<&ResourceBoundsWrapper> for GasVectorComputationMode {
332 fn from(val: &ResourceBoundsWrapper) -> GasVectorComputationMode {
333 let resource_bounds = starknet_api::transaction::fields::ValidResourceBounds::from(val);
334 match resource_bounds {
335 ValidResourceBounds::L1Gas(_) => GasVectorComputationMode::NoL2Gas,
336 ValidResourceBounds::AllResources(_) => GasVectorComputationMode::All,
337 }
338 }
339}
340
341impl_wrapper_serialize!(ResourceBoundsWrapper);
342impl_wrapper_deserialize!(ResourceBoundsWrapper, ResourceBoundsMapping);
343
344impl ResourceBoundsWrapper {
345 pub fn new(
346 l1_gas_max_amount: u64,
347 l1_gas_max_price_per_unit: u128,
348 l1_data_gas_max_amount: u64,
349 l1_data_gas_max_price_per_unit: u128,
350 l2_gas_max_amount: u64,
351 l2_gas_max_price_per_unit: u128,
352 ) -> Self {
353 ResourceBoundsWrapper {
354 inner: ResourceBoundsMapping {
355 l1_gas: ResourceBounds {
356 max_amount: l1_gas_max_amount,
357 max_price_per_unit: l1_gas_max_price_per_unit,
358 },
359 l1_data_gas: ResourceBounds {
360 max_amount: l1_data_gas_max_amount,
361 max_price_per_unit: l1_data_gas_max_price_per_unit,
362 },
363 l2_gas: ResourceBounds {
364 max_amount: l2_gas_max_amount,
365 max_price_per_unit: l2_gas_max_price_per_unit,
366 },
367 },
368 }
369 }
370}
371
372fn convert_resource_bounds_from_starknet_rs_to_starknet_api(
373 bounds: ResourceBounds,
374) -> starknet_api::transaction::fields::ResourceBounds {
375 starknet_api::transaction::fields::ResourceBounds {
376 max_amount: starknet_api::execution_resources::GasAmount(bounds.max_amount),
377 max_price_per_unit: GasPrice(bounds.max_price_per_unit),
378 }
379}
380
381impl From<&ResourceBoundsWrapper> for starknet_api::transaction::fields::ValidResourceBounds {
382 fn from(value: &ResourceBoundsWrapper) -> Self {
383 let l1_gas_max_amount = value.inner.l1_gas.max_amount;
384 let l2_gas_max_amount = value.inner.l2_gas.max_amount;
385 let l1_data_gas_max_amount = value.inner.l1_data_gas.max_amount;
386
387 match (l1_gas_max_amount, l2_gas_max_amount, l1_data_gas_max_amount) {
388 (0, 0, 0) => ValidResourceBounds::AllResources(AllResourceBounds::default()),
391 (_, 0, 0) => starknet_api::transaction::fields::ValidResourceBounds::L1Gas(
392 convert_resource_bounds_from_starknet_rs_to_starknet_api(
393 value.inner.l1_gas.clone(),
394 ),
395 ),
396 _ => starknet_api::transaction::fields::ValidResourceBounds::AllResources(
397 AllResourceBounds {
398 l1_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
399 value.inner.l1_gas.clone(),
400 ),
401 l2_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
402 value.inner.l2_gas.clone(),
403 ),
404 l1_data_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
405 value.inner.l1_data_gas.clone(),
406 ),
407 },
408 ),
409 }
410 }
411}
412
413impl BroadcastedTransactionCommonV3 {
414 pub fn are_gas_bounds_valid(&self) -> bool {
417 let ResourceBoundsMapping { l1_gas, l2_gas, l1_data_gas } = &self.resource_bounds.inner;
418 let is_gt_zero = |gas: &ResourceBounds| gas.max_amount > 0 && gas.max_price_per_unit > 0;
419 match (l1_gas, l2_gas, l1_data_gas) {
423 (l1, l2, l1_data) if is_gt_zero(l1) && !is_gt_zero(l2) && !is_gt_zero(l1_data) => {
425 tracing::warn!(
426 "From version 0.5.0 V3 transactions that specify only L1 gas bounds are \
427 invalid. Please upgrade your client to provide values for the triplet \
428 (L1_GAS, L1_DATA_GAS, L2_GAS)."
429 );
430 true
431 }
432 (_, l2, l1_data) if is_gt_zero(l2) && is_gt_zero(l1_data) => true,
436 _ => false,
437 }
438 }
439
440 pub fn is_only_query(&self) -> bool {
441 is_only_query_common(&self.version)
442 }
443
444 pub fn get_gas_vector_computation_mode(&self) -> GasVectorComputationMode {
445 (&self.resource_bounds).into()
446 }
447}
448
449#[derive(Debug, Clone, Deserialize)]
450#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
451pub enum BroadcastedTransaction {
452 Invoke(BroadcastedInvokeTransaction),
453 Declare(BroadcastedDeclareTransaction),
454 DeployAccount(BroadcastedDeployAccountTransaction),
455}
456
457impl BroadcastedTransaction {
458 pub fn to_blockifier_account_transaction(
459 &self,
460 chain_id: &Felt,
461 execution_flags: ExecutionFlags,
462 ) -> DevnetResult<blockifier::transaction::account_transaction::AccountTransaction> {
463 let sn_api_tx = self.to_sn_api_account_transaction(chain_id)?;
464 Ok(blockifier::transaction::account_transaction::AccountTransaction {
465 tx: sn_api_tx,
466 execution_flags,
467 })
468 }
469
470 pub fn to_sn_api_account_transaction(
471 &self,
472 chain_id: &Felt,
473 ) -> DevnetResult<starknet_api::executable_transaction::AccountTransaction> {
474 let sn_api_tx = match self {
475 BroadcastedTransaction::Invoke(invoke_txn) => {
476 starknet_api::executable_transaction::AccountTransaction::Invoke(
477 invoke_txn.create_sn_api_invoke(chain_id)?,
478 )
479 }
480 BroadcastedTransaction::Declare(declare_txn) => {
481 starknet_api::executable_transaction::AccountTransaction::Declare(
482 declare_txn.create_sn_api_declare(chain_id)?,
483 )
484 }
485 BroadcastedTransaction::DeployAccount(deploy_account_txn) => {
486 starknet_api::executable_transaction::AccountTransaction::DeployAccount(
487 deploy_account_txn.create_sn_api_deploy_account(chain_id)?,
488 )
489 }
490 };
491
492 Ok(sn_api_tx)
493 }
494
495 pub fn get_type(&self) -> TransactionType {
496 match self {
497 BroadcastedTransaction::Invoke(_) => TransactionType::Invoke,
498 BroadcastedTransaction::Declare(_) => TransactionType::Declare,
499 BroadcastedTransaction::DeployAccount(_) => TransactionType::DeployAccount,
500 }
501 }
502
503 pub fn are_gas_bounds_valid(&self) -> bool {
504 match self {
505 BroadcastedTransaction::Invoke(broadcasted_invoke_transaction) => {
506 broadcasted_invoke_transaction.are_gas_bounds_valid()
507 }
508 BroadcastedTransaction::Declare(broadcasted_declare_transaction) => {
509 broadcasted_declare_transaction.are_gas_bounds_valid()
510 }
511 BroadcastedTransaction::DeployAccount(broadcasted_deploy_account_transaction) => {
512 broadcasted_deploy_account_transaction.are_gas_bounds_valid()
513 }
514 }
515 }
516
517 pub fn gas_vector_computation_mode(&self) -> GasVectorComputationMode {
518 match self {
519 BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V3(declare_v3)) => {
520 declare_v3.common.get_gas_vector_computation_mode()
521 }
522 BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3(
523 deploy_account_v3,
524 )) => deploy_account_v3.common.get_gas_vector_computation_mode(),
525 BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3(invoke_v3)) => {
526 invoke_v3.common.get_gas_vector_computation_mode()
527 }
528 }
529 }
530
531 pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
532 match self {
533 BroadcastedTransaction::Invoke(tx) => {
534 tx.requires_strict_nonce_check(using_pre_confirmed_block)
535 }
536 BroadcastedTransaction::Declare(_) => true,
537 BroadcastedTransaction::DeployAccount(tx) => {
538 tx.requires_strict_nonce_check(using_pre_confirmed_block)
539 }
540 }
541 }
542}
543
544#[derive(Debug, Clone)]
545pub enum BroadcastedDeclareTransaction {
546 V3(Box<BroadcastedDeclareTransactionV3>),
547}
548
549impl BroadcastedDeclareTransaction {
550 pub fn are_gas_bounds_valid(&self) -> bool {
551 match self {
552 BroadcastedDeclareTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
553 }
554 }
555
556 pub fn is_only_query(&self) -> bool {
557 match self {
558 BroadcastedDeclareTransaction::V3(tx) => tx.common.is_only_query(),
559 }
560 }
561
562 pub fn create_sn_api_declare(
568 &self,
569 chain_id: &Felt,
570 ) -> DevnetResult<starknet_api::executable_transaction::DeclareTransaction> {
571 let sn_api_chain_id = felt_to_sn_api_chain_id(chain_id)?;
572
573 let (sn_api_transaction, tx_hash, class_info) = match self {
574 BroadcastedDeclareTransaction::V3(v3) => {
575 let sierra_class_hash = compute_sierra_class_hash(&v3.contract_class)?;
576
577 let sn_api_declare = starknet_api::transaction::DeclareTransaction::V3(
578 starknet_api::transaction::DeclareTransactionV3 {
579 resource_bounds: (&v3.common.resource_bounds).into(),
580 tip: v3.common.tip,
581 signature: starknet_api::transaction::fields::TransactionSignature(
582 Arc::new(v3.common.signature.clone()),
583 ),
584 nonce: starknet_api::core::Nonce(v3.common.nonce),
585 class_hash: starknet_api::core::ClassHash(sierra_class_hash),
586 compiled_class_hash: starknet_api::core::CompiledClassHash(
587 v3.compiled_class_hash,
588 ),
589 sender_address: v3.sender_address.into(),
590 nonce_data_availability_mode: v3.common.nonce_data_availability_mode,
591 fee_data_availability_mode: v3.common.fee_data_availability_mode,
592 paymaster_data: starknet_api::transaction::fields::PaymasterData(
593 v3.common.paymaster_data.clone(),
594 ),
595 account_deployment_data:
596 starknet_api::transaction::fields::AccountDeploymentData(
597 v3.account_deployment_data.clone(),
598 ),
599 },
600 );
601
602 let class_info: ClassInfo =
603 ContractClass::Cairo1(v3.contract_class.clone()).try_into()?;
604
605 let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
606 &sn_api_declare.version(),
607 &TransactionOptions { only_query: self.is_only_query() },
608 );
609
610 let tx_hash =
611 sn_api_declare.calculate_transaction_hash(&sn_api_chain_id, &tx_version)?;
612
613 (sn_api_declare, tx_hash, class_info)
614 }
615 };
616
617 Ok(starknet_api::executable_transaction::DeclareTransaction {
618 tx: sn_api_transaction,
619 tx_hash,
620 class_info,
621 })
622 }
623}
624
625#[derive(Debug, Clone)]
626pub enum BroadcastedDeployAccountTransaction {
627 V3(BroadcastedDeployAccountTransactionV3),
628}
629
630impl BroadcastedDeployAccountTransaction {
631 pub fn are_gas_bounds_valid(&self) -> bool {
632 match self {
633 BroadcastedDeployAccountTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
634 }
635 }
636
637 pub fn is_only_query(&self) -> bool {
638 match self {
639 BroadcastedDeployAccountTransaction::V3(tx) => tx.common.is_only_query(),
640 }
641 }
642
643 pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
644 !using_pre_confirmed_block
645 }
646
647 pub fn create_sn_api_deploy_account(
653 &self,
654 chain_id: &Felt,
655 ) -> DevnetResult<starknet_api::executable_transaction::DeployAccountTransaction> {
656 let sn_api_transaction = match self {
657 BroadcastedDeployAccountTransaction::V3(v3) => {
658 let sn_api_transaction = starknet_api::transaction::DeployAccountTransactionV3 {
659 resource_bounds: (&v3.common.resource_bounds).into(),
660 tip: v3.common.tip,
661 signature: starknet_api::transaction::fields::TransactionSignature(Arc::new(
662 v3.common.signature.clone(),
663 )),
664 nonce: starknet_api::core::Nonce(v3.common.nonce),
665 class_hash: starknet_api::core::ClassHash(v3.class_hash),
666 nonce_data_availability_mode: v3.common.nonce_data_availability_mode,
667 fee_data_availability_mode: v3.common.fee_data_availability_mode,
668 paymaster_data: starknet_api::transaction::fields::PaymasterData(
669 v3.common.paymaster_data.clone(),
670 ),
671 contract_address_salt: starknet_api::transaction::fields::ContractAddressSalt(
672 v3.contract_address_salt,
673 ),
674 constructor_calldata: starknet_api::transaction::fields::Calldata(Arc::new(
675 v3.constructor_calldata.clone(),
676 )),
677 };
678
679 starknet_api::transaction::DeployAccountTransaction::V3(sn_api_transaction)
680 }
681 };
682
683 let chain_id = felt_to_sn_api_chain_id(chain_id)?;
684 let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
685 &sn_api_transaction.version(),
686 &TransactionOptions { only_query: self.is_only_query() },
687 );
688 let tx_hash = sn_api_transaction.calculate_transaction_hash(&chain_id, &tx_version)?;
689
690 let contract_address = calculate_contract_address(
692 sn_api_transaction.contract_address_salt(),
693 sn_api_transaction.class_hash(),
694 &sn_api_transaction.constructor_calldata(),
695 starknet_api::core::ContractAddress::default(),
696 )?;
697
698 Ok(starknet_api::executable_transaction::DeployAccountTransaction {
699 tx: sn_api_transaction,
700 tx_hash,
701 contract_address,
702 })
703 }
704}
705
706#[derive(Debug, Clone)]
707pub enum BroadcastedInvokeTransaction {
708 V3(BroadcastedInvokeTransactionV3),
709}
710
711impl BroadcastedInvokeTransaction {
712 pub fn are_gas_bounds_valid(&self) -> bool {
713 match self {
714 BroadcastedInvokeTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
715 }
716 }
717
718 pub fn is_only_query(&self) -> bool {
719 match self {
720 BroadcastedInvokeTransaction::V3(tx) => tx.common.is_only_query(),
721 }
722 }
723
724 pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
725 !using_pre_confirmed_block
726 }
727
728 pub fn create_sn_api_invoke(
734 &self,
735 chain_id: &Felt,
736 ) -> DevnetResult<starknet_api::executable_transaction::InvokeTransaction> {
737 let sn_api_transaction = match self {
738 BroadcastedInvokeTransaction::V3(v3) => v3.create_sn_api_invoke()?,
739 };
740
741 let chain_id = felt_to_sn_api_chain_id(chain_id)?;
742 let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
743 &sn_api_transaction.version(),
744 &TransactionOptions { only_query: self.is_only_query() },
745 );
746 let tx_hash = sn_api_transaction.calculate_transaction_hash(&chain_id, &tx_version)?;
747
748 Ok(starknet_api::executable_transaction::InvokeTransaction {
749 tx: sn_api_transaction,
750 tx_hash,
751 })
752 }
753}
754
755impl<'de> Deserialize<'de> for BroadcastedDeclareTransaction {
756 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
757 where
758 D: Deserializer<'de>,
759 {
760 let value = serde_json::Value::deserialize(deserializer)?;
761 let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
762 match version_raw.as_str() {
763 Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
764 let unpacked = serde_json::from_value(value).map_err(|e| {
765 serde::de::Error::custom(format!("Invalid declare transaction v3: {e}"))
766 })?;
767 Ok(BroadcastedDeclareTransaction::V3(Box::new(unpacked)))
768 }
769 _ => Err(serde::de::Error::custom(format!(
770 "Invalid version of declare transaction: {version_raw}"
771 ))),
772 }
773 }
774}
775
776impl<'de> Deserialize<'de> for BroadcastedDeployAccountTransaction {
777 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
778 where
779 D: Deserializer<'de>,
780 {
781 let value = serde_json::Value::deserialize(deserializer)?;
782 let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
783 match version_raw.as_str() {
784 Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
785 let unpacked = serde_json::from_value(value).map_err(|e| {
786 serde::de::Error::custom(format!("Invalid deploy account transaction v3: {e}"))
787 })?;
788 Ok(BroadcastedDeployAccountTransaction::V3(unpacked))
789 }
790 _ => Err(serde::de::Error::custom(format!(
791 "Invalid version of deploy account transaction: {version_raw}"
792 ))),
793 }
794 }
795}
796
797impl<'de> Deserialize<'de> for BroadcastedInvokeTransaction {
798 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
799 where
800 D: Deserializer<'de>,
801 {
802 let value = serde_json::Value::deserialize(deserializer)?;
803 let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
804 match version_raw.as_str() {
805 Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
806 let unpacked = serde_json::from_value(value).map_err(|e| {
807 serde::de::Error::custom(format!("Invalid invoke transaction v3: {e}"))
808 })?;
809 Ok(BroadcastedInvokeTransaction::V3(unpacked))
810 }
811 _ => Err(serde::de::Error::custom(format!(
812 "Invalid version of invoke transaction: {version_raw}"
813 ))),
814 }
815 }
816}
817
818#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
823#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
824pub enum SimulationFlag {
825 SkipValidate,
826 SkipFeeCharge,
827}
828
829#[derive(Debug, Clone, Serialize)]
830#[cfg_attr(feature = "testing", derive(Deserialize))]
831#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
832pub enum CallType {
833 LibraryCall,
834 Call,
835 Delegate,
836}
837
838#[derive(Debug, Clone, Serialize)]
839#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
840pub struct FunctionInvocation {
841 contract_address: ContractAddress,
842 entry_point_selector: EntryPointSelector,
843 calldata: Calldata,
844 caller_address: ContractAddress,
845 class_hash: Felt,
846 entry_point_type: EntryPointType,
847 call_type: CallType,
848 result: Vec<Felt>,
849 calls: Vec<FunctionInvocation>,
850 events: Vec<OrderedEvent>,
851 messages: Vec<OrderedMessageToL1>,
852 execution_resources: InnerExecutionResources,
853 is_reverted: bool,
854}
855
856#[derive(Debug, Clone, Serialize)]
857#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
858pub struct InnerExecutionResources {
859 pub l1_gas: u64,
860 pub l2_gas: u64,
861}
862
863#[derive(Debug, Clone, Serialize)]
864#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
865#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
866pub enum TransactionTrace {
867 Invoke(InvokeTransactionTrace),
868 Declare(DeclareTransactionTrace),
869 DeployAccount(DeployAccountTransactionTrace),
870 L1Handler(L1HandlerTransactionTrace),
871}
872
873#[derive(Debug, Clone, Serialize)]
874#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
875pub struct BlockTransactionTrace {
876 pub transaction_hash: Felt,
877 pub trace_root: TransactionTrace,
878}
879
880#[derive(Debug, Clone, Serialize)]
881#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
882pub struct Reversion {
883 pub revert_reason: String, }
885
886#[derive(Debug, Clone, Serialize)]
887#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
888#[serde(untagged)]
889#[allow(clippy::large_enum_variant)]
890pub enum ExecutionInvocation {
891 Succeeded(FunctionInvocation),
892 Reverted(Reversion),
893}
894
895#[derive(Debug, Clone, Serialize)]
896#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
897pub struct InvokeTransactionTrace {
898 #[serde(skip_serializing_if = "Option::is_none")]
899 pub validate_invocation: Option<FunctionInvocation>,
900 pub execute_invocation: ExecutionInvocation,
901 #[serde(skip_serializing_if = "Option::is_none")]
902 pub fee_transfer_invocation: Option<FunctionInvocation>,
903 #[serde(skip_serializing_if = "Option::is_none")]
904 pub state_diff: Option<ThinStateDiff>,
905 pub execution_resources: ExecutionResources,
906}
907
908#[derive(Debug, Clone, Serialize)]
909#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
910pub struct DeclareTransactionTrace {
911 #[serde(skip_serializing_if = "Option::is_none")]
912 pub validate_invocation: Option<FunctionInvocation>,
913 #[serde(skip_serializing_if = "Option::is_none")]
914 pub fee_transfer_invocation: Option<FunctionInvocation>,
915 #[serde(skip_serializing_if = "Option::is_none")]
916 pub state_diff: Option<ThinStateDiff>,
917 pub execution_resources: ExecutionResources,
918}
919
920#[derive(Debug, Clone, Serialize)]
921#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
922pub struct DeployAccountTransactionTrace {
923 #[serde(skip_serializing_if = "Option::is_none")]
924 pub validate_invocation: Option<FunctionInvocation>,
925 #[serde(skip_serializing_if = "Option::is_none")]
926 pub constructor_invocation: Option<FunctionInvocation>,
927 #[serde(skip_serializing_if = "Option::is_none")]
928 pub fee_transfer_invocation: Option<FunctionInvocation>,
929 #[serde(skip_serializing_if = "Option::is_none")]
930 pub state_diff: Option<ThinStateDiff>,
931 pub execution_resources: ExecutionResources,
932}
933
934#[derive(Debug, Clone, Serialize)]
935#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
936pub struct L1HandlerTransactionTrace {
937 pub function_invocation: ExecutionInvocation,
938 #[serde(skip_serializing_if = "Option::is_none")]
939 pub state_diff: Option<ThinStateDiff>,
940 pub execution_resources: ExecutionResources,
941}
942
943#[derive(Debug, Clone, Serialize)]
944#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
945pub struct SimulatedTransaction {
946 pub transaction_trace: TransactionTrace,
947 pub fee_estimation: FeeEstimateWrapper,
948}
949
950impl FunctionInvocation {
951 pub fn try_from_call_info(
952 call_info: &blockifier::execution::call_info::CallInfo,
953 state_reader: &mut impl StateReader,
954 versioned_constants: &VersionedConstants,
955 gas_vector_computation_mode: &GasVectorComputationMode,
956 ) -> DevnetResult<Self> {
957 let mut internal_calls: Vec<FunctionInvocation> = vec![];
958 for internal_call in &call_info.inner_calls {
959 internal_calls.push(FunctionInvocation::try_from_call_info(
960 internal_call,
961 state_reader,
962 versioned_constants,
963 gas_vector_computation_mode,
964 )?);
965 }
966
967 let mut messages: Vec<OrderedMessageToL1> = vec![];
968 for msg in call_info.execution.l2_to_l1_messages.iter() {
969 messages.push(OrderedMessageToL1::new(msg, call_info.call.caller_address.into())?);
970 }
971 messages.sort_by_key(|msg| msg.order);
972
973 let mut events: Vec<OrderedEvent> =
974 call_info.execution.events.iter().map(OrderedEvent::from).collect();
975 let contract_address = call_info.call.storage_address;
976 events.sort_by_key(|event| event.order);
977
978 let class_hash = if let Some(class_hash) = call_info.call.class_hash {
981 class_hash
982 } else {
983 state_reader.get_class_hash_at(contract_address).map_err(|_| {
984 ConversionError::InvalidInternalStructure(
985 "class_hash is unexpectedly undefined".into(),
986 )
987 })?
988 };
989
990 let gas_vector = blockifier::fee::fee_utils::get_vm_resources_cost(
991 versioned_constants,
992 &call_info.resources,
993 0,
994 gas_vector_computation_mode,
995 );
996
997 Ok(FunctionInvocation {
998 contract_address: contract_address.into(),
999 entry_point_selector: call_info.call.entry_point_selector.0,
1000 calldata: call_info.call.calldata.0.to_vec(),
1001 caller_address: call_info.call.caller_address.into(),
1002 class_hash: class_hash.0,
1003 entry_point_type: call_info.call.entry_point_type,
1004 call_type: match call_info.call.call_type {
1005 blockifier::execution::entry_point::CallType::Call => CallType::Call,
1006 blockifier::execution::entry_point::CallType::Delegate => CallType::Delegate,
1007 },
1008 result: call_info.execution.retdata.0.clone(),
1009 calls: internal_calls,
1010 events,
1011 messages,
1012 execution_resources: InnerExecutionResources {
1013 l1_gas: gas_vector.l1_gas.0,
1014 l2_gas: gas_vector.l2_gas.0,
1015 },
1016 is_reverted: call_info.execution.failed,
1017 })
1018 }
1019}
1020
1021#[derive(Debug, Clone, Serialize, Deserialize)]
1022#[serde(deny_unknown_fields)]
1023pub struct L1HandlerTransactionStatus {
1024 pub transaction_hash: TransactionHash,
1025 pub finality_status: TransactionFinalityStatus,
1026 pub execution_status: TransactionExecutionStatus,
1027 pub failure_reason: Option<String>,
1028}
1029
1030#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Copy)]
1031#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1032pub enum TransactionFinalityStatus {
1033 Received,
1034 Candidate,
1035 PreConfirmed,
1036 AcceptedOnL2,
1037 AcceptedOnL1,
1038}