@@ -4,20 +4,27 @@ use std::collections::HashSet;
44use std:: fs;
55use std:: io:: BufReader ;
66
7+ use cairo_lang_sierra:: ProgramParser ;
78use cairo_lang_sierra:: ids:: GenericLibfuncId ;
9+ use cairo_lang_sierra_generator:: canonical_id_replacer:: CanonicalReplacer ;
10+ use cairo_lang_sierra_generator:: replace_ids:: SierraIdReplacer ;
811use cairo_lang_test_utils:: compare_contents_or_fix_with_path;
912use cairo_lang_test_utils:: parse_test_file:: TestRunnerResult ;
1013use cairo_lang_utils:: ordered_hash_map:: OrderedHashMap ;
14+ use indoc:: formatdoc;
1115use itertools:: Itertools ;
16+ use num_bigint:: BigUint ;
1217use starknet_types_core:: felt:: Felt as Felt252 ;
1318use test_case:: test_case;
1419
1520use crate :: allowed_libfuncs:: {
1621 BUILTIN_AUDITED_LIBFUNCS_LIST , ListSelector , lookup_allowed_libfuncs_list,
1722} ;
18- use crate :: casm_contract_class:: { BigUintAsHex , CasmContractClass } ;
23+ use crate :: casm_contract_class:: { BigUintAsHex , CasmContractClass , StarknetSierraCompilationError } ;
1924use crate :: compiler_version:: current_sierra_version_id;
20- use crate :: contract_class:: ContractClass ;
25+ use crate :: contract_class:: {
26+ ContractClass , ContractEntryPoint , ContractEntryPoints , ExtractedSierraProgram ,
27+ } ;
2128use crate :: felt252_serde:: { Felt252SerdeError , sierra_from_felt252s} ;
2229use crate :: test_utils:: get_example_file_path;
2330
@@ -34,6 +41,138 @@ fn test_casm_contract_from_contract_class_failure(name: &str) {
3441 ) ;
3542}
3643
44+ /// Hand-written Sierra for a contract with a single entry point. `RangeCheck` is fixed as the
45+ /// first builtin (it feeds `withdraw_gas`, so the entry-point cost is properly accounted); the
46+ /// next two builtins are `builtin_a` then `builtin_b`, which are just threaded through. Vary
47+ /// those two to control the builtin order the check sees. Hand-written on purpose: the frontend
48+ /// always emits builtins in the canonical order, so a non-canonical order can only be built here.
49+ fn entry_point_sierra ( builtin_a : & str , builtin_b : & str ) -> String {
50+ formatdoc ! { "
51+ type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false];
52+ type Bitwise = Bitwise [storable: true, drop: false, dup: false, zero_sized: false];
53+ type EcOp = EcOp [storable: true, drop: false, dup: false, zero_sized: false];
54+ type GasBuiltin = GasBuiltin [storable: true, drop: false, dup: false, zero_sized: false];
55+ type System = System [storable: true, drop: false, dup: false, zero_sized: false];
56+ type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false];
57+ type Array<felt252> = Array<felt252> [storable: true, drop: true, dup: false, zero_sized: false];
58+ type Snapshot<Array<felt252>> = Snapshot<Array<felt252>> [storable: true, drop: true, dup: true, zero_sized: false];
59+ type core::array::Span::<core::felt252> = Struct<ut@[782572820229152792105145177694740816763001980856532159945905090893343825762], Snapshot<Array<felt252>>> [storable: true, drop: true, dup: true, zero_sized: false];
60+ type Tuple<core::array::Span::<core::felt252>> = Struct<ut@[1325343513152088812341467750635149026053683136611136091911357178651207272643], core::array::Span::<core::felt252>> [storable: true, drop: true, dup: true, zero_sized: false];
61+ type core::panics::Panic = Struct<ut@[640126984585624630990013944782631102820301644699864366139839615702772668018]> [storable: true, drop: true, dup: true, zero_sized: true];
62+ type Tuple<core::panics::Panic, Array<felt252>> = Struct<ut@[1325343513152088812341467750635149026053683136611136091911357178651207272643], core::panics::Panic, Array<felt252>> [storable: true, drop: true, dup: false, zero_sized: false];
63+ type core::panics::PanicResult::<(core::array::Span::<core::felt252>,)> = Enum<ut@[270671131472959732993844072583327084608513343873724697777364695367457417702], Tuple<core::array::Span::<core::felt252>>, Tuple<core::panics::Panic, Array<felt252>>> [storable: true, drop: true, dup: false, zero_sized: false];
64+
65+ libfunc revoke_ap_tracking = revoke_ap_tracking;
66+ libfunc withdraw_gas = withdraw_gas;
67+ libfunc branch_align = branch_align;
68+ libfunc redeposit_gas = redeposit_gas;
69+ libfunc struct_construct<Tuple<core::array::Span::<core::felt252>>> = struct_construct<Tuple<core::array::Span::<core::felt252>>>;
70+ libfunc enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 0> = enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 0>;
71+ libfunc drop<core::array::Span::<core::felt252>> = drop<core::array::Span::<core::felt252>>;
72+ libfunc array_new<felt252> = array_new<felt252>;
73+ libfunc struct_construct<core::panics::Panic> = struct_construct<core::panics::Panic>;
74+ libfunc struct_construct<Tuple<core::panics::Panic, Array<felt252>>> = struct_construct<Tuple<core::panics::Panic, Array<felt252>>>;
75+ libfunc enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 1> = enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 1>;
76+ libfunc store_temp<RangeCheck> = store_temp<RangeCheck>;
77+ libfunc store_temp_a = store_temp<{builtin_a}>;
78+ libfunc store_temp_b = store_temp<{builtin_b}>;
79+ libfunc store_temp<GasBuiltin> = store_temp<GasBuiltin>;
80+ libfunc store_temp<System> = store_temp<System>;
81+ libfunc store_temp<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>> = store_temp<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>>;
82+
83+ F0:
84+ revoke_ap_tracking() -> ();
85+ withdraw_gas([0], [3]) {{ fallthrough([6], [7]) F0_B0([8], [9]) }};
86+ branch_align() -> ();
87+ redeposit_gas([7]) -> ([10]);
88+ struct_construct<Tuple<core::array::Span::<core::felt252>>>([5]) -> ([11]);
89+ enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 0>([11]) -> ([12]);
90+ store_temp<RangeCheck>([6]) -> ([6]);
91+ store_temp_a([1]) -> ([1]);
92+ store_temp_b([2]) -> ([2]);
93+ store_temp<GasBuiltin>([10]) -> ([10]);
94+ store_temp<System>([4]) -> ([4]);
95+ store_temp<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>>([12]) -> ([12]);
96+ return([6], [1], [2], [10], [4], [12]);
97+ F0_B0:
98+ branch_align() -> ();
99+ drop<core::array::Span::<core::felt252>>([5]) -> ();
100+ array_new<felt252>() -> ([13]);
101+ struct_construct<core::panics::Panic>() -> ([14]);
102+ struct_construct<Tuple<core::panics::Panic, Array<felt252>>>([14], [13]) -> ([15]);
103+ enum_init<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>, 1>([15]) -> ([16]);
104+ store_temp<RangeCheck>([8]) -> ([8]);
105+ store_temp_a([1]) -> ([1]);
106+ store_temp_b([2]) -> ([2]);
107+ store_temp<GasBuiltin>([9]) -> ([9]);
108+ store_temp<System>([4]) -> ([4]);
109+ store_temp<core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>>([16]) -> ([16]);
110+ return([8], [1], [2], [9], [4], [16]);
111+
112+ test::contract::__wrapper__entry@F0([0]: RangeCheck, [1]: {builtin_a}, [2]: {builtin_b}, [3]: GasBuiltin, [4]: System, [5]: core::array::Span::<core::felt252>) -> (RangeCheck, {builtin_a}, {builtin_b}, GasBuiltin, System, core::panics::PanicResult::<(core::array::Span::<core::felt252>,)>);
113+ " }
114+ }
115+
116+ /// Compiles a hand-written Sierra contract (single external entry point at function 0) to CASM.
117+ fn compile_hand_written (
118+ sierra_text : & str ,
119+ ) -> Result < CasmContractClass , StarknetSierraCompilationError > {
120+ // `ProgramParser` assigns hash-based ids to named types, but the entry-point `TypeResolver`
121+ // indexes type declarations positionally, as in a real (felt-deserialized) contract class.
122+ // Canonicalize the ids to sequential with the shared `CanonicalReplacer`.
123+ let program = ProgramParser :: new ( ) . parse ( sierra_text) . unwrap ( ) ;
124+ let program = CanonicalReplacer :: from_program ( & program) . apply ( & program) ;
125+ let extracted = ExtractedSierraProgram {
126+ program,
127+ sierra_version : current_sierra_version_id ( ) ,
128+ compiler_version : current_sierra_version_id ( ) ,
129+ } ;
130+ // `from_contract_class` takes the Sierra program from `extracted`; from the `ContractClass` it
131+ // only reads `entry_points_by_type`, so the other fields are left empty.
132+ let contract = ContractClass {
133+ sierra_program : vec ! [ ] ,
134+ sierra_program_debug_info : None ,
135+ contract_class_version : "0.1.0" . to_string ( ) ,
136+ entry_points_by_type : ContractEntryPoints {
137+ external : vec ! [ ContractEntryPoint { selector: BigUint :: from( 1u32 ) , function_idx: 0 } ] ,
138+ l1_handler : vec ! [ ] ,
139+ constructor : vec ! [ ] ,
140+ } ,
141+ abi : None ,
142+ } ;
143+ CasmContractClass :: from_contract_class ( contract, extracted, false , usize:: MAX )
144+ }
145+
146+ /// An entry point whose builtins are in the canonical order (RangeCheck, Bitwise, EcOp) compiles
147+ /// successfully.
148+ #[ test]
149+ fn test_entry_point_canonical_builtin_order_accepted ( ) {
150+ assert ! ( compile_hand_written( & entry_point_sierra( "Bitwise" , "EcOp" ) ) . is_ok( ) ) ;
151+ }
152+
153+ /// The same entry point with two builtins swapped (EcOp before Bitwise, violating the canonical
154+ /// order) is rejected. This order cannot come from the frontend, so it can only be exercised with
155+ /// hand-written Sierra.
156+ #[ test]
157+ fn test_entry_point_non_canonical_builtin_order_rejected ( ) {
158+ assert_eq ! (
159+ compile_hand_written( & entry_point_sierra( "EcOp" , "Bitwise" ) ) . unwrap_err( ) ,
160+ StarknetSierraCompilationError :: InvalidEntryPointSignatureWrongBuiltinsOrder ,
161+ ) ;
162+ }
163+
164+ /// Gas and system builtins are only allowed in their fixed trailing slots — an extra `System`
165+ /// (or `GasBuiltin`) threaded mid-list is rejected even though the trailing pair is in place.
166+ #[ test]
167+ fn test_entry_point_mid_list_gas_or_system_rejected ( ) {
168+ for mid in [ "System" , "GasBuiltin" ] {
169+ assert ! ( matches!(
170+ compile_hand_written( & entry_point_sierra( "Bitwise" , mid) ) . unwrap_err( ) ,
171+ StarknetSierraCompilationError :: InvalidBuiltinType ( _) ,
172+ ) ) ;
173+ }
174+ }
175+
37176/// Tests that the CASM compiled from a contract in the contract_crate is the same as in
38177/// <test_case>.compiled_contract_class.json.
39178#[ test_case( "account__account" ) ]
0 commit comments