1use alloc::{format, string::*, vec::*};
2
3use serde::{de::Visitor, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
4use serde_json_pythonic::to_string_pythonic;
5use serde_with::serde_as;
6use starknet_crypto::{poseidon_hash_many, PoseidonHasher};
7
8use crate::{
9 serde::unsigned_field_element::UfeHex,
10 types::{EntryPointsByType, Felt, FlattenedSierraClass, SierraEntryPoint},
11 utils::{
12 cairo_short_string_to_felt, normalize_address, starknet_keccak, CairoShortStringToFeltError,
13 },
14};
15
16pub mod legacy;
18
19const PREFIX_CONTRACT_CLASS_V0_1_0: Felt = Felt::from_raw([
21 37302452645455172,
22 18446734822722598327,
23 15539482671244488427,
24 5800711240972404213,
25]);
26
27const PREFIX_COMPILED_CLASS_V1: Felt = Felt::from_raw([
29 324306817650036332,
30 18446744073709549462,
31 1609463842841646376,
32 2291010424822318237,
33]);
34
35#[derive(Debug, Clone, Serialize)]
37#[serde(untagged)]
38#[allow(clippy::large_enum_variant)]
39pub enum ContractArtifact {
40 SierraClass(SierraClass),
42 CompiledClass(CompiledClass),
44 LegacyClass(legacy::LegacyContractClass),
46}
47
48#[serde_as]
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
52pub struct SierraClass {
53 #[serde_as(as = "Vec<UfeHex>")]
55 pub sierra_program: Vec<Felt>,
56 pub sierra_program_debug_info: SierraClassDebugInfo,
58 pub contract_class_version: String,
60 pub entry_points_by_type: EntryPointsByType,
62 pub abi: Vec<AbiEntry>,
64}
65
66#[serde_as]
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
75pub struct CompiledClass {
76 pub prime: String,
78 pub compiler_version: String,
80 #[serde_as(as = "Vec<UfeHex>")]
82 pub bytecode: Vec<Felt>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub bytecode_segment_lengths: Option<IntOrList>,
88 pub hints: Vec<Hint>,
90 pub pythonic_hints: Option<Vec<PythonicHint>>,
93 pub entry_points_by_type: CompiledClassEntrypointList,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
100pub struct SierraClassDebugInfo {
101 pub type_names: Vec<(u64, String)>,
103 pub libfunc_names: Vec<(u64, String)>,
105 pub user_func_names: Vec<(u64, String)>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
112#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
113pub struct CompiledClassEntrypointList {
114 pub external: Vec<CompiledClassEntrypoint>,
116 pub l1_handler: Vec<CompiledClassEntrypoint>,
118 pub constructor: Vec<CompiledClassEntrypoint>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(tag = "type", rename_all = "snake_case")]
125#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
126pub enum AbiEntry {
127 Function(AbiFunction),
129 Event(AbiEvent),
131 Struct(AbiStruct),
133 Enum(AbiEnum),
135 Constructor(AbiConstructor),
137 Impl(AbiImpl),
139 Interface(AbiInterface),
141 L1Handler(AbiFunction),
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct Hint {
151 pub id: u64,
153 pub code: Vec<serde_json::Value>,
157}
158
159#[derive(Debug, Clone)]
161pub struct PythonicHint {
162 pub id: u64,
164 pub code: Vec<String>,
166}
167
168#[serde_as]
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
172pub struct CompiledClassEntrypoint {
173 #[serde_as(as = "UfeHex")]
175 pub selector: Felt,
176 pub offset: u64,
178 pub builtins: Vec<String>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
185pub struct AbiFunction {
186 pub name: String,
188 pub inputs: Vec<AbiNamedMember>,
190 pub outputs: Vec<AbiOutput>,
192 pub state_mutability: StateMutability,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
202#[serde(untagged)]
203pub enum AbiEvent {
204 Typed(TypedAbiEvent),
206 Untyped(UntypedAbiEvent),
208}
209
210#[derive(Debug, Clone, Deserialize)]
212#[serde(tag = "kind", rename_all = "snake_case")]
213#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
214pub enum TypedAbiEvent {
215 Struct(AbiEventStruct),
217 Enum(AbiEventEnum),
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
224pub struct UntypedAbiEvent {
225 pub name: String,
227 pub inputs: Vec<AbiNamedMember>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
234pub struct AbiEventStruct {
235 pub name: String,
237 pub members: Vec<EventField>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
244pub struct AbiEventEnum {
245 pub name: String,
247 pub variants: Vec<EventField>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
254pub struct AbiStruct {
255 pub name: String,
257 pub members: Vec<AbiNamedMember>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
264pub struct AbiConstructor {
265 pub name: String,
267 pub inputs: Vec<AbiNamedMember>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
274pub struct AbiImpl {
275 pub name: String,
277 pub interface_name: String,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
284pub struct AbiInterface {
285 pub name: String,
287 pub items: Vec<AbiEntry>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
294pub struct AbiEnum {
295 pub name: String,
297 pub variants: Vec<AbiNamedMember>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
304pub struct AbiNamedMember {
305 pub name: String,
307 pub r#type: String,
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
314pub struct AbiOutput {
315 pub r#type: String,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
322pub struct EventField {
323 pub name: String,
325 pub r#type: String,
327 pub kind: EventFieldKind,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
336#[serde(rename_all = "snake_case")]
337pub enum StateMutability {
338 External,
340 View,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(rename_all = "snake_case")]
347pub enum EventFieldKind {
348 Key,
350 Data,
352 Nested,
354 Flat,
356}
357
358#[derive(Debug, Clone)]
360pub enum IntOrList {
361 Int(u64),
363 List(Vec<IntOrList>),
365}
366
367struct IntOrListVisitor;
368
369enum BytecodeSegmentStructure {
371 BytecodeLeaf(BytecodeLeaf),
372 BytecodeSegmentedNode(BytecodeSegmentedNode),
373}
374
375struct BytecodeLeaf {
379 data: Vec<Felt>,
381}
382
383struct BytecodeSegmentedNode {
388 segments: Vec<BytecodeSegment>,
389}
390
391struct BytecodeSegment {
395 segment_length: u64,
396 #[allow(unused)]
397 is_used: bool,
398 inner_structure: alloc::boxed::Box<BytecodeSegmentStructure>,
399}
400
401mod errors {
402 use alloc::string::*;
403 use core::fmt::{Display, Formatter, Result};
404
405 #[derive(Debug)]
407 pub enum ComputeClassHashError {
408 InvalidBuiltinName,
411 BytecodeSegmentLengthMismatch(BytecodeSegmentLengthMismatchError),
415 InvalidBytecodeSegment(InvalidBytecodeSegmentError),
418 PcOutOfRange(PcOutOfRangeError),
421 Json(JsonError),
424 }
425
426 #[cfg(feature = "std")]
428 #[derive(Debug)]
429 pub enum CompressProgramError {
430 Json(JsonError),
432 Io(std::io::Error),
434 }
435
436 #[derive(Debug)]
439 pub struct JsonError {
440 pub(crate) message: String,
441 }
442
443 #[derive(Debug)]
446 pub struct BytecodeSegmentLengthMismatchError {
447 pub segment_length: usize,
449 pub bytecode_length: usize,
451 }
452
453 #[derive(Debug)]
455 pub struct InvalidBytecodeSegmentError {
456 pub visited_pc: u64,
458 pub segment_start: u64,
460 }
461
462 #[derive(Debug)]
465 pub struct PcOutOfRangeError {
466 pub pc: u64,
468 }
469
470 #[cfg(feature = "std")]
471 impl std::error::Error for ComputeClassHashError {}
472
473 impl Display for ComputeClassHashError {
474 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
475 match self {
476 Self::InvalidBuiltinName => write!(f, "invalid builtin name"),
477 Self::BytecodeSegmentLengthMismatch(inner) => write!(f, "{inner}"),
478 Self::InvalidBytecodeSegment(inner) => write!(f, "{inner}"),
479 Self::PcOutOfRange(inner) => write!(f, "{inner}"),
480 Self::Json(inner) => write!(f, "json serialization error: {inner}"),
481 }
482 }
483 }
484
485 #[cfg(feature = "std")]
486 impl std::error::Error for CompressProgramError {}
487
488 #[cfg(feature = "std")]
489 impl Display for CompressProgramError {
490 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
491 match self {
492 Self::Json(inner) => write!(f, "json serialization error: {inner}"),
493 Self::Io(inner) => write!(f, "compression io error: {inner}"),
494 }
495 }
496 }
497
498 #[cfg(feature = "std")]
499 impl std::error::Error for JsonError {}
500
501 impl Display for JsonError {
502 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
503 write!(f, "{}", self.message)
504 }
505 }
506
507 #[cfg(feature = "std")]
508 impl std::error::Error for BytecodeSegmentLengthMismatchError {}
509
510 impl Display for BytecodeSegmentLengthMismatchError {
511 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
512 write!(
513 f,
514 "invalid bytecode segment structure length: {}, bytecode length: {}.",
515 self.segment_length, self.bytecode_length,
516 )
517 }
518 }
519
520 #[cfg(feature = "std")]
521 impl std::error::Error for InvalidBytecodeSegmentError {}
522
523 impl Display for InvalidBytecodeSegmentError {
524 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
525 write!(
526 f,
527 "invalid segment structure: PC {} was visited, \
528 but the beginning of the segment ({}) was not",
529 self.visited_pc, self.segment_start
530 )
531 }
532 }
533
534 #[cfg(feature = "std")]
535 impl std::error::Error for PcOutOfRangeError {}
536
537 impl Display for PcOutOfRangeError {
538 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
539 write!(f, "PC {} is out of range", self.pc)
540 }
541 }
542}
543pub use errors::{
544 BytecodeSegmentLengthMismatchError, ComputeClassHashError, InvalidBytecodeSegmentError,
545 JsonError, PcOutOfRangeError,
546};
547
548#[cfg(feature = "std")]
549pub use errors::CompressProgramError;
550
551impl SierraClass {
552 pub fn class_hash(&self) -> Result<Felt, ComputeClassHashError> {
554 let abi_str = to_string_pythonic(&self.abi).map_err(|err| {
559 ComputeClassHashError::Json(JsonError {
560 message: format!("{err}"),
561 })
562 })?;
563
564 let mut hasher = PoseidonHasher::new();
565 hasher.update(PREFIX_CONTRACT_CLASS_V0_1_0);
566
567 hasher.update(hash_sierra_entrypoints(&self.entry_points_by_type.external));
569 hasher.update(hash_sierra_entrypoints(
570 &self.entry_points_by_type.l1_handler,
571 ));
572 hasher.update(hash_sierra_entrypoints(
573 &self.entry_points_by_type.constructor,
574 ));
575
576 hasher.update(starknet_keccak(abi_str.as_bytes()));
578
579 hasher.update(poseidon_hash_many(&self.sierra_program));
581
582 Ok(normalize_address(hasher.finalize()))
583 }
584
585 pub fn flatten(self) -> Result<FlattenedSierraClass, JsonError> {
602 let abi = to_string_pythonic(&self.abi).map_err(|err| JsonError {
603 message: format!("{err}"),
604 })?;
605
606 Ok(FlattenedSierraClass {
607 sierra_program: self.sierra_program,
608 entry_points_by_type: self.entry_points_by_type,
609 abi,
610 contract_class_version: self.contract_class_version,
611 })
612 }
613}
614
615impl FlattenedSierraClass {
616 pub fn class_hash(&self) -> Felt {
618 let mut hasher = PoseidonHasher::new();
619 hasher.update(PREFIX_CONTRACT_CLASS_V0_1_0);
620
621 hasher.update(hash_sierra_entrypoints(&self.entry_points_by_type.external));
623 hasher.update(hash_sierra_entrypoints(
624 &self.entry_points_by_type.l1_handler,
625 ));
626 hasher.update(hash_sierra_entrypoints(
627 &self.entry_points_by_type.constructor,
628 ));
629
630 hasher.update(starknet_keccak(self.abi.as_bytes()));
632
633 hasher.update(poseidon_hash_many(&self.sierra_program));
635
636 normalize_address(hasher.finalize())
637 }
638}
639
640impl CompiledClass {
641 pub fn class_hash(&self) -> Result<Felt, ComputeClassHashError> {
643 let mut hasher = PoseidonHasher::new();
644 hasher.update(PREFIX_COMPILED_CLASS_V1);
645
646 hasher.update(
648 Self::hash_entrypoints(&self.entry_points_by_type.external)
649 .map_err(|_| ComputeClassHashError::InvalidBuiltinName)?,
650 );
651 hasher.update(
652 Self::hash_entrypoints(&self.entry_points_by_type.l1_handler)
653 .map_err(|_| ComputeClassHashError::InvalidBuiltinName)?,
654 );
655 hasher.update(
656 Self::hash_entrypoints(&self.entry_points_by_type.constructor)
657 .map_err(|_| ComputeClassHashError::InvalidBuiltinName)?,
658 );
659
660 hasher.update(
662 if let Some(bytecode_segment_lengths) = self.bytecode_segment_lengths.clone() {
663 let mut rev_visited_pcs: Vec<u64> =
671 (0..(self.bytecode.len() as u64)).rev().collect();
672
673 let (res, total_len) = Self::create_bytecode_segment_structure_inner(
674 &self.bytecode,
675 &bytecode_segment_lengths,
676 &mut rev_visited_pcs,
677 &mut 0,
678 )?;
679
680 if total_len != self.bytecode.len() as u64 {
681 return Err(ComputeClassHashError::BytecodeSegmentLengthMismatch(
682 BytecodeSegmentLengthMismatchError {
683 segment_length: total_len as usize,
684 bytecode_length: self.bytecode.len(),
685 },
686 ));
687 }
688 if !rev_visited_pcs.is_empty() {
689 return Err(ComputeClassHashError::PcOutOfRange(PcOutOfRangeError {
690 pc: rev_visited_pcs[rev_visited_pcs.len() - 1],
691 }));
692 }
693
694 res.hash()
695 } else {
696 poseidon_hash_many(&self.bytecode)
698 },
699 );
700
701 Ok(hasher.finalize())
702 }
703
704 fn hash_entrypoints(
705 entrypoints: &[CompiledClassEntrypoint],
706 ) -> Result<Felt, CairoShortStringToFeltError> {
707 let mut hasher = PoseidonHasher::new();
708
709 for entry in entrypoints {
710 hasher.update(entry.selector);
711 hasher.update(entry.offset.into());
712
713 let mut builtin_hasher = PoseidonHasher::new();
714 for builtin in &entry.builtins {
715 builtin_hasher.update(cairo_short_string_to_felt(builtin)?)
716 }
717
718 hasher.update(builtin_hasher.finalize());
719 }
720
721 Ok(hasher.finalize())
722 }
723
724 fn create_bytecode_segment_structure_inner(
729 bytecode: &[Felt],
730 bytecode_segment_lengths: &IntOrList,
731 visited_pcs: &mut Vec<u64>,
732 bytecode_offset: &mut u64,
733 ) -> Result<(BytecodeSegmentStructure, u64), ComputeClassHashError> {
734 match bytecode_segment_lengths {
735 IntOrList::Int(bytecode_segment_lengths) => {
736 let segment_end = *bytecode_offset + bytecode_segment_lengths;
737
738 while !visited_pcs.is_empty()
740 && *bytecode_offset <= visited_pcs[visited_pcs.len() - 1]
741 && visited_pcs[visited_pcs.len() - 1] < segment_end
742 {
743 visited_pcs.pop();
744 }
745
746 Ok((
747 BytecodeSegmentStructure::BytecodeLeaf(BytecodeLeaf {
748 data: bytecode[(*bytecode_offset as usize)..(segment_end as usize)]
749 .to_vec(),
750 }),
751 *bytecode_segment_lengths,
752 ))
753 }
754 IntOrList::List(bytecode_segment_lengths) => {
755 let mut res = Vec::new();
756 let mut total_len = 0;
757
758 for item in bytecode_segment_lengths {
759 let visited_pc_before = if !visited_pcs.is_empty() {
760 Some(visited_pcs[visited_pcs.len() - 1])
761 } else {
762 None
763 };
764
765 let (current_structure, item_len) =
766 Self::create_bytecode_segment_structure_inner(
767 bytecode,
768 item,
769 visited_pcs,
770 bytecode_offset,
771 )?;
772
773 let visited_pc_after = if !visited_pcs.is_empty() {
774 Some(visited_pcs[visited_pcs.len() - 1])
775 } else {
776 None
777 };
778 let is_used = visited_pc_after != visited_pc_before;
779
780 if let Some(visited_pc_before) = visited_pc_before {
781 if is_used && visited_pc_before != *bytecode_offset {
782 return Err(ComputeClassHashError::InvalidBytecodeSegment(
783 InvalidBytecodeSegmentError {
784 visited_pc: visited_pc_before,
785 segment_start: *bytecode_offset,
786 },
787 ));
788 }
789 }
790
791 res.push(BytecodeSegment {
792 segment_length: item_len,
793 is_used,
794 inner_structure: alloc::boxed::Box::new(current_structure),
795 });
796
797 *bytecode_offset += item_len;
798 total_len += item_len;
799 }
800
801 Ok((
802 BytecodeSegmentStructure::BytecodeSegmentedNode(BytecodeSegmentedNode {
803 segments: res,
804 }),
805 total_len,
806 ))
807 }
808 }
809 }
810}
811
812impl BytecodeSegmentStructure {
813 fn hash(&self) -> Felt {
814 match self {
815 Self::BytecodeLeaf(inner) => inner.hash(),
816 Self::BytecodeSegmentedNode(inner) => inner.hash(),
817 }
818 }
819}
820
821impl BytecodeLeaf {
822 fn hash(&self) -> Felt {
823 poseidon_hash_many(&self.data)
824 }
825}
826
827impl BytecodeSegmentedNode {
828 fn hash(&self) -> Felt {
829 let mut hasher = PoseidonHasher::new();
830 for node in &self.segments {
831 hasher.update(node.segment_length.into());
832 hasher.update(node.inner_structure.hash());
833 }
834 hasher.finalize() + Felt::ONE
835 }
836}
837
838impl<'de> Deserialize<'de> for ContractArtifact {
841 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
842 where
843 D: Deserializer<'de>,
844 {
845 let temp_value = serde_json::Value::deserialize(deserializer)?;
846 if let Ok(value) = SierraClass::deserialize(&temp_value) {
847 return Ok(Self::SierraClass(value));
848 }
849 if let Ok(value) = CompiledClass::deserialize(&temp_value) {
850 return Ok(Self::CompiledClass(value));
851 }
852 if let Ok(value) = legacy::LegacyContractClass::deserialize(&temp_value) {
853 return Ok(Self::LegacyClass(value));
854 }
855 Err(serde::de::Error::custom(
856 "data did not match any variant of enum ContractArtifact",
857 ))
858 }
859}
860
861impl Serialize for PythonicHint {
862 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
863 where
864 S: Serializer,
865 {
866 let mut seq = serializer.serialize_seq(Some(2))?;
867 seq.serialize_element(&self.id)?;
868 seq.serialize_element(&self.code)?;
869 seq.end()
870 }
871}
872
873impl<'de> Deserialize<'de> for PythonicHint {
874 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
875 where
876 D: Deserializer<'de>,
877 {
878 let temp_value = serde_json::Value::deserialize(deserializer)?;
879 if let serde_json::Value::Array(mut array) = temp_value {
880 if array.len() != 2 {
881 return Err(serde::de::Error::custom("length mismatch"));
882 }
883
884 let code = array.pop().unwrap();
885 let code = Vec::<String>::deserialize(code).map_err(|err| {
886 serde::de::Error::custom(format!("unable to deserialize Location: {err}"))
887 })?;
888
889 let id = array.pop().unwrap();
890 let id = match id {
891 serde_json::Value::Number(id) => id
892 .as_u64()
893 .ok_or_else(|| serde::de::Error::custom("id value out of range"))?,
894 _ => return Err(serde::de::Error::custom("unexpected value type")),
895 };
896
897 Ok(Self { id, code })
898 } else {
899 Err(serde::de::Error::custom("expected sequence"))
900 }
901 }
902}
903
904impl Serialize for TypedAbiEvent {
906 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
907 where
908 S: Serializer,
909 {
910 #[derive(Serialize)]
911 struct StructRef<'a> {
912 name: &'a str,
913 kind: &'static str,
914 members: &'a [EventField],
915 }
916
917 #[derive(Serialize)]
918 struct EnumRef<'a> {
919 name: &'a str,
920 kind: &'static str,
921 variants: &'a [EventField],
922 }
923
924 match self {
925 Self::Struct(inner) => StructRef::serialize(
926 &StructRef {
927 name: &inner.name,
928 kind: "struct",
929 members: &inner.members,
930 },
931 serializer,
932 ),
933 Self::Enum(inner) => EnumRef::serialize(
934 &EnumRef {
935 name: &inner.name,
936 kind: "enum",
937 variants: &inner.variants,
938 },
939 serializer,
940 ),
941 }
942 }
943}
944
945impl Serialize for IntOrList {
946 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
947 where
948 S: Serializer,
949 {
950 match self {
951 Self::Int(int) => serializer.serialize_u64(*int),
952 Self::List(list) => {
953 let mut seq = serializer.serialize_seq(Some(list.len()))?;
954 for item in list {
955 seq.serialize_element(item)?;
956 }
957 seq.end()
958 }
959 }
960 }
961}
962
963impl<'de> Visitor<'de> for IntOrListVisitor {
964 type Value = IntOrList;
965
966 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
967 write!(formatter, "number or list")
968 }
969
970 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
971 where
972 E: serde::de::Error,
973 {
974 Ok(IntOrList::Int(v))
975 }
976
977 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
978 where
979 A: serde::de::SeqAccess<'de>,
980 {
981 let mut items = Vec::new();
982 while let Some(element) = seq.next_element::<IntOrList>()? {
983 items.push(element);
984 }
985 Ok(IntOrList::List(items))
986 }
987}
988
989impl<'de> Deserialize<'de> for IntOrList {
990 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
991 where
992 D: Deserializer<'de>,
993 {
994 deserializer.deserialize_any(IntOrListVisitor)
995 }
996}
997
998fn hash_sierra_entrypoints(entrypoints: &[SierraEntryPoint]) -> Felt {
999 let mut hasher = PoseidonHasher::new();
1000
1001 for entry in entrypoints {
1002 hasher.update(entry.selector);
1003 hasher.update(entry.function_idx.into());
1004 }
1005
1006 hasher.finalize()
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 use super::*;
1012
1013 #[derive(serde::Deserialize)]
1014 struct ContractHashes {
1015 sierra_class_hash: String,
1016 compiled_class_hash: String,
1017 }
1018
1019 #[test]
1020 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1021 fn test_sierra_class_deser() {
1022 for raw_artifact in [
1023 include_str!("../../../test-data/contracts/cairo1/artifacts/abi_types_sierra.txt"),
1024 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20_sierra.txt"),
1025 include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_sierra.txt"),
1026 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_sierra.txt"),
1027 include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_sierra.txt"),
1028 include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial_sierra.txt"),
1029 ] {
1030 let direct_deser = serde_json::from_str::<SierraClass>(raw_artifact).unwrap();
1031 let via_contract_artifact = match serde_json::from_str::<ContractArtifact>(raw_artifact)
1032 {
1033 Ok(ContractArtifact::SierraClass(class)) => class,
1034 _ => panic!("Unexpected result"),
1035 };
1036
1037 assert_eq!(
1039 direct_deser.class_hash().unwrap(),
1040 via_contract_artifact.class_hash().unwrap()
1041 );
1042 }
1043 }
1044
1045 #[test]
1046 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1047 fn test_compiled_class_deser() {
1048 for raw_artifact in [
1049 include_str!("../../../test-data/contracts/cairo1/artifacts/abi_types_compiled.txt"),
1050 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20_compiled.txt"),
1051 include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_compiled.txt"),
1052 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_compiled.txt"),
1053 include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_compiled.txt"),
1054 include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt"),
1055 ] {
1056 let direct_deser = serde_json::from_str::<CompiledClass>(raw_artifact).unwrap();
1057 let via_contract_artifact = match serde_json::from_str::<ContractArtifact>(raw_artifact)
1058 {
1059 Ok(ContractArtifact::CompiledClass(class)) => class,
1060 _ => panic!("Unexpected result"),
1061 };
1062
1063 assert_eq!(
1065 direct_deser.class_hash().unwrap(),
1066 via_contract_artifact.class_hash().unwrap()
1067 );
1068 }
1069 }
1070
1071 #[test]
1072 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1073 fn test_legacy_class_deser() {
1074 match serde_json::from_str::<ContractArtifact>(include_str!(
1075 "../../../test-data/contracts/cairo0/artifacts/oz_account.txt"
1076 )) {
1077 Ok(ContractArtifact::LegacyClass(_)) => {}
1078 _ => panic!("Unexpected result"),
1079 }
1080 }
1081
1082 #[test]
1083 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1084 fn test_sierra_class_hash() {
1085 for (raw_artifact, raw_hashes) in [
1086 (
1087 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20_sierra.txt"),
1088 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20.hashes.json"),
1089 ),
1090 (
1091 include_str!("../../../test-data/contracts/cairo1/artifacts/abi_types_sierra.txt"),
1092 include_str!("../../../test-data/contracts/cairo1/artifacts/abi_types.hashes.json"),
1093 ),
1094 (
1095 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_sierra.txt"),
1096 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20.hashes.json"),
1097 ),
1098 (
1099 include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_sierra.txt"),
1100 include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types.hashes.json"),
1101 ),
1102 ] {
1103 let sierra_class = serde_json::from_str::<SierraClass>(raw_artifact).unwrap();
1104 let computed_hash = sierra_class.class_hash().unwrap();
1105
1106 let hashes: ContractHashes = serde_json::from_str(raw_hashes).unwrap();
1107 let expected_hash = Felt::from_hex(&hashes.sierra_class_hash).unwrap();
1108
1109 assert_eq!(computed_hash, expected_hash);
1110 }
1111 }
1112
1113 #[test]
1114 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1115 fn test_compiled_class_hash() {
1116 for (raw_artifact, raw_hashes) in [
1117 (
1118 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20_compiled.txt"),
1119 include_str!("../../../test-data/contracts/cairo1/artifacts/erc20.hashes.json"),
1120 ),
1121 (
1122 include_str!(
1123 "../../../test-data/contracts/cairo1/artifacts/abi_types_compiled.txt"
1124 ),
1125 include_str!("../../../test-data/contracts/cairo1/artifacts/abi_types.hashes.json"),
1126 ),
1127 (
1128 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_compiled.txt"),
1129 include_str!("../../../test-data/contracts/cairo2/artifacts/erc20.hashes.json"),
1130 ),
1131 (
1132 include_str!(
1133 "../../../test-data/contracts/cairo2/artifacts/abi_types_compiled.txt"
1134 ),
1135 include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types.hashes.json"),
1136 ),
1137 (
1138 include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_compiled.txt"),
1139 include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20.hashes.json"),
1140 ),
1141 (
1142 include_str!(
1143 "../../../test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt"
1144 ),
1145 include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial.hashes.json"),
1146 ),
1147 ] {
1148 let compiled_class = serde_json::from_str::<CompiledClass>(raw_artifact).unwrap();
1149 let computed_hash = compiled_class.class_hash().unwrap();
1150
1151 let hashes: ContractHashes = serde_json::from_str(raw_hashes).unwrap();
1152 let expected_hash = Felt::from_hex(&hashes.compiled_class_hash).unwrap();
1153
1154 assert_eq!(computed_hash, expected_hash);
1155 }
1156 }
1157}