diff --git a/Cargo.lock b/Cargo.lock index a9cac910bad..98df13122fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,15 +931,19 @@ dependencies = [ "cairo-lang-sierra", "cairo-lang-sierra-ap-change", "cairo-lang-sierra-gas", + "cairo-lang-sierra-generator", "cairo-lang-sierra-type-size", "cairo-lang-test-utils", "cairo-lang-utils", + "convert_case", "env_logger", "indoc", "itertools 0.12.1", "num-bigint", + "num-integer", "num-traits 0.2.19", "pretty_assertions", + "serde", "starknet-types-core", "test-case", "test-log", @@ -3554,6 +3558,17 @@ dependencies = [ "log", ] +[[package]] +name = "sierra-compile-json" +version = "2.8.4" +dependencies = [ + "anyhow", + "cairo-lang-sierra", + "cairo-lang-sierra-to-casm", + "clap", + "serde_json", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3718,6 +3733,7 @@ version = "2.8.4" dependencies = [ "anyhow", "async-channel", + "cairo-lang-sierra-to-casm", "cairo-lang-starknet-classes", "cairo-lang-utils", "clap", diff --git a/Cargo.toml b/Cargo.toml index 1e2a8fc7770..077316fdd0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "crates/bin/cairo-test", "crates/bin/generate-syntax", "crates/bin/sierra-compile", + "crates/bin/sierra-compile-json", "crates/bin/starknet-compile", "crates/bin/starknet-sierra-compile", "crates/bin/starknet-sierra-extract-code", diff --git a/crates/bin/sierra-compile-json/Cargo.toml b/crates/bin/sierra-compile-json/Cargo.toml new file mode 100644 index 00000000000..8c388f3ddbd --- /dev/null +++ b/crates/bin/sierra-compile-json/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sierra-compile-json" +version.workspace = true +edition.workspace = true +repository.workspace = true +license-file.workspace = true +description = "Compiler executable for the Sierra intemediate representation - Outputs a serialized JSON" + +[dependencies] +anyhow.workspace = true +clap.workspace = true +serde_json.workspace = true + +cairo-lang-sierra = { path = "../../cairo-lang-sierra", version = "~2.8.4" } +cairo-lang-sierra-to-casm = { path = "../../cairo-lang-sierra-to-casm", version = "~2.8.4" } diff --git a/crates/bin/sierra-compile-json/src/main.rs b/crates/bin/sierra-compile-json/src/main.rs new file mode 100644 index 00000000000..64b95d926ab --- /dev/null +++ b/crates/bin/sierra-compile-json/src/main.rs @@ -0,0 +1,53 @@ +use std::fs; + +use anyhow::Context; +use cairo_lang_sierra::ProgramParser; +use cairo_lang_sierra_to_casm::compiler::{CasmCairoProgram, SierraToCasmConfig, compile}; +use cairo_lang_sierra_to_casm::metadata::calc_metadata; +use clap::Parser; + +/// Compiles a Sierra file (Cairo Program) into serialized CASM. +/// Exits with 0/1 if the compilation succeeds/fails. +#[derive(Parser, Debug)] +#[clap(version, verbatim_doc_comment)] +struct Args { + /// The path of the file to compile. + file: String, + /// The output file name (default: stdout). + output: Option, + /// Add gas usage check + #[arg(long, default_value_t = false)] + gas_usage_check: bool, +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let sierra_code = fs::read_to_string(args.file).with_context(|| "Could not read file!")?; + let Ok(sierra_program) = ProgramParser::new().parse(&sierra_code) else { + anyhow::bail!("Failed to parse Sierra program.") + }; + + let sierra_to_casm_config = + SierraToCasmConfig { gas_usage_check: args.gas_usage_check, max_bytecode_size: usize::MAX }; + + let cairo_program = compile( + &sierra_program, + &calc_metadata(&sierra_program, Default::default()) + .with_context(|| "Failed calculating Sierra variables.")?, + sierra_to_casm_config, + ) + .with_context(|| "Compilation failed.")?; + + let casm_cairo_program = CasmCairoProgram::new(&sierra_program, &cairo_program) + .with_context(|| "Sierra to Casm compilation failed.")?; + + let res = serde_json::to_string(&casm_cairo_program) + .with_context(|| "Casm contract Serialization failed.")?; + + match args.output { + Some(path) => fs::write(path, res).with_context(|| "Failed to write casm contract.")?, + None => println!("{res}"), + } + Ok(()) +} diff --git a/crates/bin/starknet-sierra-upgrade-validate/Cargo.toml b/crates/bin/starknet-sierra-upgrade-validate/Cargo.toml index 74441080ab2..2ec3a77cd51 100644 --- a/crates/bin/starknet-sierra-upgrade-validate/Cargo.toml +++ b/crates/bin/starknet-sierra-upgrade-validate/Cargo.toml @@ -15,12 +15,11 @@ reqwest = { version = "0.12", features = ["json"] } serde = { workspace = true, default-features = true } serde_json.workspace = true tokio.workspace = true +cairo-lang-sierra-to-casm = { path = "../../cairo-lang-sierra-to-casm", version = "~2.8.4" } cairo-lang-starknet-classes = { path = "../../cairo-lang-starknet-classes", version = "~2.8.4" } -cairo-lang-utils = { path = "../../cairo-lang-utils", version = "~2.8.4", features = [ - "serde", -] } +cairo-lang-utils = { path = "../../cairo-lang-utils", version = "~2.8.4", features = ["serde"] } -# This is not a direct dependency of this package, but it is included here to make sure that the +# This is not a direct dependency of this package, but it is included here to make sure that the # `vendored` feature is enabled (needed for the compilation to succeed on remote machines). openssl = { workspace = true, features = ["vendored"] } diff --git a/crates/bin/starknet-sierra-upgrade-validate/src/main.rs b/crates/bin/starknet-sierra-upgrade-validate/src/main.rs index 27d0fef3562..74674f2baea 100644 --- a/crates/bin/starknet-sierra-upgrade-validate/src/main.rs +++ b/crates/bin/starknet-sierra-upgrade-validate/src/main.rs @@ -4,11 +4,11 @@ use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use anyhow::Context; +use cairo_lang_sierra_to_casm::compiler_version::VersionId; use cairo_lang_starknet_classes::allowed_libfuncs::{AllowedLibfuncsError, ListSelector}; use cairo_lang_starknet_classes::casm_contract_class::{ CasmContractClass, StarknetSierraCompilationError, }; -use cairo_lang_starknet_classes::compiler_version::VersionId; use cairo_lang_starknet_classes::contract_class::{ContractClass, ContractEntryPoints}; use cairo_lang_utils::bigint::BigUintAsHex; use clap::{Parser, arg}; @@ -52,8 +52,8 @@ struct Cli { /// files should be provided. #[arg( long, - requires_all = &["FullnodeArgs"], - required_unless_present = "input_files", + requires_all = &["FullnodeArgs"], + required_unless_present = "input_files", conflicts_with = "input_files" )] fullnode_url: Option, diff --git a/crates/cairo-lang-sierra-to-casm/Cargo.toml b/crates/cairo-lang-sierra-to-casm/Cargo.toml index f8d25aff38e..22412168669 100644 --- a/crates/cairo-lang-sierra-to-casm/Cargo.toml +++ b/crates/cairo-lang-sierra-to-casm/Cargo.toml @@ -8,17 +8,21 @@ description = "Emitting of CASM instructions from Sierra code." [dependencies] assert_matches.workspace = true -cairo-lang-casm = { path = "../cairo-lang-casm", version = "~2.8.4" } +cairo-lang-casm = { path = "../cairo-lang-casm", version = "~2.8.4", features = ["serde"] } cairo-lang-sierra = { path = "../cairo-lang-sierra", version = "~2.8.4" } cairo-lang-sierra-ap-change = { path = "../cairo-lang-sierra-ap-change", version = "~2.8.4" } cairo-lang-sierra-gas = { path = "../cairo-lang-sierra-gas", version = "~2.8.4" } +cairo-lang-sierra-generator = { path = "../cairo-lang-sierra-generator", version = "~2.8.4" } cairo-lang-sierra-type-size = { path = "../cairo-lang-sierra-type-size", version = "~2.8.4" } cairo-lang-utils = { path = "../cairo-lang-utils", version = "~2.8.4", features = ["serde"] } indoc.workspace = true itertools = { workspace = true, default-features = true } +convert_case.workspace = true starknet-types-core.workspace = true num-bigint = { workspace = true, default-features = true } num-traits = { workspace = true, default-features = true } +num-integer.workspace = true +serde = { workspace = true, default-features = true } thiserror.workspace = true [dev-dependencies] @@ -27,7 +31,9 @@ indoc.workspace = true pretty_assertions.workspace = true test-case.workspace = true test-log.workspace = true -cairo-lang-test-utils = { path = "../cairo-lang-test-utils", features = ["testing"] } +cairo-lang-test-utils = { path = "../cairo-lang-test-utils", features = [ + "testing", +] } [features] testing = [] diff --git a/crates/cairo-lang-sierra-to-casm/src/compiler.rs b/crates/cairo-lang-sierra-to-casm/src/compiler.rs index 6f12e00634c..f502d733332 100644 --- a/crates/cairo-lang-sierra-to-casm/src/compiler.rs +++ b/crates/cairo-lang-sierra-to-casm/src/compiler.rs @@ -1,33 +1,53 @@ use std::fmt::Display; use cairo_lang_casm::assembler::AssembledCairoProgram; +use cairo_lang_casm::hints::Hint; use cairo_lang_casm::instructions::{Instruction, InstructionBody, RetInstruction}; -use cairo_lang_sierra::extensions::ConcreteLibfunc; -use cairo_lang_sierra::extensions::circuit::{CircuitConcreteLibfunc, CircuitInfo, VALUE_SIZE}; +use cairo_lang_sierra::extensions::bitwise::BitwiseType; +use cairo_lang_sierra::extensions::circuit::{ + AddModType, CircuitConcreteLibfunc, CircuitInfo, MulModType, VALUE_SIZE, +}; use cairo_lang_sierra::extensions::const_type::ConstConcreteLibfunc; use cairo_lang_sierra::extensions::core::{ CoreConcreteLibfunc, CoreLibfunc, CoreType, CoreTypeConcrete, }; use cairo_lang_sierra::extensions::coupon::CouponConcreteLibfunc; -use cairo_lang_sierra::extensions::gas::GasConcreteLibfunc; +use cairo_lang_sierra::extensions::ec::EcOpType; +use cairo_lang_sierra::extensions::enm::EnumType; +use cairo_lang_sierra::extensions::gas::{GasBuiltinType, GasConcreteLibfunc}; use cairo_lang_sierra::extensions::lib_func::SierraApChange; -use cairo_lang_sierra::ids::{ConcreteLibfuncId, ConcreteTypeId, VarId}; +use cairo_lang_sierra::extensions::pedersen::PedersenType; +use cairo_lang_sierra::extensions::poseidon::PoseidonType; +use cairo_lang_sierra::extensions::range_check::{RangeCheck96Type, RangeCheckType}; +use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType; +use cairo_lang_sierra::extensions::starknet::syscalls::SystemType; +use cairo_lang_sierra::extensions::{ConcreteLibfunc, NamedType}; +use cairo_lang_sierra::ids::{ConcreteLibfuncId, ConcreteTypeId, GenericTypeId, VarId}; use cairo_lang_sierra::program::{ BranchTarget, GenericArg, Invocation, Program, Statement, StatementIdx, }; use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError}; +use cairo_lang_sierra::type_resolver::TypeResolver; +use cairo_lang_sierra_generator::canonical_id_replacer::CanonicalReplacer; +use cairo_lang_sierra_generator::replace_ids::SierraIdReplacer; use cairo_lang_sierra_type_size::{TypeSizeMap, get_type_size_map}; +use cairo_lang_utils::bigint::{BigUintAsHex, deserialize_big_uint, serialize_big_uint}; use cairo_lang_utils::casts::IntoOrPanic; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use cairo_lang_utils::unordered_hash_set::UnorderedHashSet; -use itertools::{chain, zip_eq}; -use num_bigint::BigInt; -use num_traits::{ToPrimitive, Zero}; +use convert_case::{Case, Casing}; +use itertools::{Either, Itertools, chain, zip_eq}; +use num_bigint::{BigInt, BigUint}; +use num_integer::Integer; +use num_traits::{Signed, ToPrimitive, Zero}; +use serde::{Deserialize, Serialize}; +use starknet_types_core::felt::Felt as Felt252; use thiserror::Error; use crate::annotations::{AnnotationError, ProgramAnnotations, StatementAnnotations}; use crate::circuit::CircuitsInfo; +use crate::compiler_version::current_compiler_version_id; use crate::invocations::enm::get_variant_selector; use crate::invocations::{ BranchChanges, InvocationError, ProgramInfo, check_references_on_stack, compile_invocation, @@ -83,6 +103,10 @@ pub enum CompilationError { StatementNotSupportingApChangeVariables(StatementIdx), #[error("Expected all gas variables to be positive.")] MetadataNegativeGasVariable, + #[error("Invalid entry point signature.")] + InvalidEntryPointSignature, + #[error("{0} is not a supported builtin type.")] + InvalidBuiltinType(ConcreteTypeId), } /// Configuration for the Sierra to CASM compilation. @@ -94,6 +118,179 @@ pub struct SierraToCasmConfig { pub max_bytecode_size: usize, } +#[derive(Debug, Error)] +pub enum CasmCairoProgramError { + #[error("Function has no debug_name?")] + MissingFunctionDebugName {}, + #[error("Invalid entry point.")] + EntryPointError, +} + +fn skip_if_none(opt_field: &Option) -> bool { + opt_field.is_none() +} + +/// Represents a serialized Cairo program. +#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CasmCairoProgram { + #[serde(serialize_with = "serialize_big_uint", deserialize_with = "deserialize_big_uint")] + pub prime: BigUint, + pub compiler_version: String, + pub bytecode: Vec, + pub hints: Vec<(usize, Vec)>, + pub entry_points_by_function: OrderedHashMap, +} + +#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CasmCairoEntryPoint { + pub offset: usize, + pub builtins: Vec, + pub input_args: Vec, + pub return_arg: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CasmCairoArg { + pub size: i16, + #[serde(skip_serializing_if = "skip_if_none")] + pub debug_name: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CasmCairoOutputArg { + pub size: i16, + #[serde(skip_serializing_if = "skip_if_none")] + pub debug_name: Option, + #[serde(skip_serializing_if = "skip_if_none")] + pub panic_inner_type: Option, +} + +impl CasmCairoProgram { + pub fn new( + sierra_program: &Program, + cairo_program: &CairoProgram, + ) -> Result { + let replacer = CanonicalReplacer::from_program(sierra_program); + let sierra_program = &replacer.apply(sierra_program); + + let prime = Felt252::prime(); + + let compiler_version = current_compiler_version_id().to_string(); + + let AssembledCairoProgram { bytecode, hints } = cairo_program.assemble(); + + let bytecode: Vec = bytecode + .iter() + .map(|big_int| { + let (_q, remainder) = big_int.magnitude().div_rem(&prime); + BigUintAsHex { + value: if big_int.is_negative() { &prime - remainder } else { remainder }, + } + }) + .collect(); + + let builtin_types = UnorderedHashSet::::from_iter([ + RangeCheckType::id(), + BitwiseType::id(), + PedersenType::id(), + EcOpType::id(), + PoseidonType::id(), + SegmentArenaType::id(), + GasBuiltinType::id(), + SystemType::id(), + RangeCheck96Type::id(), + AddModType::id(), + MulModType::id(), + ]); + + let sierra_program_registry = ProgramRegistry::::new(sierra_program) + .map_err(CompilationError::ProgramRegistryError)?; + + let type_sizes = + get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); + + let entry_points_by_function: OrderedHashMap = + OrderedHashMap::from_iter(sierra_program.funcs.iter().map(|f| { + let offset = cairo_program + .debug_info + .sierra_statement_info + .get(f.entry_point.0) + .ok_or(CasmCairoProgramError::EntryPointError) + .unwrap() + .start_offset; + + let function_name = + f.id.debug_name + .as_ref() + .ok_or(CasmCairoProgramError::MissingFunctionDebugName {}) + .unwrap() + .split("::") + .last() + .unwrap() + .to_string(); + + let type_resolver = TypeResolver { type_decl: &sierra_program.type_declarations }; + + let (builtins, input_args): (Vec, Vec) = + f.signature.param_types.iter().partition_map(|type_id| { + let debug_name = type_id.debug_name.as_ref().map(|name| name.to_string()); + let generic_id = type_resolver.get_generic_id(type_id).clone(); + if builtin_types.contains(&generic_id) { + Either::Left(generic_id.0.as_str().to_case(Case::Snake)) + } else { + let size = *type_sizes.get(type_id).unwrap(); + Either::Right(CasmCairoArg { size, debug_name }) + } + }); + + let return_arg: Vec = f + .signature + .ret_types + .last() + .filter(|type_id| { + !builtin_types.contains(type_resolver.get_generic_id(type_id)) + }) + .map(|type_id| { + let debug_name = type_id.debug_name.clone().map(|name| name.to_string()); + let generic_id = type_resolver.get_generic_id(type_id).clone(); + let size = *type_sizes.get(type_id).unwrap(); + + let long_id = type_resolver.get_long_id(type_id); + + let panic_inner_type = if generic_id == EnumType::ID { + long_id.generic_args.first().and_then(|arg| match arg { + GenericArg::UserType(ut) => ut + .debug_name + .as_ref() + .filter(|name| name.starts_with("core::panics::PanicResult::")) + .and_then(|_| long_id.generic_args.get(1)) + .and_then(|arg| match arg { + GenericArg::Type(ty) => { + let debug_name = + ty.debug_name.clone().map(|name| name.to_string()); + let size = *type_sizes.get(ty).unwrap(); + Some(CasmCairoArg { size, debug_name }) + } + _ => None, + }), + _ => None, + }) + } else { + None + }; + + CasmCairoOutputArg { size, panic_inner_type, debug_name } + }) + .into_iter() + .collect_vec(); + + (function_name, CasmCairoEntryPoint { offset, builtins, input_args, return_arg }) + })); + + Ok(Self { prime, compiler_version, bytecode, hints, entry_points_by_function }) + } +} + /// The casm program representation. #[derive(Debug, Eq, PartialEq, Clone)] pub struct CairoProgram { diff --git a/crates/cairo-lang-sierra-to-casm/src/compiler_version.rs b/crates/cairo-lang-sierra-to-casm/src/compiler_version.rs new file mode 100644 index 00000000000..56494c84662 --- /dev/null +++ b/crates/cairo-lang-sierra-to-casm/src/compiler_version.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct VersionId { + pub major: usize, + pub minor: usize, + pub patch: usize, +} + +pub const CONTRACT_SEGMENTATION_MINOR_VERSION: usize = 5; + +impl std::fmt::Display for VersionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +/// The version of the high level compiler that compiled the contract. Should be the same as the +/// rust workspace version. +pub fn current_compiler_version_id() -> VersionId { + VersionId { + major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), + minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), + patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), + } +} + +/// The version of the Sierra compiler that compiled the contract. +/// +/// Major version should be updated in any non-backwards compatible change of the Sierra compiler. +/// Minor version should be updated in any backwards compatible change of the Sierra compiler. +/// For more information see docs/CONTRIBUTING.md. +pub fn current_sierra_version_id() -> VersionId { + VersionId { major: 1, minor: 6, patch: 0 } +} diff --git a/crates/cairo-lang-sierra-to-casm/src/lib.rs b/crates/cairo-lang-sierra-to-casm/src/lib.rs index 214cc1db563..3d50b3540f6 100644 --- a/crates/cairo-lang-sierra-to-casm/src/lib.rs +++ b/crates/cairo-lang-sierra-to-casm/src/lib.rs @@ -3,6 +3,7 @@ pub mod annotations; pub mod circuit; pub mod compiler; +pub mod compiler_version; pub mod environment; pub mod invocations; pub mod metadata; diff --git a/crates/cairo-lang-sierra/src/lib.rs b/crates/cairo-lang-sierra/src/lib.rs index 7832cc43422..85f227ab6f3 100644 --- a/crates/cairo-lang-sierra/src/lib.rs +++ b/crates/cairo-lang-sierra/src/lib.rs @@ -23,6 +23,7 @@ pub mod program_registry; pub mod simulation; #[cfg(test)] mod test_utils; +pub mod type_resolver; lalrpop_mod!( #[allow(clippy::all, unused_extern_crates)] diff --git a/crates/cairo-lang-sierra/src/type_resolver.rs b/crates/cairo-lang-sierra/src/type_resolver.rs new file mode 100644 index 00000000000..bd6c25ebc28 --- /dev/null +++ b/crates/cairo-lang-sierra/src/type_resolver.rs @@ -0,0 +1,130 @@ +use cairo_lang_utils::require; + +use crate::extensions::NamedType; +use crate::extensions::array::ArrayType; +use crate::extensions::enm::EnumType; +use crate::extensions::felt252::Felt252Type; +use crate::extensions::snapshot::SnapshotType; +use crate::extensions::structure::StructType; +use crate::ids::{ConcreteTypeId, GenericTypeId}; +use crate::program::{ConcreteTypeLongId, GenericArg, TypeDeclaration}; + +/// Context for resolving types. +pub struct TypeResolver<'a> { + pub type_decl: &'a [TypeDeclaration], +} + +impl TypeResolver<'_> { + pub fn get_long_id(&self, type_id: &ConcreteTypeId) -> &ConcreteTypeLongId { + &self.type_decl[type_id.id as usize].long_id + } + + pub fn get_generic_id(&self, type_id: &ConcreteTypeId) -> &GenericTypeId { + &self.get_long_id(type_id).generic_id + } + + fn is_felt252_array_snapshot(&self, ty: &ConcreteTypeId) -> bool { + let long_id = self.get_long_id(ty); + if long_id.generic_id != SnapshotType::id() { + return false; + } + + let [GenericArg::Type(inner_ty)] = long_id.generic_args.as_slice() else { + return false; + }; + + self.is_felt252_array(inner_ty) + } + + fn is_felt252_array(&self, ty: &ConcreteTypeId) -> bool { + let long_id = self.get_long_id(ty); + if long_id.generic_id != ArrayType::id() { + return false; + } + + let [GenericArg::Type(element_ty)] = long_id.generic_args.as_slice() else { + return false; + }; + + *self.get_generic_id(element_ty) == Felt252Type::id() + } + + pub fn is_felt252_span(&self, ty: &ConcreteTypeId) -> bool { + let long_id = self.get_long_id(ty); + if long_id.generic_id != StructType::ID { + return false; + } + + let [GenericArg::UserType(_), GenericArg::Type(element_ty)] = + long_id.generic_args.as_slice() + else { + return false; + }; + + self.is_felt252_array_snapshot(element_ty) + } + + pub fn is_valid_entry_point_return_type(&self, ty: &ConcreteTypeId) -> bool { + // The return type must be an enum with two variants: (result, error). + let Some((result_tuple_ty, err_ty)) = self.extract_result_ty(ty) else { + return false; + }; + + // The result variant must be a tuple with one element: Span; + let Some(result_ty) = self.extract_struct1(result_tuple_ty) else { + return false; + }; + if !self.is_felt252_span(result_ty) { + return false; + } + + // If the error type is Array, it's a good error type, using the old panic + // mechanism. + if self.is_felt252_array(err_ty) { + return true; + } + + // Otherwise, the error type must be a struct with two fields: (panic, data) + let Some((_panic_ty, err_data_ty)) = self.extract_struct2(err_ty) else { + return false; + }; + + // The data field must be a Span. + self.is_felt252_array(err_data_ty) + } + + /// Extracts types `TOk`, `TErr` from the type `Result`. + fn extract_result_ty(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> { + let long_id = self.get_long_id(ty); + require(long_id.generic_id == EnumType::id())?; + let [GenericArg::UserType(_), GenericArg::Type(result_tuple_ty), GenericArg::Type(err_ty)] = + long_id.generic_args.as_slice() + else { + return None; + }; + Some((result_tuple_ty, err_ty)) + } + + /// Extracts type `T` from the tuple type `(T,)`. + fn extract_struct1(&self, ty: &ConcreteTypeId) -> Option<&ConcreteTypeId> { + let long_id = self.get_long_id(ty); + require(long_id.generic_id == StructType::id())?; + let [GenericArg::UserType(_), GenericArg::Type(ty0)] = long_id.generic_args.as_slice() + else { + return None; + }; + Some(ty0) + } + + /// Extracts types `T0`, `T1` from the tuple type `(T0, T1)`. + fn extract_struct2(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> { + let long_id = self.get_long_id(ty); + require(long_id.generic_id == StructType::id())?; + let [GenericArg::UserType(_), GenericArg::Type(ty0), GenericArg::Type(ty1)] = + long_id.generic_args.as_slice() + else { + return None; + }; + Some((ty0, ty1)) + } +} diff --git a/crates/cairo-lang-starknet-classes/src/casm_contract_class.rs b/crates/cairo-lang-starknet-classes/src/casm_contract_class.rs index 12ec13cb552..5be204587e0 100644 --- a/crates/cairo-lang-starknet-classes/src/casm_contract_class.rs +++ b/crates/cairo-lang-starknet-classes/src/casm_contract_class.rs @@ -4,25 +4,24 @@ use std::sync::LazyLock; use cairo_lang_casm::assembler::AssembledCairoProgram; use cairo_lang_casm::hints::{Hint, PythonicHint}; use cairo_lang_sierra::extensions::NamedType; -use cairo_lang_sierra::extensions::array::ArrayType; use cairo_lang_sierra::extensions::bitwise::BitwiseType; use cairo_lang_sierra::extensions::circuit::{AddModType, MulModType}; use cairo_lang_sierra::extensions::ec::EcOpType; -use cairo_lang_sierra::extensions::enm::EnumType; -use cairo_lang_sierra::extensions::felt252::Felt252Type; use cairo_lang_sierra::extensions::gas::{CostTokenType, GasBuiltinType}; use cairo_lang_sierra::extensions::pedersen::PedersenType; use cairo_lang_sierra::extensions::poseidon::PoseidonType; use cairo_lang_sierra::extensions::range_check::{RangeCheck96Type, RangeCheckType}; use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType; -use cairo_lang_sierra::extensions::snapshot::SnapshotType; use cairo_lang_sierra::extensions::starknet::syscalls::SystemType; -use cairo_lang_sierra::extensions::structure::StructType; use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId}; -use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg, TypeDeclaration}; +use cairo_lang_sierra::type_resolver::TypeResolver; use cairo_lang_sierra_to_casm::compiler::{ CairoProgramDebugInfo, CompilationError, SierraToCasmConfig, }; +use cairo_lang_sierra_to_casm::compiler_version::{ + CONTRACT_SEGMENTATION_MINOR_VERSION, VersionId, current_compiler_version_id, + current_sierra_version_id, +}; use cairo_lang_sierra_to_casm::metadata::{ MetadataComputationConfig, MetadataError, calc_metadata, }; @@ -42,10 +41,6 @@ use starknet_types_core::hash::{Poseidon, StarkHash}; use thiserror::Error; use crate::allowed_libfuncs::AllowedLibfuncsError; -use crate::compiler_version::{ - CONTRACT_SEGMENTATION_MINOR_VERSION, VersionId, current_compiler_version_id, - current_sierra_version_id, -}; use crate::contract_class::{ContractClass, ContractEntryPoint}; use crate::contract_segmentation::{ NestedIntList, SegmentationError, compute_bytecode_segment_lengths, @@ -202,126 +197,6 @@ fn bytecode_hash_node( } } -/// Context for resolving types. -pub struct TypeResolver<'a> { - type_decl: &'a [TypeDeclaration], -} - -impl TypeResolver<'_> { - fn get_long_id(&self, type_id: &ConcreteTypeId) -> &ConcreteTypeLongId { - &self.type_decl[type_id.id as usize].long_id - } - - fn get_generic_id(&self, type_id: &ConcreteTypeId) -> &GenericTypeId { - &self.get_long_id(type_id).generic_id - } - - fn is_felt252_array_snapshot(&self, ty: &ConcreteTypeId) -> bool { - let long_id = self.get_long_id(ty); - if long_id.generic_id != SnapshotType::id() { - return false; - } - - let [GenericArg::Type(inner_ty)] = long_id.generic_args.as_slice() else { - return false; - }; - - self.is_felt252_array(inner_ty) - } - - fn is_felt252_array(&self, ty: &ConcreteTypeId) -> bool { - let long_id = self.get_long_id(ty); - if long_id.generic_id != ArrayType::id() { - return false; - } - - let [GenericArg::Type(element_ty)] = long_id.generic_args.as_slice() else { - return false; - }; - - *self.get_generic_id(element_ty) == Felt252Type::id() - } - - fn is_felt252_span(&self, ty: &ConcreteTypeId) -> bool { - let long_id = self.get_long_id(ty); - if long_id.generic_id != StructType::ID { - return false; - } - - let [GenericArg::UserType(_), GenericArg::Type(element_ty)] = - long_id.generic_args.as_slice() - else { - return false; - }; - - self.is_felt252_array_snapshot(element_ty) - } - - fn is_valid_entry_point_return_type(&self, ty: &ConcreteTypeId) -> bool { - // The return type must be an enum with two variants: (result, error). - let Some((result_tuple_ty, err_ty)) = self.extract_result_ty(ty) else { - return false; - }; - - // The result variant must be a tuple with one element: Span; - let Some(result_ty) = self.extract_struct1(result_tuple_ty) else { - return false; - }; - if !self.is_felt252_span(result_ty) { - return false; - } - - // If the error type is Array, it's a good error type, using the old panic - // mechanism. - if self.is_felt252_array(err_ty) { - return true; - } - - // Otherwise, the error type must be a struct with two fields: (panic, data) - let Some((_panic_ty, err_data_ty)) = self.extract_struct2(err_ty) else { - return false; - }; - - // The data field must be a Span. - self.is_felt252_array(err_data_ty) - } - - /// Extracts types `TOk`, `TErr` from the type `Result`. - fn extract_result_ty(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> { - let long_id = self.get_long_id(ty); - require(long_id.generic_id == EnumType::id())?; - let [GenericArg::UserType(_), GenericArg::Type(result_tuple_ty), GenericArg::Type(err_ty)] = - long_id.generic_args.as_slice() - else { - return None; - }; - Some((result_tuple_ty, err_ty)) - } - - /// Extracts type `T` from the tuple type `(T,)`. - fn extract_struct1(&self, ty: &ConcreteTypeId) -> Option<&ConcreteTypeId> { - let long_id = self.get_long_id(ty); - require(long_id.generic_id == StructType::id())?; - let [GenericArg::UserType(_), GenericArg::Type(ty0)] = long_id.generic_args.as_slice() - else { - return None; - }; - Some(ty0) - } - - /// Extracts types `T0`, `T1` from the tuple type `(T0, T1)`. - fn extract_struct2(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> { - let long_id = self.get_long_id(ty); - require(long_id.generic_id == StructType::id())?; - let [GenericArg::UserType(_), GenericArg::Type(ty0), GenericArg::Type(ty1)] = - long_id.generic_args.as_slice() - else { - return None; - }; - Some((ty0, ty1)) - } -} - impl CasmContractClass { pub fn from_contract_class( contract_class: ContractClass, diff --git a/crates/cairo-lang-starknet-classes/src/contract_class.rs b/crates/cairo-lang-starknet-classes/src/contract_class.rs index 45047b5949e..82245df0bd4 100644 --- a/crates/cairo-lang-starknet-classes/src/contract_class.rs +++ b/crates/cairo-lang-starknet-classes/src/contract_class.rs @@ -1,4 +1,7 @@ use cairo_lang_sierra as sierra; +use cairo_lang_sierra_to_casm::compiler_version::{ + VersionId, current_compiler_version_id, current_sierra_version_id, +}; use cairo_lang_utils::bigint::{BigUintAsHex, deserialize_big_uint, serialize_big_uint}; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use num_bigint::BigUint; @@ -8,7 +11,6 @@ use thiserror::Error; use crate::abi::Contract; use crate::allowed_libfuncs::{AllowedLibfuncsError, ListSelector, lookup_allowed_libfuncs_list}; -use crate::compiler_version::{VersionId, current_compiler_version_id, current_sierra_version_id}; use crate::felt252_serde::{ Felt252SerdeError, sierra_from_felt252s, sierra_to_felt252s, version_id_from_felt252s, }; @@ -127,7 +129,7 @@ pub struct ContractEntryPoint { /// felt252s. /// /// Returns (sierra_version_id, compiler_version_id). -/// See [crate::compiler_version]. +/// See [cairo_lang_sierra_to_casm::compiler_version]. pub fn version_id_from_serialized_sierra_program( sierra_program: &[BigUintAsHex], ) -> Result<(VersionId, VersionId), Felt252SerdeError> { diff --git a/crates/cairo-lang-starknet-classes/src/felt252_serde.rs b/crates/cairo-lang-starknet-classes/src/felt252_serde.rs index 334d5acdd33..71cdd6e030a 100644 --- a/crates/cairo-lang-starknet-classes/src/felt252_serde.rs +++ b/crates/cairo-lang-starknet-classes/src/felt252_serde.rs @@ -24,6 +24,7 @@ use cairo_lang_sierra::program::{ Function, FunctionSignature, GenericArg, Invocation, LibfuncDeclaration, Param, Program, Statement, StatementIdx, TypeDeclaration, }; +use cairo_lang_sierra_to_casm::compiler_version::VersionId; use cairo_lang_utils::bigint::BigUintAsHex; use cairo_lang_utils::ordered_hash_set::OrderedHashSet; use cairo_lang_utils::require; @@ -33,7 +34,6 @@ use num_traits::{Signed, ToPrimitive}; use smol_str::SmolStr; use thiserror::Error; -use crate::compiler_version::VersionId; use crate::felt252_vec_compression::{compress, decompress}; use crate::keccak::starknet_keccak; @@ -85,7 +85,7 @@ pub fn sierra_to_felt252s( /// /// Returns (sierra_version_id, compiler_version_id, remaining), /// where 'remaining' are all the felts other than the ones dedicated to the version, unprocessed. -/// See [crate::compiler_version]. +/// See [cairo_lang_sierra_to_casm::compiler_version]. pub fn version_id_from_felt252s( sierra_program: &[BigUintAsHex], ) -> Result<(VersionId, VersionId, &[BigUintAsHex]), Felt252SerdeError> { @@ -97,7 +97,7 @@ pub fn version_id_from_felt252s( /// Deserializes a Sierra program represented as a slice of felt252s. /// /// Returns (sierra_version_id, compiler_version_id, program). -/// See [crate::compiler_version]. +/// See [cairo_lang_sierra_to_casm::compiler_version]. pub fn sierra_from_felt252s( sierra_program: &[BigUintAsHex], ) -> Result<(VersionId, VersionId, Program), Felt252SerdeError> { diff --git a/crates/cairo-lang-starknet-classes/src/felt252_serde_test.rs b/crates/cairo-lang-starknet-classes/src/felt252_serde_test.rs index 60b61fcb142..885d6e002a3 100644 --- a/crates/cairo-lang-starknet-classes/src/felt252_serde_test.rs +++ b/crates/cairo-lang-starknet-classes/src/felt252_serde_test.rs @@ -5,10 +5,10 @@ use cairo_lang_sierra::extensions::GenericLibfunc; use cairo_lang_sierra::extensions::core::CoreLibfunc; use cairo_lang_sierra_generator::canonical_id_replacer::CanonicalReplacer; use cairo_lang_sierra_generator::replace_ids::SierraIdReplacer; +use cairo_lang_sierra_to_casm::compiler_version; use test_case::test_case; use super::{Felt252Serde, SERDE_SUPPORTED_LONG_IDS}; -use crate::compiler_version; use crate::felt252_serde::{sierra_from_felt252s, sierra_to_felt252s}; use crate::test_utils::get_example_file_path; diff --git a/crates/cairo-lang-starknet-classes/src/lib.rs b/crates/cairo-lang-starknet-classes/src/lib.rs index 3fcc89e1d4d..95a608dd066 100644 --- a/crates/cairo-lang-starknet-classes/src/lib.rs +++ b/crates/cairo-lang-starknet-classes/src/lib.rs @@ -8,7 +8,6 @@ pub mod abi; pub mod allowed_libfuncs; pub mod casm_contract_class; -pub mod compiler_version; pub mod contract_class; mod contract_segmentation; mod felt252_serde;