starknet_providers/sequencer/models/
conversions.rs

1use std::sync::Arc;
2
3use starknet_core::types::{self as core, contract::legacy as contract_legacy, Felt};
4
5use super::{
6    state_update::{DeclaredContract, DeployedContract, StateDiff, StorageDiff},
7    transaction::{DataAvailabilityMode, ResourceBounds, ResourceBoundsMapping},
8    *,
9};
10
11#[derive(Debug, thiserror::Error)]
12#[error("unable to convert type")]
13pub struct ConversionError;
14
15pub(crate) struct ConfirmedReceiptWithContext {
16    pub receipt: ConfirmedTransactionReceipt,
17    pub transaction: TransactionType,
18    pub finality: BlockStatus,
19}
20
21impl TryFrom<core::BlockId> for BlockId {
22    type Error = ConversionError;
23
24    fn try_from(value: core::BlockId) -> Result<Self, Self::Error> {
25        match value {
26            core::BlockId::Hash(hash) => Ok(Self::Hash(hash)),
27            core::BlockId::Number(num) => Ok(Self::Number(num)),
28            core::BlockId::Tag(core::BlockTag::Latest) => Ok(Self::Latest),
29            core::BlockId::Tag(core::BlockTag::PreConfirmed) => Ok(Self::Pending),
30            core::BlockId::Tag(core::BlockTag::L1Accepted) => Err(ConversionError),
31        }
32    }
33}
34
35impl TryFrom<Block> for core::MaybePreConfirmedBlockWithTxHashes {
36    type Error = ConversionError;
37
38    fn try_from(value: Block) -> Result<Self, Self::Error> {
39        match (value.block_hash, value.block_number, value.state_root) {
40            // Confirmed block
41            (Some(block_hash), Some(block_number), Some(state_root)) => {
42                Ok(Self::Block(core::BlockWithTxHashes {
43                    status: value.status.try_into()?,
44                    block_hash,
45                    parent_hash: value.parent_block_hash,
46                    block_number,
47                    new_root: state_root,
48                    timestamp: value.timestamp,
49                    sequencer_address: value.sequencer_address.unwrap_or_default(),
50                    l1_gas_price: value.l1_gas_price,
51                    l2_gas_price: value.l2_gas_price,
52                    l1_data_gas_price: value.l1_data_gas_price,
53                    l1_da_mode: value.l1_da_mode,
54                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
55                    transactions: value
56                        .transactions
57                        .iter()
58                        .map(|tx| tx.transaction_hash())
59                        .collect(),
60                }))
61            }
62            // Pending block
63            (None, None, None) => {
64                // We're almost able to map this into a pre-confirmed block, but can't do it as
65                // pending blocks from the sequencer doesn't contain block number.
66                //
67                // Technically the block number can be found by looking up the parent block, but we
68                // don't have a choice but to error here.
69                Err(ConversionError)
70            }
71            // Unknown combination
72            _ => Err(ConversionError),
73        }
74    }
75}
76
77impl TryFrom<Block> for core::MaybePreConfirmedBlockWithTxs {
78    type Error = ConversionError;
79
80    fn try_from(value: Block) -> Result<Self, Self::Error> {
81        match (value.block_hash, value.block_number, value.state_root) {
82            // Confirmed block
83            (Some(block_hash), Some(block_number), Some(state_root)) => {
84                Ok(Self::Block(core::BlockWithTxs {
85                    status: value.status.try_into()?,
86                    block_hash,
87                    parent_hash: value.parent_block_hash,
88                    block_number,
89                    new_root: state_root,
90                    timestamp: value.timestamp,
91                    sequencer_address: value.sequencer_address.unwrap_or_default(),
92                    l1_gas_price: value.l1_gas_price,
93                    l2_gas_price: value.l2_gas_price,
94                    l1_data_gas_price: value.l1_data_gas_price,
95                    l1_da_mode: value.l1_da_mode,
96                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
97                    transactions: value
98                        .transactions
99                        .into_iter()
100                        .map(|tx| tx.try_into())
101                        .collect::<Result<_, _>>()?,
102                }))
103            }
104            // Pending block
105            (None, None, None) => {
106                // We're almost able to map this into a pre-confirmed block, but can't do it as
107                // pending blocks from the sequencer doesn't contain block number.
108                //
109                // Technically the block number can be found by looking up the parent block, but we
110                // don't have a choice but to error here.
111                Err(ConversionError)
112            }
113            // Unknown combination
114            _ => Err(ConversionError),
115        }
116    }
117}
118
119impl TryFrom<Block> for core::MaybePreConfirmedBlockWithReceipts {
120    type Error = ConversionError;
121
122    fn try_from(value: Block) -> Result<Self, Self::Error> {
123        if value.transactions.len() != value.transaction_receipts.len() {
124            return Err(ConversionError);
125        }
126
127        let mut transactions = vec![];
128
129        for (tx, receipt) in value
130            .transactions
131            .into_iter()
132            .zip(value.transaction_receipts.into_iter())
133        {
134            let core_tx = tx.clone().try_into()?;
135
136            let tx_with_receipt = ConfirmedReceiptWithContext {
137                receipt,
138                transaction: tx,
139                finality: value.status,
140            };
141
142            transactions.push(core::TransactionWithReceipt {
143                transaction: core_tx,
144                receipt: tx_with_receipt.try_into()?,
145            });
146        }
147
148        match (value.block_hash, value.block_number, value.state_root) {
149            // Confirmed block
150            (Some(block_hash), Some(block_number), Some(state_root)) => {
151                Ok(Self::Block(core::BlockWithReceipts {
152                    status: value.status.try_into()?,
153                    block_hash,
154                    parent_hash: value.parent_block_hash,
155                    block_number,
156                    new_root: state_root,
157                    timestamp: value.timestamp,
158                    sequencer_address: value.sequencer_address.unwrap_or_default(),
159                    l1_gas_price: value.l1_gas_price,
160                    l2_gas_price: value.l2_gas_price,
161                    l1_data_gas_price: value.l1_data_gas_price,
162                    l1_da_mode: value.l1_da_mode,
163                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
164                    transactions,
165                }))
166            }
167            // Pending block
168            (None, None, None) => {
169                // We're almost able to map this into a pre-confirmed block, but can't do it as
170                // pending blocks from the sequencer doesn't contain block number.
171                //
172                // Technically the block number can be found by looking up the parent block, but we
173                // don't have a choice but to error here.
174                Err(ConversionError)
175            }
176            // Unknown combination
177            _ => Err(ConversionError),
178        }
179    }
180}
181
182impl TryFrom<BlockStatus> for core::BlockStatus {
183    type Error = ConversionError;
184
185    fn try_from(value: BlockStatus) -> Result<Self, Self::Error> {
186        match value {
187            BlockStatus::Pending => Ok(Self::PreConfirmed),
188            BlockStatus::Aborted | BlockStatus::Reverted => Err(ConversionError),
189            BlockStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
190            BlockStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
191        }
192    }
193}
194
195impl TryFrom<TransactionType> for core::Transaction {
196    type Error = ConversionError;
197
198    fn try_from(value: TransactionType) -> Result<Self, Self::Error> {
199        match value {
200            TransactionType::Declare(inner) => Ok(Self::Declare(inner.try_into()?)),
201            TransactionType::Deploy(inner) => Ok(Self::Deploy(inner.try_into()?)),
202            TransactionType::DeployAccount(inner) => Ok(Self::DeployAccount(inner.try_into()?)),
203            TransactionType::InvokeFunction(inner) => Ok(Self::Invoke(inner.try_into()?)),
204            TransactionType::L1Handler(inner) => Ok(Self::L1Handler(inner.try_into()?)),
205        }
206    }
207}
208
209impl TryFrom<TransactionType> for core::TransactionContent {
210    type Error = ConversionError;
211
212    fn try_from(value: TransactionType) -> Result<Self, Self::Error> {
213        let tx: core::Transaction = value.try_into()?;
214        Ok(tx.into())
215    }
216}
217
218impl TryFrom<DeclareTransaction> for core::DeclareTransaction {
219    type Error = ConversionError;
220
221    fn try_from(value: DeclareTransaction) -> Result<Self, Self::Error> {
222        if value.version == Felt::ZERO {
223            Ok(Self::V0(core::DeclareTransactionV0 {
224                transaction_hash: value.transaction_hash,
225                max_fee: value.max_fee.ok_or(ConversionError)?,
226                signature: value.signature,
227                class_hash: value.class_hash,
228                sender_address: value.sender_address,
229            }))
230        } else if value.version == Felt::ONE {
231            Ok(Self::V1(core::DeclareTransactionV1 {
232                transaction_hash: value.transaction_hash,
233                max_fee: value.max_fee.ok_or(ConversionError)?,
234                signature: value.signature,
235                nonce: value.nonce,
236                class_hash: value.class_hash,
237                sender_address: value.sender_address,
238            }))
239        } else if value.version == Felt::TWO {
240            Ok(Self::V2(core::DeclareTransactionV2 {
241                transaction_hash: value.transaction_hash,
242                max_fee: value.max_fee.ok_or(ConversionError)?,
243                signature: value.signature,
244                nonce: value.nonce,
245                class_hash: value.class_hash,
246                compiled_class_hash: value.compiled_class_hash.ok_or(ConversionError)?,
247                sender_address: value.sender_address,
248            }))
249        } else if value.version == Felt::THREE {
250            Ok(Self::V3(core::DeclareTransactionV3 {
251                transaction_hash: value.transaction_hash,
252                sender_address: value.sender_address,
253                compiled_class_hash: value.compiled_class_hash.ok_or(ConversionError)?,
254                signature: value.signature,
255                nonce: value.nonce,
256                class_hash: value.class_hash,
257                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
258                tip: value.tip.ok_or(ConversionError)?,
259                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
260                account_deployment_data: value.account_deployment_data.ok_or(ConversionError)?,
261                nonce_data_availability_mode: value
262                    .nonce_data_availability_mode
263                    .ok_or(ConversionError)?
264                    .into(),
265                fee_data_availability_mode: value
266                    .fee_data_availability_mode
267                    .ok_or(ConversionError)?
268                    .into(),
269            }))
270        } else {
271            Err(ConversionError)
272        }
273    }
274}
275
276impl TryFrom<DeployTransaction> for core::DeployTransaction {
277    type Error = ConversionError;
278
279    fn try_from(value: DeployTransaction) -> Result<Self, Self::Error> {
280        Ok(Self {
281            transaction_hash: value.transaction_hash,
282            class_hash: value.class_hash,
283            version: value.version,
284            contract_address_salt: value.contract_address_salt,
285            constructor_calldata: value.constructor_calldata,
286        })
287    }
288}
289
290impl TryFrom<DeployAccountTransaction> for core::DeployAccountTransaction {
291    type Error = ConversionError;
292
293    fn try_from(value: DeployAccountTransaction) -> Result<Self, Self::Error> {
294        if value.version == Felt::ONE {
295            Ok(Self::V1(core::DeployAccountTransactionV1 {
296                transaction_hash: value.transaction_hash,
297                max_fee: value.max_fee.ok_or(ConversionError)?,
298                signature: value.signature,
299                nonce: value.nonce,
300                contract_address_salt: value.contract_address_salt,
301                constructor_calldata: value.constructor_calldata,
302                class_hash: value.class_hash,
303            }))
304        } else if value.version == Felt::THREE {
305            Ok(Self::V3(core::DeployAccountTransactionV3 {
306                transaction_hash: value.transaction_hash,
307                signature: value.signature,
308                nonce: value.nonce,
309                contract_address_salt: value.contract_address_salt,
310                constructor_calldata: value.constructor_calldata,
311                class_hash: value.class_hash,
312                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
313                tip: value.tip.ok_or(ConversionError)?,
314                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
315                nonce_data_availability_mode: value
316                    .nonce_data_availability_mode
317                    .ok_or(ConversionError)?
318                    .into(),
319                fee_data_availability_mode: value
320                    .fee_data_availability_mode
321                    .ok_or(ConversionError)?
322                    .into(),
323            }))
324        } else {
325            Err(ConversionError)
326        }
327    }
328}
329
330impl TryFrom<InvokeFunctionTransaction> for core::InvokeTransaction {
331    type Error = ConversionError;
332
333    fn try_from(value: InvokeFunctionTransaction) -> Result<Self, Self::Error> {
334        if value.version == Felt::ZERO {
335            Ok(Self::V0(core::InvokeTransactionV0 {
336                transaction_hash: value.transaction_hash,
337                max_fee: value.max_fee.ok_or(ConversionError)?,
338                signature: value.signature,
339                contract_address: value.sender_address,
340                entry_point_selector: value.entry_point_selector.ok_or(ConversionError)?,
341                calldata: value.calldata,
342            }))
343        } else if value.version == Felt::ONE {
344            Ok(Self::V1(core::InvokeTransactionV1 {
345                transaction_hash: value.transaction_hash,
346                max_fee: value.max_fee.ok_or(ConversionError)?,
347                signature: value.signature,
348                nonce: value.nonce.ok_or(ConversionError)?,
349                sender_address: value.sender_address,
350                calldata: value.calldata,
351            }))
352        } else if value.version == Felt::THREE {
353            Ok(Self::V3(core::InvokeTransactionV3 {
354                transaction_hash: value.transaction_hash,
355                sender_address: value.sender_address,
356                calldata: value.calldata,
357                signature: value.signature,
358                nonce: value.nonce.ok_or(ConversionError)?,
359                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
360                tip: value.tip.ok_or(ConversionError)?,
361                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
362                account_deployment_data: value.account_deployment_data.ok_or(ConversionError)?,
363                nonce_data_availability_mode: value
364                    .nonce_data_availability_mode
365                    .ok_or(ConversionError)?
366                    .into(),
367                fee_data_availability_mode: value
368                    .fee_data_availability_mode
369                    .ok_or(ConversionError)?
370                    .into(),
371            }))
372        } else {
373            Err(ConversionError)
374        }
375    }
376}
377
378impl TryFrom<L1HandlerTransaction> for core::L1HandlerTransaction {
379    type Error = ConversionError;
380
381    fn try_from(value: L1HandlerTransaction) -> Result<Self, Self::Error> {
382        Ok(Self {
383            transaction_hash: value.transaction_hash,
384            version: value.version,
385            nonce: {
386                // TODO: remove this when a proper u64 conversion is implemented for `Felt`
387                let nonce_bytes = value.nonce.unwrap_or_default().to_bytes_le();
388                if nonce_bytes.iter().skip(8).any(|&x| x != 0) {
389                    return Err(ConversionError);
390                }
391                u64::from_le_bytes(nonce_bytes[..8].try_into().unwrap())
392            },
393            contract_address: value.contract_address,
394            entry_point_selector: value.entry_point_selector,
395            calldata: value.calldata,
396        })
397    }
398}
399
400impl From<ResourceBoundsMapping> for core::ResourceBoundsMapping {
401    fn from(value: ResourceBoundsMapping) -> Self {
402        Self {
403            l1_gas: value.l1_gas.into(),
404            l1_data_gas: value.l1_data_gas.into(),
405            l2_gas: value.l2_gas.into(),
406        }
407    }
408}
409
410impl From<core::ResourceBoundsMapping> for ResourceBoundsMapping {
411    fn from(value: core::ResourceBoundsMapping) -> Self {
412        Self {
413            l1_gas: value.l1_gas.into(),
414            l1_data_gas: value.l1_data_gas.into(),
415            l2_gas: value.l2_gas.into(),
416        }
417    }
418}
419
420impl From<ResourceBounds> for core::ResourceBounds {
421    fn from(value: ResourceBounds) -> Self {
422        Self {
423            max_amount: value.max_amount,
424            max_price_per_unit: value.max_price_per_unit,
425        }
426    }
427}
428
429impl From<core::ResourceBounds> for ResourceBounds {
430    fn from(value: core::ResourceBounds) -> Self {
431        Self {
432            max_amount: value.max_amount,
433            max_price_per_unit: value.max_price_per_unit,
434        }
435    }
436}
437
438impl From<DataAvailabilityMode> for core::DataAvailabilityMode {
439    fn from(value: DataAvailabilityMode) -> Self {
440        match value {
441            DataAvailabilityMode::L1 => Self::L1,
442            DataAvailabilityMode::L2 => Self::L2,
443        }
444    }
445}
446
447impl From<core::DataAvailabilityMode> for DataAvailabilityMode {
448    fn from(value: core::DataAvailabilityMode) -> Self {
449        match value {
450            core::DataAvailabilityMode::L1 => Self::L1,
451            core::DataAvailabilityMode::L2 => Self::L2,
452        }
453    }
454}
455
456impl TryFrom<StateUpdate> for core::MaybePreConfirmedStateUpdate {
457    type Error = ConversionError;
458
459    fn try_from(value: StateUpdate) -> Result<Self, Self::Error> {
460        match (value.block_hash, value.new_root) {
461            (Some(block_hash), Some(new_root)) => Ok(Self::Update(core::StateUpdate {
462                block_hash,
463                new_root,
464                old_root: value.old_root,
465                state_diff: value.state_diff.into(),
466            })),
467            (None, None) => Ok(Self::PreConfirmedUpdate(core::PreConfirmedStateUpdate {
468                old_root: value.old_root,
469                state_diff: value.state_diff.into(),
470            })),
471            _ => Err(ConversionError),
472        }
473    }
474}
475
476impl From<StateDiff> for core::StateDiff {
477    fn from(value: StateDiff) -> Self {
478        Self {
479            storage_diffs: value
480                .storage_diffs
481                .into_iter()
482                .map(|(key, value)| core::ContractStorageDiffItem {
483                    address: key,
484                    storage_entries: value.into_iter().map(|item| item.into()).collect(),
485                })
486                .collect(),
487            deprecated_declared_classes: value.old_declared_contracts,
488            declared_classes: value
489                .declared_classes
490                .into_iter()
491                .map(|item| item.into())
492                .collect(),
493            deployed_contracts: value
494                .deployed_contracts
495                .into_iter()
496                .map(|item| item.into())
497                .collect(),
498            replaced_classes: value
499                .replaced_classes
500                .into_iter()
501                .map(|item| item.into())
502                .collect(),
503            nonces: value
504                .nonces
505                .into_iter()
506                .map(|(key, value)| core::NonceUpdate {
507                    contract_address: key,
508                    nonce: value,
509                })
510                .collect(),
511        }
512    }
513}
514
515impl From<StorageDiff> for core::StorageEntry {
516    fn from(value: StorageDiff) -> Self {
517        Self {
518            key: value.key,
519            value: value.value,
520        }
521    }
522}
523
524impl From<DeclaredContract> for core::DeclaredClassItem {
525    fn from(value: DeclaredContract) -> Self {
526        Self {
527            class_hash: value.class_hash,
528            compiled_class_hash: value.compiled_class_hash,
529        }
530    }
531}
532
533impl From<DeployedContract> for core::DeployedContractItem {
534    fn from(value: DeployedContract) -> Self {
535        Self {
536            address: value.address,
537            class_hash: value.class_hash,
538        }
539    }
540}
541
542impl From<DeployedContract> for core::ReplacedClassItem {
543    fn from(value: DeployedContract) -> Self {
544        Self {
545            contract_address: value.address,
546            class_hash: value.class_hash,
547        }
548    }
549}
550
551impl TryFrom<TransactionInfo> for core::Transaction {
552    type Error = ConversionError;
553
554    fn try_from(value: TransactionInfo) -> Result<Self, Self::Error> {
555        match value.r#type {
556            Some(tx) => tx.try_into(),
557            None => Err(ConversionError),
558        }
559    }
560}
561
562impl From<L2ToL1Message> for core::MsgToL1 {
563    fn from(value: L2ToL1Message) -> Self {
564        Self {
565            from_address: value.from_address,
566            // Unwrapping here is safe
567            to_address: Felt::from_bytes_be_slice(&value.to_address.0),
568            payload: value.payload,
569        }
570    }
571}
572
573impl From<Event> for core::Event {
574    fn from(value: Event) -> Self {
575        Self {
576            from_address: value.from_address,
577            keys: value.keys,
578            data: value.data,
579        }
580    }
581}
582
583impl TryFrom<TransactionExecutionStatus> for core::TransactionExecutionStatus {
584    type Error = ConversionError;
585
586    fn try_from(value: TransactionExecutionStatus) -> Result<Self, Self::Error> {
587        match value {
588            TransactionExecutionStatus::Succeeded => Ok(Self::Succeeded),
589            TransactionExecutionStatus::Reverted => Ok(Self::Reverted),
590            TransactionExecutionStatus::Rejected => Err(ConversionError),
591        }
592    }
593}
594
595impl TryFrom<TransactionFinalityStatus> for core::TransactionFinalityStatus {
596    type Error = ConversionError;
597
598    fn try_from(value: TransactionFinalityStatus) -> Result<Self, Self::Error> {
599        match value {
600            TransactionFinalityStatus::NotReceived | TransactionFinalityStatus::Received => {
601                Err(ConversionError)
602            }
603            TransactionFinalityStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
604            TransactionFinalityStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
605        }
606    }
607}
608
609impl From<core::BroadcastedInvokeTransaction> for InvokeFunctionTransactionRequest {
610    fn from(value: core::BroadcastedInvokeTransaction) -> Self {
611        Self::V3(value.into())
612    }
613}
614
615impl From<core::BroadcastedInvokeTransactionV3> for InvokeFunctionV3TransactionRequest {
616    fn from(value: core::BroadcastedInvokeTransactionV3) -> Self {
617        Self {
618            sender_address: value.sender_address,
619            calldata: value.calldata,
620            signature: value.signature,
621            nonce: value.nonce,
622            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
623            fee_data_availability_mode: value.fee_data_availability_mode.into(),
624            resource_bounds: value.resource_bounds.into(),
625            tip: value.tip,
626            paymaster_data: value.paymaster_data,
627            account_deployment_data: value.account_deployment_data,
628            is_query: value.is_query,
629        }
630    }
631}
632
633impl TryFrom<core::BroadcastedDeclareTransaction> for DeclareTransactionRequest {
634    type Error = ConversionError;
635
636    fn try_from(value: core::BroadcastedDeclareTransaction) -> Result<Self, Self::Error> {
637        Ok(Self::V3(value.try_into()?))
638    }
639}
640
641impl TryFrom<core::BroadcastedDeclareTransactionV3> for DeclareV3TransactionRequest {
642    type Error = ConversionError;
643
644    fn try_from(value: core::BroadcastedDeclareTransactionV3) -> Result<Self, Self::Error> {
645        Ok(Self {
646            contract_class: Arc::new(
647                contract::CompressedSierraClass::from_flattened(&value.contract_class)
648                    .map_err(|_| ConversionError)?,
649            ),
650            compiled_class_hash: value.compiled_class_hash,
651            sender_address: value.sender_address,
652            signature: value.signature,
653            nonce: value.nonce,
654            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
655            fee_data_availability_mode: value.fee_data_availability_mode.into(),
656            resource_bounds: value.resource_bounds.into(),
657            tip: value.tip,
658            paymaster_data: value.paymaster_data,
659            account_deployment_data: value.account_deployment_data,
660            is_query: value.is_query,
661        })
662    }
663}
664
665impl From<core::BroadcastedDeployAccountTransaction> for DeployAccountTransactionRequest {
666    fn from(value: core::BroadcastedDeployAccountTransaction) -> Self {
667        Self::V3(value.into())
668    }
669}
670
671impl From<core::BroadcastedDeployAccountTransactionV3> for DeployAccountV3TransactionRequest {
672    fn from(value: core::BroadcastedDeployAccountTransactionV3) -> Self {
673        Self {
674            class_hash: value.class_hash,
675            contract_address_salt: value.contract_address_salt,
676            constructor_calldata: value.constructor_calldata,
677            signature: value.signature,
678            nonce: value.nonce,
679            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
680            fee_data_availability_mode: value.fee_data_availability_mode.into(),
681            resource_bounds: value.resource_bounds.into(),
682            tip: value.tip,
683            paymaster_data: value.paymaster_data,
684            is_query: value.is_query,
685        }
686    }
687}
688
689impl From<core::CompressedLegacyContractClass> for CompressedLegacyContractClass {
690    fn from(value: core::CompressedLegacyContractClass) -> Self {
691        Self {
692            program: value.program,
693            entry_points_by_type: contract_legacy::RawLegacyEntryPoints {
694                constructor: value
695                    .entry_points_by_type
696                    .constructor
697                    .into_iter()
698                    .map(convert_legacy_entry_point)
699                    .collect(),
700                external: value
701                    .entry_points_by_type
702                    .external
703                    .into_iter()
704                    .map(convert_legacy_entry_point)
705                    .collect(),
706                l1_handler: value
707                    .entry_points_by_type
708                    .l1_handler
709                    .into_iter()
710                    .map(convert_legacy_entry_point)
711                    .collect(),
712            },
713            abi: value
714                .abi
715                .map(|abi| abi.into_iter().map(|item| item.into()).collect()),
716        }
717    }
718}
719
720impl TryFrom<DeployedClass> for core::ContractClass {
721    type Error = ConversionError;
722
723    fn try_from(value: DeployedClass) -> Result<Self, Self::Error> {
724        match value {
725            DeployedClass::SierraClass(inner) => Ok(Self::Sierra(inner)),
726            DeployedClass::LegacyClass(inner) => {
727                Ok(Self::Legacy(inner.compress().map_err(|_| ConversionError)?))
728            }
729        }
730    }
731}
732
733impl From<EntryPointType> for core::EntryPointType {
734    fn from(value: EntryPointType) -> Self {
735        match value {
736            EntryPointType::External => Self::External,
737            EntryPointType::L1Handler => Self::L1Handler,
738            EntryPointType::Constructor => Self::Constructor,
739        }
740    }
741}
742
743impl TryFrom<TransactionStatusInfo> for core::TransactionStatus {
744    type Error = ConversionError;
745
746    fn try_from(value: TransactionStatusInfo) -> Result<Self, Self::Error> {
747        if value.status.is_rejected() {
748            // Since Starknet v0.14.0 it's no longer possible to express rejected transactions.
749            return Err(ConversionError);
750        }
751
752        let exec_status = match value.execution_status.ok_or(ConversionError)? {
753            TransactionExecutionStatus::Succeeded => {
754                Some(core::TransactionExecutionStatus::Succeeded)
755            }
756            TransactionExecutionStatus::Reverted => {
757                Some(core::TransactionExecutionStatus::Reverted)
758            }
759            TransactionExecutionStatus::Rejected => None,
760        };
761
762        match value.finality_status {
763            Some(TransactionFinalityStatus::Received) => Ok(Self::Received),
764            Some(TransactionFinalityStatus::AcceptedOnL2) => {
765                let exec = match (
766                    exec_status.ok_or(ConversionError)?,
767                    value.transaction_failure_reason,
768                ) {
769                    (core::TransactionExecutionStatus::Succeeded, None) => {
770                        Ok(core::ExecutionResult::Succeeded)
771                    }
772                    (core::TransactionExecutionStatus::Reverted, Some(reason)) => {
773                        Ok(core::ExecutionResult::Reverted {
774                            reason: reason.error_message.unwrap_or(reason.code),
775                        })
776                    }
777                    _ => Err(ConversionError),
778                };
779
780                Ok(Self::AcceptedOnL2(exec?))
781            }
782            Some(TransactionFinalityStatus::AcceptedOnL1) => {
783                let exec = match (
784                    exec_status.ok_or(ConversionError)?,
785                    value.transaction_failure_reason,
786                ) {
787                    (core::TransactionExecutionStatus::Succeeded, None) => {
788                        Ok(core::ExecutionResult::Succeeded)
789                    }
790                    (core::TransactionExecutionStatus::Reverted, Some(reason)) => {
791                        Ok(core::ExecutionResult::Reverted {
792                            reason: reason.error_message.unwrap_or(reason.code),
793                        })
794                    }
795                    _ => Err(ConversionError),
796                };
797
798                Ok(Self::AcceptedOnL1(exec?))
799            }
800            // `NotReceived` must be handled on the caller before converting
801            _ => Err(ConversionError),
802        }
803    }
804}
805
806impl TryFrom<ConfirmedReceiptWithContext> for core::TransactionReceipt {
807    type Error = ConversionError;
808
809    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
810        match value.transaction {
811            TransactionType::Declare(_) => Ok(Self::Declare(value.try_into()?)),
812            TransactionType::Deploy(_) => Ok(Self::Deploy(value.try_into()?)),
813            TransactionType::DeployAccount(_) => Ok(Self::DeployAccount(value.try_into()?)),
814            TransactionType::InvokeFunction(_) => Ok(Self::Invoke(value.try_into()?)),
815            TransactionType::L1Handler(_) => Ok(Self::L1Handler(value.try_into()?)),
816        }
817    }
818}
819
820impl TryFrom<ConfirmedReceiptWithContext> for core::DeclareTransactionReceipt {
821    type Error = ConversionError;
822
823    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
824        Ok(Self {
825            transaction_hash: value.receipt.transaction_hash,
826            actual_fee: core::FeePayment {
827                amount: value.receipt.actual_fee,
828                unit: core::PriceUnit::Wei,
829            },
830            finality_status: value.finality.try_into()?,
831            messages_sent: value
832                .receipt
833                .l2_to_l1_messages
834                .into_iter()
835                .map(|item| item.into())
836                .collect(),
837            events: value
838                .receipt
839                .events
840                .into_iter()
841                .map(|item| item.into())
842                .collect(),
843            execution_resources: value
844                .receipt
845                .execution_resources
846                .ok_or(ConversionError)?
847                .total_gas_consumed
848                .ok_or(ConversionError)?,
849            execution_result: convert_execution_result(
850                value.receipt.execution_status,
851                value.receipt.revert_error,
852            )?,
853        })
854    }
855}
856
857impl TryFrom<ConfirmedReceiptWithContext> for core::DeployTransactionReceipt {
858    type Error = ConversionError;
859
860    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
861        Ok(Self {
862            transaction_hash: value.receipt.transaction_hash,
863            actual_fee: core::FeePayment {
864                amount: value.receipt.actual_fee,
865                unit: core::PriceUnit::Wei,
866            },
867            finality_status: value.finality.try_into()?,
868            messages_sent: value
869                .receipt
870                .l2_to_l1_messages
871                .into_iter()
872                .map(|item| item.into())
873                .collect(),
874            events: value
875                .receipt
876                .events
877                .into_iter()
878                .map(|item| item.into())
879                .collect(),
880            contract_address: match value.transaction {
881                TransactionType::Deploy(inner) => inner.contract_address,
882                _ => return Err(ConversionError),
883            },
884            execution_resources: value
885                .receipt
886                .execution_resources
887                .ok_or(ConversionError)?
888                .total_gas_consumed
889                .ok_or(ConversionError)?,
890            execution_result: convert_execution_result(
891                value.receipt.execution_status,
892                value.receipt.revert_error,
893            )?,
894        })
895    }
896}
897
898impl TryFrom<ConfirmedReceiptWithContext> for core::DeployAccountTransactionReceipt {
899    type Error = ConversionError;
900
901    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
902        Ok(Self {
903            transaction_hash: value.receipt.transaction_hash,
904            actual_fee: core::FeePayment {
905                amount: value.receipt.actual_fee,
906                unit: core::PriceUnit::Wei,
907            },
908            finality_status: value.finality.try_into()?,
909            messages_sent: value
910                .receipt
911                .l2_to_l1_messages
912                .into_iter()
913                .map(|item| item.into())
914                .collect(),
915            events: value
916                .receipt
917                .events
918                .into_iter()
919                .map(|item| item.into())
920                .collect(),
921            contract_address: match value.transaction {
922                TransactionType::DeployAccount(inner) => {
923                    inner.contract_address.ok_or(ConversionError)?
924                }
925                _ => return Err(ConversionError),
926            },
927            execution_resources: value
928                .receipt
929                .execution_resources
930                .ok_or(ConversionError)?
931                .total_gas_consumed
932                .ok_or(ConversionError)?,
933            execution_result: convert_execution_result(
934                value.receipt.execution_status,
935                value.receipt.revert_error,
936            )?,
937        })
938    }
939}
940
941impl TryFrom<ConfirmedReceiptWithContext> for core::InvokeTransactionReceipt {
942    type Error = ConversionError;
943
944    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
945        Ok(Self {
946            transaction_hash: value.receipt.transaction_hash,
947            actual_fee: core::FeePayment {
948                amount: value.receipt.actual_fee,
949                unit: core::PriceUnit::Wei,
950            },
951            finality_status: value.finality.try_into()?,
952            messages_sent: value
953                .receipt
954                .l2_to_l1_messages
955                .into_iter()
956                .map(|item| item.into())
957                .collect(),
958            events: value
959                .receipt
960                .events
961                .into_iter()
962                .map(|item| item.into())
963                .collect(),
964            execution_resources: value
965                .receipt
966                .execution_resources
967                .ok_or(ConversionError)?
968                .total_gas_consumed
969                .ok_or(ConversionError)?,
970            execution_result: convert_execution_result(
971                value.receipt.execution_status,
972                value.receipt.revert_error,
973            )?,
974        })
975    }
976}
977
978impl TryFrom<ConfirmedReceiptWithContext> for core::L1HandlerTransactionReceipt {
979    type Error = ConversionError;
980
981    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
982        // The sequencer never serves the message hash, so we have to compute it ourselves.
983        let l1_handler_tx: core::L1HandlerTransaction = match value.transaction {
984            TransactionType::L1Handler(tx) => tx.try_into().map_err(|_| ConversionError)?,
985            _ => return Err(ConversionError),
986        };
987        let msg_to_l2 = l1_handler_tx
988            .parse_msg_to_l2()
989            .map_err(|_| ConversionError)?;
990
991        Ok(Self {
992            transaction_hash: value.receipt.transaction_hash,
993            actual_fee: core::FeePayment {
994                amount: value.receipt.actual_fee,
995                unit: core::PriceUnit::Wei,
996            },
997            finality_status: value.finality.try_into()?,
998            messages_sent: value
999                .receipt
1000                .l2_to_l1_messages
1001                .into_iter()
1002                .map(|item| item.into())
1003                .collect(),
1004            events: value
1005                .receipt
1006                .events
1007                .into_iter()
1008                .map(|item| item.into())
1009                .collect(),
1010            execution_resources: value
1011                .receipt
1012                .execution_resources
1013                .ok_or(ConversionError)?
1014                .total_gas_consumed
1015                .ok_or(ConversionError)?,
1016            execution_result: convert_execution_result(
1017                value.receipt.execution_status,
1018                value.receipt.revert_error,
1019            )?,
1020            message_hash: msg_to_l2.hash(),
1021        })
1022    }
1023}
1024
1025impl TryFrom<BlockStatus> for core::TransactionFinalityStatus {
1026    type Error = ConversionError;
1027
1028    fn try_from(value: BlockStatus) -> Result<Self, Self::Error> {
1029        match value {
1030            // Transactions in pending blocks are considered "accepted on L2" now
1031            BlockStatus::Pending | BlockStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
1032            BlockStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
1033            BlockStatus::Aborted | BlockStatus::Reverted => Err(ConversionError),
1034        }
1035    }
1036}
1037
1038fn convert_execution_result(
1039    execution_status: Option<TransactionExecutionStatus>,
1040    revert_error: Option<String>,
1041) -> Result<core::ExecutionResult, ConversionError> {
1042    match (execution_status, revert_error) {
1043        (None, None) => {
1044            // This is a response from pre-v0.12.1. Pre-v0.12.1 transactions are always successful
1045            // as long as they're in a block.
1046            //
1047            // After feeder deprecation, the only way to fetch transaction receipts is by fetching
1048            // them as part of a block. Therefore, it's always the case that this tx in question is
1049            // in a block.
1050
1051            Ok(core::ExecutionResult::Succeeded)
1052        }
1053        (Some(TransactionExecutionStatus::Succeeded), None) => Ok(core::ExecutionResult::Succeeded),
1054        (Some(TransactionExecutionStatus::Reverted), Some(revert_error)) => {
1055            Ok(core::ExecutionResult::Reverted {
1056                reason: revert_error,
1057            })
1058        }
1059        // All other combinations are illegal
1060        _ => Err(ConversionError),
1061    }
1062}
1063
1064const fn convert_legacy_entry_point(
1065    value: core::LegacyContractEntryPoint,
1066) -> contract_legacy::RawLegacyEntryPoint {
1067    // WARNING: this causes pre-0.11.0 contract declaration to fail due to `offset` issue
1068    // TODO: support declaring pre-0.11.0 contracts here (do we even care though?)
1069    contract_legacy::RawLegacyEntryPoint {
1070        offset: contract_legacy::LegacyEntrypointOffset::U64AsInt(value.offset),
1071        selector: value.selector,
1072    }
1073}