pathfinder_common/
lib.rs

1//! Contains core functions and types that are widely used but have no real
2//! home of their own.
3//!
4//! This includes many trivial wrappers around [Felt] which help by providing
5//! additional type safety.
6use std::fmt::Display;
7use std::ops::Rem;
8use std::str::FromStr;
9
10use anyhow::Context;
11use fake::Dummy;
12use pathfinder_crypto::hash::HashChain;
13use pathfinder_crypto::Felt;
14use primitive_types::H160;
15use serde::{Deserialize, Serialize};
16
17pub mod casm_class;
18pub mod class_definition;
19pub mod consts;
20pub mod event;
21pub mod hash;
22mod header;
23pub mod integration_testing;
24mod l1;
25mod macros;
26pub mod prelude;
27pub mod receipt;
28pub mod signature;
29pub mod state_update;
30pub mod test_utils;
31pub mod transaction;
32pub mod trie;
33
34pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader};
35pub use l1::{L1BlockNumber, L1TransactionHash};
36pub use signature::BlockCommitmentSignature;
37pub use state_update::StateUpdate;
38
39impl ContractAddress {
40    /// The contract at 0x1 is special. It was never deployed and therefore
41    /// has no class hash. It does however receive storage changes.
42    ///
43    /// It is used by starknet to store values for smart contracts to access
44    /// using syscalls. For example the block hash.
45    pub const ONE: ContractAddress = contract_address!("0x1");
46    /// The contract at 0x2 was introduced in Starknet version 0.13.4. It is
47    /// used for stateful compression:
48    /// - storage key 0 points to the global counter, which is the base for
49    ///   index values in the next block,
50    /// - other storage k-v pairs store the mapping of key to index,
51    /// - the global counter starts at value 0x80 in the first block from
52    ///   0.13.4,
53    /// - keys of value lower than 0x80 are not indexed.
54    pub const TWO: ContractAddress = contract_address!("0x2");
55    /// Useful for iteration over the system contracts
56    pub const SYSTEM: [ContractAddress; 2] = [ContractAddress::ONE, ContractAddress::TWO];
57}
58
59// Bytecode and entry point list of a class
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct ContractClass {
62    // A base64 encoding of the gzip-compressed JSON representation of program.
63    pub program: String,
64    // A JSON representation of the entry points
65    // We don't actually process this value, just serialize/deserialize
66    // from an already validated JSON.
67    // This is kept as a Value to avoid dependency on sequencer API types.
68    pub entry_points_by_type: serde_json::Value,
69}
70
71impl EntryPoint {
72    /// Returns a new EntryPoint which has been truncated to fit from Keccak256
73    /// digest of input.
74    ///
75    /// See: <https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-classes/>
76    pub fn hashed(input: &[u8]) -> Self {
77        use sha3::Digest;
78        EntryPoint(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
79            input,
80        ))))
81    }
82
83    /// The constructor [EntryPoint], defined as the truncated keccak of
84    /// b"constructor".
85    pub const CONSTRUCTOR: Self =
86        entry_point!("0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194");
87}
88
89impl StateCommitment {
90    /// Calculates  global state commitment by combining the storage and class
91    /// commitment.
92    ///
93    /// See
94    /// <https://github.com/starkware-libs/cairo-lang/blob/12ca9e91bbdc8a423c63280949c7e34382792067/src/starkware/starknet/core/os/state.cairo#L125>
95    /// for details.
96    pub fn calculate(
97        storage_commitment: StorageCommitment,
98        class_commitment: ClassCommitment,
99    ) -> Self {
100        if class_commitment == ClassCommitment::ZERO {
101            Self(storage_commitment.0)
102        } else {
103            const GLOBAL_STATE_VERSION: Felt = felt_bytes!(b"STARKNET_STATE_V0");
104
105            StateCommitment(
106                pathfinder_crypto::hash::poseidon::poseidon_hash_many(&[
107                    GLOBAL_STATE_VERSION.into(),
108                    storage_commitment.0.into(),
109                    class_commitment.0.into(),
110                ])
111                .into(),
112            )
113        }
114    }
115}
116
117impl StorageAddress {
118    pub fn from_name(input: &[u8]) -> Self {
119        use sha3::Digest;
120        Self(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
121            input,
122        ))))
123    }
124
125    pub fn from_map_name_and_key(name: &[u8], key: Felt) -> Self {
126        use sha3::Digest;
127
128        let intermediate = truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(name)));
129        let value = pathfinder_crypto::hash::pedersen_hash(intermediate, key);
130
131        let value = primitive_types::U256::from_big_endian(value.as_be_bytes());
132        let max_address = primitive_types::U256::from_str_radix(
133            "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
134            16,
135        )
136        .unwrap();
137
138        let value = value.rem(max_address);
139        let mut b = [0u8; 32];
140        value.to_big_endian(&mut b);
141        Self(Felt::from_be_slice(&b).expect("Truncated value should fit into a felt"))
142    }
143}
144
145/// A Starknet block number.
146#[derive(Copy, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub struct BlockNumber(u64);
148
149macros::i64_backed_u64::new_get_partialeq!(BlockNumber);
150macros::i64_backed_u64::serdes!(BlockNumber);
151
152impl From<BlockNumber> for Felt {
153    fn from(x: BlockNumber) -> Self {
154        Felt::from(x.0)
155    }
156}
157
158impl std::iter::Iterator for BlockNumber {
159    type Item = BlockNumber;
160
161    fn next(&mut self) -> Option<Self::Item> {
162        Some(*self + 1)
163    }
164}
165
166/// The timestamp of a Starknet block.
167#[derive(Copy, Debug, Clone, PartialEq, Eq, Default)]
168pub struct BlockTimestamp(u64);
169
170macros::i64_backed_u64::new_get_partialeq!(BlockTimestamp);
171macros::i64_backed_u64::serdes!(BlockTimestamp);
172
173/// A Starknet transaction index.
174#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
175pub struct TransactionIndex(u64);
176
177macros::i64_backed_u64::new_get_partialeq!(TransactionIndex);
178macros::i64_backed_u64::serdes!(TransactionIndex);
179
180/// Starknet gas price.
181#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
182pub struct GasPrice(pub u128);
183
184/// A hex representation of a [GasPrice].
185#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
186pub struct GasPriceHex(pub GasPrice);
187
188/// Starknet resource bound: amount.
189#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
190pub struct ResourceAmount(pub u64);
191
192// Transaction tip: the prioritization metric determines the sorting order of
193// transactions in the mempool.
194#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
195pub struct Tip(pub u64);
196
197// A hex representation of a [Tip].
198#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Dummy)]
199pub struct TipHex(pub Tip);
200
201/// Starknet resource bound: price per unit.
202#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
203pub struct ResourcePricePerUnit(pub u128);
204
205/// Starknet transaction version.
206#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
207pub struct TransactionVersion(pub Felt);
208
209impl TransactionVersion {
210    /// Checks if version is zero, handling QUERY_VERSION_BASE.
211    pub fn is_zero(&self) -> bool {
212        self.without_query_version() == 0
213    }
214
215    /// Returns the transaction version without QUERY_VERSION_BASE.
216    ///
217    /// QUERY_VERSION_BASE (2**128) is a large constant that gets
218    /// added to the real version to make sure transactions constructed for
219    /// call or estimateFee cannot be submitted for inclusion on the chain.
220    pub fn without_query_version(&self) -> u128 {
221        let lower = &self.0.as_be_bytes()[16..];
222        u128::from_be_bytes(lower.try_into().expect("slice should be the right length"))
223    }
224
225    pub const fn with_query_version(self) -> Self {
226        let mut bytes = self.0.to_be_bytes();
227        bytes[15] |= 0b0000_0001;
228
229        let felt = match Felt::from_be_bytes(bytes) {
230            Ok(x) => x,
231            Err(_) => panic!("Adding query bit to transaction version failed."),
232        };
233        Self(felt)
234    }
235
236    pub const fn has_query_version(&self) -> bool {
237        self.0.as_be_bytes()[15] & 0b0000_0001 != 0
238    }
239
240    pub fn with_query_only(self, query_only: bool) -> Self {
241        if query_only {
242            self.with_query_version()
243        } else {
244            Self(self.without_query_version().into())
245        }
246    }
247
248    pub const ZERO: Self = Self(Felt::ZERO);
249    pub const ONE: Self = Self(Felt::from_u64(1));
250    pub const TWO: Self = Self(Felt::from_u64(2));
251    pub const THREE: Self = Self(Felt::from_u64(3));
252    pub const ZERO_WITH_QUERY_VERSION: Self = Self::ZERO.with_query_version();
253    pub const ONE_WITH_QUERY_VERSION: Self = Self::ONE.with_query_version();
254    pub const TWO_WITH_QUERY_VERSION: Self = Self::TWO.with_query_version();
255    pub const THREE_WITH_QUERY_VERSION: Self = Self::THREE.with_query_version();
256}
257
258/// A way of identifying a specific block that has been finalized.
259///
260/// Useful in contexts that do not work with pending blocks.
261#[derive(Debug, Copy, Clone, PartialEq, Eq)]
262pub enum BlockId {
263    Number(BlockNumber),
264    Hash(BlockHash),
265    Latest,
266}
267
268impl BlockId {
269    pub fn is_latest(&self) -> bool {
270        self == &Self::Latest
271    }
272}
273
274impl BlockNumber {
275    pub const GENESIS: BlockNumber = BlockNumber::new_or_panic(0);
276    /// The maximum [BlockNumber] we can support. Restricted to `u64::MAX/2` to
277    /// match Sqlite's maximum integer value.
278    pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
279
280    /// Returns the parent's [BlockNumber] or [None] if the current number is
281    /// genesis.
282    pub fn parent(&self) -> Option<Self> {
283        if self == &Self::GENESIS {
284            None
285        } else {
286            Some(*self - 1)
287        }
288    }
289
290    pub fn is_zero(&self) -> bool {
291        self == &Self::GENESIS
292    }
293
294    pub fn checked_add(&self, rhs: u64) -> Option<Self> {
295        Self::new(self.0.checked_add(rhs)?)
296    }
297
298    pub fn checked_sub(&self, rhs: u64) -> Option<Self> {
299        self.0.checked_sub(rhs).map(Self)
300    }
301
302    pub fn saturating_sub(&self, rhs: u64) -> Self {
303        Self(self.0.saturating_sub(rhs))
304    }
305}
306
307impl std::ops::Add<u64> for BlockNumber {
308    type Output = BlockNumber;
309
310    fn add(self, rhs: u64) -> Self::Output {
311        Self(self.0 + rhs)
312    }
313}
314
315impl std::ops::AddAssign<u64> for BlockNumber {
316    fn add_assign(&mut self, rhs: u64) {
317        self.0 += rhs;
318    }
319}
320
321impl std::ops::Sub<u64> for BlockNumber {
322    type Output = BlockNumber;
323
324    fn sub(self, rhs: u64) -> Self::Output {
325        Self(self.0 - rhs)
326    }
327}
328
329impl std::ops::SubAssign<u64> for BlockNumber {
330    fn sub_assign(&mut self, rhs: u64) {
331        self.0 -= rhs;
332    }
333}
334
335/// An Ethereum address.
336#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
337pub struct EthereumAddress(pub H160);
338
339impl<T> Dummy<T> for EthereumAddress {
340    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
341        Self(H160::random_using(rng))
342    }
343}
344
345#[derive(Debug, thiserror::Error)]
346#[error("expected slice length of 16 or less, got {0}")]
347pub struct FromSliceError(usize);
348
349impl GasPrice {
350    pub const ZERO: GasPrice = GasPrice(0u128);
351
352    /// Returns the big-endian representation of this [GasPrice].
353    pub fn to_be_bytes(&self) -> [u8; 16] {
354        self.0.to_be_bytes()
355    }
356
357    /// Constructs [GasPrice] from an array of bytes. Big endian byte order is
358    /// assumed.
359    pub fn from_be_bytes(src: [u8; 16]) -> Self {
360        Self(u128::from_be_bytes(src))
361    }
362
363    /// Constructs [GasPrice] from a slice of bytes. Big endian byte order is
364    /// assumed.
365    pub fn from_be_slice(src: &[u8]) -> Result<Self, FromSliceError> {
366        if src.len() > 16 {
367            return Err(FromSliceError(src.len()));
368        }
369
370        let mut buf = [0u8; 16];
371        buf[16 - src.len()..].copy_from_slice(src);
372
373        Ok(Self::from_be_bytes(buf))
374    }
375}
376
377impl From<u64> for GasPrice {
378    fn from(src: u64) -> Self {
379        Self(u128::from(src))
380    }
381}
382
383impl TryFrom<Felt> for GasPrice {
384    type Error = anyhow::Error;
385
386    fn try_from(src: Felt) -> Result<Self, Self::Error> {
387        anyhow::ensure!(
388            src.as_be_bytes()[0..16] == [0; 16],
389            "Gas price fits into u128"
390        );
391
392        let mut bytes = [0u8; 16];
393        bytes.copy_from_slice(&src.as_be_bytes()[16..]);
394        Ok(Self(u128::from_be_bytes(bytes)))
395    }
396}
397
398impl From<BlockNumber> for BlockId {
399    fn from(number: BlockNumber) -> Self {
400        Self::Number(number)
401    }
402}
403
404impl From<BlockHash> for BlockId {
405    fn from(hash: BlockHash) -> Self {
406        Self::Hash(hash)
407    }
408}
409
410/// Ethereum network chains running Starknet.
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412pub enum EthereumChain {
413    Mainnet,
414    Sepolia,
415    Other(primitive_types::U256),
416}
417
418/// Starknet chain.
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum Chain {
421    Mainnet,
422    SepoliaTestnet,
423    SepoliaIntegration,
424    Custom,
425}
426
427#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
428pub struct ChainId(pub Felt);
429
430impl ChainId {
431    /// Convenience function for the constants because unwrap() is not const.
432    const fn from_slice_unwrap(slice: &[u8]) -> Self {
433        Self(match Felt::from_be_slice(slice) {
434            Ok(v) => v,
435            Err(_) => panic!("Bad value"),
436        })
437    }
438
439    /// A hex string representation, eg.: `"0x534e5f4d41494e"` stands for
440    /// Mainnet (`SN_MAIN`)
441    pub fn to_hex_str(&self) -> std::borrow::Cow<'static, str> {
442        self.0.to_hex_str()
443    }
444
445    /// A human readable representation, eg.: `"SN_MAIN"` stands for Mainnet
446    pub fn as_str(&self) -> &str {
447        std::str::from_utf8(self.0.as_be_bytes())
448            .expect("valid utf8")
449            .trim_start_matches('\0')
450    }
451
452    pub const MAINNET: Self = Self::from_slice_unwrap(b"SN_MAIN");
453    pub const SEPOLIA_TESTNET: Self = Self::from_slice_unwrap(b"SN_SEPOLIA");
454    pub const SEPOLIA_INTEGRATION: Self = Self::from_slice_unwrap(b"SN_INTEGRATION_SEPOLIA");
455}
456
457impl std::fmt::Display for Chain {
458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        match self {
460            Chain::Mainnet => f.write_str("Mainnet"),
461            Chain::SepoliaTestnet => f.write_str("Testnet/Sepolia"),
462            Chain::SepoliaIntegration => f.write_str("Integration/Sepolia"),
463            Chain::Custom => f.write_str("Custom"),
464        }
465    }
466}
467
468#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Dummy)]
469pub struct StarknetVersion(u8, u8, u8, u8);
470
471impl StarknetVersion {
472    pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self {
473        StarknetVersion(a, b, c, d)
474    }
475
476    pub fn as_u32(&self) -> u32 {
477        u32::from_le_bytes([self.0, self.1, self.2, self.3])
478    }
479
480    pub fn from_u32(version: u32) -> Self {
481        let [a, b, c, d] = version.to_le_bytes();
482        StarknetVersion(a, b, c, d)
483    }
484
485    pub const V_0_13_2: Self = Self::new(0, 13, 2, 0);
486
487    // TODO: version at which block hash definition changes taken from
488    // Starkware implementation but might yet change
489    pub const V_0_13_4: Self = Self::new(0, 13, 4, 0);
490}
491
492impl FromStr for StarknetVersion {
493    type Err = anyhow::Error;
494
495    fn from_str(s: &str) -> Result<Self, Self::Err> {
496        if s.is_empty() {
497            return Ok(StarknetVersion::new(0, 0, 0, 0));
498        }
499
500        let parts: Vec<_> = s.split('.').collect();
501        anyhow::ensure!(
502            parts.len() == 3 || parts.len() == 4,
503            "Invalid version string, expected 3 or 4 parts but got {}",
504            parts.len()
505        );
506
507        let a = parts[0].parse()?;
508        let b = parts[1].parse()?;
509        let c = parts[2].parse()?;
510        let d = parts.get(3).map(|x| x.parse()).transpose()?.unwrap_or(0);
511
512        Ok(StarknetVersion(a, b, c, d))
513    }
514}
515
516impl Display for StarknetVersion {
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        if self.0 == 0 && self.1 == 0 && self.2 == 0 && self.3 == 0 {
519            return Ok(());
520        }
521        if self.3 == 0 {
522            write!(f, "{}.{}.{}", self.0, self.1, self.2)
523        } else {
524            write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
525        }
526    }
527}
528
529macros::felt_newtypes!(
530    [
531        AccountDeploymentDataElem,
532        BlockHash,
533        ByteCodeOffset,
534        BlockCommitmentSignatureElem,
535        CallParam,
536        CallResultValue,
537        ClassCommitment,
538        ClassCommitmentLeafHash,
539        ConstructorParam,
540        ContractAddressSalt,
541        ContractNonce,
542        ContractStateHash,
543        ContractRoot,
544        EntryPoint,
545        EventCommitment,
546        EventData,
547        EventKey,
548        Fee,
549        L1ToL2MessageNonce,
550        L1ToL2MessagePayloadElem,
551        L2ToL1MessagePayloadElem,
552        PaymasterDataElem,
553        ProposalCommitment,
554        PublicKey,
555        SequencerAddress,
556        StateCommitment,
557        StateDiffCommitment,
558        StorageCommitment,
559        StorageValue,
560        TransactionCommitment,
561        ReceiptCommitment,
562        TransactionHash,
563        TransactionNonce,
564        TransactionSignatureElem,
565    ];
566    [
567        CasmHash,
568        ClassHash,
569        ContractAddress,
570        SierraHash,
571        StorageAddress,
572    ]
573);
574
575macros::fmt::thin_display!(BlockNumber);
576macros::fmt::thin_display!(BlockTimestamp);
577
578impl ContractAddress {
579    pub fn deployed_contract_address(
580        constructor_calldata: impl Iterator<Item = CallParam>,
581        contract_address_salt: &ContractAddressSalt,
582        class_hash: &ClassHash,
583    ) -> Self {
584        let constructor_calldata_hash = constructor_calldata
585            .fold(HashChain::default(), |mut h, param| {
586                h.update(param.0);
587                h
588            })
589            .finalize();
590
591        let contract_address = [
592            Felt::from_be_slice(b"STARKNET_CONTRACT_ADDRESS").expect("prefix is convertible"),
593            Felt::ZERO,
594            contract_address_salt.0,
595            class_hash.0,
596            constructor_calldata_hash,
597        ]
598        .into_iter()
599        .fold(HashChain::default(), |mut h, e| {
600            h.update(e);
601            h
602        })
603        .finalize();
604
605        // Contract addresses are _less than_ 2**251 - 256
606        const MAX_CONTRACT_ADDRESS: Felt =
607            felt!("0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00");
608        let contract_address = if contract_address >= MAX_CONTRACT_ADDRESS {
609            contract_address - MAX_CONTRACT_ADDRESS
610        } else {
611            contract_address
612        };
613
614        ContractAddress::new_or_panic(contract_address)
615    }
616
617    pub fn is_system_contract(&self) -> bool {
618        (*self == ContractAddress::ONE) || (*self == ContractAddress::TWO)
619    }
620}
621
622impl From<ContractAddress> for Vec<u8> {
623    fn from(value: ContractAddress) -> Self {
624        value.0.to_be_bytes().to_vec()
625    }
626}
627
628#[derive(Clone, Debug, PartialEq)]
629pub enum AllowedOrigins {
630    Any,
631    List(Vec<String>),
632}
633
634impl<S> From<S> for AllowedOrigins
635where
636    S: ToString,
637{
638    fn from(value: S) -> Self {
639        let s = value.to_string();
640
641        if s == "*" {
642            Self::Any
643        } else {
644            Self::List(vec![s])
645        }
646    }
647}
648
649/// See:
650/// <https://github.com/starkware-libs/cairo-lang/blob/64a7f6aed9757d3d8d6c28bd972df73272b0cb0a/src/starkware/starknet/public/abi.py#L21-L26>
651pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
652    // python code masks with (2**250 - 1) which starts 0x03 and is followed by 31
653    // 0xff in be truncation is needed not to overflow the field element.
654    plain[0] &= 0x03;
655    Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
656}
657
658/// Calculate class commitment tree leaf hash value.
659///
660/// See: <https://docs.starknet.io/documentation/starknet_versions/upcoming_versions/#state_commitment>
661pub fn calculate_class_commitment_leaf_hash(
662    compiled_class_hash: CasmHash,
663) -> ClassCommitmentLeafHash {
664    const CONTRACT_CLASS_HASH_VERSION: pathfinder_crypto::Felt =
665        felt_bytes!(b"CONTRACT_CLASS_LEAF_V0");
666    ClassCommitmentLeafHash(
667        pathfinder_crypto::hash::poseidon_hash(
668            CONTRACT_CLASS_HASH_VERSION.into(),
669            compiled_class_hash.0.into(),
670        )
671        .into(),
672    )
673}
674
675#[derive(Debug, Clone, Copy)]
676pub struct ConsensusInfo {
677    pub highest_decided_height: BlockNumber,
678    pub highest_decided_value: ProposalCommitment,
679}
680
681#[cfg(test)]
682mod tests {
683    use crate::{felt, CallParam, ClassHash, ContractAddress, ContractAddressSalt};
684
685    #[test]
686    fn constructor_entry_point() {
687        use sha3::{Digest, Keccak256};
688
689        use crate::{truncated_keccak, EntryPoint};
690
691        let mut keccak = Keccak256::default();
692        keccak.update(b"constructor");
693        let expected = EntryPoint(truncated_keccak(<[u8; 32]>::from(keccak.finalize())));
694
695        assert_eq!(EntryPoint::CONSTRUCTOR, expected);
696    }
697
698    mod starknet_version {
699        use std::str::FromStr;
700
701        use super::super::StarknetVersion;
702
703        #[test]
704        fn valid_version_parsing() {
705            let cases = [
706                ("1.2.3.4", "1.2.3.4", StarknetVersion::new(1, 2, 3, 4)),
707                ("1.2.3", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
708                ("1.2.3.0", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
709                ("", "", StarknetVersion::new(0, 0, 0, 0)),
710            ];
711
712            for (input, output, actual) in cases.iter() {
713                let version = StarknetVersion::from_str(input).unwrap();
714                assert_eq!(version, *actual);
715                assert_eq!(version.to_string(), *output);
716            }
717        }
718
719        #[test]
720        fn invalid_version_parsing() {
721            assert!(StarknetVersion::from_str("1.2").is_err());
722            assert!(StarknetVersion::from_str("1").is_err());
723            assert!(StarknetVersion::from_str("1.2.a").is_err());
724        }
725    }
726
727    #[test]
728    fn deployed_contract_address() {
729        let expected_contract_address = ContractAddress(felt!(
730            "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
731        ));
732        let actual_contract_address = ContractAddress::deployed_contract_address(
733            std::iter::once(CallParam(felt!(
734                "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c"
735            ))),
736            &ContractAddressSalt(felt!("0x0")),
737            &ClassHash(felt!(
738                "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"
739            )),
740        );
741        assert_eq!(actual_contract_address, expected_contract_address);
742    }
743}