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.
258#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
259#[cfg_attr(any(test, feature = "full-serde"), derive(Serialize))]
260#[serde(deny_unknown_fields)]
261pub enum BlockId {
262    #[serde(rename = "block_number")]
263    Number(BlockNumber),
264    #[serde(rename = "block_hash")]
265    Hash(BlockHash),
266    #[serde(rename = "latest")]
267    Latest,
268    #[serde(rename = "pending")]
269    Pending,
270}
271
272impl BlockId {
273    pub fn is_pending(&self) -> bool {
274        self == &BlockId::Pending
275    }
276
277    pub fn is_latest(&self) -> bool {
278        self == &BlockId::Latest
279    }
280}
281
282impl BlockNumber {
283    pub const GENESIS: BlockNumber = BlockNumber::new_or_panic(0);
284    /// The maximum [BlockNumber] we can support. Restricted to `u64::MAX/2` to
285    /// match Sqlite's maximum integer value.
286    pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
287
288    /// Returns the parent's [BlockNumber] or [None] if the current number is
289    /// genesis.
290    pub fn parent(&self) -> Option<Self> {
291        if self == &Self::GENESIS {
292            None
293        } else {
294            Some(*self - 1)
295        }
296    }
297
298    pub fn is_zero(&self) -> bool {
299        self == &Self::GENESIS
300    }
301
302    pub fn checked_sub(&self, rhs: u64) -> Option<Self> {
303        self.0.checked_sub(rhs).map(Self)
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        PublicKey,
554        SequencerAddress,
555        StateCommitment,
556        StateDiffCommitment,
557        StorageCommitment,
558        StorageValue,
559        TransactionCommitment,
560        ReceiptCommitment,
561        TransactionHash,
562        TransactionNonce,
563        TransactionSignatureElem,
564    ];
565    [
566        CasmHash,
567        ClassHash,
568        ContractAddress,
569        SierraHash,
570        StorageAddress,
571    ]
572);
573
574macros::fmt::thin_display!(BlockNumber);
575macros::fmt::thin_display!(BlockTimestamp);
576
577impl ContractAddress {
578    pub fn deployed_contract_address(
579        constructor_calldata: impl Iterator<Item = CallParam>,
580        contract_address_salt: &ContractAddressSalt,
581        class_hash: &ClassHash,
582    ) -> Self {
583        let constructor_calldata_hash = constructor_calldata
584            .fold(HashChain::default(), |mut h, param| {
585                h.update(param.0);
586                h
587            })
588            .finalize();
589
590        let contract_address = [
591            Felt::from_be_slice(b"STARKNET_CONTRACT_ADDRESS").expect("prefix is convertible"),
592            Felt::ZERO,
593            contract_address_salt.0,
594            class_hash.0,
595            constructor_calldata_hash,
596        ]
597        .into_iter()
598        .fold(HashChain::default(), |mut h, e| {
599            h.update(e);
600            h
601        })
602        .finalize();
603
604        // Contract addresses are _less than_ 2**251 - 256
605        const MAX_CONTRACT_ADDRESS: Felt =
606            felt!("0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00");
607        let contract_address = if contract_address >= MAX_CONTRACT_ADDRESS {
608            contract_address - MAX_CONTRACT_ADDRESS
609        } else {
610            contract_address
611        };
612
613        ContractAddress::new_or_panic(contract_address)
614    }
615
616    pub fn is_system_contract(&self) -> bool {
617        (*self == ContractAddress::ONE) || (*self == ContractAddress::TWO)
618    }
619}
620
621#[derive(Clone, Debug, PartialEq)]
622pub enum AllowedOrigins {
623    Any,
624    List(Vec<String>),
625}
626
627impl<S> From<S> for AllowedOrigins
628where
629    S: ToString,
630{
631    fn from(value: S) -> Self {
632        let s = value.to_string();
633
634        if s == "*" {
635            Self::Any
636        } else {
637            Self::List(vec![s])
638        }
639    }
640}
641
642/// See:
643/// <https://github.com/starkware-libs/cairo-lang/blob/64a7f6aed9757d3d8d6c28bd972df73272b0cb0a/src/starkware/starknet/public/abi.py#L21-L26>
644pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
645    // python code masks with (2**250 - 1) which starts 0x03 and is followed by 31
646    // 0xff in be truncation is needed not to overflow the field element.
647    plain[0] &= 0x03;
648    Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
649}
650
651/// Calculate class commitment tree leaf hash value.
652///
653/// See: <https://docs.starknet.io/documentation/starknet_versions/upcoming_versions/#state_commitment>
654pub fn calculate_class_commitment_leaf_hash(
655    compiled_class_hash: CasmHash,
656) -> ClassCommitmentLeafHash {
657    const CONTRACT_CLASS_HASH_VERSION: pathfinder_crypto::Felt =
658        felt_bytes!(b"CONTRACT_CLASS_LEAF_V0");
659    ClassCommitmentLeafHash(
660        pathfinder_crypto::hash::poseidon_hash(
661            CONTRACT_CLASS_HASH_VERSION.into(),
662            compiled_class_hash.0.into(),
663        )
664        .into(),
665    )
666}
667
668#[cfg(test)]
669mod tests {
670    use crate::{felt, CallParam, ClassHash, ContractAddress, ContractAddressSalt};
671
672    #[test]
673    fn constructor_entry_point() {
674        use sha3::{Digest, Keccak256};
675
676        use crate::{truncated_keccak, EntryPoint};
677
678        let mut keccak = Keccak256::default();
679        keccak.update(b"constructor");
680        let expected = EntryPoint(truncated_keccak(<[u8; 32]>::from(keccak.finalize())));
681
682        assert_eq!(EntryPoint::CONSTRUCTOR, expected);
683    }
684
685    mod starknet_version {
686        use std::str::FromStr;
687
688        use super::super::StarknetVersion;
689
690        #[test]
691        fn valid_version_parsing() {
692            let cases = [
693                ("1.2.3.4", "1.2.3.4", StarknetVersion::new(1, 2, 3, 4)),
694                ("1.2.3", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
695                ("1.2.3.0", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
696                ("", "", StarknetVersion::new(0, 0, 0, 0)),
697            ];
698
699            for (input, output, actual) in cases.iter() {
700                let version = StarknetVersion::from_str(input).unwrap();
701                assert_eq!(version, *actual);
702                assert_eq!(version.to_string(), *output);
703            }
704        }
705
706        #[test]
707        fn invalid_version_parsing() {
708            assert!(StarknetVersion::from_str("1.2").is_err());
709            assert!(StarknetVersion::from_str("1").is_err());
710            assert!(StarknetVersion::from_str("1.2.a").is_err());
711        }
712    }
713
714    mod block_id_serde {
715        use super::super::BlockId;
716
717        #[test]
718        fn latest() {
719            let result = serde_json::from_str::<BlockId>(r#""latest""#).unwrap();
720            assert_eq!(result, BlockId::Latest);
721        }
722
723        #[test]
724        fn pending() {
725            let result = serde_json::from_str::<BlockId>(r#""pending""#).unwrap();
726            assert_eq!(result, BlockId::Pending);
727        }
728
729        #[test]
730        fn number() {
731            use crate::BlockNumber;
732            let result = serde_json::from_str::<BlockId>(r#"{"block_number": 123456}"#).unwrap();
733            assert_eq!(result, BlockId::Number(BlockNumber(123456)));
734        }
735
736        #[test]
737        fn hash() {
738            use crate::macro_prelude::block_hash;
739
740            let result =
741                serde_json::from_str::<BlockId>(r#"{"block_hash": "0xdeadbeef"}"#).unwrap();
742            assert_eq!(result, BlockId::Hash(block_hash!("0xdeadbeef")));
743        }
744    }
745
746    #[test]
747    fn deployed_contract_address() {
748        let expected_contract_address = ContractAddress(felt!(
749            "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
750        ));
751        let actual_contract_address = ContractAddress::deployed_contract_address(
752            std::iter::once(CallParam(felt!(
753                "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c"
754            ))),
755            &ContractAddressSalt(felt!("0x0")),
756            &ClassHash(felt!(
757                "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"
758            )),
759        );
760        assert_eq!(actual_contract_address, expected_contract_address);
761    }
762}