starknet_devnet_types/rpc/
transactions.rs

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/// Common fields for all transaction type of version 3
313#[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            // providing max amount 0 for each resource bound is possible in the case of
389            // estimate fee
390            (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    /// Checks if total accumulated fee of resource_bounds for l1 is equal to 0 or for l2 is not
415    /// zero
416    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        // valid set of resources are:
420        // 1. l1_gas > 0, l2_gas = 0, l1_data_gas = 0
421        // 2. l2_gas > 0, l1_data_gas > 0
422        match (l1_gas, l2_gas, l1_data_gas) {
423            // l1 > 0 and l2 = 0 and l1_data = 0 => valid
424            (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 > 0 and l1_data > 0
433            // l1 gas is not checked, because l1 gas is used for L2->L1 messages and not every
434            // transaction sends data to L1
435            (_, 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    /// Creates a blockifier declare transaction from the current transaction.
563    /// The transaction hash is computed using the given chain id.
564    ///
565    /// # Arguments
566    /// `chain_id` - the chain id to use for the transaction hash computation
567    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    /// Creates a blockifier deploy account transaction from the current transaction.
648    /// The transaction hash is computed using the given chain id.
649    ///
650    /// # Arguments
651    /// `chain_id` - the chain id to use for the transaction hash computation
652    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        // copied from starknet_api::executable_transaction::DeployAccountTransaction::create(
691        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    /// Creates a blockifier invoke transaction from the current transaction.
729    /// The transaction hash is computed using the given chain id.
730    ///
731    /// # Arguments
732    /// `chain_id` - the chain id to use for the transaction hash computation
733    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/// Flags that indicate how to simulate a given transaction.
819/// By default, the sequencer behavior is replicated locally (enough funds are expected to be in the
820/// account, and fee will be deducted from the balance before the simulation of the next
821/// transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag.
822#[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, // TODO use blockifier's RevertError
884}
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        // call_info.call.class_hash could be None, so we deduce it from
979        // call_info.call.storage_address which is function_call.contract_address
980        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}