1use 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 consensus_info;
20pub mod consts;
21pub mod event;
22pub mod hash;
23mod header;
24pub mod integration_testing;
25mod l1;
26mod l2;
27mod macros;
28pub mod prelude;
29pub mod receipt;
30pub mod signature;
31pub mod state_update;
32pub mod test_utils;
33pub mod transaction;
34pub mod trie;
35
36pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader};
37pub use l1::{L1BlockNumber, L1TransactionHash};
38pub use l2::{ConsensusFinalizedBlockHeader, ConsensusFinalizedL2Block, L2Block, L2BlockToCommit};
39pub use signature::BlockCommitmentSignature;
40pub use state_update::StateUpdate;
41
42impl ContractAddress {
43 pub const ONE: ContractAddress = contract_address!("0x1");
49 pub const TWO: ContractAddress = contract_address!("0x2");
58 pub const SYSTEM: [ContractAddress; 2] = [ContractAddress::ONE, ContractAddress::TWO];
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct ContractClass {
65 pub program: String,
67 pub entry_points_by_type: serde_json::Value,
72}
73
74impl EntryPoint {
75 pub fn hashed(input: &[u8]) -> Self {
80 use sha3::Digest;
81 EntryPoint(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
82 input,
83 ))))
84 }
85
86 pub const CONSTRUCTOR: Self =
89 entry_point!("0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194");
90}
91
92impl StateCommitment {
93 pub fn calculate(
105 storage_commitment: StorageCommitment,
106 class_commitment: ClassCommitment,
107 version: StarknetVersion,
108 ) -> Self {
109 if class_commitment == ClassCommitment::ZERO
110 && storage_commitment == StorageCommitment::ZERO
111 {
112 return StateCommitment::ZERO;
113 }
114
115 if class_commitment == ClassCommitment::ZERO && version < StarknetVersion::V_0_14_0 {
116 return Self(storage_commitment.0);
117 }
118
119 const GLOBAL_STATE_VERSION: Felt = felt_bytes!(b"STARKNET_STATE_V0");
120
121 StateCommitment(
122 pathfinder_crypto::hash::poseidon::poseidon_hash_many(&[
123 GLOBAL_STATE_VERSION.into(),
124 storage_commitment.0.into(),
125 class_commitment.0.into(),
126 ])
127 .into(),
128 )
129 }
130}
131
132impl StorageAddress {
133 pub fn from_name(input: &[u8]) -> Self {
134 use sha3::Digest;
135 Self(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
136 input,
137 ))))
138 }
139
140 pub fn from_map_name_and_key(name: &[u8], key: Felt) -> Self {
141 use sha3::Digest;
142
143 let intermediate = truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(name)));
144 let value = pathfinder_crypto::hash::pedersen_hash(intermediate, key);
145
146 let value = primitive_types::U256::from_big_endian(value.as_be_bytes());
147 let max_address = primitive_types::U256::from_str_radix(
148 "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
149 16,
150 )
151 .unwrap();
152
153 let value = value.rem(max_address);
154 let mut b = [0u8; 32];
155 value.to_big_endian(&mut b);
156 Self(Felt::from_be_slice(&b).expect("Truncated value should fit into a felt"))
157 }
158}
159
160#[derive(Copy, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
162pub struct BlockNumber(u64);
163
164macros::i64_backed_u64::new_get_partialeq!(BlockNumber);
165macros::i64_backed_u64::serdes!(BlockNumber);
166
167impl From<BlockNumber> for Felt {
168 fn from(x: BlockNumber) -> Self {
169 Felt::from(x.0)
170 }
171}
172
173impl std::iter::Iterator for BlockNumber {
174 type Item = BlockNumber;
175
176 fn next(&mut self) -> Option<Self::Item> {
177 Some(*self + 1)
178 }
179}
180
181#[derive(Copy, Debug, Clone, PartialEq, Eq, Default)]
183pub struct BlockTimestamp(u64);
184
185macros::i64_backed_u64::new_get_partialeq!(BlockTimestamp);
186macros::i64_backed_u64::serdes!(BlockTimestamp);
187
188#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
190pub struct TransactionIndex(u64);
191
192macros::i64_backed_u64::new_get_partialeq!(TransactionIndex);
193macros::i64_backed_u64::serdes!(TransactionIndex);
194
195#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
197pub struct GasPrice(pub u128);
198
199#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
201pub struct GasPriceHex(pub GasPrice);
202
203#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
205pub struct ResourceAmount(pub u64);
206
207#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
210pub struct Tip(pub u64);
211
212#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Dummy)]
214pub struct TipHex(pub Tip);
215
216#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
218pub struct ResourcePricePerUnit(pub u128);
219
220#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
222pub struct TransactionVersion(pub Felt);
223
224impl TransactionVersion {
225 pub fn is_zero(&self) -> bool {
227 self.without_query_version() == 0
228 }
229
230 pub fn without_query_version(&self) -> u128 {
236 let lower = &self.0.as_be_bytes()[16..];
237 u128::from_be_bytes(lower.try_into().expect("slice should be the right length"))
238 }
239
240 pub const fn with_query_version(self) -> Self {
241 let mut bytes = self.0.to_be_bytes();
242 bytes[15] |= 0b0000_0001;
243
244 let felt = match Felt::from_be_bytes(bytes) {
245 Ok(x) => x,
246 Err(_) => panic!("Adding query bit to transaction version failed."),
247 };
248 Self(felt)
249 }
250
251 pub const fn has_query_version(&self) -> bool {
252 self.0.as_be_bytes()[15] & 0b0000_0001 != 0
253 }
254
255 pub fn with_query_only(self, query_only: bool) -> Self {
256 if query_only {
257 self.with_query_version()
258 } else {
259 Self(self.without_query_version().into())
260 }
261 }
262
263 pub const ZERO: Self = Self(Felt::ZERO);
264 pub const ONE: Self = Self(Felt::from_u64(1));
265 pub const TWO: Self = Self(Felt::from_u64(2));
266 pub const THREE: Self = Self(Felt::from_u64(3));
267 pub const ZERO_WITH_QUERY_VERSION: Self = Self::ZERO.with_query_version();
268 pub const ONE_WITH_QUERY_VERSION: Self = Self::ONE.with_query_version();
269 pub const TWO_WITH_QUERY_VERSION: Self = Self::TWO.with_query_version();
270 pub const THREE_WITH_QUERY_VERSION: Self = Self::THREE.with_query_version();
271}
272
273#[derive(Debug, Copy, Clone, PartialEq, Eq)]
277pub enum BlockId {
278 Number(BlockNumber),
279 Hash(BlockHash),
280 Latest,
281}
282
283impl BlockId {
284 pub fn is_latest(&self) -> bool {
285 self == &Self::Latest
286 }
287}
288
289impl BlockNumber {
290 pub const GENESIS: BlockNumber = BlockNumber::new_or_panic(0);
291 pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
294
295 pub fn parent(&self) -> Option<Self> {
298 if self == &Self::GENESIS {
299 None
300 } else {
301 Some(*self - 1)
302 }
303 }
304
305 pub fn is_zero(&self) -> bool {
306 self == &Self::GENESIS
307 }
308
309 pub fn checked_add(&self, rhs: u64) -> Option<Self> {
310 Self::new(self.0.checked_add(rhs)?)
311 }
312
313 pub fn checked_sub(&self, rhs: u64) -> Option<Self> {
314 self.0.checked_sub(rhs).map(Self)
315 }
316
317 pub fn saturating_sub(&self, rhs: u64) -> Self {
318 Self(self.0.saturating_sub(rhs))
319 }
320}
321
322impl std::ops::Add<u64> for BlockNumber {
323 type Output = BlockNumber;
324
325 fn add(self, rhs: u64) -> Self::Output {
326 Self(self.0 + rhs)
327 }
328}
329
330impl std::ops::AddAssign<u64> for BlockNumber {
331 fn add_assign(&mut self, rhs: u64) {
332 self.0 += rhs;
333 }
334}
335
336impl std::ops::Sub<u64> for BlockNumber {
337 type Output = BlockNumber;
338
339 fn sub(self, rhs: u64) -> Self::Output {
340 Self(self.0 - rhs)
341 }
342}
343
344impl std::ops::SubAssign<u64> for BlockNumber {
345 fn sub_assign(&mut self, rhs: u64) {
346 self.0 -= rhs;
347 }
348}
349
350#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
352pub struct EthereumAddress(pub H160);
353
354impl<T> Dummy<T> for EthereumAddress {
355 fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, rng: &mut R) -> Self {
356 Self(H160::random_using(rng))
357 }
358}
359
360#[derive(Debug, thiserror::Error)]
361#[error("expected slice length of 16 or less, got {0}")]
362pub struct FromSliceError(usize);
363
364impl GasPrice {
365 pub const ZERO: GasPrice = GasPrice(0u128);
366
367 pub fn to_be_bytes(&self) -> [u8; 16] {
369 self.0.to_be_bytes()
370 }
371
372 pub fn from_be_bytes(src: [u8; 16]) -> Self {
375 Self(u128::from_be_bytes(src))
376 }
377
378 pub fn from_be_slice(src: &[u8]) -> Result<Self, FromSliceError> {
381 if src.len() > 16 {
382 return Err(FromSliceError(src.len()));
383 }
384
385 let mut buf = [0u8; 16];
386 buf[16 - src.len()..].copy_from_slice(src);
387
388 Ok(Self::from_be_bytes(buf))
389 }
390}
391
392impl From<u64> for GasPrice {
393 fn from(src: u64) -> Self {
394 Self(u128::from(src))
395 }
396}
397
398impl TryFrom<Felt> for GasPrice {
399 type Error = anyhow::Error;
400
401 fn try_from(src: Felt) -> Result<Self, Self::Error> {
402 anyhow::ensure!(
403 src.as_be_bytes()[0..16] == [0; 16],
404 "Gas price fits into u128"
405 );
406
407 let mut bytes = [0u8; 16];
408 bytes.copy_from_slice(&src.as_be_bytes()[16..]);
409 Ok(Self(u128::from_be_bytes(bytes)))
410 }
411}
412
413impl From<BlockNumber> for BlockId {
414 fn from(number: BlockNumber) -> Self {
415 Self::Number(number)
416 }
417}
418
419impl From<BlockHash> for BlockId {
420 fn from(hash: BlockHash) -> Self {
421 Self::Hash(hash)
422 }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum EthereumChain {
428 Mainnet,
429 Sepolia,
430 Other(primitive_types::U256),
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
435pub enum Chain {
436 Mainnet,
437 SepoliaTestnet,
438 SepoliaIntegration,
439 Custom,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
443pub struct ChainId(pub Felt);
444
445impl ChainId {
446 const fn from_slice_unwrap(slice: &[u8]) -> Self {
448 Self(match Felt::from_be_slice(slice) {
449 Ok(v) => v,
450 Err(_) => panic!("Bad value"),
451 })
452 }
453
454 pub fn to_hex_str(&self) -> std::borrow::Cow<'static, str> {
457 self.0.to_hex_str()
458 }
459
460 pub fn as_str(&self) -> &str {
462 std::str::from_utf8(self.0.as_be_bytes())
463 .expect("valid utf8")
464 .trim_start_matches('\0')
465 }
466
467 pub const MAINNET: Self = Self::from_slice_unwrap(b"SN_MAIN");
468 pub const SEPOLIA_TESTNET: Self = Self::from_slice_unwrap(b"SN_SEPOLIA");
469 pub const SEPOLIA_INTEGRATION: Self = Self::from_slice_unwrap(b"SN_INTEGRATION_SEPOLIA");
470}
471
472impl std::fmt::Display for Chain {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 match self {
475 Chain::Mainnet => f.write_str("Mainnet"),
476 Chain::SepoliaTestnet => f.write_str("Testnet/Sepolia"),
477 Chain::SepoliaIntegration => f.write_str("Integration/Sepolia"),
478 Chain::Custom => f.write_str("Custom"),
479 }
480 }
481}
482
483#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Dummy)]
484pub struct StarknetVersion(u8, u8, u8, u8);
485
486impl StarknetVersion {
487 pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self {
488 StarknetVersion(a, b, c, d)
489 }
490
491 pub fn as_u32(&self) -> u32 {
492 u32::from_le_bytes([self.0, self.1, self.2, self.3])
493 }
494
495 pub fn from_u32(version: u32) -> Self {
496 let [a, b, c, d] = version.to_le_bytes();
497 StarknetVersion(a, b, c, d)
498 }
499
500 pub const V_0_13_2: Self = Self::new(0, 13, 2, 0);
501
502 pub const V_0_13_4: Self = Self::new(0, 13, 4, 0);
505 pub const V_0_14_0: Self = Self::new(0, 14, 0, 0);
508}
509
510impl FromStr for StarknetVersion {
511 type Err = anyhow::Error;
512
513 fn from_str(s: &str) -> Result<Self, Self::Err> {
514 if s.is_empty() {
515 return Ok(StarknetVersion::new(0, 0, 0, 0));
516 }
517
518 let parts: Vec<_> = s.split('.').collect();
519 anyhow::ensure!(
520 parts.len() == 3 || parts.len() == 4,
521 "Invalid version string, expected 3 or 4 parts but got {}",
522 parts.len()
523 );
524
525 let a = parts[0].parse()?;
526 let b = parts[1].parse()?;
527 let c = parts[2].parse()?;
528 let d = parts.get(3).map(|x| x.parse()).transpose()?.unwrap_or(0);
529
530 Ok(StarknetVersion(a, b, c, d))
531 }
532}
533
534impl Display for StarknetVersion {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 if self.0 == 0 && self.1 == 0 && self.2 == 0 && self.3 == 0 {
537 return Ok(());
538 }
539 if self.3 == 0 {
540 write!(f, "{}.{}.{}", self.0, self.1, self.2)
541 } else {
542 write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
543 }
544 }
545}
546
547macros::felt_newtypes!(
548 [
549 AccountDeploymentDataElem,
550 BlockHash,
551 ByteCodeOffset,
552 BlockCommitmentSignatureElem,
553 CallParam,
554 CallResultValue,
555 ClassCommitment,
556 ClassCommitmentLeafHash,
557 ConstructorParam,
558 ContractAddressSalt,
559 ContractNonce,
560 ContractStateHash,
561 ContractRoot,
562 EntryPoint,
563 EventCommitment,
564 EventData,
565 EventKey,
566 Fee,
567 L1ToL2MessageNonce,
568 L1ToL2MessagePayloadElem,
569 L2ToL1MessagePayloadElem,
570 PaymasterDataElem,
571 ProofFactElem,
572 ProposalCommitment,
573 PublicKey,
574 SequencerAddress,
575 StateCommitment,
576 StateDiffCommitment,
577 StorageCommitment,
578 StorageValue,
579 TransactionCommitment,
580 ReceiptCommitment,
581 TransactionHash,
582 TransactionNonce,
583 TransactionSignatureElem,
584 ];
585 [
586 CasmHash,
587 ClassHash,
588 ContractAddress,
589 SierraHash,
590 StorageAddress,
591 ]
592);
593
594macros::fmt::thin_display!(BlockNumber);
595macros::fmt::thin_display!(BlockTimestamp);
596
597impl ContractAddress {
598 pub fn deployed_contract_address(
599 constructor_calldata: impl Iterator<Item = CallParam>,
600 contract_address_salt: &ContractAddressSalt,
601 class_hash: &ClassHash,
602 ) -> Self {
603 let constructor_calldata_hash = constructor_calldata
604 .fold(HashChain::default(), |mut h, param| {
605 h.update(param.0);
606 h
607 })
608 .finalize();
609
610 let contract_address = [
611 Felt::from_be_slice(b"STARKNET_CONTRACT_ADDRESS").expect("prefix is convertible"),
612 Felt::ZERO,
613 contract_address_salt.0,
614 class_hash.0,
615 constructor_calldata_hash,
616 ]
617 .into_iter()
618 .fold(HashChain::default(), |mut h, e| {
619 h.update(e);
620 h
621 })
622 .finalize();
623
624 const MAX_CONTRACT_ADDRESS: Felt =
626 felt!("0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00");
627 let contract_address = if contract_address >= MAX_CONTRACT_ADDRESS {
628 contract_address - MAX_CONTRACT_ADDRESS
629 } else {
630 contract_address
631 };
632
633 ContractAddress::new_or_panic(contract_address)
634 }
635
636 pub fn is_system_contract(&self) -> bool {
637 (*self == ContractAddress::ONE) || (*self == ContractAddress::TWO)
638 }
639}
640
641impl From<ContractAddress> for Vec<u8> {
642 fn from(value: ContractAddress) -> Self {
643 value.0.to_be_bytes().to_vec()
644 }
645}
646
647#[derive(Clone, Debug, PartialEq)]
648pub enum AllowedOrigins {
649 Any,
650 List(Vec<String>),
651}
652
653impl<S> From<S> for AllowedOrigins
654where
655 S: ToString,
656{
657 fn from(value: S) -> Self {
658 let s = value.to_string();
659
660 if s == "*" {
661 Self::Any
662 } else {
663 Self::List(vec![s])
664 }
665 }
666}
667
668pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
671 plain[0] &= 0x03;
674 Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
675}
676
677pub fn calculate_class_commitment_leaf_hash(
681 compiled_class_hash: CasmHash,
682) -> ClassCommitmentLeafHash {
683 const CONTRACT_CLASS_HASH_VERSION: pathfinder_crypto::Felt =
684 felt_bytes!(b"CONTRACT_CLASS_LEAF_V0");
685 ClassCommitmentLeafHash(
686 pathfinder_crypto::hash::poseidon_hash(
687 CONTRACT_CLASS_HASH_VERSION.into(),
688 compiled_class_hash.0.into(),
689 )
690 .into(),
691 )
692}
693
694#[derive(
696 Copy,
697 Debug,
698 Clone,
699 Default,
700 PartialEq,
701 Eq,
702 PartialOrd,
703 Ord,
704 Hash,
705 serde::Serialize,
706 serde::Deserialize,
707)]
708pub struct ProofElem(pub u32);
709
710#[cfg(test)]
711mod tests {
712 use crate::{felt, CallParam, ClassHash, ContractAddress, ContractAddressSalt};
713
714 #[test]
715 fn constructor_entry_point() {
716 use sha3::{Digest, Keccak256};
717
718 use crate::{truncated_keccak, EntryPoint};
719
720 let mut keccak = Keccak256::default();
721 keccak.update(b"constructor");
722 let expected = EntryPoint(truncated_keccak(<[u8; 32]>::from(keccak.finalize())));
723
724 assert_eq!(EntryPoint::CONSTRUCTOR, expected);
725 }
726
727 mod starknet_version {
728 use std::str::FromStr;
729
730 use super::super::StarknetVersion;
731
732 #[test]
733 fn valid_version_parsing() {
734 let cases = [
735 ("1.2.3.4", "1.2.3.4", StarknetVersion::new(1, 2, 3, 4)),
736 ("1.2.3", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
737 ("1.2.3.0", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
738 ("", "", StarknetVersion::new(0, 0, 0, 0)),
739 ];
740
741 for (input, output, actual) in cases.iter() {
742 let version = StarknetVersion::from_str(input).unwrap();
743 assert_eq!(version, *actual);
744 assert_eq!(version.to_string(), *output);
745 }
746 }
747
748 #[test]
749 fn invalid_version_parsing() {
750 assert!(StarknetVersion::from_str("1.2").is_err());
751 assert!(StarknetVersion::from_str("1").is_err());
752 assert!(StarknetVersion::from_str("1.2.a").is_err());
753 }
754 }
755
756 #[test]
757 fn deployed_contract_address() {
758 let expected_contract_address = ContractAddress(felt!(
759 "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
760 ));
761 let actual_contract_address = ContractAddress::deployed_contract_address(
762 std::iter::once(CallParam(felt!(
763 "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c"
764 ))),
765 &ContractAddressSalt(felt!("0x0")),
766 &ClassHash(felt!(
767 "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"
768 )),
769 );
770 assert_eq!(actual_contract_address, expected_contract_address);
771 }
772}