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