Skip to main content

starknet_devnet_types/rpc/
transactions.rs

1use std::sync::Arc;
2
3use blockifier::blockifier_versioned_constants::VersionedConstants;
4use blockifier::state::cached_state::StateMaps;
5use blockifier::state::state_api::StateReader;
6use blockifier::transaction::account_transaction::ExecutionFlags;
7use blockifier::transaction::objects::TransactionExecutionInfo;
8use deploy_transaction::DeployTransaction;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use starknet_api::block::{BlockNumber, GasPrice};
11use starknet_api::contract_class::{ClassInfo, EntryPointType};
12use starknet_api::core::calculate_contract_address;
13use starknet_api::data_availability::DataAvailabilityMode;
14use starknet_api::transaction::fields::{
15    AllResourceBounds, GasVectorComputationMode, Tip, ValidResourceBounds,
16};
17use starknet_api::transaction::{TransactionHasher, TransactionOptions, signed_tx_version};
18use starknet_rs_core::types::{
19    EventsPage, ExecutionResult, Felt, ResourceBounds, ResourceBoundsMapping,
20    TransactionExecutionStatus,
21};
22use starknet_rs_core::utils::parse_cairo_short_string;
23
24use self::broadcasted_declare_transaction_v3::BroadcastedDeclareTransactionV3;
25use self::broadcasted_deploy_account_transaction_v3::BroadcastedDeployAccountTransactionV3;
26use self::broadcasted_invoke_transaction_v3::BroadcastedInvokeTransactionV3;
27use self::declare_transaction_v3::DeclareTransactionV3;
28use self::deploy_account_transaction_v3::DeployAccountTransactionV3;
29use self::invoke_transaction_v3::InvokeTransactionV3;
30use self::l1_handler_transaction::L1HandlerTransaction;
31use super::block::BlockId;
32use super::estimate_message_fee::FeeEstimateWrapper;
33use super::felt::BlockHash;
34use super::messaging::{MessageToL1, OrderedMessageToL1};
35use super::state::ThinStateDiff;
36use super::transaction_receipt::{ExecutionResources, FeeInUnits, TransactionReceipt};
37use crate::constants::QUERY_VERSION_OFFSET;
38use crate::contract_address::ContractAddress;
39use crate::contract_class::{ContractClass, compute_sierra_class_hash};
40use crate::emitted_event::{Event, OrderedEvent};
41use crate::error::{ConversionError, DevnetResult};
42use crate::felt::{
43    Calldata, ClassHash, EntryPointSelector, Nonce, ProofFacts, TransactionHash,
44    TransactionSignature, TransactionVersion,
45};
46use crate::patricia_key::StorageKey;
47use crate::proof::Proof;
48use crate::rpc::transaction_receipt::CommonTransactionReceipt;
49use crate::{impl_wrapper_deserialize, impl_wrapper_serialize};
50
51pub mod broadcasted_declare_transaction_v3;
52pub mod broadcasted_deploy_account_transaction_v3;
53pub mod broadcasted_invoke_transaction_v3;
54
55pub mod declare_transaction_v3;
56pub mod deploy_account_transaction_v3;
57pub mod deploy_transaction;
58pub mod invoke_transaction_v3;
59
60pub mod l1_handler_transaction;
61
62#[derive(Debug, Clone, Serialize)]
63#[cfg_attr(feature = "testing", derive(Deserialize))]
64#[serde(untagged)]
65pub enum Transactions {
66    Hashes(Vec<TransactionHash>),
67    Full(Vec<TransactionWithHash>),
68    FullWithReceipts(Vec<TransactionWithReceipt>),
69}
70
71impl Transactions {
72    pub fn clone_without_proof_facts(&self) -> Self {
73        match &self {
74            Self::Hashes(hashes) => Self::Hashes(hashes.clone()),
75            Self::FullWithReceipts(txs) => Self::FullWithReceipts(
76                txs.iter().map(|tx| tx.clone_without_proof_facts()).collect(),
77            ),
78            Self::Full(txs) => {
79                Self::Full(txs.iter().map(|tx| tx.clone_without_proof_facts()).collect())
80            }
81        }
82    }
83
84    pub fn clone_with_proof_facts(&self) -> Self {
85        match &self {
86            Self::Hashes(hashes) => Self::Hashes(hashes.clone()),
87            Self::FullWithReceipts(txs) => {
88                Self::FullWithReceipts(txs.iter().map(|tx| tx.clone_with_proof_facts()).collect())
89            }
90            Self::Full(txs) => {
91                Self::Full(txs.iter().map(|tx| tx.clone_with_proof_facts()).collect())
92            }
93        }
94    }
95}
96
97#[derive(Debug, Copy, Clone, Serialize, Default)]
98#[cfg_attr(feature = "testing", derive(Deserialize))]
99pub enum TransactionType {
100    #[serde(rename(deserialize = "DECLARE", serialize = "DECLARE"))]
101    Declare,
102    #[serde(rename(deserialize = "DEPLOY", serialize = "DEPLOY"))]
103    Deploy,
104    #[serde(rename(deserialize = "DEPLOY_ACCOUNT", serialize = "DEPLOY_ACCOUNT"))]
105    DeployAccount,
106    #[serde(rename(deserialize = "INVOKE", serialize = "INVOKE"))]
107    #[default]
108    Invoke,
109    #[serde(rename(deserialize = "L1_HANDLER", serialize = "L1_HANDLER"))]
110    L1Handler,
111}
112
113#[derive(Debug, Clone, Serialize)]
114#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
115#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq), serde(deny_unknown_fields))]
116pub enum Transaction {
117    Declare(DeclareTransaction),
118    DeployAccount(DeployAccountTransaction),
119    Deploy(DeployTransaction),
120    Invoke(InvokeTransaction),
121    L1Handler(L1HandlerTransaction),
122}
123
124impl Transaction {
125    pub fn gas_vector_computation_mode(&self) -> GasVectorComputationMode {
126        match self {
127            Transaction::Declare(DeclareTransaction::V3(v3)) => v3.get_resource_bounds().into(),
128            Transaction::DeployAccount(DeployAccountTransaction::V3(v3)) => {
129                v3.get_resource_bounds().into()
130            }
131            Transaction::Invoke(InvokeTransaction::V3(v3)) => v3.get_resource_bounds().into(),
132            _ => GasVectorComputationMode::NoL2Gas,
133        }
134    }
135
136    pub fn get_sender_address(&self) -> Option<ContractAddress> {
137        match self {
138            Self::Declare(tx) => Some(tx.get_sender_address()),
139            Self::DeployAccount(tx) => Some(*tx.get_contract_address()),
140            Self::Deploy(_) => None,
141            Self::Invoke(tx) => Some(tx.get_sender_address()),
142            Self::L1Handler(tx) => Some(tx.contract_address),
143        }
144    }
145}
146
147#[derive(Debug, Clone, Serialize)]
148#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
149pub struct TransactionWithHash {
150    transaction_hash: TransactionHash,
151    #[serde(flatten)]
152    pub transaction: Transaction,
153}
154
155impl TransactionWithHash {
156    pub fn new(transaction_hash: TransactionHash, transaction: Transaction) -> Self {
157        Self { transaction_hash, transaction }
158    }
159
160    pub fn get_transaction_hash(&self) -> &TransactionHash {
161        &self.transaction_hash
162    }
163
164    pub fn get_type(&self) -> TransactionType {
165        match self.transaction {
166            Transaction::Declare(_) => TransactionType::Declare,
167            Transaction::DeployAccount(_) => TransactionType::DeployAccount,
168            Transaction::Deploy(_) => TransactionType::Deploy,
169            Transaction::Invoke(_) => TransactionType::Invoke,
170            Transaction::L1Handler(_) => TransactionType::L1Handler,
171        }
172    }
173
174    pub fn get_signature(&self) -> TransactionSignature {
175        match &self.transaction {
176            Transaction::Declare(DeclareTransaction::V3(tx)) => tx.signature.clone(),
177            Transaction::DeployAccount(DeployAccountTransaction::V3(tx)) => tx.signature.clone(),
178            Transaction::Invoke(InvokeTransaction::V3(tx)) => tx.signature.clone(),
179            _ => TransactionSignature::default(),
180        }
181    }
182
183    pub fn clone_without_proof_facts(&self) -> Self {
184        match &self.transaction {
185            Transaction::Invoke(InvokeTransaction::V3(tx)) => Self {
186                transaction: Transaction::Invoke(InvokeTransaction::V3(
187                    tx.clone_without_proof_facts(),
188                )),
189                transaction_hash: self.transaction_hash,
190            },
191            _ => self.clone(),
192        }
193    }
194
195    pub fn clone_with_proof_facts(&self) -> Self {
196        match &self.transaction {
197            Transaction::Invoke(InvokeTransaction::V3(tx)) => Self {
198                transaction: Transaction::Invoke(InvokeTransaction::V3(
199                    tx.clone_with_proof_facts(),
200                )),
201                transaction_hash: self.transaction_hash,
202            },
203            _ => self.clone(),
204        }
205    }
206
207    #[allow(clippy::too_many_arguments)]
208    pub fn create_common_receipt(
209        &self,
210        transaction_events: &[Event],
211        transaction_messages_sent: &[MessageToL1],
212        block_hash: Option<&BlockHash>,
213        block_number: Option<BlockNumber>,
214        execution_result: &ExecutionResult,
215        finality_status: TransactionFinalityStatus,
216        actual_fee: FeeInUnits,
217        execution_info: &TransactionExecutionInfo,
218    ) -> CommonTransactionReceipt {
219        let r#type = self.get_type();
220        let execution_resources = ExecutionResources::from(execution_info);
221
222        CommonTransactionReceipt {
223            r#type,
224            transaction_hash: *self.get_transaction_hash(),
225            actual_fee,
226            messages_sent: transaction_messages_sent.to_vec(),
227            events: transaction_events.to_vec(),
228            execution_status: execution_result.clone(),
229            finality_status,
230            block_hash: block_hash.cloned(),
231            block_number,
232            execution_resources,
233        }
234    }
235
236    pub fn get_sender_address(&self) -> Option<ContractAddress> {
237        self.transaction.get_sender_address()
238    }
239}
240
241#[derive(Debug, Clone, Serialize)]
242#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
243pub struct TransactionWithReceipt {
244    pub receipt: TransactionReceipt,
245    pub transaction: Transaction,
246}
247
248impl TransactionWithReceipt {
249    pub fn clone_without_proof_facts(&self) -> Self {
250        match &self.transaction {
251            Transaction::Invoke(InvokeTransaction::V3(tx)) => Self {
252                transaction: Transaction::Invoke(InvokeTransaction::V3(
253                    tx.clone_without_proof_facts(),
254                )),
255                receipt: self.receipt.clone(),
256            },
257            _ => self.clone(),
258        }
259    }
260
261    pub fn clone_with_proof_facts(&self) -> Self {
262        match &self.transaction {
263            Transaction::Invoke(InvokeTransaction::V3(tx)) => Self {
264                transaction: Transaction::Invoke(InvokeTransaction::V3(
265                    tx.clone_with_proof_facts(),
266                )),
267                receipt: self.receipt.clone(),
268            },
269            _ => self.clone(),
270        }
271    }
272}
273
274#[derive(Debug, Clone, Serialize)]
275#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
276#[serde(deny_unknown_fields)]
277pub struct TransactionStatus {
278    pub finality_status: TransactionFinalityStatus,
279    pub failure_reason: Option<String>,
280    pub execution_status: TransactionExecutionStatus,
281}
282
283#[derive(Debug, Clone, Serialize)]
284#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
285#[serde(untagged)]
286pub enum DeclareTransaction {
287    V3(DeclareTransactionV3),
288}
289
290impl DeclareTransaction {
291    pub fn get_sender_address(&self) -> ContractAddress {
292        match self {
293            DeclareTransaction::V3(tx) => tx.sender_address,
294        }
295    }
296}
297
298#[derive(Debug, Clone, Serialize)]
299#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
300#[serde(untagged)]
301pub enum InvokeTransaction {
302    V3(InvokeTransactionV3),
303}
304
305impl InvokeTransaction {
306    pub fn get_sender_address(&self) -> ContractAddress {
307        match self {
308            InvokeTransaction::V3(tx) => tx.sender_address,
309        }
310    }
311}
312
313#[derive(Debug, Clone, Serialize)]
314#[cfg_attr(feature = "testing", derive(Deserialize, PartialEq, Eq))]
315#[serde(untagged)]
316pub enum DeployAccountTransaction {
317    V3(Box<DeployAccountTransactionV3>),
318}
319
320impl DeployAccountTransaction {
321    pub fn get_contract_address(&self) -> &ContractAddress {
322        match self {
323            DeployAccountTransaction::V3(tx) => tx.get_contract_address(),
324        }
325    }
326}
327
328pub fn deserialize_paid_fee_on_l1<'de, D>(deserializer: D) -> Result<u128, D::Error>
329where
330    D: Deserializer<'de>,
331{
332    let buf = String::deserialize(deserializer)?;
333    let err_msg = format!("paid_fee_on_l1: expected 0x-prefixed hex string, got: {buf}");
334    if !buf.starts_with("0x") {
335        return Err(serde::de::Error::custom(err_msg));
336    }
337    u128::from_str_radix(&buf[2..], 16).map_err(|_| serde::de::Error::custom(err_msg))
338}
339
340fn serialize_paid_fee_on_l1<S>(paid_fee_on_l1: &u128, s: S) -> Result<S::Ok, S::Error>
341where
342    S: Serializer,
343{
344    s.serialize_str(&format!("{paid_fee_on_l1:#x}"))
345}
346
347fn deserialize_address<'de, D>(deserializer: D) -> Result<Option<Vec<ContractAddress>>, D::Error>
348where
349    D: Deserializer<'de>,
350{
351    #[derive(Deserialize)]
352    #[serde(untagged)]
353    enum AddressOrVec {
354        Single(ContractAddress),
355        Multiple(Vec<ContractAddress>),
356    }
357
358    let value = Option::<AddressOrVec>::deserialize(deserializer)?;
359    Ok(value.map(|v| match v {
360        AddressOrVec::Single(addr) => vec![addr],
361        AddressOrVec::Multiple(addrs) => addrs,
362    }))
363}
364
365#[derive(Debug, Clone, Deserialize)]
366pub struct EventFilter {
367    pub from_block: Option<BlockId>,
368    pub to_block: Option<BlockId>,
369    #[serde(default, deserialize_with = "deserialize_address")]
370    pub address: Option<Vec<ContractAddress>>,
371    pub keys: Option<Vec<Vec<Felt>>>,
372    pub continuation_token: Option<String>,
373    pub chunk_size: u64,
374}
375
376#[derive(Debug, Clone, Serialize)]
377#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
378pub struct EventsChunk {
379    pub events: Vec<crate::emitted_event::EmittedEvent>,
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub continuation_token: Option<String>,
382}
383
384impl From<EventsPage> for EventsChunk {
385    fn from(events_page: EventsPage) -> Self {
386        Self {
387            events: events_page.events.into_iter().map(|e| e.into()).collect(),
388            continuation_token: events_page.continuation_token,
389        }
390    }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
394#[cfg_attr(feature = "testing", derive(PartialEq, Eq), serde(deny_unknown_fields))]
395pub struct FunctionCall {
396    pub contract_address: ContractAddress,
397    pub entry_point_selector: EntryPointSelector,
398    pub calldata: Calldata,
399}
400
401fn is_only_query_common(version: &Felt) -> bool {
402    version >= &QUERY_VERSION_OFFSET
403}
404
405fn felt_to_sn_api_chain_id(f: &Felt) -> DevnetResult<starknet_api::core::ChainId> {
406    Ok(starknet_api::core::ChainId::Other(
407        parse_cairo_short_string(f).map_err(|e| ConversionError::OutOfRangeError(e.to_string()))?,
408    ))
409}
410
411/// Common fields for all transaction type of version 3
412#[derive(Debug, Clone, Deserialize, Serialize)]
413pub struct BroadcastedTransactionCommonV3 {
414    pub version: TransactionVersion,
415    pub signature: TransactionSignature,
416    pub nonce: Nonce,
417    pub resource_bounds: ResourceBoundsWrapper,
418    pub tip: Tip,
419    pub paymaster_data: Vec<Felt>,
420    pub nonce_data_availability_mode: DataAvailabilityMode,
421    pub fee_data_availability_mode: DataAvailabilityMode,
422}
423
424#[derive(Debug, Clone)]
425#[cfg_attr(feature = "testing", derive(PartialEq, Eq))]
426pub struct ResourceBoundsWrapper {
427    inner: ResourceBoundsMapping,
428}
429
430impl From<&ResourceBoundsWrapper> for GasVectorComputationMode {
431    fn from(val: &ResourceBoundsWrapper) -> GasVectorComputationMode {
432        let resource_bounds = starknet_api::transaction::fields::ValidResourceBounds::from(val);
433        match resource_bounds {
434            ValidResourceBounds::L1Gas(_) => GasVectorComputationMode::NoL2Gas,
435            ValidResourceBounds::AllResources(_) => GasVectorComputationMode::All,
436        }
437    }
438}
439
440impl_wrapper_serialize!(ResourceBoundsWrapper);
441impl_wrapper_deserialize!(ResourceBoundsWrapper, ResourceBoundsMapping);
442
443impl ResourceBoundsWrapper {
444    pub fn new(
445        l1_gas_max_amount: u64,
446        l1_gas_max_price_per_unit: u128,
447        l1_data_gas_max_amount: u64,
448        l1_data_gas_max_price_per_unit: u128,
449        l2_gas_max_amount: u64,
450        l2_gas_max_price_per_unit: u128,
451    ) -> Self {
452        ResourceBoundsWrapper {
453            inner: ResourceBoundsMapping {
454                l1_gas: ResourceBounds {
455                    max_amount: l1_gas_max_amount,
456                    max_price_per_unit: l1_gas_max_price_per_unit,
457                },
458                l1_data_gas: ResourceBounds {
459                    max_amount: l1_data_gas_max_amount,
460                    max_price_per_unit: l1_data_gas_max_price_per_unit,
461                },
462                l2_gas: ResourceBounds {
463                    max_amount: l2_gas_max_amount,
464                    max_price_per_unit: l2_gas_max_price_per_unit,
465                },
466            },
467        }
468    }
469}
470
471fn convert_resource_bounds_from_starknet_rs_to_starknet_api(
472    bounds: ResourceBounds,
473) -> starknet_api::transaction::fields::ResourceBounds {
474    starknet_api::transaction::fields::ResourceBounds {
475        max_amount: starknet_api::execution_resources::GasAmount(bounds.max_amount),
476        max_price_per_unit: GasPrice(bounds.max_price_per_unit),
477    }
478}
479
480impl From<&ResourceBoundsWrapper> for starknet_api::transaction::fields::ValidResourceBounds {
481    fn from(value: &ResourceBoundsWrapper) -> Self {
482        let l1_gas_max_amount = value.inner.l1_gas.max_amount;
483        let l2_gas_max_amount = value.inner.l2_gas.max_amount;
484        let l1_data_gas_max_amount = value.inner.l1_data_gas.max_amount;
485
486        match (l1_gas_max_amount, l2_gas_max_amount, l1_data_gas_max_amount) {
487            // providing max amount 0 for each resource bound is possible in the case of
488            // estimate fee
489            (0, 0, 0) => ValidResourceBounds::AllResources(AllResourceBounds::default()),
490            (_, 0, 0) => starknet_api::transaction::fields::ValidResourceBounds::L1Gas(
491                convert_resource_bounds_from_starknet_rs_to_starknet_api(
492                    value.inner.l1_gas.clone(),
493                ),
494            ),
495            _ => starknet_api::transaction::fields::ValidResourceBounds::AllResources(
496                AllResourceBounds {
497                    l1_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
498                        value.inner.l1_gas.clone(),
499                    ),
500                    l2_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
501                        value.inner.l2_gas.clone(),
502                    ),
503                    l1_data_gas: convert_resource_bounds_from_starknet_rs_to_starknet_api(
504                        value.inner.l1_data_gas.clone(),
505                    ),
506                },
507            ),
508        }
509    }
510}
511
512impl BroadcastedTransactionCommonV3 {
513    /// Checks if total accumulated fee of resource_bounds for l1 is equal to 0 or for l2 is not
514    /// zero
515    pub fn are_gas_bounds_valid(&self) -> bool {
516        let ResourceBoundsMapping { l1_gas, l2_gas, l1_data_gas } = &self.resource_bounds.inner;
517        let is_gt_zero = |gas: &ResourceBounds| gas.max_amount > 0 && gas.max_price_per_unit > 0;
518        // valid set of resources are:
519        // 1. l1_gas > 0, l2_gas = 0, l1_data_gas = 0
520        // 2. l2_gas > 0, l1_data_gas > 0
521        match (l1_gas, l2_gas, l1_data_gas) {
522            // l1 > 0 and l2 = 0 and l1_data = 0 => valid
523            (l1, l2, l1_data) if is_gt_zero(l1) && !is_gt_zero(l2) && !is_gt_zero(l1_data) => {
524                tracing::warn!(
525                    "From version 0.5.0 V3 transactions that specify only L1 gas bounds are \
526                     invalid. Please upgrade your client to provide values for the triplet \
527                     (L1_GAS, L1_DATA_GAS, L2_GAS)."
528                );
529                true
530            }
531            // l2 > 0 and l1_data > 0
532            // l1 gas is not checked, because l1 gas is used for L2->L1 messages and not every
533            // transaction sends data to L1
534            (_, l2, l1_data) if is_gt_zero(l2) && is_gt_zero(l1_data) => true,
535            _ => false,
536        }
537    }
538
539    pub fn is_only_query(&self) -> bool {
540        is_only_query_common(&self.version)
541    }
542
543    pub fn get_gas_vector_computation_mode(&self) -> GasVectorComputationMode {
544        (&self.resource_bounds).into()
545    }
546}
547
548#[derive(Debug, Clone, Deserialize)]
549#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
550pub enum BroadcastedTransaction {
551    Invoke(BroadcastedInvokeTransaction),
552    Declare(BroadcastedDeclareTransaction),
553    DeployAccount(BroadcastedDeployAccountTransaction),
554}
555
556impl BroadcastedTransaction {
557    pub fn to_blockifier_account_transaction(
558        &self,
559        chain_id: &Felt,
560        execution_flags: ExecutionFlags,
561    ) -> DevnetResult<blockifier::transaction::account_transaction::AccountTransaction> {
562        let sn_api_tx = self.to_sn_api_account_transaction(chain_id)?;
563        Ok(blockifier::transaction::account_transaction::AccountTransaction {
564            tx: sn_api_tx,
565            execution_flags,
566        })
567    }
568
569    pub fn to_sn_api_account_transaction(
570        &self,
571        chain_id: &Felt,
572    ) -> DevnetResult<starknet_api::executable_transaction::AccountTransaction> {
573        let sn_api_tx = match self {
574            BroadcastedTransaction::Invoke(invoke_txn) => {
575                starknet_api::executable_transaction::AccountTransaction::Invoke(
576                    invoke_txn.create_sn_api_invoke(chain_id)?,
577                )
578            }
579            BroadcastedTransaction::Declare(declare_txn) => {
580                starknet_api::executable_transaction::AccountTransaction::Declare(
581                    declare_txn.create_sn_api_declare(chain_id)?,
582                )
583            }
584            BroadcastedTransaction::DeployAccount(deploy_account_txn) => {
585                starknet_api::executable_transaction::AccountTransaction::DeployAccount(
586                    deploy_account_txn.create_sn_api_deploy_account(chain_id)?,
587                )
588            }
589        };
590
591        Ok(sn_api_tx)
592    }
593
594    pub fn get_type(&self) -> TransactionType {
595        match self {
596            BroadcastedTransaction::Invoke(_) => TransactionType::Invoke,
597            BroadcastedTransaction::Declare(_) => TransactionType::Declare,
598            BroadcastedTransaction::DeployAccount(_) => TransactionType::DeployAccount,
599        }
600    }
601
602    pub fn are_gas_bounds_valid(&self) -> bool {
603        match self {
604            BroadcastedTransaction::Invoke(broadcasted_invoke_transaction) => {
605                broadcasted_invoke_transaction.are_gas_bounds_valid()
606            }
607            BroadcastedTransaction::Declare(broadcasted_declare_transaction) => {
608                broadcasted_declare_transaction.are_gas_bounds_valid()
609            }
610            BroadcastedTransaction::DeployAccount(broadcasted_deploy_account_transaction) => {
611                broadcasted_deploy_account_transaction.are_gas_bounds_valid()
612            }
613        }
614    }
615
616    pub fn gas_vector_computation_mode(&self) -> GasVectorComputationMode {
617        match self {
618            BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V3(declare_v3)) => {
619                declare_v3.common.get_gas_vector_computation_mode()
620            }
621            BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3(
622                deploy_account_v3,
623            )) => deploy_account_v3.common.get_gas_vector_computation_mode(),
624            BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3(invoke_v3)) => {
625                invoke_v3.common.get_gas_vector_computation_mode()
626            }
627        }
628    }
629
630    pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
631        match self {
632            BroadcastedTransaction::Invoke(tx) => {
633                tx.requires_strict_nonce_check(using_pre_confirmed_block)
634            }
635            BroadcastedTransaction::Declare(_) => true,
636            BroadcastedTransaction::DeployAccount(tx) => {
637                tx.requires_strict_nonce_check(using_pre_confirmed_block)
638            }
639        }
640    }
641}
642
643#[derive(Debug, Clone)]
644pub enum BroadcastedDeclareTransaction {
645    V3(Box<BroadcastedDeclareTransactionV3>),
646}
647
648impl BroadcastedDeclareTransaction {
649    pub fn are_gas_bounds_valid(&self) -> bool {
650        match self {
651            BroadcastedDeclareTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
652        }
653    }
654
655    pub fn is_only_query(&self) -> bool {
656        match self {
657            BroadcastedDeclareTransaction::V3(tx) => tx.common.is_only_query(),
658        }
659    }
660
661    /// Creates a blockifier declare transaction from the current transaction.
662    /// The transaction hash is computed using the given chain id.
663    ///
664    /// # Arguments
665    /// `chain_id` - the chain id to use for the transaction hash computation
666    pub fn create_sn_api_declare(
667        &self,
668        chain_id: &Felt,
669    ) -> DevnetResult<starknet_api::executable_transaction::DeclareTransaction> {
670        let sn_api_chain_id = felt_to_sn_api_chain_id(chain_id)?;
671
672        let (sn_api_transaction, tx_hash, class_info) = match self {
673            BroadcastedDeclareTransaction::V3(v3) => {
674                let sierra_class_hash = compute_sierra_class_hash(&v3.contract_class)?;
675
676                let sn_api_declare = starknet_api::transaction::DeclareTransaction::V3(
677                    starknet_api::transaction::DeclareTransactionV3 {
678                        resource_bounds: (&v3.common.resource_bounds).into(),
679                        tip: v3.common.tip,
680                        signature: starknet_api::transaction::fields::TransactionSignature(
681                            Arc::new(v3.common.signature.clone()),
682                        ),
683                        nonce: starknet_api::core::Nonce(v3.common.nonce),
684                        class_hash: starknet_api::core::ClassHash(sierra_class_hash),
685                        compiled_class_hash: starknet_api::core::CompiledClassHash(
686                            v3.compiled_class_hash,
687                        ),
688                        sender_address: v3.sender_address.into(),
689                        nonce_data_availability_mode: v3.common.nonce_data_availability_mode,
690                        fee_data_availability_mode: v3.common.fee_data_availability_mode,
691                        paymaster_data: starknet_api::transaction::fields::PaymasterData(
692                            v3.common.paymaster_data.clone(),
693                        ),
694                        account_deployment_data:
695                            starknet_api::transaction::fields::AccountDeploymentData(
696                                v3.account_deployment_data.clone(),
697                            ),
698                    },
699                );
700
701                let class_info: ClassInfo =
702                    ContractClass::Cairo1(v3.contract_class.clone()).try_into()?;
703
704                let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
705                    &sn_api_declare.version(),
706                    &TransactionOptions { only_query: self.is_only_query() },
707                );
708
709                let tx_hash =
710                    sn_api_declare.calculate_transaction_hash(&sn_api_chain_id, &tx_version)?;
711
712                (sn_api_declare, tx_hash, class_info)
713            }
714        };
715
716        Ok(starknet_api::executable_transaction::DeclareTransaction {
717            tx: sn_api_transaction,
718            tx_hash,
719            class_info,
720        })
721    }
722}
723
724#[derive(Debug, Clone)]
725pub enum BroadcastedDeployAccountTransaction {
726    V3(BroadcastedDeployAccountTransactionV3),
727}
728
729impl BroadcastedDeployAccountTransaction {
730    pub fn are_gas_bounds_valid(&self) -> bool {
731        match self {
732            BroadcastedDeployAccountTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
733        }
734    }
735
736    pub fn is_only_query(&self) -> bool {
737        match self {
738            BroadcastedDeployAccountTransaction::V3(tx) => tx.common.is_only_query(),
739        }
740    }
741
742    pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
743        !using_pre_confirmed_block
744    }
745
746    /// Creates a blockifier deploy account transaction from the current transaction.
747    /// The transaction hash is computed using the given chain id.
748    ///
749    /// # Arguments
750    /// `chain_id` - the chain id to use for the transaction hash computation
751    pub fn create_sn_api_deploy_account(
752        &self,
753        chain_id: &Felt,
754    ) -> DevnetResult<starknet_api::executable_transaction::DeployAccountTransaction> {
755        let sn_api_transaction = match self {
756            BroadcastedDeployAccountTransaction::V3(v3) => {
757                let sn_api_transaction = starknet_api::transaction::DeployAccountTransactionV3 {
758                    resource_bounds: (&v3.common.resource_bounds).into(),
759                    tip: v3.common.tip,
760                    signature: starknet_api::transaction::fields::TransactionSignature(Arc::new(
761                        v3.common.signature.clone(),
762                    )),
763                    nonce: starknet_api::core::Nonce(v3.common.nonce),
764                    class_hash: starknet_api::core::ClassHash(v3.class_hash),
765                    nonce_data_availability_mode: v3.common.nonce_data_availability_mode,
766                    fee_data_availability_mode: v3.common.fee_data_availability_mode,
767                    paymaster_data: starknet_api::transaction::fields::PaymasterData(
768                        v3.common.paymaster_data.clone(),
769                    ),
770                    contract_address_salt: starknet_api::transaction::fields::ContractAddressSalt(
771                        v3.contract_address_salt,
772                    ),
773                    constructor_calldata: starknet_api::transaction::fields::Calldata(Arc::new(
774                        v3.constructor_calldata.clone(),
775                    )),
776                };
777
778                starknet_api::transaction::DeployAccountTransaction::V3(sn_api_transaction)
779            }
780        };
781
782        let chain_id = felt_to_sn_api_chain_id(chain_id)?;
783        let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
784            &sn_api_transaction.version(),
785            &TransactionOptions { only_query: self.is_only_query() },
786        );
787        let tx_hash = sn_api_transaction.calculate_transaction_hash(&chain_id, &tx_version)?;
788
789        // copied from starknet_api::executable_transaction::DeployAccountTransaction::create(
790        let contract_address = calculate_contract_address(
791            sn_api_transaction.contract_address_salt(),
792            sn_api_transaction.class_hash(),
793            &sn_api_transaction.constructor_calldata(),
794            starknet_api::core::ContractAddress::default(),
795        )?;
796
797        Ok(starknet_api::executable_transaction::DeployAccountTransaction {
798            tx: sn_api_transaction,
799            tx_hash,
800            contract_address,
801        })
802    }
803}
804
805#[derive(Debug, Clone)]
806pub enum BroadcastedInvokeTransaction {
807    V3(BroadcastedInvokeTransactionV3),
808}
809
810impl BroadcastedInvokeTransaction {
811    pub fn are_gas_bounds_valid(&self) -> bool {
812        match self {
813            BroadcastedInvokeTransaction::V3(v3) => v3.common.are_gas_bounds_valid(),
814        }
815    }
816
817    pub fn is_only_query(&self) -> bool {
818        match self {
819            BroadcastedInvokeTransaction::V3(tx) => tx.common.is_only_query(),
820        }
821    }
822
823    pub fn requires_strict_nonce_check(&self, using_pre_confirmed_block: bool) -> bool {
824        !using_pre_confirmed_block
825    }
826
827    /// Creates a blockifier invoke transaction from the current transaction.
828    /// The transaction hash is computed using the given chain id.
829    ///
830    /// # Arguments
831    /// `chain_id` - the chain id to use for the transaction hash computation
832    pub fn create_sn_api_invoke(
833        &self,
834        chain_id: &Felt,
835    ) -> DevnetResult<starknet_api::executable_transaction::InvokeTransaction> {
836        let sn_api_transaction = match self {
837            BroadcastedInvokeTransaction::V3(v3) => v3.create_sn_api_invoke()?,
838        };
839
840        let chain_id = felt_to_sn_api_chain_id(chain_id)?;
841        let tx_version: starknet_api::transaction::TransactionVersion = signed_tx_version(
842            &sn_api_transaction.version(),
843            &TransactionOptions { only_query: self.is_only_query() },
844        );
845        let tx_hash = sn_api_transaction.calculate_transaction_hash(&chain_id, &tx_version)?;
846
847        Ok(starknet_api::executable_transaction::InvokeTransaction {
848            tx: sn_api_transaction,
849            tx_hash,
850        })
851    }
852
853    pub fn get_proof(&self) -> Option<Proof> {
854        match self {
855            BroadcastedInvokeTransaction::V3(v3) => v3.proof.clone(),
856        }
857    }
858
859    pub fn get_proof_facts(&self) -> Option<ProofFacts> {
860        match self {
861            BroadcastedInvokeTransaction::V3(v3) => v3.proof_facts.clone(),
862        }
863    }
864}
865
866impl<'de> Deserialize<'de> for BroadcastedDeclareTransaction {
867    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
868    where
869        D: Deserializer<'de>,
870    {
871        let value = serde_json::Value::deserialize(deserializer)?;
872        let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
873        match version_raw.as_str() {
874            Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
875                let unpacked = serde_json::from_value(value).map_err(|e| {
876                    serde::de::Error::custom(format!("Invalid declare transaction v3: {e}"))
877                })?;
878                Ok(BroadcastedDeclareTransaction::V3(Box::new(unpacked)))
879            }
880            _ => Err(serde::de::Error::custom(format!(
881                "Invalid version of declare transaction: {version_raw}"
882            ))),
883        }
884    }
885}
886
887impl<'de> Deserialize<'de> for BroadcastedDeployAccountTransaction {
888    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
889    where
890        D: Deserializer<'de>,
891    {
892        let value = serde_json::Value::deserialize(deserializer)?;
893        let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
894        match version_raw.as_str() {
895            Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
896                let unpacked = serde_json::from_value(value).map_err(|e| {
897                    serde::de::Error::custom(format!("Invalid deploy account transaction v3: {e}"))
898                })?;
899                Ok(BroadcastedDeployAccountTransaction::V3(unpacked))
900            }
901            _ => Err(serde::de::Error::custom(format!(
902                "Invalid version of deploy account transaction: {version_raw}"
903            ))),
904        }
905    }
906}
907
908impl<'de> Deserialize<'de> for BroadcastedInvokeTransaction {
909    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
910    where
911        D: Deserializer<'de>,
912    {
913        let value = serde_json::Value::deserialize(deserializer)?;
914        let version_raw = value.get("version").ok_or(serde::de::Error::missing_field("version"))?;
915        match version_raw.as_str() {
916            Some(v) if ["0x3", "0x100000000000000000000000000000003"].contains(&v) => {
917                let unpacked = serde_json::from_value(value).map_err(|e| {
918                    serde::de::Error::custom(format!("Invalid invoke transaction v3: {e}"))
919                })?;
920                Ok(BroadcastedInvokeTransaction::V3(unpacked))
921            }
922            _ => Err(serde::de::Error::custom(format!(
923                "Invalid version of invoke transaction: {version_raw}"
924            ))),
925        }
926    }
927}
928
929/// Flags that indicate how to simulate a given transaction.
930/// By default, the sequencer behavior is replicated locally (enough funds are expected to be in the
931/// account, and fee will be deducted from the balance before the simulation of the next
932/// transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag.
933#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
934#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
935pub enum SimulationFlag {
936    SkipValidate,
937    SkipFeeCharge,
938    ReturnInitialReads,
939}
940
941/// Flags that indicate what additional information should be included in the trace.
942#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
943#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
944pub enum TraceFlag {
945    ReturnInitialReads,
946}
947
948#[derive(Debug, Clone, Serialize)]
949#[cfg_attr(feature = "testing", derive(Deserialize))]
950#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
951pub enum CallType {
952    LibraryCall,
953    Call,
954    Delegate,
955}
956
957#[derive(Debug, Clone, Serialize)]
958#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
959pub struct FunctionInvocation {
960    contract_address: ContractAddress,
961    entry_point_selector: EntryPointSelector,
962    calldata: Calldata,
963    caller_address: ContractAddress,
964    class_hash: Felt,
965    entry_point_type: EntryPointType,
966    call_type: CallType,
967    result: Vec<Felt>,
968    calls: Vec<FunctionInvocation>,
969    events: Vec<OrderedEvent>,
970    messages: Vec<OrderedMessageToL1>,
971    execution_resources: InnerExecutionResources,
972    is_reverted: bool,
973}
974
975#[derive(Debug, Clone, Serialize)]
976#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
977pub struct InnerExecutionResources {
978    pub l1_gas: u64,
979    pub l2_gas: u64,
980}
981
982#[derive(Debug, Clone, Serialize)]
983#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
984#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
985pub enum TransactionTrace {
986    Invoke(InvokeTransactionTrace),
987    Declare(DeclareTransactionTrace),
988    DeployAccount(DeployAccountTransactionTrace),
989    L1Handler(L1HandlerTransactionTrace),
990}
991
992#[derive(Debug, Clone, Serialize)]
993#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
994pub struct BlockTransactionTrace {
995    pub transaction_hash: Felt,
996    pub trace_root: TransactionTrace,
997}
998
999#[derive(Debug, Clone, Serialize)]
1000#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1001pub struct Reversion {
1002    pub revert_reason: String, // TODO use blockifier's RevertError
1003}
1004
1005#[derive(Debug, Clone, Serialize)]
1006#[cfg_attr(feature = "testing", derive(serde::Deserialize))]
1007#[serde(untagged)]
1008#[allow(clippy::large_enum_variant)]
1009pub enum ExecutionInvocation {
1010    Succeeded(FunctionInvocation),
1011    Reverted(Reversion),
1012}
1013
1014#[derive(Debug, Clone, Serialize)]
1015#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1016pub struct InvokeTransactionTrace {
1017    #[serde(skip_serializing_if = "Option::is_none")]
1018    pub validate_invocation: Option<FunctionInvocation>,
1019    pub execute_invocation: ExecutionInvocation,
1020    #[serde(skip_serializing_if = "Option::is_none")]
1021    pub fee_transfer_invocation: Option<FunctionInvocation>,
1022    #[serde(skip_serializing_if = "Option::is_none")]
1023    pub state_diff: Option<ThinStateDiff>,
1024    pub execution_resources: ExecutionResources,
1025}
1026
1027#[derive(Debug, Clone, Serialize)]
1028#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1029pub struct DeclareTransactionTrace {
1030    #[serde(skip_serializing_if = "Option::is_none")]
1031    pub validate_invocation: Option<FunctionInvocation>,
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub fee_transfer_invocation: Option<FunctionInvocation>,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    pub state_diff: Option<ThinStateDiff>,
1036    pub execution_resources: ExecutionResources,
1037}
1038
1039#[derive(Debug, Clone, Serialize)]
1040#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1041pub struct DeployAccountTransactionTrace {
1042    #[serde(skip_serializing_if = "Option::is_none")]
1043    pub validate_invocation: Option<FunctionInvocation>,
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub constructor_invocation: Option<FunctionInvocation>,
1046    #[serde(skip_serializing_if = "Option::is_none")]
1047    pub fee_transfer_invocation: Option<FunctionInvocation>,
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    pub state_diff: Option<ThinStateDiff>,
1050    pub execution_resources: ExecutionResources,
1051}
1052
1053#[derive(Debug, Clone, Serialize)]
1054#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1055pub struct L1HandlerTransactionTrace {
1056    pub function_invocation: ExecutionInvocation,
1057    #[serde(skip_serializing_if = "Option::is_none")]
1058    pub state_diff: Option<ThinStateDiff>,
1059    pub execution_resources: ExecutionResources,
1060}
1061
1062#[derive(Debug, Clone, Serialize)]
1063#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1064pub struct SimulatedTransaction {
1065    pub transaction_trace: TransactionTrace,
1066    pub fee_estimation: FeeEstimateWrapper,
1067}
1068
1069#[derive(Debug, Clone, Serialize)]
1070#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1071pub struct SimulatedTransactionsWithInitialReads {
1072    pub simulated_transactions: Vec<SimulatedTransaction>,
1073    pub initial_reads: InitialReads,
1074}
1075
1076#[derive(Debug, Clone)]
1077pub enum SimulationResult {
1078    SimulatedTransactions(Vec<SimulatedTransaction>),
1079    SimulatedTransactionsWithInitialReads(Box<SimulatedTransactionsWithInitialReads>),
1080}
1081
1082#[derive(Debug, Clone, Serialize)]
1083#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1084pub struct BlockTransactionTracesWithInitialReads {
1085    pub traces: Vec<BlockTransactionTrace>,
1086    pub initial_reads: InitialReads,
1087}
1088
1089#[derive(Debug, Clone)]
1090pub enum BlockTraceResult {
1091    Traces(Vec<BlockTransactionTrace>),
1092    TracesWithInitialReads(Box<BlockTransactionTracesWithInitialReads>),
1093}
1094
1095#[derive(Debug, Clone, Serialize)]
1096#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1097pub struct InitialReadsStorageEntry {
1098    pub contract_address: ContractAddress,
1099    pub key: StorageKey,
1100    pub value: Felt,
1101}
1102
1103#[derive(Debug, Clone, Serialize)]
1104#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1105pub struct InitialReadsNonceEntry {
1106    pub contract_address: ContractAddress,
1107    pub nonce: Felt,
1108}
1109
1110#[derive(Debug, Clone, Serialize)]
1111#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1112pub struct InitialReadsClassHashEntry {
1113    pub contract_address: ContractAddress,
1114    pub class_hash: ClassHash,
1115}
1116
1117#[derive(Debug, Clone, Serialize)]
1118#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1119pub struct InitialReadsDeclaredContractsEntry {
1120    pub class_hash: ClassHash,
1121    pub is_declared: bool,
1122}
1123
1124#[derive(Debug, Clone, Default, Serialize)]
1125#[cfg_attr(feature = "testing", derive(serde::Deserialize), serde(deny_unknown_fields))]
1126pub struct InitialReads {
1127    pub storage: Vec<InitialReadsStorageEntry>,
1128    pub nonces: Vec<InitialReadsNonceEntry>,
1129    pub class_hashes: Vec<InitialReadsClassHashEntry>,
1130    pub declared_contracts: Vec<InitialReadsDeclaredContractsEntry>,
1131}
1132
1133impl From<StateMaps> for InitialReads {
1134    fn from(value: StateMaps) -> Self {
1135        InitialReads {
1136            storage: value
1137                .storage
1138                .iter()
1139                .map(|s| InitialReadsStorageEntry {
1140                    contract_address: s.0.0.into(),
1141                    key: (*s.0.1).into(),
1142                    value: *s.1,
1143                })
1144                .collect(),
1145            nonces: value
1146                .nonces
1147                .iter()
1148                .map(|n| InitialReadsNonceEntry { contract_address: (*n.0).into(), nonce: n.1.0 })
1149                .collect(),
1150            class_hashes: value
1151                .class_hashes
1152                .iter()
1153                .map(|c| InitialReadsClassHashEntry {
1154                    contract_address: (*c.0).into(),
1155                    class_hash: (*c.1).into(),
1156                })
1157                .collect(),
1158            declared_contracts: value
1159                .declared_contracts
1160                .iter()
1161                .map(|c| InitialReadsDeclaredContractsEntry {
1162                    class_hash: (*c.0).into(),
1163                    is_declared: *c.1,
1164                })
1165                .collect(),
1166        }
1167    }
1168}
1169
1170impl FunctionInvocation {
1171    pub fn try_from_call_info(
1172        call_info: &blockifier::execution::call_info::CallInfo,
1173        state_reader: &mut impl StateReader,
1174        versioned_constants: &VersionedConstants,
1175        gas_vector_computation_mode: &GasVectorComputationMode,
1176    ) -> DevnetResult<Self> {
1177        let mut internal_calls: Vec<FunctionInvocation> = vec![];
1178        for internal_call in &call_info.inner_calls {
1179            internal_calls.push(FunctionInvocation::try_from_call_info(
1180                internal_call,
1181                state_reader,
1182                versioned_constants,
1183                gas_vector_computation_mode,
1184            )?);
1185        }
1186
1187        let mut messages: Vec<OrderedMessageToL1> = vec![];
1188        for msg in call_info.execution.l2_to_l1_messages.iter() {
1189            // Keep sender semantics aligned with transaction receipts.
1190            // `storage_address` identifies the contract that emitted the message,
1191            // including delegate/library syscall flows.
1192            messages.push(OrderedMessageToL1::new(msg, call_info.call.storage_address.into())?);
1193        }
1194        messages.sort_by_key(|msg| msg.order);
1195
1196        let mut events: Vec<OrderedEvent> =
1197            call_info.execution.events.iter().map(OrderedEvent::from).collect();
1198        let contract_address = call_info.call.storage_address;
1199        events.sort_by_key(|event| event.order);
1200
1201        // call_info.call.class_hash could be None, so we deduce it from
1202        // call_info.call.storage_address which is function_call.contract_address
1203        let class_hash = if let Some(class_hash) = call_info.call.class_hash {
1204            class_hash
1205        } else {
1206            state_reader.get_class_hash_at(contract_address).map_err(|_| {
1207                ConversionError::InvalidInternalStructure(
1208                    "class_hash is unexpectedly undefined".into(),
1209                )
1210            })?
1211        };
1212
1213        let gas_vector = blockifier::fee::fee_utils::get_extended_vm_resources_cost(
1214            versioned_constants,
1215            &call_info.resources,
1216            0,
1217            gas_vector_computation_mode,
1218        );
1219
1220        Ok(FunctionInvocation {
1221            contract_address: contract_address.into(),
1222            entry_point_selector: call_info.call.entry_point_selector.0,
1223            calldata: call_info.call.calldata.0.to_vec(),
1224            caller_address: call_info.call.caller_address.into(),
1225            class_hash: class_hash.0,
1226            entry_point_type: call_info.call.entry_point_type,
1227            call_type: match call_info.call.call_type {
1228                blockifier::execution::entry_point::CallType::Call => CallType::Call,
1229                blockifier::execution::entry_point::CallType::Delegate => CallType::Delegate,
1230            },
1231            result: call_info.execution.retdata.0.clone(),
1232            calls: internal_calls,
1233            events,
1234            messages,
1235            execution_resources: InnerExecutionResources {
1236                l1_gas: gas_vector.l1_gas.0,
1237                l2_gas: gas_vector.l2_gas.0,
1238            },
1239            is_reverted: call_info.execution.failed,
1240        })
1241    }
1242
1243    /// Returns the direct L2→L1 messages from this invocation.
1244    pub fn messages(&self) -> &[OrderedMessageToL1] {
1245        &self.messages
1246    }
1247
1248    /// Recursively collects all L2→L1 messages from this invocation and nested calls.
1249    pub fn all_messages(&self) -> Vec<OrderedMessageToL1> {
1250        let mut result = self.messages.clone();
1251        for call in &self.calls {
1252            result.extend(call.all_messages());
1253        }
1254        result.sort_by_key(|msg| msg.order);
1255        result
1256    }
1257}
1258
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[serde(deny_unknown_fields)]
1261pub struct L1HandlerTransactionStatus {
1262    pub transaction_hash: TransactionHash,
1263    pub finality_status: TransactionFinalityStatus,
1264    pub execution_status: TransactionExecutionStatus,
1265    pub failure_reason: Option<String>,
1266}
1267
1268#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Copy)]
1269#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1270pub enum TransactionFinalityStatus {
1271    Received,
1272    Candidate,
1273    PreConfirmed,
1274    AcceptedOnL2,
1275    AcceptedOnL1,
1276}