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;
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 pub const ONE: ContractAddress = contract_address!("0x1");
45 pub const TWO: ContractAddress = contract_address!("0x2");
54 pub const SYSTEM: [ContractAddress; 2] = [ContractAddress::ONE, ContractAddress::TWO];
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct ContractClass {
61 pub program: String,
63 pub entry_points_by_type: serde_json::Value,
68}
69
70impl EntryPoint {
71 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 pub const CONSTRUCTOR: Self =
85 entry_point!("0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194");
86}
87
88impl StateCommitment {
89 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#[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#[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#[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#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
181pub struct GasPrice(pub u128);
182
183#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
185pub struct GasPriceHex(pub GasPrice);
186
187#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
189pub struct ResourceAmount(pub u64);
190
191#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
194pub struct Tip(pub u64);
195
196#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Dummy)]
198pub struct TipHex(pub Tip);
199
200#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
202pub struct ResourcePricePerUnit(pub u128);
203
204#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
206pub struct TransactionVersion(pub Felt);
207
208impl TransactionVersion {
209 pub fn is_zero(&self) -> bool {
211 self.without_query_version() == 0
212 }
213
214 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#[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 pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
278
279 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#[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 pub fn to_be_bytes(&self) -> [u8; 16] {
353 self.0.to_be_bytes()
354 }
355
356 pub fn from_be_bytes(src: [u8; 16]) -> Self {
359 Self(u128::from_be_bytes(src))
360 }
361
362 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411pub enum EthereumChain {
412 Mainnet,
413 Sepolia,
414 Other(primitive_types::U256),
415}
416
417#[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 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 pub fn to_hex_str(&self) -> std::borrow::Cow<'static, str> {
441 self.0.to_hex_str()
442 }
443
444 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 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 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
647pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
650 plain[0] &= 0x03;
653 Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
654}
655
656pub 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}