@@ -301,17 +301,35 @@ impl CairoPie {
301
301
}
302
302
303
303
#[ 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
+
305
322
let file = File :: create ( file_path) ?;
306
323
let mut zip_writer = ZipWriter :: new ( file) ;
307
324
let options =
308
325
zip:: write:: FileOptions :: default ( ) . compression_method ( zip:: CompressionMethod :: Deflated ) ;
326
+
309
327
zip_writer. start_file ( "version.json" , options) ?;
310
328
serde_json:: to_writer ( & mut zip_writer, & self . version ) ?;
311
329
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) ?;
313
331
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 ) ) ?;
315
333
zip_writer. start_file ( "additional_data.json" , options) ?;
316
334
serde_json:: to_writer ( & mut zip_writer, & self . additional_data ) ?;
317
335
zip_writer. start_file ( "execution_resources.json" , options) ?;
@@ -372,6 +390,48 @@ impl CairoPie {
372
390
373
391
Self :: from_zip_archive ( zip)
374
392
}
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
+ }
375
435
}
376
436
377
437
pub ( super ) mod serde_impl {
@@ -595,26 +655,50 @@ pub(super) mod serde_impl {
595
655
}
596
656
597
657
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 > {
599
678
// Missing segment and memory holes can be ignored
600
679
// as they can be inferred by the address on the prover side
601
680
let values = & self . 0 ;
602
681
let mem_cap = values. len ( ) * ADDR_BYTE_LEN + values. len ( ) * FIELD_BYTE_LEN ;
603
682
let mut res = Vec :: with_capacity ( mem_cap) ;
604
683
605
684
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 ;
607
687
res. extend_from_slice ( mem_addr. to_le_bytes ( ) . as_ref ( ) ) ;
608
688
match value {
609
689
// Serializes RelocatableValue(little endian):
610
690
// 1bit | SEGMENT_BITS | OFFSET_BITS
611
691
// 1 | segment | offset
612
692
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
+ ) ;
613
698
let reloc_base = BigUint :: from_str_radix ( RELOCATE_BASE , 16 ) . unwrap ( ) ;
614
699
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) ;
618
702
res. extend_from_slice ( reloc_value. to_bytes_le ( ) . as_ref ( ) ) ;
619
703
}
620
704
// Serializes Int(little endian):
@@ -780,7 +864,14 @@ pub(super) mod serde_impl {
780
864
#[ cfg( test) ]
781
865
mod test {
782
866
#[ 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
+ } ;
784
875
785
876
use super :: * ;
786
877
@@ -858,11 +949,6 @@ mod test {
858
949
#[ case( include_bytes!( "../../../../cairo_programs/bitwise_output.json" ) , "bitwise" ) ]
859
950
#[ case( include_bytes!( "../../../../cairo_programs/value_beyond_segment.json" ) , "relocate_beyond" ) ]
860
951
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
- } ;
866
952
// Run a program to obtain the CairoPie
867
953
let cairo_pie = {
868
954
let cairo_run_config = CairoRunConfig {
@@ -880,12 +966,157 @@ mod test {
880
966
// Serialize the CairoPie into a zip file
881
967
let filename = format ! ( "temp_file_{}" , identifier) ; // Identifier used to avoid name clashes
882
968
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 ( ) ;
884
970
// Deserialize the zip file
885
971
let deserialized_pie = CairoPie :: read_zip_file ( file_path) . unwrap ( ) ;
886
972
// Check that both pies are equal
887
973
assert_eq ! ( cairo_pie, deserialized_pie) ;
888
974
// Remove zip file created by the test
889
975
std:: fs:: remove_file ( file_path) . unwrap ( ) ;
890
976
}
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
+ }
891
1122
}
0 commit comments