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 consts;
20pub mod event;
21pub mod hash;
22mod header;
23pub mod integration_testing;
24mod l1;
25mod macros;
26pub mod prelude;
27pub mod receipt;
28pub mod signature;
29pub mod state_update;
30pub mod test_utils;
31pub mod transaction;
32pub mod trie;
33
34pub use header::{BlockHeader, BlockHeaderBuilder, L1DataAvailabilityMode, SignedBlockHeader};
35pub use l1::{L1BlockNumber, L1TransactionHash};
36pub use signature::BlockCommitmentSignature;
37pub use state_update::StateUpdate;
38
39impl ContractAddress {
40 pub const ONE: ContractAddress = contract_address!("0x1");
46 pub const TWO: ContractAddress = contract_address!("0x2");
55 pub const SYSTEM: [ContractAddress; 2] = [ContractAddress::ONE, ContractAddress::TWO];
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct ContractClass {
62 pub program: String,
64 pub entry_points_by_type: serde_json::Value,
69}
70
71impl EntryPoint {
72 pub fn hashed(input: &[u8]) -> Self {
77 use sha3::Digest;
78 EntryPoint(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
79 input,
80 ))))
81 }
82
83 pub const CONSTRUCTOR: Self =
86 entry_point!("0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194");
87}
88
89impl StateCommitment {
90 pub fn calculate(
97 storage_commitment: StorageCommitment,
98 class_commitment: ClassCommitment,
99 ) -> Self {
100 if class_commitment == ClassCommitment::ZERO {
101 Self(storage_commitment.0)
102 } else {
103 const GLOBAL_STATE_VERSION: Felt = felt_bytes!(b"STARKNET_STATE_V0");
104
105 StateCommitment(
106 pathfinder_crypto::hash::poseidon::poseidon_hash_many(&[
107 GLOBAL_STATE_VERSION.into(),
108 storage_commitment.0.into(),
109 class_commitment.0.into(),
110 ])
111 .into(),
112 )
113 }
114 }
115}
116
117impl StorageAddress {
118 pub fn from_name(input: &[u8]) -> Self {
119 use sha3::Digest;
120 Self(truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(
121 input,
122 ))))
123 }
124
125 pub fn from_map_name_and_key(name: &[u8], key: Felt) -> Self {
126 use sha3::Digest;
127
128 let intermediate = truncated_keccak(<[u8; 32]>::from(sha3::Keccak256::digest(name)));
129 let value = pathfinder_crypto::hash::pedersen_hash(intermediate, key);
130
131 let value = primitive_types::U256::from_big_endian(value.as_be_bytes());
132 let max_address = primitive_types::U256::from_str_radix(
133 "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
134 16,
135 )
136 .unwrap();
137
138 let value = value.rem(max_address);
139 let mut b = [0u8; 32];
140 value.to_big_endian(&mut b);
141 Self(Felt::from_be_slice(&b).expect("Truncated value should fit into a felt"))
142 }
143}
144
145#[derive(Copy, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub struct BlockNumber(u64);
148
149macros::i64_backed_u64::new_get_partialeq!(BlockNumber);
150macros::i64_backed_u64::serdes!(BlockNumber);
151
152impl From<BlockNumber> for Felt {
153 fn from(x: BlockNumber) -> Self {
154 Felt::from(x.0)
155 }
156}
157
158impl std::iter::Iterator for BlockNumber {
159 type Item = BlockNumber;
160
161 fn next(&mut self) -> Option<Self::Item> {
162 Some(*self + 1)
163 }
164}
165
166#[derive(Copy, Debug, Clone, PartialEq, Eq, Default)]
168pub struct BlockTimestamp(u64);
169
170macros::i64_backed_u64::new_get_partialeq!(BlockTimestamp);
171macros::i64_backed_u64::serdes!(BlockTimestamp);
172
173#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
175pub struct TransactionIndex(u64);
176
177macros::i64_backed_u64::new_get_partialeq!(TransactionIndex);
178macros::i64_backed_u64::serdes!(TransactionIndex);
179
180#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
182pub struct GasPrice(pub u128);
183
184#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
186pub struct GasPriceHex(pub GasPrice);
187
188#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
190pub struct ResourceAmount(pub u64);
191
192#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
195pub struct Tip(pub u64);
196
197#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Dummy)]
199pub struct TipHex(pub Tip);
200
201#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
203pub struct ResourcePricePerUnit(pub u128);
204
205#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
207pub struct TransactionVersion(pub Felt);
208
209impl TransactionVersion {
210 pub fn is_zero(&self) -> bool {
212 self.without_query_version() == 0
213 }
214
215 pub fn without_query_version(&self) -> u128 {
221 let lower = &self.0.as_be_bytes()[16..];
222 u128::from_be_bytes(lower.try_into().expect("slice should be the right length"))
223 }
224
225 pub const fn with_query_version(self) -> Self {
226 let mut bytes = self.0.to_be_bytes();
227 bytes[15] |= 0b0000_0001;
228
229 let felt = match Felt::from_be_bytes(bytes) {
230 Ok(x) => x,
231 Err(_) => panic!("Adding query bit to transaction version failed."),
232 };
233 Self(felt)
234 }
235
236 pub const fn has_query_version(&self) -> bool {
237 self.0.as_be_bytes()[15] & 0b0000_0001 != 0
238 }
239
240 pub fn with_query_only(self, query_only: bool) -> Self {
241 if query_only {
242 self.with_query_version()
243 } else {
244 Self(self.without_query_version().into())
245 }
246 }
247
248 pub const ZERO: Self = Self(Felt::ZERO);
249 pub const ONE: Self = Self(Felt::from_u64(1));
250 pub const TWO: Self = Self(Felt::from_u64(2));
251 pub const THREE: Self = Self(Felt::from_u64(3));
252 pub const ZERO_WITH_QUERY_VERSION: Self = Self::ZERO.with_query_version();
253 pub const ONE_WITH_QUERY_VERSION: Self = Self::ONE.with_query_version();
254 pub const TWO_WITH_QUERY_VERSION: Self = Self::TWO.with_query_version();
255 pub const THREE_WITH_QUERY_VERSION: Self = Self::THREE.with_query_version();
256}
257
258#[derive(Debug, Copy, Clone, PartialEq, Eq)]
262pub enum BlockId {
263 Number(BlockNumber),
264 Hash(BlockHash),
265 Latest,
266}
267
268impl BlockId {
269 pub fn is_latest(&self) -> bool {
270 self == &Self::Latest
271 }
272}
273
274impl BlockNumber {
275 pub const GENESIS: BlockNumber = BlockNumber::new_or_panic(0);
276 pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
279
280 pub fn parent(&self) -> Option<Self> {
283 if self == &Self::GENESIS {
284 None
285 } else {
286 Some(*self - 1)
287 }
288 }
289
290 pub fn is_zero(&self) -> bool {
291 self == &Self::GENESIS
292 }
293
294 pub fn checked_add(&self, rhs: u64) -> Option<Self> {
295 Self::new(self.0.checked_add(rhs)?)
296 }
297
298 pub fn checked_sub(&self, rhs: u64) -> Option<Self> {
299 self.0.checked_sub(rhs).map(Self)
300 }
301
302 pub fn saturating_sub(&self, rhs: u64) -> Self {
303 Self(self.0.saturating_sub(rhs))
304 }
305}
306
307impl std::ops::Add<u64> for BlockNumber {
308 type Output = BlockNumber;
309
310 fn add(self, rhs: u64) -> Self::Output {
311 Self(self.0 + rhs)
312 }
313}
314
315impl std::ops::AddAssign<u64> for BlockNumber {
316 fn add_assign(&mut self, rhs: u64) {
317 self.0 += rhs;
318 }
319}
320
321impl std::ops::Sub<u64> for BlockNumber {
322 type Output = BlockNumber;
323
324 fn sub(self, rhs: u64) -> Self::Output {
325 Self(self.0 - rhs)
326 }
327}
328
329impl std::ops::SubAssign<u64> for BlockNumber {
330 fn sub_assign(&mut self, rhs: u64) {
331 self.0 -= rhs;
332 }
333}
334
335#[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 pub fn to_be_bytes(&self) -> [u8; 16] {
354 self.0.to_be_bytes()
355 }
356
357 pub fn from_be_bytes(src: [u8; 16]) -> Self {
360 Self(u128::from_be_bytes(src))
361 }
362
363 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412pub enum EthereumChain {
413 Mainnet,
414 Sepolia,
415 Other(primitive_types::U256),
416}
417
418#[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 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 pub fn to_hex_str(&self) -> std::borrow::Cow<'static, str> {
442 self.0.to_hex_str()
443 }
444
445 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 pub const V_0_13_4: Self = Self::new(0, 13, 4, 0);
490}
491
492impl FromStr for StarknetVersion {
493 type Err = anyhow::Error;
494
495 fn from_str(s: &str) -> Result<Self, Self::Err> {
496 if s.is_empty() {
497 return Ok(StarknetVersion::new(0, 0, 0, 0));
498 }
499
500 let parts: Vec<_> = s.split('.').collect();
501 anyhow::ensure!(
502 parts.len() == 3 || parts.len() == 4,
503 "Invalid version string, expected 3 or 4 parts but got {}",
504 parts.len()
505 );
506
507 let a = parts[0].parse()?;
508 let b = parts[1].parse()?;
509 let c = parts[2].parse()?;
510 let d = parts.get(3).map(|x| x.parse()).transpose()?.unwrap_or(0);
511
512 Ok(StarknetVersion(a, b, c, d))
513 }
514}
515
516impl Display for StarknetVersion {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 if self.0 == 0 && self.1 == 0 && self.2 == 0 && self.3 == 0 {
519 return Ok(());
520 }
521 if self.3 == 0 {
522 write!(f, "{}.{}.{}", self.0, self.1, self.2)
523 } else {
524 write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
525 }
526 }
527}
528
529macros::felt_newtypes!(
530 [
531 AccountDeploymentDataElem,
532 BlockHash,
533 ByteCodeOffset,
534 BlockCommitmentSignatureElem,
535 CallParam,
536 CallResultValue,
537 ClassCommitment,
538 ClassCommitmentLeafHash,
539 ConstructorParam,
540 ContractAddressSalt,
541 ContractNonce,
542 ContractStateHash,
543 ContractRoot,
544 EntryPoint,
545 EventCommitment,
546 EventData,
547 EventKey,
548 Fee,
549 L1ToL2MessageNonce,
550 L1ToL2MessagePayloadElem,
551 L2ToL1MessagePayloadElem,
552 PaymasterDataElem,
553 ProposalCommitment,
554 PublicKey,
555 SequencerAddress,
556 StateCommitment,
557 StateDiffCommitment,
558 StorageCommitment,
559 StorageValue,
560 TransactionCommitment,
561 ReceiptCommitment,
562 TransactionHash,
563 TransactionNonce,
564 TransactionSignatureElem,
565 ];
566 [
567 CasmHash,
568 ClassHash,
569 ContractAddress,
570 SierraHash,
571 StorageAddress,
572 ]
573);
574
575macros::fmt::thin_display!(BlockNumber);
576macros::fmt::thin_display!(BlockTimestamp);
577
578impl ContractAddress {
579 pub fn deployed_contract_address(
580 constructor_calldata: impl Iterator<Item = CallParam>,
581 contract_address_salt: &ContractAddressSalt,
582 class_hash: &ClassHash,
583 ) -> Self {
584 let constructor_calldata_hash = constructor_calldata
585 .fold(HashChain::default(), |mut h, param| {
586 h.update(param.0);
587 h
588 })
589 .finalize();
590
591 let contract_address = [
592 Felt::from_be_slice(b"STARKNET_CONTRACT_ADDRESS").expect("prefix is convertible"),
593 Felt::ZERO,
594 contract_address_salt.0,
595 class_hash.0,
596 constructor_calldata_hash,
597 ]
598 .into_iter()
599 .fold(HashChain::default(), |mut h, e| {
600 h.update(e);
601 h
602 })
603 .finalize();
604
605 const MAX_CONTRACT_ADDRESS: Felt =
607 felt!("0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00");
608 let contract_address = if contract_address >= MAX_CONTRACT_ADDRESS {
609 contract_address - MAX_CONTRACT_ADDRESS
610 } else {
611 contract_address
612 };
613
614 ContractAddress::new_or_panic(contract_address)
615 }
616
617 pub fn is_system_contract(&self) -> bool {
618 (*self == ContractAddress::ONE) || (*self == ContractAddress::TWO)
619 }
620}
621
622impl From<ContractAddress> for Vec<u8> {
623 fn from(value: ContractAddress) -> Self {
624 value.0.to_be_bytes().to_vec()
625 }
626}
627
628#[derive(Clone, Debug, PartialEq)]
629pub enum AllowedOrigins {
630 Any,
631 List(Vec<String>),
632}
633
634impl<S> From<S> for AllowedOrigins
635where
636 S: ToString,
637{
638 fn from(value: S) -> Self {
639 let s = value.to_string();
640
641 if s == "*" {
642 Self::Any
643 } else {
644 Self::List(vec![s])
645 }
646 }
647}
648
649pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
652 plain[0] &= 0x03;
655 Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
656}
657
658pub fn calculate_class_commitment_leaf_hash(
662 compiled_class_hash: CasmHash,
663) -> ClassCommitmentLeafHash {
664 const CONTRACT_CLASS_HASH_VERSION: pathfinder_crypto::Felt =
665 felt_bytes!(b"CONTRACT_CLASS_LEAF_V0");
666 ClassCommitmentLeafHash(
667 pathfinder_crypto::hash::poseidon_hash(
668 CONTRACT_CLASS_HASH_VERSION.into(),
669 compiled_class_hash.0.into(),
670 )
671 .into(),
672 )
673}
674
675#[derive(Debug, Clone, Copy)]
676pub struct ConsensusInfo {
677 pub highest_decided_height: BlockNumber,
678 pub highest_decided_value: ProposalCommitment,
679}
680
681#[cfg(test)]
682mod tests {
683 use crate::{felt, CallParam, ClassHash, ContractAddress, ContractAddressSalt};
684
685 #[test]
686 fn constructor_entry_point() {
687 use sha3::{Digest, Keccak256};
688
689 use crate::{truncated_keccak, EntryPoint};
690
691 let mut keccak = Keccak256::default();
692 keccak.update(b"constructor");
693 let expected = EntryPoint(truncated_keccak(<[u8; 32]>::from(keccak.finalize())));
694
695 assert_eq!(EntryPoint::CONSTRUCTOR, expected);
696 }
697
698 mod starknet_version {
699 use std::str::FromStr;
700
701 use super::super::StarknetVersion;
702
703 #[test]
704 fn valid_version_parsing() {
705 let cases = [
706 ("1.2.3.4", "1.2.3.4", StarknetVersion::new(1, 2, 3, 4)),
707 ("1.2.3", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
708 ("1.2.3.0", "1.2.3", StarknetVersion::new(1, 2, 3, 0)),
709 ("", "", StarknetVersion::new(0, 0, 0, 0)),
710 ];
711
712 for (input, output, actual) in cases.iter() {
713 let version = StarknetVersion::from_str(input).unwrap();
714 assert_eq!(version, *actual);
715 assert_eq!(version.to_string(), *output);
716 }
717 }
718
719 #[test]
720 fn invalid_version_parsing() {
721 assert!(StarknetVersion::from_str("1.2").is_err());
722 assert!(StarknetVersion::from_str("1").is_err());
723 assert!(StarknetVersion::from_str("1.2.a").is_err());
724 }
725 }
726
727 #[test]
728 fn deployed_contract_address() {
729 let expected_contract_address = ContractAddress(felt!(
730 "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"
731 ));
732 let actual_contract_address = ContractAddress::deployed_contract_address(
733 std::iter::once(CallParam(felt!(
734 "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c"
735 ))),
736 &ContractAddressSalt(felt!("0x0")),
737 &ClassHash(felt!(
738 "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"
739 )),
740 );
741 assert_eq!(actual_contract_address, expected_contract_address);
742 }
743}