Sitelet https://github.com/starkware-libs/cairo/commit/46939ecea159f42b5a1fcf8c6f6915aa82bf7c42
Skip to content

Commit 46939ec

Browse files
(refactor): validate entry-point signatures before compilation in class-to-casm
Move the entry-point signature checks (return/panic shape, input == output builtins, felt252-span argument, builtin types, trailing gas/system) out of the post-compilation `as_casm_entry_point` into an `entry_point_builtins` closure that runs over all entry points before the (much heavier) sierra-to-casm compilation. `as_casm_entry_point` reuses it for the builtin names and keeps only the compilation-dependent parts (code offset, cost assert). This reports signature errors without paying for compilation, and also fixes a potential panic: an out-of-range entry-point function index used to be indexed unchecked when building the metadata config; validation now precedes it.
1 parent daff9bc commit 46939ec

1 file changed

Lines changed: 99 additions & 76 deletions

File tree

‎crates/cairo-lang-starknet-classes/src/casm_contract_class.rs‎

Lines changed: 99 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType;
1818
use cairo_lang_sierra::extensions::snapshot::SnapshotType;
1919
use cairo_lang_sierra::extensions::starknet::syscalls::SystemType;
2020
use cairo_lang_sierra::extensions::structure::StructType;
21-
use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId};
22-
use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg, TypeDeclaration};
21+
use cairo_lang_sierra::ids::{ConcreteTypeId, FunctionId, GenericTypeId};
22+
use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg, StatementIdx, TypeDeclaration};
2323
use cairo_lang_sierra_to_casm::compiler::{
2424
CairoProgramDebugInfo, CompilationError, SierraToCasmConfig,
2525
};
@@ -32,7 +32,7 @@ use cairo_lang_utils::require;
3232
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
3333
use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
3434
use convert_case::{Case, Casing};
35-
use itertools::{Itertools, chain};
35+
use itertools::{Itertools, chain, zip_eq};
3636
use num_bigint::BigUint;
3737
use num_integer::Integer;
3838
use num_traits::Signed;
@@ -428,52 +428,6 @@ impl CasmContractClass {
428428
});
429429
}
430430
}
431-
let entrypoint_ids = entrypoint_function_indices.map(|idx| program.funcs[idx].id.clone());
432-
// TODO(lior): Remove this assert and condition once the equation solver is removed in major
433-
// version 2.
434-
assert_eq!(sierra_version.major, 1);
435-
const NO_EQ_SOLVER_VERSION: VersionId = VersionId { major: 1, minor: 4, patch: 0 };
436-
let no_eq_solver = sierra_version.supports(NO_EQ_SOLVER_VERSION);
437-
let metadata_computation_config = MetadataComputationConfig {
438-
function_set_costs: entrypoint_ids
439-
.map(|id| (id, CostTokenMap::from_iter([(CostTokenType::Const, ENTRY_POINT_COST)])))
440-
.collect(),
441-
linear_gas_solver: no_eq_solver,
442-
linear_ap_change_solver: no_eq_solver,
443-
skip_non_linear_solver_comparisons: false,
444-
compute_runtime_costs: false,
445-
};
446-
let program_info = ProgramRegistryInfo::new(&program).map_err(|err| {
447-
StarknetSierraCompilationError::CompilationError(Box::new(
448-
CompilationError::ProgramRegistryError(err),
449-
))
450-
})?;
451-
let metadata = calc_metadata(&program, &program_info, metadata_computation_config)?;
452-
let cairo_program = cairo_lang_sierra_to_casm::compiler::compile(
453-
&program,
454-
&program_info,
455-
&metadata,
456-
SierraToCasmConfig { gas_usage_check: true, max_bytecode_size },
457-
)?;
458-
459-
let AssembledCairoProgram { bytecode, hints } = cairo_program.assemble();
460-
let prime = Felt252::prime();
461-
let bytecode = bytecode
462-
.iter()
463-
.map(|big_int| {
464-
let (_q, reminder) = big_int.magnitude().div_rem(&prime);
465-
BigUintAsHex {
466-
value: if big_int.is_negative() { &prime - reminder } else { reminder },
467-
}
468-
})
469-
.collect_vec();
470-
471-
const CONTRACT_SEGMENTATION_VERSION: VersionId = VersionId { major: 1, minor: 5, patch: 0 };
472-
let bytecode_segment_lengths = if sierra_version.supports(CONTRACT_SEGMENTATION_VERSION) {
473-
Some(compute_bytecode_segment_lengths(&program, &cairo_program, bytecode.len())?)
474-
} else {
475-
None
476-
};
477431

