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, 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 pub const MAX: BlockNumber = BlockNumber::new_or_panic(i64::MAX as u64);
287
288 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#[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 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 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
642pub fn truncated_keccak(mut plain: [u8; 32]) -> Felt {
645 plain[0] &= 0x03;
648 Felt::from_be_bytes(plain).expect("cannot overflow: smaller than modulus")
649}
650
651pub 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}