@@ -301,17 +301,35 @@ impl CairoPie {
301301 }
302302
303303 #[ cfg( feature = "std" ) ]
304- pub fn write_zip_file ( & self , file_path : & Path ) -> Result < ( ) , std:: io:: Error > {
304+ pub fn write_zip_file (
305+ & self ,
306+ file_path : & Path ,
307+ merge_extra_segments : bool ,
308+ ) -> Result < ( ) , std:: io:: Error > {
309+ let mut metadata = self . metadata . clone ( ) ;
310+
311+ let segment_offsets = if merge_extra_segments {
312+ if let Some ( ( segment, segment_offsets) ) = self . merge_extra_segments ( ) {
313+ metadata. extra_segments = vec ! [ segment] ;
314+ Some ( segment_offsets)
315+ } else {
316+ None
317+ }
318+ } else {
319+ None
320+ } ;
321+
305322 let file = File :: create ( file_path) ?;
306323 let mut zip_writer = ZipWriter :: new ( file) ;
307324 let options =
308325 zip:: write:: FileOptions :: default ( ) . compression_method ( zip:: CompressionMethod :: Deflated ) ;
326+
309327 zip_writer. start_file ( "version.json" , options) ?;
310328 serde_json:: to_writer ( & mut zip_writer, & self . version ) ?;
311329 zip_writer. start_file ( "metadata.json" , options) ?;
312- serde_json:: to_writer ( & mut zip_writer, & self . metadata ) ?;
330+ serde_json:: to_writer ( & mut zip_writer, & metadata) ?;
313331 zip_writer. start_file ( "memory.bin" , options) ?;
314- zip_writer. write_all ( & self . memory . to_bytes ( ) ) ?;
332+ zip_writer. write_all ( & self . memory . to_bytes ( segment_offsets ) ) ?;
315333 zip_writer. start_file ( "additional_data.json" , options) ?;
316334 serde_json:: to_writer ( & mut zip_writer, & self . additional_data ) ?;
317335 zip_writer. start_file ( "execution_resources.json" , options) ?;
@@ -372,6 +390,48 @@ impl CairoPie {
372390
373391 Self :: from_zip_archive ( zip)
374392 }
393+
394+ // Heavily inspired in:
395+ // https://github.com/starkware-libs/cairo-lang/blob/8276ac35830148a397e1143389f23253c8b80e93/src/starkware/cairo/lang/vm/cairo_pie.py#L286-L306
396+ /// Merges `extra_segments` to a single segment.
397+ ///
398+ /// Returns a tuple with the new `extra_segments` (containing just the merged segment)
399+ /// and a HashMap with the old segment indices mapped to their new offset in the new segment
400+ #[ cfg( feature = "std" ) ]
401+ fn merge_extra_segments ( & self ) -> Option < ( SegmentInfo , HashMap < usize , Relocatable > ) > {
402+ if self . metadata . extra_segments . is_empty ( ) {
403+ return None ;
404+ }
405+
406+ let new_index = self . metadata . extra_segments [ 0 ] . index ;
407+ let mut accumulated_size = 0 ;
408+ let offsets: HashMap < usize , Relocatable > = self
409+ . metadata
410+ . extra_segments
411+ . iter ( )
412+ . map ( |seg| {
413+ let value = (
414+ seg. index as usize ,
415+ Relocatable {
416+ segment_index : new_index,
417+ offset : accumulated_size,
418+ } ,
419+ ) ;
420+
421+ accumulated_size += seg. size ;
422+
423+ value
424+ } )
425+ . collect ( ) ;
426+
427+ Some ( (
428+ SegmentInfo {
429+ index : new_index,
430+ size : accumulated_size,
431+ } ,
432+ offsets,
433+ ) )
434+ }
375435}
376436
377437pub ( super ) mod serde_impl {
@@ -595,26 +655,50 @@ pub(super) mod serde_impl {
595655 }
596656
597657 impl CairoPieMemory {
598- pub fn to_bytes ( & self ) -> Vec < u8 > {
658+ /// Relocates a `Relocatable` value, which represented by its
659+ /// index and offset, according to a given segment offsets
660+ fn relocate_value (
661+ index : usize ,
662+ offset : usize ,
663+ segment_offsets : & Option < HashMap < usize , Relocatable > > ,
664+ ) -> ( usize , usize ) {
665+ segment_offsets
666+ . as_ref ( )
667+ . and_then ( |offsets| offsets. get ( & index) )
668+ . map ( |relocatable| {
669+ (
670+ relocatable. segment_index as usize ,
671+ relocatable. offset + offset,
672+ )
673+ } )
674+ . unwrap_or ( ( index, offset) )
675+ }
676+
677+ pub fn to_bytes ( & self , seg_offsets : Option < HashMap < usize , Relocatable > > ) -> Vec < u8 > {
599678 // Missing segment and memory holes can be ignored
600679 // as they can be inferred by the address on the prover side
601680 let values = & self . 0 ;
602681 let mem_cap = values. len ( ) * ADDR_BYTE_LEN + values. len ( ) * FIELD_BYTE_LEN ;
603682 let mut res = Vec :: with_capacity ( mem_cap) ;
604683
605684 for ( ( segment, offset) , value) in values. iter ( ) {
606- let mem_addr = ADDR_BASE + * segment as u64 * OFFSET_BASE + * offset as u64 ;
685+ let ( segment, offset) = Self :: relocate_value ( * segment, * offset, & seg_offsets) ;
686+ let mem_addr = ADDR_BASE + segment as u64 * OFFSET_BASE + offset as u64 ;
607687 res. extend_from_slice ( mem_addr. to_le_bytes ( ) . as_ref ( ) ) ;
608688 match value {
609689 // Serializes RelocatableValue(little endian):
610690 // 1bit | SEGMENT_BITS | OFFSET_BITS
611691 // 1 | segment | offset
612692 MaybeRelocatable :: RelocatableValue ( rel_val) => {
693+ let ( segment, offset) = Self :: relocate_value (
694+ rel_val. segment_index as usize ,
695+ rel_val. offset ,
696+ & seg_offsets,
697+ ) ;
613698 let reloc_base = BigUint :: from_str_radix ( RELOCATE_BASE , 16 ) . unwrap ( ) ;
614699 let reloc_value = reloc_base
615- + BigUint :: from ( rel_val. segment_index as usize )
616- * BigUint :: from ( OFFSET_BASE )
617- + BigUint :: from ( rel_val. offset ) ;
700+ + BigUint :: from ( segment) * BigUint :: from ( OFFSET_BASE )
701+ + BigUint :: from ( offset) ;
618702 res. extend_from_slice ( reloc_value. to_bytes_le ( ) . as_ref ( ) ) ;
619703 }
620704 // Serializes Int(little endian):
@@ -780,7 +864,14 @@ pub(super) mod serde_impl {
780864#[ cfg( test) ]
781865mod test {
782866 #[ cfg( feature = "std" ) ]
783- use rstest:: rstest;
867+ use {
868+ crate :: {
869+ cairo_run:: CairoRunConfig ,
870+ hint_processor:: builtin_hint_processor:: builtin_hint_processor_definition:: BuiltinHintProcessor ,
871+ types:: layout_name:: LayoutName ,
872+ } ,
873+ rstest:: rstest,
874+ } ;
784875
785876 use super :: * ;
786877
@@ -858,11 +949,6 @@ mod test {
858949 #[ case( include_bytes!( "../../../../cairo_programs/bitwise_output.json" ) , "bitwise" ) ]
859950 #[ case( include_bytes!( "../../../../cairo_programs/value_beyond_segment.json" ) , "relocate_beyond" ) ]
860951 fn read_write_pie_zip ( #[ case] program_content : & [ u8 ] , #[ case] identifier : & str ) {
861- use crate :: {
862- cairo_run:: CairoRunConfig ,
863- hint_processor:: builtin_hint_processor:: builtin_hint_processor_definition:: BuiltinHintProcessor ,
864- types:: layout_name:: LayoutName ,
865- } ;
866952 // Run a program to obtain the CairoPie
867953 let cairo_pie = {
868954 let cairo_run_config = CairoRunConfig {
@@ -880,12 +966,157 @@ mod test {
880966 // Serialize the CairoPie into a zip file
881967 let filename = format ! ( "temp_file_{}" , identifier) ; // Identifier used to avoid name clashes
882968 let file_path = Path :: new ( & filename) ;
883- cairo_pie. write_zip_file ( file_path) . unwrap ( ) ;
969+ cairo_pie. write_zip_file ( file_path, false ) . unwrap ( ) ;
884970 // Deserialize the zip file
885971 let deserialized_pie = CairoPie :: read_zip_file ( file_path) . unwrap ( ) ;
886972 // Check that both pies are equal
887973 assert_eq ! ( cairo_pie, deserialized_pie) ;
888974 // Remove zip file created by the test
889975 std:: fs:: remove_file ( file_path) . unwrap ( ) ;
890976 }
977+
978+ #[ test]
979+ #[ cfg( feature = "std" ) ]
980+ fn cairo_pie_with_extra_segments ( ) {
981+ let program_content = include_bytes ! ( "../../../../cairo_programs/fibonacci.json" ) ;
982+ let mut cairo_pie = {
983+ let cairo_run_config = CairoRunConfig {
984+ layout : LayoutName :: starknet_with_keccak,
985+ ..Default :: default ( )
986+ } ;
987+ let runner = crate :: cairo_run:: cairo_run (
988+ program_content,
989+ & cairo_run_config,
990+ & mut BuiltinHintProcessor :: new_empty ( ) ,
991+ )
992+ . unwrap ( ) ;
993+ runner. get_cairo_pie ( ) . unwrap ( )
994+ } ;
995+
996+ cairo_pie. metadata . extra_segments = vec ! [
997+ SegmentInfo { index: 8 , size: 10 } ,
998+ SegmentInfo { index: 9 , size: 20 } ,
999+ ] ;
1000+ let memory = CairoPieMemory ( vec ! [
1001+ (
1002+ ( 3 , 4 ) ,
1003+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1004+ segment_index: 6 ,
1005+ offset: 7 ,
1006+ } ) ,
1007+ ) ,
1008+ (
1009+ ( 8 , 0 ) ,
1010+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1011+ segment_index: 8 ,
1012+ offset: 4 ,
1013+ } ) ,
1014+ ) ,
1015+ (
1016+ ( 9 , 3 ) ,
1017+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1018+ segment_index: 9 ,
1019+ offset: 7 ,
1020+ } ) ,
1021+ ) ,
1022+ ] ) ;
1023+
1024+ cairo_pie. memory = memory;
1025+
1026+ let file_path = Path :: new ( "merge_extra_segments_test" ) ;
1027+
1028+ cairo_pie. write_zip_file ( file_path, true ) . unwrap ( ) ;
1029+
1030+ let result_cairo_pie = CairoPie :: read_zip_file ( file_path) . unwrap ( ) ;
1031+
1032+ std:: fs:: remove_file ( file_path) . unwrap ( ) ;
1033+
1034+ assert_eq ! (
1035+ result_cairo_pie. metadata. extra_segments,
1036+ vec![ SegmentInfo { index: 8 , size: 30 } ]
1037+ ) ;
1038+ assert_eq ! (
1039+ result_cairo_pie. memory,
1040+ CairoPieMemory ( vec![
1041+ (
1042+ ( 3 , 4 ) ,
1043+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1044+ segment_index: 6 ,
1045+ offset: 7
1046+ } )
1047+ ) ,
1048+ (
1049+ ( 8 , 0 ) ,
1050+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1051+ segment_index: 8 ,
1052+ offset: 4
1053+ } )
1054+ ) ,
1055+ (
1056+ ( 8 , 13 ) ,
1057+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1058+ segment_index: 8 ,
1059+ offset: 17
1060+ } )
1061+ ) ,
1062+ ] )
1063+ )
1064+ }
1065+
1066+ #[ test]
1067+ #[ cfg( feature = "std" ) ]
1068+ fn cairo_pie_without_extra_segments ( ) {
1069+ let program_content = include_bytes ! ( "../../../../cairo_programs/fibonacci.json" ) ;
1070+ let mut cairo_pie = {
1071+ let cairo_run_config = CairoRunConfig {
1072+ layout : LayoutName :: starknet_with_keccak,
1073+ ..Default :: default ( )
1074+ } ;
1075+ let runner = crate :: cairo_run:: cairo_run (
1076+ program_content,
1077+ & cairo_run_config,
1078+ & mut BuiltinHintProcessor :: new_empty ( ) ,
1079+ )
1080+ . unwrap ( ) ;
1081+ runner. get_cairo_pie ( ) . unwrap ( )
1082+ } ;
1083+
1084+ cairo_pie. metadata . extra_segments = vec ! [ ] ;
1085+ let memory = CairoPieMemory ( vec ! [
1086+ (
1087+ ( 3 , 4 ) ,
1088+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1089+ segment_index: 6 ,
1090+ offset: 7 ,
1091+ } ) ,
1092+ ) ,
1093+ (
1094+ ( 8 , 0 ) ,
1095+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1096+ segment_index: 8 ,
1097+ offset: 4 ,
1098+ } ) ,
1099+ ) ,
1100+ (
1101+ ( 9 , 3 ) ,
1102+ MaybeRelocatable :: RelocatableValue ( Relocatable {
1103+ segment_index: 9 ,
1104+ offset: 7 ,
1105+ } ) ,
1106+ ) ,
1107+ ] ) ;
1108+
1109+ cairo_pie. memory = memory. clone ( ) ;
1110+
1111+ let file_path = Path :: new ( "merge_without_extra_segments_test" ) ;
1112+
1113+ cairo_pie. write_zip_file ( file_path, true ) . unwrap ( ) ;
1114+
1115+ let result_cairo_pie = CairoPie :: read_zip_file ( file_path) . unwrap ( ) ;
1116+
1117+ std:: fs:: remove_file ( file_path) . unwrap ( ) ;
1118+
1119+ assert_eq ! ( result_cairo_pie. metadata. extra_segments, vec![ ] ) ;
1120+ assert_eq ! ( result_cairo_pie. memory, memory)
1121+ }
8911122}
0 commit comments