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 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 pub const ONE: ContractAddress = contract_address!("0x1");
48 pub const TWO: ContractAddress = contract_address!("0x2");
57 pub const SYSTEM: [ContractAddress; 2] = [ContractAddress::ONE, ContractAddress::TWO];
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct ContractClass {
64 pub program: String,
66 pub entry_points_by_type: serde_json::Value,
71}
72
73impl EntryPoint {
74 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 pub const CONSTRUCTOR: Self =
88 entry_point!("0x028FFE4FF0F226A9107253E17A904099AA4F63A02A5621DE0576E5AA71BC5194");
89}
90
91impl StateCommitment {
92 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#[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#[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#[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#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
184pub struct GasPrice(pub u128);
185
186#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
188pub struct GasPriceHex(pub GasPrice);
189
190#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
192pub struct ResourceAmount(pub u64);
193
194#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
197pub struct Tip(pub u64);
198
199#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Dummy)]
201pub struct TipHex(pub Tip);
202
203#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
205pub struct ResourcePricePerUnit(pub u128);
206
207#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Dummy)]
209pub struct TransactionVersion(pub Felt);
210
211impl TransactionVersion {
212 pub fn is_zero(&self) -> bool {
214 self.without_query_version() == 0
215 }
216
217 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#[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 pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
281
282 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#[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 pub fn to_be_bytes(&self) -> [u8; 16] {
356 self.0.to_be_bytes()
357 }
358
359 pub fn from_be_bytes(src: [u8; 16]) -> Self {
362 Self(u128::from_be_bytes(src))
363 }
364
365 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub enum EthereumChain {
415 Mainnet,
416 Sepolia,
417 Other(primitive_types::U256),
418}
419
420#[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 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 pub fn to_hex_str(&self) -> std::borrow::Cow<'static, str> {
444 self.0.to_hex_str()
445 }
446
447 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 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 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
651pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
654 plain[0] &= 0x03;
657 Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
658}
659
660pub 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 pub highest_decision: Option<(BlockNumber, ProposalCommitment)>,
681 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}