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