478432
let builtin_types = UnorderedHashSet::<GenericTypeId>::from_iter([
479433
RangeCheckType::id(),
@@ -489,11 +443,12 @@ impl CasmContractClass {
489443
MulModType::id(),
490444
]);
491445

492-
let as_casm_entry_point = |contract_entry_point: ContractEntryPoint| {
446+
// Validates the entry point's signature, returning its entry statement, function id and
447+
// builtin names.
448+
let validate_entry_point = |contract_entry_point: &ContractEntryPoint| {
493449
let Some(function) = program.funcs.get(contract_entry_point.function_idx) else {
494450
return Err(StarknetSierraCompilationError::EntryPointError);
495451
};
496-
let statement_id = function.entry_point;
497452

498453
// The expected return types are [builtins.., gas_builtin, system, PanicResult].
499454
let (panic_result, output_builtins) = function
@@ -543,33 +498,92 @@ impl CasmContractClass {
543498
name => name.to_case(Case::Snake),
544499
})
545500
.collect_vec();
501+
Ok((function.entry_point, &function.id, builtins))
502+
};
546503

547-
let code_offset = cairo_program
548-
.debug_info
549-
.sierra_statement_info
550-
.get(statement_id.0)
551-
.ok_or(StarknetSierraCompilationError::EntryPointError)?
552-
.start_offset;
553-
assert_eq!(
554-
metadata.gas_info.function_costs[&function.id],
555-
CostTokenMap::from_iter([(CostTokenType::Const, ENTRY_POINT_COST as i64)]),
556-
"Unexpected entry point cost."
557-
);
558-
Ok::<CasmContractEntryPoint, StarknetSierraCompilationError>(CasmContractEntryPoint {
559-
selector: contract_entry_point.selector,
560-
offset: code_offset,
561-
builtins,
562-
})
504+
// Validate all entry-point signatures before the (much heavier) compilation, keeping the
505+
// validated info for building the CASM entry points afterwards.
506+
let validate_entry_points = |entry_points: &[ContractEntryPoint]| {
507+
entry_points.iter().map(validate_entry_point).collect::<Result<Vec<_>, _>>()
563508
};
509+
let external_infos = validate_entry_points(&contract_class.entry_points_by_type.external)?;
510+
let l1_handler_infos =
511+
validate_entry_points(&contract_class.entry_points_by_type.l1_handler)?;
512+
let constructor_infos =
513+
validate_entry_points(&contract_class.entry_points_by_type.constructor)?;
564514

565-
let as_casm_entry_points = |contract_entry_points: Vec<ContractEntryPoint>| {
566-
let mut entry_points = vec![];
567-
for contract_entry_point in contract_entry_points {
568-
entry_points.push(as_casm_entry_point(contract_entry_point)?);
569-
}
570-
Ok::<Vec<CasmContractEntryPoint>, StarknetSierraCompilationError>(entry_points)
515+
let entrypoint_ids = entrypoint_function_indices.map(|idx| program.funcs[idx].id.clone());
516+
// TODO(lior): Remove this assert and condition once the equation solver is removed in major
517+
// version 2.
518+
assert_eq!(sierra_version.major, 1);
519+
const NO_EQ_SOLVER_VERSION: VersionId = VersionId { major: 1, minor: 4, patch: 0 };
520+
let no_eq_solver = sierra_version.supports(NO_EQ_SOLVER_VERSION);
521+
let metadata_computation_config = MetadataComputationConfig {
522+
function_set_costs: entrypoint_ids
523+
.map(|id| (id, CostTokenMap::from_iter([(CostTokenType::Const, ENTRY_POINT_COST)])))
524+
.collect(),
525+
linear_gas_solver: no_eq_solver,
526+
linear_ap_change_solver: no_eq_solver,
527+
skip_non_linear_solver_comparisons: false,
528+
compute_runtime_costs: false,
529+
};
530+
let program_info = ProgramRegistryInfo::new(&program).map_err(|err| {
531+
StarknetSierraCompilationError::CompilationError(Box::new(
532+
CompilationError::ProgramRegistryError(err),
533+
))
534+
})?;
535+
let metadata = calc_metadata(&program, &program_info, metadata_computation_config)?;
536+
let cairo_program = cairo_lang_sierra_to_casm::compiler::compile(
537+
&program,
538+
&program_info,
539+
&metadata,
540+
SierraToCasmConfig { gas_usage_check: true, max_bytecode_size },
541+
)?;
542+
543+
let AssembledCairoProgram { bytecode, hints } = cairo_program.assemble();
544+
let prime = Felt252::prime();
545+
let bytecode = bytecode
546+
.iter()
547+
.map(|big_int| {
548+
let (_q, reminder) = big_int.magnitude().div_rem(&prime);
549+
BigUintAsHex {
550+
value: if big_int.is_negative() { &prime - reminder } else { reminder },
551+
}
552+
})
553+
.collect_vec();
554+
555+
const CONTRACT_SEGMENTATION_VERSION: VersionId = VersionId { major: 1, minor: 5, patch: 0 };
556+
let bytecode_segment_lengths = if sierra_version.supports(CONTRACT_SEGMENTATION_VERSION) {
557+
Some(compute_bytecode_segment_lengths(&program, &cairo_program, bytecode.len())?)
558+
} else {
559+
None
571560
};
572561

562+
let as_casm_entry_points =
563+
|contract_entry_points: Vec<ContractEntryPoint>,
564+
infos: Vec<(StatementIdx, &FunctionId, Vec<String>)>| {
565+
zip_eq(contract_entry_points, infos)
566+
.map(|(contract_entry_point, (statement_id, function_id, builtins))| {
567+
let code_offset = cairo_program.debug_info.sierra_statement_info
568+
[statement_id.0]
569+
.start_offset;
570+
assert_eq!(
571+
metadata.gas_info.function_costs[function_id],
572+
CostTokenMap::from_iter([(
573+
CostTokenType::Const,
574+
ENTRY_POINT_COST as i64
575+
)]),
576+
"Unexpected entry point cost."
577+
);
578+
CasmContractEntryPoint {
579+
selector: contract_entry_point.selector,
580+
offset: code_offset,
581+
builtins,
582+
}
583+
})
584+
.collect_vec()
585+
};
586+
573587
let pythonic_hints = if add_pythonic_hints {
574588
Some(
575589
hints
@@ -592,9 +606,18 @@ impl CasmContractClass {
592606
hints,
593607
pythonic_hints,
594608
entry_points_by_type: CasmContractEntryPoints {
595-
external: as_casm_entry_points(contract_class.entry_points_by_type.external)?,
596-
l1_handler: as_casm_entry_points(contract_class.entry_points_by_type.l1_handler)?,
597-
constructor: as_casm_entry_points(contract_class.entry_points_by_type.constructor)?,
609+
external: as_casm_entry_points(
610+
contract_class.entry_points_by_type.external,
611+
external_infos,
612+
),
613+
l1_handler: as_casm_entry_points(
614+
contract_class.entry_points_by_type.l1_handler,
615+
l1_handler_infos,
616+
),
617+
constructor: as_casm_entry_points(
618+
contract_class.entry_points_by_type.constructor,
619+
constructor_infos,
620+
),
598621
},
599622
};
600623

0 commit comments

Comments
 (0)