Skip to main content

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