pathfinder_common/
transaction.rs

1use fake::{Dummy, Fake, Faker};
2use pathfinder_crypto::hash::{HashChain as PedersenHasher, PoseidonHasher};
3use pathfinder_crypto::Felt;
4use primitive_types::H256;
5
6use crate::prelude::*;
7use crate::{
8    felt_bytes,
9    AccountDeploymentDataElem,
10    PaymasterDataElem,
11    ResourceAmount,
12    ResourcePricePerUnit,
13    Tip,
14};
15
16#[derive(Clone, Debug, Default, PartialEq, Eq, Dummy)]
17pub struct Transaction {
18    pub hash: TransactionHash,
19    pub variant: TransactionVariant,
20}
21
22impl Transaction {
23    /// Verifies the transaction hash against the transaction data.
24    #[must_use = "Should act on verification result"]
25    pub fn verify_hash(&self, chain_id: ChainId) -> bool {
26        self.variant.verify_hash(chain_id, self.hash)
27    }
28
29    pub fn version(&self) -> TransactionVersion {
30        match &self.variant {
31            TransactionVariant::DeclareV0(_) => TransactionVersion::ZERO,
32            TransactionVariant::DeclareV1(_) => TransactionVersion::ONE,
33            TransactionVariant::DeclareV2(_) => TransactionVersion::TWO,
34            TransactionVariant::DeclareV3(_) => TransactionVersion::THREE,
35            TransactionVariant::DeployV0(_) => TransactionVersion::ZERO,
36            TransactionVariant::DeployV1(_) => TransactionVersion::ONE,
37            TransactionVariant::DeployAccountV1(_) => TransactionVersion::ONE,
38            TransactionVariant::DeployAccountV3(_) => TransactionVersion::THREE,
39            TransactionVariant::InvokeV0(_) => TransactionVersion::ZERO,
40            TransactionVariant::InvokeV1(_) => TransactionVersion::ONE,
41            TransactionVariant::InvokeV3(_) => TransactionVersion::THREE,
42            TransactionVariant::L1Handler(_) => TransactionVersion::ZERO,
43        }
44    }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Dummy)]
48pub enum TransactionVariant {
49    DeclareV0(DeclareTransactionV0V1),
50    DeclareV1(DeclareTransactionV0V1),
51    DeclareV2(DeclareTransactionV2),
52    DeclareV3(DeclareTransactionV3),
53    DeployV0(DeployTransactionV0),
54    DeployV1(DeployTransactionV1),
55    DeployAccountV1(DeployAccountTransactionV1),
56    DeployAccountV3(DeployAccountTransactionV3),
57    InvokeV0(InvokeTransactionV0),
58    InvokeV1(InvokeTransactionV1),
59    InvokeV3(InvokeTransactionV3),
60    L1Handler(L1HandlerTransaction),
61}
62
63impl Default for TransactionVariant {
64    fn default() -> Self {
65        Self::DeclareV0(Default::default())
66    }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum TransactionKind {
71    Declare,
72    Deploy,
73    DeployAccount,
74    Invoke,
75    L1Handler,
76}
77
78impl TransactionVariant {
79    #[must_use = "Should act on verification result"]
80    fn verify_hash(&self, chain_id: ChainId, expected: TransactionHash) -> bool {
81        if expected == self.calculate_hash(chain_id, false) {
82            return true;
83        }
84
85        // Some transaction variants had a different hash calculation in ancient times.
86        if Some(expected) == self.calculate_legacy_hash(chain_id) {
87            return true;
88        }
89
90        // L1 Handlers had a specific hash calculation for Starknet v0.7 blocks.
91        if let Self::L1Handler(l1_handler) = self {
92            if expected == l1_handler.calculate_v07_hash(chain_id) {
93                return true;
94            }
95        }
96
97        false
98    }
99
100    pub fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
101        match self {
102            TransactionVariant::DeclareV0(tx) => tx.calculate_hash_v0(chain_id, query_only),
103            TransactionVariant::DeclareV1(tx) => tx.calculate_hash_v1(chain_id, query_only),
104            TransactionVariant::DeclareV2(tx) => tx.calculate_hash(chain_id, query_only),
105            TransactionVariant::DeclareV3(tx) => tx.calculate_hash(chain_id, query_only),
106            TransactionVariant::DeployV0(tx) => tx.calculate_hash(chain_id, query_only),
107            TransactionVariant::DeployV1(tx) => tx.calculate_hash(chain_id, query_only),
108            TransactionVariant::DeployAccountV1(tx) => tx.calculate_hash(chain_id, query_only),
109            TransactionVariant::DeployAccountV3(tx) => tx.calculate_hash(chain_id, query_only),
110            TransactionVariant::InvokeV0(tx) => tx.calculate_hash(chain_id, query_only),
111            TransactionVariant::InvokeV1(tx) => tx.calculate_hash(chain_id, query_only),
112            TransactionVariant::InvokeV3(tx) => tx.calculate_hash(chain_id, query_only),
113            TransactionVariant::L1Handler(tx) => tx.calculate_hash(chain_id),
114        }
115    }
116
117    pub fn kind(&self) -> TransactionKind {
118        match self {
119            TransactionVariant::DeclareV0(_) => TransactionKind::Declare,
120            TransactionVariant::DeclareV1(_) => TransactionKind::Declare,
121            TransactionVariant::DeclareV2(_) => TransactionKind::Declare,
122            TransactionVariant::DeclareV3(_) => TransactionKind::Declare,
123            TransactionVariant::DeployV0(_) => TransactionKind::Deploy,
124            TransactionVariant::DeployV1(_) => TransactionKind::Deploy,
125            TransactionVariant::DeployAccountV1(_) => TransactionKind::DeployAccount,
126            TransactionVariant::DeployAccountV3(_) => TransactionKind::DeployAccount,
127            TransactionVariant::InvokeV0(_) => TransactionKind::Invoke,
128            TransactionVariant::InvokeV1(_) => TransactionKind::Invoke,
129            TransactionVariant::InvokeV3(_) => TransactionKind::Invoke,
130            TransactionVariant::L1Handler(_) => TransactionKind::L1Handler,
131        }
132    }
133
134    /// Some variants had a different hash calculations for blocks around
135    /// Starknet v0.8 and earlier. The hash excluded the transaction version
136    /// and nonce.
137    fn calculate_legacy_hash(&self, chain_id: ChainId) -> Option<TransactionHash> {
138        let hash = match self {
139            TransactionVariant::DeployV0(tx) => tx.calculate_legacy_hash(chain_id),
140            TransactionVariant::DeployV1(tx) => tx.calculate_legacy_hash(chain_id),
141            TransactionVariant::InvokeV0(tx) => tx.calculate_legacy_hash(chain_id),
142            TransactionVariant::L1Handler(tx) => tx.calculate_legacy_hash(chain_id),
143            _ => return None,
144        };
145
146        Some(hash)
147    }
148
149    /// Compute contract address for deploy and deploy account transactions. Do
150    /// nothing in case of other transactions.
151    ///
152    /// ### Important
153    ///
154    /// This function should not be called in async context.
155    pub fn calculate_contract_address(&mut self) {
156        match self {
157            TransactionVariant::DeployV0(DeployTransactionV0 {
158                class_hash,
159                constructor_calldata,
160                contract_address,
161                contract_address_salt,
162            })
163            | TransactionVariant::DeployV1(DeployTransactionV1 {
164                class_hash,
165                constructor_calldata,
166                contract_address,
167                contract_address_salt,
168            }) => {
169                *contract_address = ContractAddress::deployed_contract_address(
170                    constructor_calldata.iter().map(|d| CallParam(d.0)),
171                    contract_address_salt,
172                    class_hash,
173                );
174            }
175            TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 {
176                class_hash,
177                constructor_calldata,
178                contract_address,
179                contract_address_salt,
180                ..
181            })
182            | TransactionVariant::DeployAccountV3(DeployAccountTransactionV3 {
183                class_hash,
184                constructor_calldata,
185                contract_address,
186                contract_address_salt,
187                ..
188            }) => {
189                *contract_address = ContractAddress::deployed_contract_address(
190                    constructor_calldata.iter().copied(),
191                    contract_address_salt,
192                    class_hash,
193                );
194            }
195            _ => {}
196        }
197    }
198
199    pub fn sender_address(&self) -> ContractAddress {
200        match self {
201            TransactionVariant::DeclareV0(tx) => tx.sender_address,
202            TransactionVariant::DeclareV1(tx) => tx.sender_address,
203            TransactionVariant::DeclareV2(tx) => tx.sender_address,
204            TransactionVariant::DeclareV3(tx) => tx.sender_address,
205            TransactionVariant::DeployV0(tx) => tx.contract_address,
206            TransactionVariant::DeployV1(tx) => tx.contract_address,
207            TransactionVariant::DeployAccountV1(tx) => tx.contract_address,
208            TransactionVariant::DeployAccountV3(tx) => tx.contract_address,
209            TransactionVariant::InvokeV0(tx) => tx.sender_address,
210            TransactionVariant::InvokeV1(tx) => tx.sender_address,
211            TransactionVariant::InvokeV3(tx) => tx.sender_address,
212            TransactionVariant::L1Handler(tx) => tx.contract_address,
213        }
214    }
215}
216
217impl From<DeclareTransactionV2> for TransactionVariant {
218    fn from(value: DeclareTransactionV2) -> Self {
219        Self::DeclareV2(value)
220    }
221}
222impl From<DeclareTransactionV3> for TransactionVariant {
223    fn from(value: DeclareTransactionV3) -> Self {
224        Self::DeclareV3(value)
225    }
226}
227impl From<DeployTransactionV0> for TransactionVariant {
228    fn from(value: DeployTransactionV0) -> Self {
229        Self::DeployV0(value)
230    }
231}
232impl From<DeployTransactionV1> for TransactionVariant {
233    fn from(value: DeployTransactionV1) -> Self {
234        Self::DeployV1(value)
235    }
236}
237impl From<DeployAccountTransactionV1> for TransactionVariant {
238    fn from(value: DeployAccountTransactionV1) -> Self {
239        Self::DeployAccountV1(value)
240    }
241}
242impl From<DeployAccountTransactionV3> for TransactionVariant {
243    fn from(value: DeployAccountTransactionV3) -> Self {
244        Self::DeployAccountV3(value)
245    }
246}
247impl From<InvokeTransactionV0> for TransactionVariant {
248    fn from(value: InvokeTransactionV0) -> Self {
249        Self::InvokeV0(value)
250    }
251}
252impl From<InvokeTransactionV1> for TransactionVariant {
253    fn from(value: InvokeTransactionV1) -> Self {
254        Self::InvokeV1(value)
255    }
256}
257impl From<InvokeTransactionV3> for TransactionVariant {
258    fn from(value: InvokeTransactionV3) -> Self {
259        Self::InvokeV3(value)
260    }
261}
262impl From<L1HandlerTransaction> for TransactionVariant {
263    fn from(value: L1HandlerTransaction) -> Self {
264        Self::L1Handler(value)
265    }
266}
267
268#[derive(Clone, Default, Debug, PartialEq, Eq)]
269pub struct DeclareTransactionV0V1 {
270    pub class_hash: ClassHash,
271    pub max_fee: Fee,
272    pub nonce: TransactionNonce,
273    pub signature: Vec<TransactionSignatureElem>,
274    pub sender_address: ContractAddress,
275}
276
277#[derive(Clone, Default, Debug, PartialEq, Eq, Dummy)]
278pub struct DeclareTransactionV2 {
279    pub class_hash: ClassHash,
280    pub max_fee: Fee,
281    pub nonce: TransactionNonce,
282    pub signature: Vec<TransactionSignatureElem>,
283    pub sender_address: ContractAddress,
284    pub compiled_class_hash: CasmHash,
285}
286
287#[derive(Clone, Default, Debug, PartialEq, Eq, Dummy)]
288pub struct DeclareTransactionV3 {
289    pub class_hash: ClassHash,
290    pub nonce: TransactionNonce,
291    pub nonce_data_availability_mode: DataAvailabilityMode,
292    pub fee_data_availability_mode: DataAvailabilityMode,
293    pub resource_bounds: ResourceBounds,
294    pub tip: Tip,
295    pub paymaster_data: Vec<PaymasterDataElem>,
296    pub signature: Vec<TransactionSignatureElem>,
297    pub account_deployment_data: Vec<AccountDeploymentDataElem>,
298    pub sender_address: ContractAddress,
299    pub compiled_class_hash: CasmHash,
300}
301
302#[derive(Clone, Default, Debug, PartialEq, Eq)]
303pub struct DeployTransactionV0 {
304    pub class_hash: ClassHash,
305    pub contract_address: ContractAddress,
306    pub contract_address_salt: ContractAddressSalt,
307    pub constructor_calldata: Vec<ConstructorParam>,
308}
309
310#[derive(Clone, Default, Debug, PartialEq, Eq)]
311pub struct DeployTransactionV1 {
312    pub class_hash: ClassHash,
313    pub contract_address: ContractAddress,
314    pub contract_address_salt: ContractAddressSalt,
315    pub constructor_calldata: Vec<ConstructorParam>,
316}
317
318#[derive(Clone, Default, Debug, PartialEq, Eq)]
319pub struct DeployAccountTransactionV1 {
320    pub contract_address: ContractAddress,
321    pub max_fee: Fee,
322    pub signature: Vec<TransactionSignatureElem>,
323    pub nonce: TransactionNonce,
324    pub contract_address_salt: ContractAddressSalt,
325    pub constructor_calldata: Vec<CallParam>,
326    pub class_hash: ClassHash,
327}
328
329#[derive(Clone, Default, Debug, PartialEq, Eq)]
330pub struct DeployAccountTransactionV3 {
331    pub contract_address: ContractAddress,
332    pub signature: Vec<TransactionSignatureElem>,
333    pub nonce: TransactionNonce,
334    pub nonce_data_availability_mode: DataAvailabilityMode,
335    pub fee_data_availability_mode: DataAvailabilityMode,
336    pub resource_bounds: ResourceBounds,
337    pub tip: Tip,
338    pub paymaster_data: Vec<PaymasterDataElem>,
339    pub contract_address_salt: ContractAddressSalt,
340    pub constructor_calldata: Vec<CallParam>,
341    pub class_hash: ClassHash,
342}
343
344#[derive(Clone, Default, Debug, PartialEq, Eq)]
345pub struct InvokeTransactionV0 {
346    pub calldata: Vec<CallParam>,
347    pub sender_address: ContractAddress,
348    pub entry_point_selector: EntryPoint,
349    pub entry_point_type: Option<EntryPointType>,
350    pub max_fee: Fee,
351    pub signature: Vec<TransactionSignatureElem>,
352}
353
354#[derive(Clone, Default, Debug, PartialEq, Eq, Dummy)]
355pub struct InvokeTransactionV1 {
356    pub calldata: Vec<CallParam>,
357    pub sender_address: ContractAddress,
358    pub max_fee: Fee,
359    pub signature: Vec<TransactionSignatureElem>,
360    pub nonce: TransactionNonce,
361}
362
363#[derive(Clone, Default, Debug, PartialEq, Eq, Dummy)]
364pub struct InvokeTransactionV3 {
365    pub signature: Vec<TransactionSignatureElem>,
366    pub nonce: TransactionNonce,
367    pub nonce_data_availability_mode: DataAvailabilityMode,
368    pub fee_data_availability_mode: DataAvailabilityMode,
369    pub resource_bounds: ResourceBounds,
370    pub tip: Tip,
371    pub paymaster_data: Vec<PaymasterDataElem>,
372    pub account_deployment_data: Vec<AccountDeploymentDataElem>,
373    pub calldata: Vec<CallParam>,
374    pub sender_address: ContractAddress,
375}
376
377#[derive(Clone, Default, Debug, PartialEq, Eq, Dummy)]
378pub struct L1HandlerTransaction {
379    pub contract_address: ContractAddress,
380    pub entry_point_selector: EntryPoint,
381    pub nonce: TransactionNonce,
382    pub calldata: Vec<CallParam>,
383}
384
385#[derive(Copy, Clone, Debug, PartialEq, Eq, Dummy)]
386pub enum EntryPointType {
387    External,
388    L1Handler,
389}
390
391#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Dummy)]
392pub struct ResourceBounds {
393    pub l1_gas: ResourceBound,
394    pub l2_gas: ResourceBound,
395    pub l1_data_gas: Option<ResourceBound>,
396}
397
398#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Dummy)]
399pub struct ResourceBound {
400    pub max_amount: ResourceAmount,
401    pub max_price_per_unit: ResourcePricePerUnit,
402}
403
404#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Dummy)]
405pub enum DataAvailabilityMode {
406    #[default]
407    L1,
408    L2,
409}
410
411impl From<DataAvailabilityMode> for u64 {
412    fn from(value: DataAvailabilityMode) -> Self {
413        match value {
414            DataAvailabilityMode::L1 => 0,
415            DataAvailabilityMode::L2 => 1,
416        }
417    }
418}
419
420impl DeclareTransactionV0V1 {
421    fn calculate_hash_v0(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
422        PreV3Hasher {
423            prefix: felt_bytes!(b"declare"),
424            version: TransactionVersion::ZERO.with_query_only(query_only),
425            address: self.sender_address,
426            data_hash: PedersenHasher::default().finalize(),
427            nonce_or_class: Some(self.class_hash.0),
428            ..Default::default()
429        }
430        .hash(chain_id)
431    }
432
433    fn calculate_hash_v1(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
434        PreV3Hasher {
435            prefix: felt_bytes!(b"declare"),
436            version: TransactionVersion::ONE.with_query_only(query_only),
437            address: self.sender_address,
438            data_hash: PedersenHasher::single(self.class_hash.0),
439            max_fee: self.max_fee,
440            nonce_or_class: Some(self.nonce.0),
441            ..Default::default()
442        }
443        .hash(chain_id)
444    }
445}
446
447impl DeclareTransactionV2 {
448    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
449        PreV3Hasher {
450            prefix: felt_bytes!(b"declare"),
451            version: TransactionVersion::TWO.with_query_only(query_only),
452            address: self.sender_address,
453            data_hash: PedersenHasher::single(self.class_hash.0),
454            max_fee: self.max_fee,
455            nonce_or_class: Some(self.nonce.0),
456            casm_hash: Some(self.compiled_class_hash),
457            ..Default::default()
458        }
459        .hash(chain_id)
460    }
461}
462
463impl DeployTransactionV0 {
464    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
465        PreV3Hasher {
466            prefix: felt_bytes!(b"deploy"),
467            version: TransactionVersion::ZERO.with_query_only(query_only),
468            address: self.contract_address,
469            entry_point: EntryPoint::CONSTRUCTOR,
470            data_hash: self.constructor_calldata_hash(),
471            ..Default::default()
472        }
473        .hash(chain_id)
474    }
475
476    fn calculate_legacy_hash(&self, chain_id: ChainId) -> TransactionHash {
477        LegacyHasher {
478            prefix: felt_bytes!(b"deploy"),
479            address: self.contract_address,
480            entry_point: EntryPoint::CONSTRUCTOR,
481            data_hash: self.constructor_calldata_hash(),
482            nonce: None,
483        }
484        .hash(chain_id)
485    }
486
487    fn constructor_calldata_hash(&self) -> Felt {
488        self.constructor_calldata
489            .iter()
490            .fold(PedersenHasher::default(), |hasher, data| {
491                hasher.chain_update(data.0)
492            })
493            .finalize()
494    }
495}
496
497impl DeployTransactionV1 {
498    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
499        PreV3Hasher {
500            prefix: felt_bytes!(b"deploy"),
501            version: TransactionVersion::ONE.with_query_only(query_only),
502            address: self.contract_address,
503            entry_point: EntryPoint::CONSTRUCTOR,
504            data_hash: self.constructor_calldata_hash(),
505            ..Default::default()
506        }
507        .hash(chain_id)
508    }
509
510    fn calculate_legacy_hash(&self, chain_id: ChainId) -> TransactionHash {
511        LegacyHasher {
512            prefix: felt_bytes!(b"deploy"),
513            address: self.contract_address,
514            entry_point: EntryPoint::CONSTRUCTOR,
515            data_hash: self.constructor_calldata_hash(),
516            nonce: None,
517        }
518        .hash(chain_id)
519    }
520
521    fn constructor_calldata_hash(&self) -> Felt {
522        self.constructor_calldata
523            .iter()
524            .fold(PedersenHasher::default(), |hasher, data| {
525                hasher.chain_update(data.0)
526            })
527            .finalize()
528    }
529}
530
531impl DeployAccountTransactionV1 {
532    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
533        let constructor_calldata_hash = std::iter::once(self.class_hash.0)
534            .chain(std::iter::once(self.contract_address_salt.0))
535            .chain(self.constructor_calldata.iter().map(|x| x.0))
536            .fold(PedersenHasher::default(), |hasher, data| {
537                hasher.chain_update(data)
538            })
539            .finalize();
540
541        PreV3Hasher {
542            prefix: felt_bytes!(b"deploy_account"),
543            version: TransactionVersion::ONE.with_query_only(query_only),
544            address: self.contract_address,
545            data_hash: constructor_calldata_hash,
546            max_fee: self.max_fee,
547            nonce_or_class: Some(self.nonce.0),
548            ..Default::default()
549        }
550        .hash(chain_id)
551    }
552}
553
554impl InvokeTransactionV0 {
555    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
556        PreV3Hasher {
557            prefix: felt_bytes!(b"invoke"),
558            version: TransactionVersion::ZERO.with_query_only(query_only),
559            address: self.sender_address,
560            entry_point: self.entry_point_selector,
561            data_hash: self.calldata_hash(),
562            max_fee: self.max_fee,
563            ..Default::default()
564        }
565        .hash(chain_id)
566    }
567
568    fn calculate_legacy_hash(&self, chain_id: ChainId) -> TransactionHash {
569        LegacyHasher {
570            prefix: felt_bytes!(b"invoke"),
571            address: self.sender_address,
572            entry_point: self.entry_point_selector,
573            data_hash: self.calldata_hash(),
574            nonce: None,
575        }
576        .hash(chain_id)
577    }
578
579    fn calldata_hash(&self) -> Felt {
580        self.calldata
581            .iter()
582            .fold(PedersenHasher::default(), |hasher, data| {
583                hasher.chain_update(data.0)
584            })
585            .finalize()
586    }
587}
588
589impl L1HandlerTransaction {
590    pub fn calculate_message_hash(&self) -> H256 {
591        use sha3::{Digest, Keccak256};
592
593        let Some((from_address, payload)) = self.calldata.split_first() else {
594            // This would indicate a pretty severe error in the L1 transaction.
595            // But since we haven't encoded this during serialization, this could in
596            // theory mess us up here.
597            //
598            // We should incorporate this into the deserialization instead. Returning an
599            // error here is unergonomic and far too late.
600            return H256::zero();
601        };
602
603        let mut hash = Keccak256::new();
604
605        // This is an ethereum address
606        hash.update(from_address.0.as_be_bytes());
607        hash.update(self.contract_address.0.as_be_bytes());
608        hash.update(self.nonce.0.as_be_bytes());
609        hash.update(self.entry_point_selector.0.as_be_bytes());
610
611        // Pad the u64 to 32 bytes to match a felt.
612        hash.update([0u8; 24]);
613        hash.update((payload.len() as u64).to_be_bytes());
614
615        for elem in payload {
616            hash.update(elem.0.as_be_bytes());
617        }
618
619        let hash = <[u8; 32]>::from(hash.finalize());
620
621        hash.into()
622    }
623
624    pub fn calculate_hash(&self, chain_id: ChainId) -> TransactionHash {
625        PreV3Hasher {
626            prefix: felt_bytes!(b"l1_handler"),
627            version: TransactionVersion::ZERO,
628            address: self.contract_address,
629            entry_point: self.entry_point_selector,
630            data_hash: self.calldata_hash(),
631            nonce_or_class: Some(self.nonce.0),
632            ..Default::default()
633        }
634        .hash(chain_id)
635    }
636
637    fn calculate_legacy_hash(&self, chain_id: ChainId) -> TransactionHash {
638        LegacyHasher {
639            // Old L1 handler's were actually invokes under the hood.
640            prefix: felt_bytes!(b"invoke"),
641            address: self.contract_address,
642            entry_point: self.entry_point_selector,
643            data_hash: self.calldata_hash(),
644            nonce: None,
645        }
646        .hash(chain_id)
647    }
648
649    // L1 handlers had a slightly different hash for Starknet v0.7.
650    fn calculate_v07_hash(&self, chain_id: ChainId) -> TransactionHash {
651        LegacyHasher {
652            prefix: felt_bytes!(b"l1_handler"),
653            address: self.contract_address,
654            entry_point: self.entry_point_selector,
655            data_hash: self.calldata_hash(),
656            nonce: Some(self.nonce),
657        }
658        .hash(chain_id)
659    }
660
661    fn calldata_hash(&self) -> Felt {
662        self.calldata
663            .iter()
664            .fold(PedersenHasher::default(), |hasher, data| {
665                hasher.chain_update(data.0)
666            })
667            .finalize()
668    }
669}
670
671impl DeclareTransactionV3 {
672    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
673        let deployment_hash = self
674            .account_deployment_data
675            .iter()
676            .fold(PoseidonHasher::default(), |hasher, data| {
677                hasher.chain(data.0.into())
678            })
679            .finish()
680            .into();
681
682        V3Hasher {
683            prefix: felt_bytes!(b"declare"),
684            sender_address: self.sender_address,
685            nonce: self.nonce,
686            data_hashes: &[
687                deployment_hash,
688                self.class_hash.0,
689                self.compiled_class_hash.0,
690            ],
691            tip: self.tip,
692            paymaster_data: &self.paymaster_data,
693            nonce_data_availability_mode: self.nonce_data_availability_mode,
694            fee_data_availability_mode: self.fee_data_availability_mode,
695            resource_bounds: self.resource_bounds,
696            query_only,
697        }
698        .hash(chain_id)
699    }
700}
701
702impl DeployAccountTransactionV3 {
703    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
704        let deployment_hash = self
705            .constructor_calldata
706            .iter()
707            .fold(PoseidonHasher::default(), |hasher, data| {
708                hasher.chain(data.0.into())
709            })
710            .finish()
711            .into();
712
713        V3Hasher {
714            prefix: felt_bytes!(b"deploy_account"),
715            sender_address: self.contract_address,
716            nonce: self.nonce,
717            data_hashes: &[
718                deployment_hash,
719                self.class_hash.0,
720                self.contract_address_salt.0,
721            ],
722            tip: self.tip,
723            paymaster_data: &self.paymaster_data,
724            nonce_data_availability_mode: self.nonce_data_availability_mode,
725            fee_data_availability_mode: self.fee_data_availability_mode,
726            resource_bounds: self.resource_bounds,
727            query_only,
728        }
729        .hash(chain_id)
730    }
731}
732
733impl InvokeTransactionV3 {
734    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
735        let deployment_hash = self
736            .account_deployment_data
737            .iter()
738            .fold(PoseidonHasher::default(), |hasher, data| {
739                hasher.chain(data.0.into())
740            })
741            .finish()
742            .into();
743        let calldata_hash = self
744            .calldata
745            .iter()
746            .fold(PoseidonHasher::default(), |hasher, data| {
747                hasher.chain(data.0.into())
748            })
749            .finish()
750            .into();
751
752        V3Hasher {
753            prefix: felt_bytes!(b"invoke"),
754            sender_address: self.sender_address,
755            nonce: self.nonce,
756            data_hashes: &[deployment_hash, calldata_hash],
757            tip: self.tip,
758            paymaster_data: &self.paymaster_data,
759            nonce_data_availability_mode: self.nonce_data_availability_mode,
760            fee_data_availability_mode: self.fee_data_availability_mode,
761            resource_bounds: self.resource_bounds,
762            query_only,
763        }
764        .hash(chain_id)
765    }
766}
767
768impl InvokeTransactionV1 {
769    fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash {
770        let list_hash = self
771            .calldata
772            .iter()
773            .fold(PedersenHasher::default(), |hasher, data| {
774                hasher.chain_update(data.0)
775            })
776            .finalize();
777
778        PreV3Hasher {
779            prefix: felt_bytes!(b"invoke"),
780            version: TransactionVersion::ONE.with_query_only(query_only),
781            address: self.sender_address,
782            data_hash: list_hash,
783            max_fee: self.max_fee,
784            nonce_or_class: Some(self.nonce.0),
785            ..Default::default()
786        }
787        .hash(chain_id)
788    }
789}
790
791#[derive(Default)]
792struct LegacyHasher {
793    pub prefix: Felt,
794    pub address: ContractAddress,
795    pub entry_point: EntryPoint,
796    pub data_hash: Felt,
797    pub nonce: Option<TransactionNonce>,
798}
799
800impl LegacyHasher {
801    fn hash(self, chain_id: ChainId) -> TransactionHash {
802        let mut hasher = PedersenHasher::default()
803            .chain_update(self.prefix)
804            .chain_update(*self.address.get())
805            .chain_update(self.entry_point.0)
806            .chain_update(self.data_hash)
807            .chain_update(chain_id.0);
808
809        if let Some(nonce) = self.nonce {
810            hasher.update(nonce.0);
811        }
812
813        TransactionHash(hasher.finalize())
814    }
815}
816
817#[derive(Default)]
818struct PreV3Hasher {
819    pub prefix: Felt,
820    pub version: TransactionVersion,
821    pub address: ContractAddress,
822    pub entry_point: EntryPoint,
823    pub data_hash: Felt,
824    pub max_fee: Fee,
825    pub nonce_or_class: Option<Felt>,
826    pub casm_hash: Option<CasmHash>,
827}
828
829impl PreV3Hasher {
830    fn hash(self, chain_id: ChainId) -> TransactionHash {
831        let mut hash = PedersenHasher::default()
832            .chain_update(self.prefix)
833            .chain_update(self.version.0)
834            .chain_update(self.address.0)
835            .chain_update(self.entry_point.0)
836            .chain_update(self.data_hash)
837            .chain_update(self.max_fee.0)
838            .chain_update(chain_id.0);
839
840        if let Some(felt) = self.nonce_or_class {
841            hash.update(felt);
842        }
843
844        if let Some(felt) = self.casm_hash {
845            hash.update(felt.0);
846        }
847
848        TransactionHash(hash.finalize())
849    }
850}
851
852/// Provides hashing for V3 transactions.
853struct V3Hasher<'a> {
854    pub prefix: Felt,
855    pub sender_address: ContractAddress,
856    pub nonce: TransactionNonce,
857    pub data_hashes: &'a [Felt],
858    pub tip: Tip,
859    pub paymaster_data: &'a [PaymasterDataElem],
860    pub nonce_data_availability_mode: DataAvailabilityMode,
861    pub fee_data_availability_mode: DataAvailabilityMode,
862    pub resource_bounds: ResourceBounds,
863    pub query_only: bool,
864}
865
866impl V3Hasher<'_> {
867    fn hash(self, chain_id: ChainId) -> TransactionHash {
868        let hasher = PoseidonHasher::default()
869            .chain(self.prefix.into())
870            .chain(
871                TransactionVersion::THREE
872                    .with_query_only(self.query_only)
873                    .0
874                    .into(),
875            )
876            .chain(self.sender_address.0.into())
877            .chain(self.hash_fee_fields().into())
878            .chain(self.hash_paymaster_data().into())
879            .chain(chain_id.0.into())
880            .chain(self.nonce.0.into())
881            .chain(self.pack_data_availability().into());
882
883        let hash = self
884            .data_hashes
885            .iter()
886            .fold(hasher, |hasher, &data| hasher.chain(data.into()))
887            .finish();
888
889        TransactionHash(hash.into())
890    }
891
892    fn pack_data_availability(&self) -> u64 {
893        let nonce = u64::from(self.nonce_data_availability_mode) << 32;
894        let fee = u64::from(self.fee_data_availability_mode);
895
896        nonce + fee
897    }
898
899    fn hash_paymaster_data(&self) -> Felt {
900        self.paymaster_data
901            .iter()
902            .fold(PoseidonHasher::default(), |hasher, data| {
903                hasher.chain(data.0.into())
904            })
905            .finish()
906            .into()
907    }
908
909    fn hash_fee_fields(&self) -> Felt {
910        let mut hasher = PoseidonHasher::default()
911            .chain(self.tip.0.into())
912            .chain(Self::pack_gas_bound(b"L1_GAS", &self.resource_bounds.l1_gas).into())
913            .chain(Self::pack_gas_bound(b"L2_GAS", &self.resource_bounds.l2_gas).into());
914
915        if let Some(l1_data_gas) = self.resource_bounds.l1_data_gas {
916            hasher = hasher.chain(Self::pack_gas_bound(b"L1_DATA", &l1_data_gas).into());
917        }
918
919        hasher.finish().into()
920    }
921
922    fn pack_gas_bound(name: &[u8], bound: &ResourceBound) -> Felt {
923        let mut buffer: [u8; 32] = Default::default();
924        let (remainder, max_price) = buffer.split_at_mut(128 / 8);
925        let (gas_kind, max_amount) = remainder.split_at_mut(64 / 8);
926
927        let padding = gas_kind.len() - name.len();
928        gas_kind[padding..].copy_from_slice(name);
929        max_amount.copy_from_slice(&bound.max_amount.0.to_be_bytes());
930        max_price.copy_from_slice(&bound.max_price_per_unit.0.to_be_bytes());
931
932        Felt::from_be_bytes(buffer).expect("Packed resource should fit into felt")
933    }
934}
935
936impl<T> Dummy<T> for DeclareTransactionV0V1 {
937    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
938        Self {
939            class_hash: Faker.fake_with_rng(rng),
940            max_fee: Faker.fake_with_rng(rng),
941            // This is to keep DeclareV0 p2p compliant
942            nonce: TransactionNonce::ZERO,
943            signature: Faker.fake_with_rng(rng),
944            sender_address: Faker.fake_with_rng(rng),
945        }
946    }
947}
948
949impl<T> Dummy<T> for DeployTransactionV0 {
950    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
951        let class_hash = Faker.fake_with_rng(rng);
952        let contract_address_salt = Faker.fake_with_rng(rng);
953        let constructor_calldata: Vec<ConstructorParam> = Faker.fake_with_rng(rng);
954        let contract_address = ContractAddress::deployed_contract_address(
955            constructor_calldata.iter().map(|d| CallParam(d.0)),
956            &contract_address_salt,
957            &class_hash,
958        );
959
960        Self {
961            class_hash,
962            contract_address,
963            contract_address_salt,
964            constructor_calldata,
965        }
966    }
967}
968
969impl<T> Dummy<T> for DeployTransactionV1 {
970    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
971        let class_hash = Faker.fake_with_rng(rng);
972        let contract_address_salt = Faker.fake_with_rng(rng);
973        let constructor_calldata: Vec<ConstructorParam> = Faker.fake_with_rng(rng);
974        let contract_address = ContractAddress::deployed_contract_address(
975            constructor_calldata.iter().map(|d| CallParam(d.0)),
976            &contract_address_salt,
977            &class_hash,
978        );
979
980        Self {
981            class_hash,
982            contract_address,
983            contract_address_salt,
984            constructor_calldata,
985        }
986    }
987}
988
989impl<T> Dummy<T> for DeployAccountTransactionV1 {
990    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
991        let class_hash = Faker.fake_with_rng(rng);
992        let contract_address_salt = Faker.fake_with_rng(rng);
993        let constructor_calldata: Vec<CallParam> = Faker.fake_with_rng(rng);
994        let contract_address = ContractAddress::deployed_contract_address(
995            constructor_calldata.iter().map(|d| CallParam(d.0)),
996            &contract_address_salt,
997            &class_hash,
998        );
999
1000        Self {
1001            contract_address,
1002            max_fee: Faker.fake_with_rng(rng),
1003            signature: Faker.fake_with_rng(rng),
1004            nonce: Faker.fake_with_rng(rng),
1005            contract_address_salt,
1006            constructor_calldata,
1007            class_hash,
1008        }
1009    }
1010}
1011
1012impl<T> Dummy<T> for DeployAccountTransactionV3 {
1013    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
1014        let class_hash = Faker.fake_with_rng(rng);
1015        let contract_address_salt = Faker.fake_with_rng(rng);
1016        let constructor_calldata: Vec<CallParam> = Faker.fake_with_rng(rng);
1017        let contract_address = ContractAddress::deployed_contract_address(
1018            constructor_calldata.iter().map(|d| CallParam(d.0)),
1019            &contract_address_salt,
1020            &class_hash,
1021        );
1022        Self {
1023            contract_address,
1024            signature: Faker.fake_with_rng(rng),
1025            nonce: Faker.fake_with_rng(rng),
1026            nonce_data_availability_mode: Faker.fake_with_rng(rng),
1027            fee_data_availability_mode: Faker.fake_with_rng(rng),
1028            resource_bounds: Faker.fake_with_rng(rng),
1029            tip: Faker.fake_with_rng(rng),
1030            paymaster_data: Faker.fake_with_rng(rng),
1031            contract_address_salt,
1032            constructor_calldata,
1033            class_hash,
1034        }
1035    }
1036}
1037
1038impl<T> Dummy<T> for InvokeTransactionV0 {
1039    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
1040        Self {
1041            calldata: Faker.fake_with_rng(rng),
1042            sender_address: Faker.fake_with_rng(rng),
1043            entry_point_selector: Faker.fake_with_rng(rng),
1044            // This is a legacy field, not used in p2p
1045            entry_point_type: None,
1046            max_fee: Faker.fake_with_rng(rng),
1047            signature: Faker.fake_with_rng(rng),
1048        }
1049    }
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::*;
1055    use crate::macro_prelude::*;
1056
1057    // Goerli support was removed, however some of the fixtures originally come from
1058    // Goerli.
1059    const GOERLI_TESTNET: ChainId = ChainId(match Felt::from_be_slice(b"SN_GOERLI") {
1060        Ok(chain_id) => chain_id,
1061        Err(_) => unreachable!(),
1062    });
1063
1064    #[rstest::rstest]
1065    #[test]
1066    #[case::declare_v0(declare_v0(), GOERLI_TESTNET)]
1067    #[case::declare_v1(declare_v1(), ChainId::SEPOLIA_TESTNET)]
1068    #[case::declare_v2(declare_v2(), ChainId::SEPOLIA_TESTNET)]
1069    #[case::declare_v3(declare_v3(), GOERLI_TESTNET)]
1070    #[case::deploy(deploy(), GOERLI_TESTNET)]
1071    #[case::deploy_legacy(deploy_legacy(), GOERLI_TESTNET)]
1072    #[case::deploy_account_v1(deploy_account_v1(), ChainId::MAINNET)]
1073    #[case::deploy_account_v3(deploy_account_v3(), GOERLI_TESTNET)]
1074    #[case::invoke_v0(invoke_v0(), GOERLI_TESTNET)]
1075    #[case::invoke_v0_legacy(invoke_v0_legacy(), GOERLI_TESTNET)]
1076    #[case::invoke_v1(invoke_v1(), ChainId::MAINNET)]
1077    #[case::invoke_v3(invoke_v3(), ChainId::SEPOLIA_TESTNET)]
1078    #[case::l1_handler(l1_handler(), ChainId::MAINNET)]
1079    #[case::l1_handler_v07(l1_handler_v07(), ChainId::MAINNET)]
1080    #[case::l1_handler_legacy(l1_handler_legacy(), GOERLI_TESTNET)]
1081    fn verify_hash(#[case] transaction: Transaction, #[case] chain_id: ChainId) {
1082        assert!(transaction.verify_hash(chain_id));
1083    }
1084
1085    fn declare_v0() -> Transaction {
1086        Transaction {
1087            hash: transaction_hash!(
1088                "0x6d346ba207eb124355960c19c737698ad37a3c920a588b741e0130ff5bd4d6d"
1089            ),
1090            variant: TransactionVariant::DeclareV0(DeclareTransactionV0V1 {
1091                class_hash: class_hash!(
1092                    "0x71e6ef53e53e6f5ca792fc4a5799a33e6f4118e4fd1d948dca3a371506f0cc7"
1093                ),
1094                sender_address: contract_address!("0x1"),
1095                ..Default::default()
1096            }),
1097        }
1098    }
1099
1100    fn declare_v1() -> Transaction {
1101        Transaction {
1102            hash: transaction_hash!(
1103                "0xb2d88f64d9655a7d47a5519d66b969168d02d0d33f6476f0d2539c51686329"
1104            ),
1105            variant: TransactionVariant::DeclareV1(DeclareTransactionV0V1 {
1106                class_hash: class_hash!(
1107                    "0x3131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e"
1108                ),
1109                max_fee: fee!("0x625e5879c08f4"),
1110                nonce: transaction_nonce!("0x7"),
1111                signature: vec![
1112                    transaction_signature_elem!(
1113                        "0x3609667964a8ed946bc507721ec35a851d97a097d159ef0ec2af8fab490223f"
1114                    ),
1115                    transaction_signature_elem!(
1116                        "0x68846bad9f0f010fac4eeaf39f9dd609b28765fd2336b70ce026e33e2421c15"
1117                    ),
1118                ],
1119                sender_address: contract_address!(
1120                    "0x68922eb87daed71fc3099031e178b6534fc39a570022342e8c166024da893f5"
1121                ),
1122            }),
1123        }
1124    }
1125
1126    fn declare_v2() -> Transaction {
1127        Transaction {
1128            hash: transaction_hash!(
1129                "0x4cacc2bbdd5ec77b20e908f311ab27d6495b69761e929bb24ba02632716944"
1130            ),
1131            variant: TransactionVariant::DeclareV2(DeclareTransactionV2 {
1132                class_hash: class_hash!(
1133                    "0x1a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003"
1134                ),
1135                max_fee: fee!("0x92fa1ac712614"),
1136                nonce: transaction_nonce!("0x6"),
1137                signature: vec![
1138                    transaction_signature_elem!(
1139                        "0x4ab3e77908396c66b39326f52334b447fe878d1d899a287c9e3cf7bd09839ea"
1140                    ),
1141                    transaction_signature_elem!(
1142                        "0x79a56f9e61eb834f1ac524eb35da33cccf92ff3b01a7a8eaf68cbb64bebdba9"
1143                    ),
1144                ],
1145                sender_address: contract_address!(
1146                    "0x68922eb87daed71fc3099031e178b6534fc39a570022342e8c166024da893f5"
1147                ),
1148                compiled_class_hash: casm_hash!(
1149                    "0x29787a427a423ffc5986d43e630077a176e4391fcef3ebf36014b154069ae4"
1150                ),
1151            }),
1152        }
1153    }
1154
1155    fn declare_v3() -> Transaction {
1156        Transaction {
1157            hash: transaction_hash!(
1158                "0x41d1f5206ef58a443e7d3d1ca073171ec25fa75313394318fc83a074a6631c3"
1159            ),
1160            variant: TransactionVariant::DeclareV3(DeclareTransactionV3 {
1161                signature: vec![
1162                    transaction_signature_elem!(
1163                        "0x29a49dff154fede73dd7b5ca5a0beadf40b4b069f3a850cd8428e54dc809ccc"
1164                    ),
1165                    transaction_signature_elem!(
1166                        "0x429d142a17223b4f2acde0f5ecb9ad453e188b245003c86fab5c109bad58fc3"
1167                    ),
1168                ],
1169                nonce: transaction_nonce!("0x1"),
1170                nonce_data_availability_mode: DataAvailabilityMode::L1,
1171                fee_data_availability_mode: DataAvailabilityMode::L1,
1172                resource_bounds: ResourceBounds {
1173                    l1_gas: ResourceBound {
1174                        max_amount: ResourceAmount(0x186a0),
1175                        max_price_per_unit: ResourcePricePerUnit(0x2540be400),
1176                    },
1177                    l2_gas: Default::default(),
1178                    l1_data_gas: Default::default(),
1179                },
1180                sender_address: contract_address!(
1181                    "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
1182                ),
1183                class_hash: class_hash!(
1184                    "0x5ae9d09292a50ed48c5930904c880dab56e85b825022a7d689cfc9e65e01ee7"
1185                ),
1186                compiled_class_hash: casm_hash!(
1187                    "0x1add56d64bebf8140f3b8a38bdf102b7874437f0c861ab4ca7526ec33b4d0f8"
1188                ),
1189                ..Default::default()
1190            }),
1191        }
1192    }
1193
1194    fn deploy() -> Transaction {
1195        Transaction {
1196            hash: transaction_hash!(
1197                "0x3d7623443283d9a0cec946492db78b06d57642a551745ddfac8d3f1f4fcc2a8"
1198            ),
1199            variant: TransactionVariant::DeployV0(DeployTransactionV0 {
1200                contract_address: contract_address!(
1201                    "0x54c6883e459baeac4a9052ee109b86b9f81adbcdcb1f65a05dceec4c34d5cf9"
1202                ),
1203                contract_address_salt: contract_address_salt!(
1204                    "0x655a594122f68f5e821834e606e1243b249a88555fac2d548f7acbee7863f62"
1205                ),
1206                constructor_calldata: vec![
1207                    constructor_param!(
1208                        "0x734d2849eb47e10c59e5a433d425675849cb37338b1d7c4c4afb1e0ca42133"
1209                    ),
1210                    constructor_param!(
1211                        "0xffad0128dbd859ef97a246a2d2c00680dedc8d850ff9b6ebcc8b94ee9625bb"
1212                    ),
1213                ],
1214                class_hash: class_hash!(
1215                    "0x3523d31a077d891b4d888f9d3c7d33bdac2c0a06f89c08307a7f7b68f681c98"
1216                ),
1217            }),
1218        }
1219    }
1220
1221    fn deploy_legacy() -> Transaction {
1222        Transaction {
1223            hash: transaction_hash!(
1224                "0x45c61314be4da85f0e13df53d18062e002c04803218f08061e4b274d4b38537"
1225            ),
1226            variant: TransactionVariant::DeployV0(DeployTransactionV0 {
1227                contract_address: contract_address!(
1228                    "0x2f40faa63fdd5871415b2dcfb1a5e3e1ca06435b3dda6e2ba9df3f726fd3251"
1229                ),
1230                contract_address_salt: contract_address_salt!(
1231                    "0x7284a0367fdd636434f76da25532785690d5f27db40ba38b0cfcbc89a472507"
1232                ),
1233                constructor_calldata: vec![
1234                    constructor_param!(
1235                        "0x635b73abaa9efff71570cb08f3e5014424788470c3b972b952368fb3fc27cc3"
1236                    ),
1237                    constructor_param!(
1238                        "0x7e92479a573a24241ee6f3e4ade742ff37bae4a60bacef5be1caaff5e7e04f3"
1239                    ),
1240                ],
1241                class_hash: class_hash!(
1242                    "0x10455c752b86932ce552f2b0fe81a880746649b9aee7e0d842bf3f52378f9f8"
1243                ),
1244            }),
1245        }
1246    }
1247
1248    fn deploy_account_v1() -> Transaction {
1249        Transaction {
1250            hash: transaction_hash!(
1251                "0x63b72dba5a1b5cdd2585b0c7103242244860453f7013023c1a21f32e1863ec"
1252            ),
1253            variant: TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 {
1254                contract_address: contract_address!(
1255                    "0x3faed8332496d9de9c546e7942b35ba3ea323a6af72d6033f746ea60ecc02ef"
1256                ),
1257                max_fee: fee!("0xb48040809d4b"),
1258                signature: vec![
1259                    transaction_signature_elem!(
1260                        "0x463d21c552a810c59be86c336c0cc68f28e3815eafbe1a2eaf9b3a6fe1c2b82"
1261                    ),
1262                    transaction_signature_elem!(
1263                        "0x2932cb2583da5d8d08f6f0179cc3d4aaae2b46123f02f00bfd544105671adfd"
1264                    ),
1265                ],
1266                nonce: transaction_nonce!("0x0"),
1267                contract_address_salt: contract_address_salt!(
1268                    "0x771b3077f205e2d77c06c9a3bd49d730a4fd8453941d031009fa40936912030"
1269                ),
1270                constructor_calldata: vec![
1271                    call_param!(
1272                        "0x771b3077f205e2d77c06c9a3bd49d730a4fd8453941d031009fa40936912030"
1273                    ),
1274                    call_param!("0x0"),
1275                ],
1276                class_hash: class_hash!(
1277                    "0x1a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003"
1278                ),
1279            }),
1280        }
1281    }
1282
1283    fn deploy_account_v3() -> Transaction {
1284        Transaction {
1285            hash: transaction_hash!(
1286                "0x29fd7881f14380842414cdfdd8d6c0b1f2174f8916edcfeb1ede1eb26ac3ef0"
1287            ),
1288            variant: TransactionVariant::DeployAccountV3(DeployAccountTransactionV3 {
1289                contract_address: contract_address!(
1290                    "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
1291                ),
1292                resource_bounds: ResourceBounds {
1293                    l1_gas: ResourceBound {
1294                        max_amount: ResourceAmount(0x186a0),
1295                        max_price_per_unit: ResourcePricePerUnit(0x5af3107a4000),
1296                    },
1297                    l2_gas: Default::default(),
1298                    l1_data_gas: Default::default(),
1299                },
1300                constructor_calldata: vec![call_param!(
1301                    "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c"
1302                )],
1303                class_hash: class_hash!(
1304                    "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"
1305                ),
1306                signature: vec![
1307                    transaction_signature_elem!(
1308                        "0x6d756e754793d828c6c1a89c13f7ec70dbd8837dfeea5028a673b80e0d6b4ec"
1309                    ),
1310                    transaction_signature_elem!(
1311                        "0x4daebba599f860daee8f6e100601d98873052e1c61530c630cc4375c6bd48e3"
1312                    ),
1313                ],
1314                ..Default::default()
1315            }),
1316        }
1317    }
1318
1319    fn invoke_v0() -> Transaction {
1320        Transaction {
1321            hash: transaction_hash!(
1322                "0x587d93f2339b7f2beda040187dbfcb9e076ce4a21eb8d15ae64819718817fbe"
1323            ),
1324            variant: TransactionVariant::InvokeV0(InvokeTransactionV0 {
1325                sender_address: contract_address!(
1326                    "0x7463cdd01f6e6a4f13084ea9eee170298b0bbe3faa17f46924c85bb284d4c98"
1327                ),
1328                max_fee: fee!("0x1ee7b2b881350"),
1329                signature: vec![
1330                    transaction_signature_elem!(
1331                        "0x6e82c6752bd13e29b68cf0c8b0d4eb9133b5a056336a842bff01756e514d04a"
1332                    ),
1333                    transaction_signature_elem!(
1334                        "0xa87f00c9e39fd0711aaea4edae0f00044384188a87f489170ac383e3ad087f"
1335                    ),
1336                ],
1337                calldata: vec![
1338                    call_param!("0x3"),
1339                    call_param!(
1340                        "0x72df4dc5b6c4df72e4288857317caf2ce9da166ab8719ab8306516a2fddfff7"
1341                    ),
1342                    call_param!(
1343                        "0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c"
1344                    ),
1345                    call_param!("0x0"),
1346                    call_param!("0x3"),
1347                    call_param!(
1348                        "0x7394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10"
1349                    ),
1350                    call_param!(
1351                        "0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c"
1352                    ),
1353                    call_param!("0x3"),
1354                    call_param!("0x3"),
1355                    call_param!(
1356                        "0x4aec73f0611a9be0524e7ef21ab1679bdf9c97dc7d72614f15373d431226b6a"
1357                    ),
1358                    call_param!(
1359                        "0x3f35dbce7a07ce455b128890d383c554afbc1b07cf7390a13e2d602a38c1a0a"
1360                    ),
1361                    call_param!("0x6"),
1362                    call_param!("0xa"),
1363                    call_param!("0x10"),
1364                    call_param!(
1365                        "0x4aec73f0611a9be0524e7ef21ab1679bdf9c97dc7d72614f15373d431226b6a"
1366                    ),
1367                    call_param!("0x14934a76f"),
1368                    call_param!("0x0"),
1369                    call_param!(
1370                        "0x4aec73f0611a9be0524e7ef21ab1679bdf9c97dc7d72614f15373d431226b6a"
1371                    ),
1372                    call_param!("0x2613cd2f52b54fb440"),
1373                    call_param!("0x0"),
1374                    call_param!(
1375                        "0x72df4dc5b6c4df72e4288857317caf2ce9da166ab8719ab8306516a2fddfff7"
1376                    ),
1377                    call_param!(
1378                        "0x7394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10"
1379                    ),
1380                    call_param!("0x14934a76f"),
1381                    call_param!("0x0"),
1382                    call_param!("0x2613cd2f52b54fb440"),
1383                    call_param!("0x0"),
1384                    call_param!("0x135740b18"),
1385                    call_param!("0x0"),
1386                    call_param!("0x23caeef429e7df66e0"),
1387                    call_param!("0x0"),
1388                    call_param!("0x17"),
1389                ],
1390                entry_point_selector: entry_point!(
1391                    "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad"
1392                ),
1393                ..Default::default()
1394            }),
1395        }
1396    }
1397
1398    fn invoke_v0_legacy() -> Transaction {
1399        Transaction {
1400            hash: transaction_hash!(
1401                "0x493d8fab73af67e972788e603aee18130facd3c7685f16084ecd98b07153e24"
1402            ),
1403            variant: TransactionVariant::InvokeV0(InvokeTransactionV0 {
1404                sender_address: contract_address!(
1405                    "0x639322e9822149638b70f6de65bc18f3563bd6fa16f0106e8162618eb72f7e"
1406                ),
1407                calldata: vec![
1408                    call_param!(
1409                        "0x49e2e40a0b61a4d6fe4c85cbbf61b5ba372427c852f88509350c4b1eeb88426"
1410                    ),
1411                    call_param!("0x2"),
1412                    call_param!(
1413                        "0x1576521d9ed09609f55b86740de4ae6abdb2837d5d960ae71083ccd39c715d2"
1414                    ),
1415                    call_param!(
1416                        "0x6897cf3003dc45dd016a34ee4309fc97f3bd471513553e64bc070b4eedf4eae"
1417                    ),
1418                ],
1419                entry_point_selector: entry_point!(
1420                    "0x317eb442b72a9fae758d4fb26830ed0d9f31c8e7da4dbff4e8c59ea6a158e7f"
1421                ),
1422                ..Default::default()
1423            }),
1424        }
1425    }
1426
1427    fn invoke_v1() -> Transaction {
1428        Transaction {
1429            hash: transaction_hash!(
1430                "0x53ee528f0572d6e43b3318ba59a02be15d51f66d8b5dc1f84af2ccbe606e769"
1431            ),
1432            variant: TransactionVariant::InvokeV1(InvokeTransactionV1 {
1433                sender_address: contract_address!(
1434                    "0x3b184c08ea47b80bbe024f42ca94210de552fe2096b0907b6a45809fee82779"
1435                ),
1436                max_fee: fee!("0x125c44c433000"),
1437                nonce: transaction_nonce!("0x1b"),
1438                signature: vec![
1439                    transaction_signature_elem!(
1440                        "0x50e7acc40dcdcad7bf5a758a85f6676620be6f76668913e07c58c4a8d4a45f8"
1441                    ),
1442                    transaction_signature_elem!(
1443                        "0x5eb8f2407a69ed0c19565267c0c67b588056f7201e471d687a3041be3732f35"
1444                    ),
1445                ],
1446                calldata: vec![
1447                    call_param!("0x1"),
1448                    call_param!(
1449                        "0x4c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05"
1450                    ),
1451                    call_param!(
1452                        "0x2f68935fe2620d447e6dee46fb77624aee380c157f7675e9e4220599f4a04bd"
1453                    ),
1454                    call_param!("0x0"),
1455                    call_param!("0x1"),
1456                    call_param!("0x1"),
1457                    call_param!(
1458                        "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"
1459                    ),
1460                ],
1461            }),
1462        }
1463    }
1464
1465    fn invoke_v3() -> Transaction {
1466        Transaction {
1467            hash: transaction_hash!(
1468                "0x22772429229cbca26cb062f6f6a0991a4e84d0f11f3b1bda1913613a5e609e0"
1469            ),
1470            variant: TransactionVariant::InvokeV3(InvokeTransactionV3 {
1471                signature: vec![
1472                    transaction_signature_elem!(
1473                        "0x389bca189562763f6a73da4aaab30d87d8bbc243571f4a353c48493a43a0634"
1474                    ),
1475                    transaction_signature_elem!(
1476                        "0x62d30041a0b1199b3ad93515066d5c7791211fa32f585956fafe630082270e9"
1477                    ),
1478                ],
1479                nonce: transaction_nonce!("0x1084b"),
1480                nonce_data_availability_mode: DataAvailabilityMode::L1,
1481                fee_data_availability_mode: DataAvailabilityMode::L1,
1482                resource_bounds: ResourceBounds {
1483                    l1_gas: ResourceBound {
1484                        max_amount: ResourceAmount(0x61a80),
1485                        max_price_per_unit: ResourcePricePerUnit(0x5af3107a4000),
1486                    },
1487                    l2_gas: Default::default(),
1488                    l1_data_gas: Default::default(),
1489                },
1490                sender_address: contract_address!(
1491                    "0x35acd6dd6c5045d18ca6d0192af46b335a5402c02d41f46e4e77ea2c951d9a3"
1492                ),
1493                calldata: vec![
1494                    call_param!("0x1"),
1495                    call_param!(
1496                        "0x47ad6a25df680763e5663bd0eba3d2bfd18b24b1e8f6bd36b71c37433c63ed0"
1497                    ),
1498                    call_param!(
1499                        "0x19a35a6e95cb7a3318dbb244f20975a1cd8587cc6b5259f15f61d7beb7ee43b"
1500                    ),
1501                    call_param!("0x2"),
1502                    call_param!(
1503                        "0x4d0b88ace5705bb7825f91ee95557d906600b7e7762f5615e6a4f407185a43a"
1504                    ),
1505                    call_param!(
1506                        "0x630ac7edd6c7c097e4f9774fe5855bed3a2b8886286c61f1f7afd601e124d60"
1507                    ),
1508                ],
1509                ..Default::default()
1510            }),
1511        }
1512    }
1513
1514    fn l1_handler() -> Transaction {
1515        Transaction {
1516            hash: transaction_hash!(
1517                "0x8d7d99f96167a01f2406ae25dd6bdeb4f903fd4ed433d96dcf2564b7ab0a8f"
1518            ),
1519            variant: TransactionVariant::L1Handler(L1HandlerTransaction {
1520                contract_address: contract_address!(
1521                    "0x73314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82"
1522                ),
1523                entry_point_selector: entry_point!(
1524                    "0x2d757788a8d8d6f21d1cd40bce38a8222d70654214e96ff95d8086e684fbee5"
1525                ),
1526                nonce: transaction_nonce!("0x18cb20"),
1527                calldata: vec![
1528                    call_param!("0xae0ee0a63a2ce6baeeffe56e7714fb4efe48d419"),
1529                    call_param!(
1530                        "0x13f55ae8d173a036cf8bdf0448f04b835a5d42cda5fe6b4678217ed92cabc94"
1531                    ),
1532                    call_param!("0xd7621dc58210000"),
1533                    call_param!("0x0"),
1534                ],
1535            }),
1536        }
1537    }
1538
1539    fn l1_handler_v07() -> Transaction {
1540        Transaction {
1541            hash: transaction_hash!(
1542                "0x61b518bb1f97c49244b8a7a1a984798b4c2876d42920eca2b6ba8dfb1bddc54"
1543            ),
1544            variant: TransactionVariant::L1Handler(L1HandlerTransaction {
1545                contract_address: contract_address!(
1546                    "0xda8054260ec00606197a4103eb2ef08d6c8af0b6a808b610152d1ce498f8c3"
1547                ),
1548                entry_point_selector: entry_point!(
1549                    "0xe3f5e9e1456ffa52a3fbc7e8c296631d4cc2120c0be1e2829301c0d8fa026b"
1550                ),
1551                nonce: transaction_nonce!("0x0"),
1552                calldata: vec![
1553                    call_param!("0x142273bcbfca76512b2a05aed21f134c4495208"),
1554                    call_param!("0xa0c316cb0bb0c9632315ddc8f49c7921f2c80daa"),
1555                    call_param!("0x2"),
1556                    call_param!(
1557                        "0x453b0310bcdfa50d3c2e7f757e284ac6cd4171933a4e67d1bdcfdbc7f3cbc93"
1558                    ),
1559                ],
1560            }),
1561        }
1562    }
1563
1564    fn l1_handler_legacy() -> Transaction {
1565        Transaction {
1566            hash: transaction_hash!(
1567                "0xfb118dc1d4a4141b7718da4b7fa98980b11caf5aa5d6e1e35e9b050aae788b"
1568            ),
1569            variant: TransactionVariant::L1Handler(L1HandlerTransaction {
1570                contract_address: contract_address!(
1571                    "0x55a46448decca3b138edf0104b7a47d41365b8293bdfd59b03b806c102b12b7"
1572                ),
1573                entry_point_selector: entry_point!(
1574                    "0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01"
1575                ),
1576                calldata: vec![
1577                    call_param!("0x2db8c2615db39a5ed8750b87ac8f217485be11ec"),
1578                    call_param!("0xbc614e"),
1579                    call_param!("0x258"),
1580                ],
1581                ..Default::default()
1582            }),
1583        }
1584    }
1585
1586    #[test]
1587    fn invoke_v1_with_query_version() {
1588        let invoke = TransactionVariant::InvokeV1(InvokeTransactionV1 {
1589            sender_address: contract_address!(
1590                "0x05b53783880534bf39ba4224ffbf6cdbca5d9f8f018a21cf1fe870dff409b3ce"
1591            ),
1592            max_fee: fee!("0x0"),
1593            nonce: transaction_nonce!("0x3"),
1594            signature: vec![
1595                transaction_signature_elem!(
1596                    "0x29784653f04451ad9abb301d06320816756396b0bda4e598559eff4718fe6f9"
1597                ),
1598                transaction_signature_elem!(
1599                    "0x6c5288a10f44612ffdbfa8681af54f97e53339a5119713bdee36a05485abe60"
1600                ),
1601            ],
1602            calldata: vec![
1603                call_param!("0x1"),
1604                call_param!("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
1605                call_param!("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"),
1606                call_param!("0x0"),
1607                call_param!("0x3"),
1608                call_param!("0x3"),
1609                call_param!("0x5b53783880534bf39ba4224ffbf6cdbca5d9f8f018a21cf1fe870dff409b3ce"),
1610                call_param!("0x9184e72a000"),
1611                call_param!("0x0"),
1612            ],
1613        });
1614
1615        assert_eq!(
1616            transaction_hash!("0x00acd1213b669b094390c5b70a447cb2335ee40bbe21c4544db57450aa0e5c04"),
1617            invoke.calculate_hash(GOERLI_TESTNET, true)
1618        );
1619    }
1620}