1use super::cairo_runner::ExecutionResources;
2use crate::types::builtin_name::BuiltinName;
3use crate::vm::errors::cairo_pie_errors::CairoPieValidationError;
4use std::collections::{BTreeMap, HashMap};
5
6use crate::{
7 types::relocatable::{MaybeRelocatable, Relocatable},
8 Felt252,
9};
10use num_traits::{One, Zero};
11use serde::{Deserialize, Serialize};
12use {
13 std::{fs::File, io::Write, path::Path},
14 zip::ZipWriter,
15};
16
17const CAIRO_PIE_VERSION: &str = "1.1";
18
19#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
20pub struct SegmentInfo {
21 pub index: isize,
22 pub size: usize,
23}
24
25impl From<(isize, usize)> for SegmentInfo {
26 fn from(value: (isize, usize)) -> Self {
27 SegmentInfo {
28 index: value.0,
29 size: value.1,
30 }
31 }
32}
33
34#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
38pub struct CairoPieMemory(
39 #[serde(serialize_with = "serde_impl::serialize_memory")]
40 pub Vec<((usize, usize), MaybeRelocatable)>,
41);
42
43impl PartialEq for CairoPieMemory {
44 fn eq(&self, other: &Self) -> bool {
45 fn as_hashmap(
46 cairo_pie_memory: &CairoPieMemory,
47 ) -> HashMap<&(usize, usize), &MaybeRelocatable> {
48 cairo_pie_memory
49 .0
50 .iter()
51 .map(|tuple| (&tuple.0, &tuple.1))
52 .collect::<HashMap<&(usize, usize), &MaybeRelocatable>>()
53 }
54 as_hashmap(self) == as_hashmap(other)
55 }
56}
57
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
59pub struct PublicMemoryPage {
60 pub start: usize,
61 pub size: usize,
62}
63
64impl From<&Vec<usize>> for PublicMemoryPage {
65 fn from(vec: &Vec<usize>) -> Self {
66 Self {
67 start: vec[0],
68 size: vec[1],
69 }
70 }
71}
72
73pub type Attributes = BTreeMap<String, Vec<usize>>;
75pub type Pages = BTreeMap<usize, PublicMemoryPage>;
76
77#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
78pub struct OutputBuiltinAdditionalData {
79 #[serde(with = "serde_impl::pages")]
80 pub pages: Pages,
81 pub attributes: Attributes,
82}
83
84#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
85#[serde(untagged)]
86pub enum BuiltinAdditionalData {
87 Empty([(); 0]),
89 #[serde(with = "serde_impl::hash_additional_data")]
91 Hash(Vec<Relocatable>),
92 Output(OutputBuiltinAdditionalData),
93 #[serde(with = "serde_impl::signature_additional_data")]
95 Signature(BTreeMap<Relocatable, (Felt252, Felt252)>),
96 None,
97}
98
99impl BuiltinAdditionalData {
100 fn is_empty(&self) -> bool {
101 match self {
102 Self::Empty(_) => true,
103 Self::Hash(data) => data.is_empty(),
104 Self::Signature(data) => data.is_empty(),
105 Self::Output(_) => false,
106 Self::None => false,
107 }
108 }
109}
110
111impl PartialEq for BuiltinAdditionalData {
112 fn eq(&self, other: &BuiltinAdditionalData) -> bool {
113 match (self, other) {
114 (Self::Hash(data), Self::Hash(other_data)) => data == other_data,
115 (Self::Signature(data), Self::Signature(other_data)) => data == other_data,
116 (Self::Output(data), Self::Output(other_data)) => data == other_data,
117 (Self::None, Self::None) => true,
118 (Self::Empty(_), x) | (x, Self::Empty(_)) => x.is_empty(),
119 _ => false,
120 }
121 }
122}
123
124#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
125pub struct CairoPieAdditionalData(
126 #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")]
127 pub BTreeMap<BuiltinName, BuiltinAdditionalData>,
128);
129
130#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
131pub struct CairoPie {
132 pub metadata: CairoPieMetadata,
133 pub memory: CairoPieMemory,
134 pub execution_resources: ExecutionResources,
135 pub additional_data: CairoPieAdditionalData,
136 pub version: CairoPieVersion,
137}
138
139#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
140pub struct CairoPieMetadata {
141 pub program: StrippedProgram,
142 pub program_segment: SegmentInfo,
143 pub execution_segment: SegmentInfo,
144 pub ret_fp_segment: SegmentInfo,
145 pub ret_pc_segment: SegmentInfo,
146 #[serde(serialize_with = "serde_impl::serialize_builtin_segments")]
147 pub builtin_segments: BTreeMap<BuiltinName, SegmentInfo>,
148 pub extra_segments: Vec<SegmentInfo>,
149}
150
151#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
152pub struct StrippedProgram {
153 #[serde(with = "serde_impl::program_data")]
154 pub data: Vec<MaybeRelocatable>,
155 pub builtins: Vec<BuiltinName>,
156 pub main: usize,
157 #[serde(with = "serde_impl::prime")]
159 pub prime: (),
160}
161
162#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
163pub struct CairoPieVersion {
164 #[serde(with = "serde_impl::version")]
166 pub cairo_pie: (),
167}
168
169impl CairoPieMetadata {
170 pub(crate) fn run_validity_checks(&self) -> Result<(), CairoPieValidationError> {
171 if self.program.main > self.program.data.len() {
172 return Err(CairoPieValidationError::InvalidMainAddress);
173 }
174 if self.program.data.len() != self.program_segment.size {
175 return Err(CairoPieValidationError::ProgramLenVsSegmentSizeMismatch);
176 }
177 if self.builtin_segments.len() != self.program.builtins.len()
178 || !self
179 .program
180 .builtins
181 .iter()
182 .all(|b| self.builtin_segments.contains_key(b))
183 {
184 return Err(CairoPieValidationError::BuiltinListVsSegmentsMismatch);
185 }
186 if !self.ret_fp_segment.size.is_zero() {
187 return Err(CairoPieValidationError::InvalidRetFpSegmentSize);
188 }
189 if !self.ret_pc_segment.size.is_zero() {
190 return Err(CairoPieValidationError::InvalidRetPcSegmentSize);
191 }
192 self.validate_segment_order()
193 }
194
195 fn validate_segment_order(&self) -> Result<(), CairoPieValidationError> {
196 if !self.program_segment.index.is_zero() {
197 return Err(CairoPieValidationError::InvalidProgramSegmentIndex);
198 }
199 if !self.execution_segment.index.is_one() {
200 return Err(CairoPieValidationError::InvalidExecutionSegmentIndex);
201 }
202 for (i, builtin_name) in self.program.builtins.iter().enumerate() {
203 if self.builtin_segments[builtin_name].index != 2 + i as isize {
205 return Err(CairoPieValidationError::InvalidBuiltinSegmentIndex(
206 *builtin_name,
207 ));
208 }
209 }
210 let n_builtins = self.program.builtins.len() as isize;
211 if self.ret_fp_segment.index != n_builtins + 2 {
212 return Err(CairoPieValidationError::InvalidRetFpSegmentIndex);
213 }
214 if self.ret_pc_segment.index != n_builtins + 3 {
215 return Err(CairoPieValidationError::InvalidRetPcSegmentIndex);
216 }
217 for (i, segment) in self.extra_segments.iter().enumerate() {
218 if segment.index != 4 + n_builtins + i as isize {
219 return Err(CairoPieValidationError::InvalidExtraSegmentIndex);
220 }
221 }
222 Ok(())
223 }
224}
225
226impl CairoPie {
227 pub fn run_validity_checks(&self) -> Result<(), CairoPieValidationError> {
229 self.metadata.run_validity_checks()?;
230 self.run_memory_validity_checks()?;
231 if self.execution_resources.builtin_instance_counter.len()
232 != self.metadata.program.builtins.len()
233 || !self.metadata.program.builtins.iter().all(|b| {
234 self.execution_resources
235 .builtin_instance_counter
236 .contains_key(b)
237 })
238 {
239 return Err(CairoPieValidationError::BuiltinListVsSegmentsMismatch);
240 }
241 Ok(())
242 }
243
244 fn run_memory_validity_checks(&self) -> Result<(), CairoPieValidationError> {
245 let mut segment_sizes = vec![
246 &self.metadata.program_segment,
247 &self.metadata.execution_segment,
248 &self.metadata.ret_fp_segment,
249 &self.metadata.ret_pc_segment,
250 ];
251 segment_sizes.extend(self.metadata.builtin_segments.values());
252 segment_sizes.extend(self.metadata.extra_segments.iter());
253 let segment_sizes: HashMap<isize, usize> =
254 HashMap::from_iter(segment_sizes.iter().map(|si| (si.index, si.size)));
255
256 let validate_addr = |addr: Relocatable| -> Result<(), CairoPieValidationError> {
257 if segment_sizes
258 .get(&addr.segment_index)
259 .is_none_or(|size| addr.offset >= *size)
260 {
261 return Err(CairoPieValidationError::InvalidAddress);
262 }
263 Ok(())
264 };
265
266 for ((si, so), _) in self.memory.0.iter() {
267 validate_addr((*si as isize, *so).into())?;
268 }
269 Ok(())
270 }
271
272 pub fn check_pie_compatibility(&self, pie: &CairoPie) -> Result<(), CairoPieValidationError> {
275 if self.metadata != pie.metadata {
276 return Err(CairoPieValidationError::DiffMetadata);
277 }
278 if self.memory != pie.memory {
279 return Err(CairoPieValidationError::DiffMemory);
280 }
281 if self.execution_resources.n_steps != pie.execution_resources.n_steps
282 || self.execution_resources.builtin_instance_counter
283 != pie.execution_resources.builtin_instance_counter
284 {
285 return Err(CairoPieValidationError::DiffExecutionResources);
286 }
287 if self.additional_data.0.len() != pie.additional_data.0.len() {
288 return Err(CairoPieValidationError::DiffAdditionalData);
289 }
290 for (name, data) in self.additional_data.0.iter() {
291 if *name == BuiltinName::pedersen {
293 continue;
294 }
295 if !pie.additional_data.0.get(name).is_some_and(|d| d == data) {
296 return Err(CairoPieValidationError::DiffAdditionalDataForBuiltin(*name));
297 }
298 }
299 Ok(())
300 }
301
302 pub fn write_zip_file(
303 &self,
304 file_path: &Path,
305 merge_extra_segments: bool,
306 ) -> Result<(), std::io::Error> {
307 let mut metadata = self.metadata.clone();
308
309 let segment_offsets = if merge_extra_segments {
310 if let Some((segment, segment_offsets)) = self.merge_extra_segments() {
311 metadata.extra_segments = vec![segment];
312 Some(segment_offsets)
313 } else {
314 None
315 }
316 } else {
317 None
318 };
319
320 let file = File::create(file_path)?;
321 let mut zip_writer = ZipWriter::new(file);
322 let options = zip::write::FileOptions::default()
323 .compression_method(zip::CompressionMethod::Deflated)
324 .large_file(true);
325
326 zip_writer.start_file("version.json", options)?;
327 serde_json::to_writer(&mut zip_writer, &self.version)?;
328 zip_writer.start_file("metadata.json", options)?;
329 serde_json::to_writer(&mut zip_writer, &metadata)?;
330 zip_writer.start_file("memory.bin", options)?;
331 zip_writer.write_all(&self.memory.to_bytes(segment_offsets))?;
332 zip_writer.start_file("additional_data.json", options)?;
333 serde_json::to_writer(&mut zip_writer, &self.additional_data)?;
334 zip_writer.start_file("execution_resources.json", options)?;
335 serde_json::to_writer(&mut zip_writer, &self.execution_resources)?;
336 zip_writer.finish()?;
337 Ok(())
338 }
339
340 pub fn from_zip_archive<R: std::io::Read + std::io::Seek>(
341 mut zip_reader: zip::ZipArchive<R>,
342 ) -> Result<CairoPie, std::io::Error> {
343 use std::io::Read;
344
345 let version = match zip_reader.by_name("version.json") {
346 Ok(version_buffer) => {
347 let reader = std::io::BufReader::new(version_buffer);
348 serde_json::from_reader(reader)?
349 }
350 Err(_) => CairoPieVersion { cairo_pie: () },
351 };
352
353 let reader = std::io::BufReader::new(zip_reader.by_name("metadata.json")?);
354 let metadata: CairoPieMetadata = serde_json::from_reader(reader)?;
355
356 let mut memory = vec![];
357 zip_reader.by_name("memory.bin")?.read_to_end(&mut memory)?;
358 let memory = CairoPieMemory::from_bytes(&memory)
359 .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?;
360
361 let reader = std::io::BufReader::new(zip_reader.by_name("execution_resources.json")?);
362 let execution_resources: ExecutionResources = serde_json::from_reader(reader)?;
363
364 let reader = std::io::BufReader::new(zip_reader.by_name("additional_data.json")?);
365 let additional_data: CairoPieAdditionalData = serde_json::from_reader(reader)?;
366
367 Ok(CairoPie {
368 metadata,
369 memory,
370 execution_resources,
371 additional_data,
372 version,
373 })
374 }
375
376 pub fn from_bytes(bytes: &[u8]) -> Result<Self, std::io::Error> {
377 let reader = std::io::Cursor::new(bytes);
378 let zip_archive = zip::ZipArchive::new(reader)?;
379
380 Self::from_zip_archive(zip_archive)
381 }
382
383 pub fn read_zip_file(path: &Path) -> Result<Self, std::io::Error> {
384 let file = File::open(path)?;
385 let zip = zip::ZipArchive::new(file)?;
386
387 Self::from_zip_archive(zip)
388 }
389
390 fn merge_extra_segments(&self) -> Option<(SegmentInfo, HashMap<usize, Relocatable>)> {
397 if self.metadata.extra_segments.is_empty() {
398 return None;
399 }
400
401 let new_index = self.metadata.extra_segments[0].index;
402 let mut accumulated_size = 0;
403 let offsets: HashMap<usize, Relocatable> = self
404 .metadata
405 .extra_segments
406 .iter()
407 .map(|seg| {
408 let value = (
409 seg.index as usize,
410 Relocatable {
411 segment_index: new_index,
412 offset: accumulated_size,
413 },
414 );
415
416 accumulated_size += seg.size;
417
418 value
419 })
420 .collect();
421
422 Some((
423 SegmentInfo {
424 index: new_index,
425 size: accumulated_size,
426 },
427 offsets,
428 ))
429 }
430}
431
432pub(super) mod serde_impl {
433 use crate::types::builtin_name::BuiltinName;
434 use crate::vm::runners::cairo_runner::ORDERED_BUILTIN_LIST;
435 use num_traits::Num;
436 use std::collections::{BTreeMap, HashMap};
437
438 use super::CAIRO_PIE_VERSION;
439 use super::{CairoPieMemory, Pages, PublicMemoryPage, SegmentInfo};
440 use crate::{
441 types::relocatable::{MaybeRelocatable, Relocatable},
442 utils::CAIRO_PRIME,
443 Felt252,
444 };
445 use num_bigint::BigUint;
446 use serde::{
447 de::Error, ser::SerializeMap, ser::SerializeSeq, Deserialize, Deserializer, Serialize,
448 Serializer,
449 };
450 use serde_json::Number;
451
452 pub const ADDR_BYTE_LEN: usize = 8;
453 pub const FIELD_BYTE_LEN: usize = 32;
454 pub const CELL_BYTE_LEN: usize = ADDR_BYTE_LEN + FIELD_BYTE_LEN;
455 pub const ADDR_BASE: u64 = 0x8000000000000000; pub const OFFSET_BASE: u64 = 0x800000000000; pub const RELOCATE_BASE: &str =
458 "8000000000000000000000000000000000000000000000000000000000000000"; pub(crate) struct Felt252Wrapper<'a>(&'a Felt252);
461
462 impl Serialize for Felt252Wrapper<'_> {
463 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
464 where
465 S: Serializer,
466 {
467 serde_json::Number::from_string_unchecked(self.0.to_string()).serialize(serializer)
469 }
470 }
471
472 pub mod version {
473 use super::*;
474
475 pub fn serialize<S>(_value: &(), serializer: S) -> Result<S::Ok, S::Error>
476 where
477 S: Serializer,
478 {
479 serializer.serialize_str(CAIRO_PIE_VERSION)
480 }
481
482 pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
483 where
484 D: Deserializer<'de>,
485 {
486 let version = String::deserialize(d)?;
487
488 if version != CAIRO_PIE_VERSION {
489 Err(D::Error::custom("Invalid cairo_pie version"))
490 } else {
491 Ok(())
492 }
493 }
494 }
495
496 pub mod program_data {
497 use super::*;
498
499 pub fn serialize<S>(values: &[MaybeRelocatable], serializer: S) -> Result<S::Ok, S::Error>
500 where
501 S: Serializer,
502 {
503 use serde::ser::Error;
504 let mut seq_serializer = serializer.serialize_seq(Some(values.len()))?;
505
506 for value in values {
507 match value {
508 MaybeRelocatable::RelocatableValue(_) => {
509 return Err(S::Error::custom("Invalid program data"))
510 }
511 MaybeRelocatable::Int(x) => {
512 seq_serializer.serialize_element(&Felt252Wrapper(x))?;
513 }
514 };
515 }
516
517 seq_serializer.end()
518 }
519
520 pub fn deserialize<'de, D>(d: D) -> Result<Vec<MaybeRelocatable>, D::Error>
521 where
522 D: Deserializer<'de>,
523 {
524 let numbers = Vec::<serde_json::Number>::deserialize(d)?;
525 numbers
526 .into_iter()
527 .map(|n| Felt252::from_dec_str(n.as_str()).map(MaybeRelocatable::from))
528 .collect::<Result<Vec<_>, _>>()
529 .map_err(|_| D::Error::custom("Failed to deserilaize Felt252 value"))
530 }
531 }
532
533 pub mod prime {
534 use super::*;
535
536 use lazy_static::lazy_static;
537 lazy_static! {
538 static ref CAIRO_PRIME_NUMBER: Number =
539 Number::from_string_unchecked(CAIRO_PRIME.to_string());
540 }
541
542 pub fn serialize<S>(_value: &(), serializer: S) -> Result<S::Ok, S::Error>
543 where
544 S: Serializer,
545 {
546 CAIRO_PRIME_NUMBER.serialize(serializer)
548 }
549
550 pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
551 where
552 D: Deserializer<'de>,
553 {
554 let prime = Number::deserialize(d)?;
555
556 if prime != *CAIRO_PRIME_NUMBER {
557 Err(D::Error::custom("Invalid prime"))
558 } else {
559 Ok(())
560 }
561 }
562 }
563
564 pub fn serialize_memory<S>(
565 values: &[((usize, usize), MaybeRelocatable)],
566 serializer: S,
567 ) -> Result<S::Ok, S::Error>
568 where
569 S: Serializer,
570 {
571 let mem_cap = values.len() * ADDR_BYTE_LEN + values.len() * FIELD_BYTE_LEN;
574 let mut res = Vec::with_capacity(mem_cap);
575
576 for ((segment, offset), value) in values.iter() {
577 let mem_addr = (*segment as u64)
579 .checked_mul(OFFSET_BASE)
580 .and_then(|n| n.checked_add(ADDR_BASE))
581 .and_then(|n| n.checked_add(*offset as u64))
582 .ok_or_else(|| {
583 serde::ser::Error::custom(format!(
584 "failed to serialize address: {segment}:{offset}"
585 ))
586 })?;
587
588 res.extend_from_slice(mem_addr.to_le_bytes().as_ref());
589 match value {
590 MaybeRelocatable::RelocatableValue(rel_val) => {
594 let reloc_base = BigUint::from_str_radix(RELOCATE_BASE, 16)
595 .map_err(|_| serde::ser::Error::custom("invalid relocation base str"))?;
596 let reloc_value = reloc_base
597 + BigUint::from(rel_val.segment_index as usize)
598 * BigUint::from(OFFSET_BASE)
599 + BigUint::from(rel_val.offset);
600 res.extend_from_slice(reloc_value.to_bytes_le().as_ref());
601 }
602 MaybeRelocatable::Int(data_val) => {
606 res.extend_from_slice(data_val.to_bytes_le().as_ref());
607 }
608 };
609 }
610
611 let string = res
612 .iter()
613 .fold(String::new(), |string, b| string + &format!("{:02x}", b));
614
615 serializer.serialize_str(&string)
616 }
617
618 pub mod pages {
619 use super::*;
620
621 pub fn serialize<S>(pages: &Pages, serializer: S) -> Result<S::Ok, S::Error>
622 where
623 S: Serializer,
624 {
625 let mut map = serializer.serialize_map(Some(pages.len()))?;
626 for (k, v) in pages {
627 map.serialize_entry(&k.to_string(), &vec![v.start, v.size])?;
628 }
629 map.end()
630 }
631
632 pub fn deserialize<'de, D>(deserializer: D) -> Result<Pages, D::Error>
633 where
634 D: Deserializer<'de>,
635 {
636 Ok(HashMap::<String, Vec<usize>>::deserialize(deserializer)?
637 .iter()
638 .map(|(k, v)| {
639 if v.len() == 2 {
640 Ok((
641 k.parse::<usize>().map_err(|_| {
642 D::Error::custom("Failed to deserialize page index.")
643 })?,
644 PublicMemoryPage::from(v),
645 ))
646 } else {
647 Err(D::Error::custom(
648 "Memory page description must be of length 2.",
649 ))
650 }
651 })
652 .collect::<Result<Vec<_>, _>>()
653 .map_err(|_| D::Error::custom("PublicMemoryPage deserialization failed."))?
654 .into_iter()
655 .collect::<Pages>())
656 }
657 }
658
659 impl CairoPieMemory {
660 fn relocate_value(
663 index: usize,
664 offset: usize,
665 segment_offsets: &Option<HashMap<usize, Relocatable>>,
666 ) -> (usize, usize) {
667 segment_offsets
668 .as_ref()
669 .and_then(|offsets| offsets.get(&index))
670 .map(|relocatable| {
671 (
672 relocatable.segment_index as usize,
673 relocatable.offset + offset,
674 )
675 })
676 .unwrap_or((index, offset))
677 }
678
679 pub fn to_bytes(&self, seg_offsets: Option<HashMap<usize, Relocatable>>) -> Vec<u8> {
680 let values = &self.0;
683 let mem_cap = values.len() * ADDR_BYTE_LEN + values.len() * FIELD_BYTE_LEN;
684 let mut res = Vec::with_capacity(mem_cap);
685
686 for ((segment, offset), value) in values.iter() {
687 let (segment, offset) = Self::relocate_value(*segment, *offset, &seg_offsets);
688 let mem_addr = ADDR_BASE + segment as u64 * OFFSET_BASE + offset as u64;
689 res.extend_from_slice(mem_addr.to_le_bytes().as_ref());
690 match value {
691 MaybeRelocatable::RelocatableValue(rel_val) => {
695 let (segment, offset) = Self::relocate_value(
696 rel_val.segment_index as usize,
697 rel_val.offset,
698 &seg_offsets,
699 );
700 let reloc_base = BigUint::from_str_radix(RELOCATE_BASE, 16).unwrap();
701 let reloc_value = reloc_base
702 + BigUint::from(segment) * BigUint::from(OFFSET_BASE)
703 + BigUint::from(offset);
704 res.extend_from_slice(reloc_value.to_bytes_le().as_ref());
705 }
706 MaybeRelocatable::Int(data_val) => {
710 res.extend_from_slice(data_val.to_bytes_le().as_ref());
711 }
712 };
713 }
714 res
715 }
716
717 pub fn from_bytes(bytes: &[u8]) -> Option<CairoPieMemory> {
718 if !num_integer::Integer::is_multiple_of(&bytes.len(), &CELL_BYTE_LEN) {
719 return None;
720 }
721
722 let relocatable_from_bytes = |bytes: [u8; 8]| -> (usize, usize) {
723 const N_SEGMENT_BITS: usize = 16;
724 const N_OFFSET_BITS: usize = 47;
725 const SEGMENT_MASK: u64 = ((1 << N_SEGMENT_BITS) - 1) << N_OFFSET_BITS;
726 const OFFSET_MASK: u64 = (1 << N_OFFSET_BITS) - 1;
727
728 let addr = u64::from_le_bytes(bytes);
729 let segment = (addr & SEGMENT_MASK) >> N_OFFSET_BITS;
730 let offset = addr & OFFSET_MASK;
731 (segment as usize, offset as usize)
732 };
733
734 let mut res = vec![];
735 for cell_bytes in bytes.chunks(CELL_BYTE_LEN) {
736 let addr = relocatable_from_bytes(cell_bytes[0..ADDR_BYTE_LEN].try_into().ok()?);
737 let field_bytes = &cell_bytes[ADDR_BYTE_LEN..CELL_BYTE_LEN];
738 let value = if (field_bytes[field_bytes.len() - 1] & 0x80) != 0 {
740 let (segment, offset) =
741 relocatable_from_bytes(field_bytes[0..ADDR_BYTE_LEN].try_into().ok()?);
742 MaybeRelocatable::from((segment as isize, offset))
743 } else {
744 MaybeRelocatable::from(Felt252::from_bytes_le_slice(field_bytes))
745 };
746 res.push((addr, value));
747 }
748
749 Some(CairoPieMemory(res))
750 }
751 }
752
753 pub mod signature_additional_data {
754 use super::*;
755
756 pub fn serialize<S>(
757 values: &BTreeMap<Relocatable, (Felt252, Felt252)>,
758 serializer: S,
759 ) -> Result<S::Ok, S::Error>
760 where
761 S: Serializer,
762 {
763 let mut seq_serializer = serializer.serialize_seq(Some(values.len()))?;
764
765 for (key, (x, y)) in values {
766 seq_serializer.serialize_element(&[
767 [
768 Felt252Wrapper(&Felt252::from(key.segment_index)),
769 Felt252Wrapper(&Felt252::from(key.offset)),
770 ],
771 [Felt252Wrapper(x), Felt252Wrapper(y)],
772 ])?;
773 }
774 seq_serializer.end()
775 }
776
777 pub fn deserialize<'de, D>(
778 d: D,
779 ) -> Result<BTreeMap<Relocatable, (Felt252, Felt252)>, D::Error>
780 where
781 D: Deserializer<'de>,
782 {
783 let number_map = Vec::<((Number, Number), (Number, Number))>::deserialize(d)?;
784 number_map
785 .into_iter()
786 .map(|((index, offset), (r, s))| {
787 let idx = index
788 .as_u64()
789 .ok_or_else(|| D::Error::custom("Invalid address"))?
790 as isize;
791 let off = offset
792 .as_u64()
793 .ok_or_else(|| D::Error::custom("Invalid address"))?
794 as usize;
795 let addr = Relocatable::from((idx, off));
796 let r = Felt252::from_dec_str(r.as_str())
797 .map_err(|_| D::Error::custom("Invalid Felt252 value"))?;
798 let s = Felt252::from_dec_str(s.as_str())
799 .map_err(|_| D::Error::custom("Invalid Felt252 value"))?;
800 Ok((addr, (r, s)))
801 })
802 .collect::<Result<BTreeMap<_, _>, D::Error>>()
803 }
804 }
805
806 pub mod hash_additional_data {
807 use super::*;
808
809 pub fn serialize<S>(values: &[Relocatable], serializer: S) -> Result<S::Ok, S::Error>
810 where
811 S: Serializer,
812 {
813 let mut seq_serializer: <S as Serializer>::SerializeSeq =
814 serializer.serialize_seq(Some(values.len()))?;
815
816 for value in values {
817 seq_serializer.serialize_element(&[value.segment_index, value.offset as isize])?;
818 }
819
820 seq_serializer.end()
821 }
822
823 pub fn deserialize<'de, D>(d: D) -> Result<Vec<Relocatable>, D::Error>
824 where
825 D: Deserializer<'de>,
826 {
827 let tuples = Vec::<(usize, usize)>::deserialize(d)?;
828 Ok(tuples
829 .into_iter()
830 .map(|(x, y)| Relocatable::from((x as isize, y)))
831 .collect())
832 }
833 }
834
835 pub fn serialize_builtin_segments<S>(
836 values: &BTreeMap<BuiltinName, SegmentInfo>,
837 serializer: S,
838 ) -> Result<S::Ok, S::Error>
839 where
840 S: Serializer,
841 {
842 let mut map_serializer = serializer.serialize_map(Some(values.len()))?;
843
844 for name in ORDERED_BUILTIN_LIST {
845 if let Some(info) = values.get(name) {
846 map_serializer.serialize_entry(name, info)?
847 }
848 }
849 map_serializer.end()
850 }
851}
852
853#[cfg(test)]
854mod test {
855 use {
856 crate::{
857 cairo_run::CairoRunConfig,
858 hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
859 types::layout_name::LayoutName,
860 },
861 rstest::rstest,
862 };
863
864 use super::*;
865
866 #[test]
867 fn serialize_cairo_pie_memory() {
868 let addrs = [
869 ((1, 0), "0000000000800080"),
870 ((1, 1), "0100000000800080"),
871 ((1, 4), "0400000000800080"),
872 ((1, 8), "0800000000800080"),
873 ((2, 0), "0000000000000180"),
874 ((5, 8), "0800000000800280"),
875 ];
876
877 let memory = CairoPieMemory(vec![
878 (addrs[0].0, MaybeRelocatable::Int(1234.into())),
879 (addrs[1].0, MaybeRelocatable::Int(11.into())),
880 (addrs[2].0, MaybeRelocatable::Int(12.into())),
881 (
882 addrs[3].0,
883 MaybeRelocatable::RelocatableValue((1, 2).into()),
884 ),
885 (
886 addrs[4].0,
887 MaybeRelocatable::RelocatableValue((3, 4).into()),
888 ),
889 (
890 addrs[5].0,
891 MaybeRelocatable::RelocatableValue((5, 6).into()),
892 ),
893 ]);
894
895 let mem = serde_json::to_value(memory).unwrap();
896 let mem_str = mem.as_str().unwrap();
897 let shift_len = (serde_impl::ADDR_BYTE_LEN + serde_impl::FIELD_BYTE_LEN) * 2;
898 let shift_field = serde_impl::FIELD_BYTE_LEN * 2;
899 let shift_addr = serde_impl::ADDR_BYTE_LEN * 2;
900
901 for (i, expected_addr) in addrs.into_iter().enumerate() {
903 let shift = shift_len * i;
904 assert_eq!(
905 &mem_str[shift..shift + shift_addr],
906 expected_addr.1,
907 "addr mismatch({i}): {mem_str:?}",
908 );
909 }
910
911 assert_eq!(
915 &mem_str[shift_addr..shift_addr + shift_field],
916 "d204000000000000000000000000000000000000000000000000000000000000",
917 "value mismatch: {mem_str:?}",
918 );
919 let shift_first_relocatable = shift_len * 3 + shift_addr;
923 assert_eq!(
924 &mem_str[shift_first_relocatable..shift_first_relocatable + shift_field],
925 "0200000000800000000000000000000000000000000000000000000000000080",
926 "value mismatch: {mem_str:?}",
927 );
928 }
929
930 #[test]
931 fn serialize_cairo_pie_memory_with_overflow() {
932 let memory = CairoPieMemory(vec![
933 ((0, 0), MaybeRelocatable::Int(0.into())),
934 ((0, 1), MaybeRelocatable::Int(1.into())),
935 ((usize::MAX, 0), MaybeRelocatable::Int(2.into())),
936 ]);
937
938 serde_json::to_value(memory).unwrap_err();
939 }
940
941 #[rstest]
942 #[case(include_bytes!("../../../../cairo_programs/fibonacci.json"), "fibonacci")]
943 #[case(include_bytes!("../../../../cairo_programs/integration.json"), "integration")]
944 #[case(include_bytes!("../../../../cairo_programs/common_signature.json"), "signature")]
945 #[case(include_bytes!("../../../../cairo_programs/relocate_segments.json"), "relocate")]
946 #[case(include_bytes!("../../../../cairo_programs/ec_op.json"), "ec_op")]
947 #[case(include_bytes!("../../../../cairo_programs/bitwise_output.json"), "bitwise")]
948 #[case(include_bytes!("../../../../cairo_programs/value_beyond_segment.json"), "relocate_beyond")]
949 fn read_write_pie_zip(#[case] program_content: &[u8], #[case] identifier: &str) {
950 let cairo_pie = {
952 let cairo_run_config = CairoRunConfig {
953 layout: LayoutName::starknet_with_keccak,
954 ..Default::default()
955 };
956 let runner = crate::cairo_run::cairo_run(
957 program_content,
958 &cairo_run_config,
959 &mut BuiltinHintProcessor::new_empty(),
960 )
961 .unwrap();
962 runner.get_cairo_pie().unwrap()
963 };
964 let filename = format!("temp_file_{}", identifier); let file_path = Path::new(&filename);
967 cairo_pie.write_zip_file(file_path, false).unwrap();
968 let deserialized_pie = CairoPie::read_zip_file(file_path).unwrap();
970 assert_eq!(cairo_pie, deserialized_pie);
972 std::fs::remove_file(file_path).unwrap();
974 }
975
976 #[test]
977 fn cairo_pie_with_extra_segments() {
978 let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
979 let mut cairo_pie = {
980 let cairo_run_config = CairoRunConfig {
981 layout: LayoutName::starknet_with_keccak,
982 ..Default::default()
983 };
984 let runner = crate::cairo_run::cairo_run(
985 program_content,
986 &cairo_run_config,
987 &mut BuiltinHintProcessor::new_empty(),
988 )
989 .unwrap();
990 runner.get_cairo_pie().unwrap()
991 };
992
993 cairo_pie.metadata.extra_segments = vec![
994 SegmentInfo { index: 8, size: 10 },
995 SegmentInfo { index: 9, size: 20 },
996 ];
997 let memory = CairoPieMemory(vec![
998 (
999 (3, 4),
1000 MaybeRelocatable::RelocatableValue(Relocatable {
1001 segment_index: 6,
1002 offset: 7,
1003 }),
1004 ),
1005 (
1006 (8, 0),
1007 MaybeRelocatable::RelocatableValue(Relocatable {
1008 segment_index: 8,
1009 offset: 4,
1010 }),
1011 ),
1012 (
1013 (9, 3),
1014 MaybeRelocatable::RelocatableValue(Relocatable {
1015 segment_index: 9,
1016 offset: 7,
1017 }),
1018 ),
1019 ]);
1020
1021 cairo_pie.memory = memory;
1022
1023 let file_path = Path::new("merge_extra_segments_test");
1024
1025 cairo_pie.write_zip_file(file_path, true).unwrap();
1026
1027 let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1028
1029 std::fs::remove_file(file_path).unwrap();
1030
1031 assert_eq!(
1032 result_cairo_pie.metadata.extra_segments,
1033 vec![SegmentInfo { index: 8, size: 30 }]
1034 );
1035 assert_eq!(
1036 result_cairo_pie.memory,
1037 CairoPieMemory(vec![
1038 (
1039 (3, 4),
1040 MaybeRelocatable::RelocatableValue(Relocatable {
1041 segment_index: 6,
1042 offset: 7
1043 })
1044 ),
1045 (
1046 (8, 0),
1047 MaybeRelocatable::RelocatableValue(Relocatable {
1048 segment_index: 8,
1049 offset: 4
1050 })
1051 ),
1052 (
1053 (8, 13),
1054 MaybeRelocatable::RelocatableValue(Relocatable {
1055 segment_index: 8,
1056 offset: 17
1057 })
1058 ),
1059 ])
1060 )
1061 }
1062
1063 #[test]
1064 fn cairo_pie_without_extra_segments() {
1065 let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
1066 let mut cairo_pie = {
1067 let cairo_run_config = CairoRunConfig {
1068 layout: LayoutName::starknet_with_keccak,
1069 ..Default::default()
1070 };
1071 let runner = crate::cairo_run::cairo_run(
1072 program_content,
1073 &cairo_run_config,
1074 &mut BuiltinHintProcessor::new_empty(),
1075 )
1076 .unwrap();
1077 runner.get_cairo_pie().unwrap()
1078 };
1079
1080 cairo_pie.metadata.extra_segments = vec![];
1081 let memory = CairoPieMemory(vec![
1082 (
1083 (3, 4),
1084 MaybeRelocatable::RelocatableValue(Relocatable {
1085 segment_index: 6,
1086 offset: 7,
1087 }),
1088 ),
1089 (
1090 (8, 0),
1091 MaybeRelocatable::RelocatableValue(Relocatable {
1092 segment_index: 8,
1093 offset: 4,
1094 }),
1095 ),
1096 (
1097 (9, 3),
1098 MaybeRelocatable::RelocatableValue(Relocatable {
1099 segment_index: 9,
1100 offset: 7,
1101 }),
1102 ),
1103 ]);
1104
1105 cairo_pie.memory = memory.clone();
1106
1107 let file_path = Path::new("merge_without_extra_segments_test");
1108
1109 cairo_pie.write_zip_file(file_path, true).unwrap();
1110
1111 let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1112
1113 std::fs::remove_file(file_path).unwrap();
1114
1115 assert_eq!(result_cairo_pie.metadata.extra_segments, vec![]);
1116 assert_eq!(result_cairo_pie.memory, memory)
1117 }
1118}