1- use std:: collections:: HashSet ;
1+ use std:: collections:: { HashMap , HashSet } ;
22
3+ use apollo_starknet_os_program:: OS_PROGRAM_BYTES ;
34use assert_matches:: assert_matches;
5+ use cairo_vm:: types:: builtin_name:: BuiltinName ;
6+ use cairo_vm:: types:: layout_name:: LayoutName ;
7+ use cairo_vm:: types:: relocatable:: MaybeRelocatable ;
8+ use cairo_vm:: vm:: runners:: cairo_runner:: ExecutionResources ;
9+ use expect_test:: { expect, Expect } ;
410use num_bigint:: BigUint ;
511use rstest:: rstest;
612use starknet_types_core:: felt:: Felt ;
@@ -27,6 +33,87 @@ use crate::hints::hint_implementation::stateless_compression::utils::{
2733 unpack_felts,
2834 unpack_felts_to_usize,
2935} ;
36+ use crate :: test_utils:: cairo_runner:: {
37+ initialize_cairo_runner,
38+ run_cairo_0_entrypoint,
39+ EndpointArg ,
40+ EntryPointRunnerConfig ,
41+ ImplicitArg ,
42+ ValueArg ,
43+ } ;
44+
45+ /// Runs the OS compression function and returns the compressed data, plus the execution resources
46+ /// used to compress the data.
47+ fn cairo_compress ( data : & [ Felt ] ) -> ( Vec < Felt > , ExecutionResources ) {
48+ let range_check_arg = ImplicitArg :: Builtin ( BuiltinName :: range_check) ;
49+ let runner_config = EntryPointRunnerConfig {
50+ layout : LayoutName :: starknet,
51+ add_main_prefix_to_entrypoint : false ,
52+ ..Default :: default ( )
53+ } ;
54+ let ( mut runner, program, entrypoint) = initialize_cairo_runner (
55+ & runner_config,
56+ OS_PROGRAM_BYTES ,
57+ "starkware.starknet.core.os.data_availability.compression.compress" ,
58+ std:: slice:: from_ref ( & range_check_arg) ,
59+ HashMap :: new ( ) ,
60+ )
61+ . unwrap ( ) ;
62+
63+ // Function accepts start and end pointers explicitly, and the destination pointer is passed
64+ // as an implicit argument (along with the range check pointer).
65+ let data_start = runner
66+ . vm
67+ . gen_arg ( & data. iter ( ) . map ( |x| MaybeRelocatable :: Int ( * x) ) . collect :: < Vec < _ > > ( ) )
68+ . unwrap ( )
69+ . get_relocatable ( )
70+ . unwrap ( ) ;
71+ let compressed_dst = runner. vm . add_memory_segment ( ) ;
72+ let explicit_args = vec ! [
73+ EndpointArg :: Value ( ValueArg :: Single ( data_start. into( ) ) ) ,
74+ EndpointArg :: Value ( ValueArg :: Single ( ( data_start + data. len( ) ) . unwrap( ) . into( ) ) ) ,
75+ ] ;
76+ let implicit_args = vec ! [
77+ range_check_arg,
78+ ImplicitArg :: NonBuiltin ( EndpointArg :: Value ( ValueArg :: Single ( compressed_dst. into( ) ) ) ) ,
79+ ] ;
80+
81+ // Run the entrypoint.
82+ // The compressed data is stored in the segment starting at `compressed_dst`, the returned
83+ // implicit value is the end of the compressed data.
84+ let state_reader = None ;
85+ let expected_explicit_return_values = vec ! [ ] ;
86+ let ( implicit_return_values, _explicit_return_values, _hint_processor) =
87+ run_cairo_0_entrypoint (
88+ entrypoint,
89+ & explicit_args,
90+ & implicit_args,
91+ state_reader,
92+ & mut runner,
93+ & program,
94+ & runner_config,
95+ & expected_explicit_return_values,
96+ )
97+ . unwrap ( ) ;
98+
99+ // The implicit return values are [range_check_ptr, compressed_end].
100+ assert_eq ! ( implicit_return_values. len( ) , 2 ) ;
101+ let EndpointArg :: Value ( ValueArg :: Single ( MaybeRelocatable :: RelocatableValue ( compressed_end) ) ) =
102+ implicit_return_values[ 1 ]
103+ else {
104+ panic ! (
105+ "Unexpected implicit return value for compressed_end, got: {:?}" ,
106+ implicit_return_values[ 1 ]
107+ ) ;
108+ } ;
109+
110+ // Read the compressed data from the segment and return.
111+ let compressed_data = runner
112+ . vm
113+ . get_integer_range ( compressed_dst, ( compressed_end - compressed_dst) . unwrap ( ) )
114+ . unwrap ( ) ;
115+ ( compressed_data. into_iter ( ) . map ( |f| * f) . collect ( ) , runner. get_execution_resources ( ) . unwrap ( ) )
116+ }
30117
31118#[ rstest]
32119#[ case:: zero( [ false ; 10 ] , Felt :: ZERO ) ]
@@ -274,3 +361,36 @@ fn test_compression_length(
274361fn test_get_n_elms_per_felt ( #[ case] elm_bound : u32 , #[ case] expected_n_elems : usize ) {
275362 assert_eq ! ( get_n_elms_per_felt( elm_bound) , expected_n_elems) ;
276363}
364+
365+ /// Test that Cairo and Rust implementations of compress are identical. Also verifies resources
366+ /// required for cairo compression.
367+ #[ rstest]
368+ #[ case:: no_buckets( vec![ ] , None ) ]
369+ #[ case:: single_bucket_one_value( vec![ Felt :: from( 7777777 ) ] , Some ( expect![ [ r#"
370+ 1271
371+ "# ] ] ) ) ]
372+ #[ case:: large_duplicates( vec![ Felt :: from( BigUint :: from( 2_u8 ) . pow( 250 ) ) ; 100 ] , Some ( expect![ [ r#"
373+ 78
374+ "# ] ] ) ) ]
375+ #[ case:: small_values( ( 0 ..( 1 << 15 ) ) . map( Felt :: from) . collect( ) , Some ( expect![ [ r#"
376+ 60
377+ "# ] ] ) ) ]
378+ #[ case:: mixed_buckets(
379+ ( 0 ..252 ) . map( |i| Felt :: from( BigUint :: from( 2_u8 ) . pow( i) ) ) . collect( ) ,
380+ Some ( expect![ [ r#"
381+ 62
382+ "# ] ] ) ) ]
383+ fn test_cairo_compress ( #[ case] data : Vec < Felt > , #[ case] expected_n_steps_per_elm : Option < Expect > ) {
384+ let compressed = compress ( & data) ;
385+ let ( cairo_compressed, execution_resources) = cairo_compress ( & data) ;
386+ assert_eq ! ( cairo_compressed, compressed) ;
387+
388+ if !data. is_empty ( ) {
389+ expected_n_steps_per_elm
390+ . unwrap ( )
391+ . assert_debug_eq ( & ( execution_resources. n_steps / data. len ( ) ) ) ;
392+ }
393+
394+ assert_eq ! ( data, decompress( & mut compressed. into_iter( ) ) ) ;
395+ // TODO(Dori): Check cairo decompression.
396+ }
0 commit comments