diff --git a/Cargo.lock b/Cargo.lock index 760fa984..5dfe409f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5492,6 +5492,7 @@ dependencies = [ "axum", "blockifier 0.7.0-dev.1 (git+https://github.com/starkware-libs/blockifier.git?branch=main-mempool)", "cairo-lang-starknet-classes", + "cairo-vm", "hyper", "papyrus_config", "pretty_assertions", @@ -5502,6 +5503,7 @@ dependencies = [ "starknet_api", "starknet_mempool", "starknet_mempool_types", + "starknet_sierra_compile", "thiserror", "tokio", "validator", @@ -5606,8 +5608,10 @@ dependencies = [ "cairo-lang-sierra", "cairo-lang-starknet-classes", "cairo-lang-utils", + "num-bigint", "serde", "serde_json", + "starknet_api", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 1dc2aec9..cca58215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ bincode = "1.3.3" cairo-lang-sierra = "2.6.0" cairo-lang-starknet-classes = "2.6.0" cairo-lang-utils = "2.6.0" +cairo-vm = "0.9.2" clap = "4.3.10" colored = "2.1.0" const_format = "0.2.30" @@ -51,6 +52,7 @@ futures = "0.3.30" hyper = { version = "0.14", features = ["client", "http1", "http2"] } indexmap = "2.1.0" itertools = "0.13.0" +num-bigint = { version = "0.4.5", default-features = false } # TODO(YaelD, 28/5/2024): The special Papyrus version is needed in order to be aligned with the # starknet-api version. This should be removed once we have a mono-repo. papyrus_common = { git = "https://github.com/starkware-libs/papyrus.git", rev = "050e470f" } diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index 35f4f2f9..5402db98 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -15,6 +15,7 @@ testing = [] axum.workspace = true blockifier.workspace = true cairo-lang-starknet-classes.workspace = true +cairo-vm.workspace = true hyper.workspace = true papyrus_config.workspace = true reqwest.workspace = true @@ -22,6 +23,7 @@ serde.workspace = true serde_json.workspace = true starknet_api.workspace = true starknet_mempool_types = { path = "../mempool_types", version = "0.0" } +starknet_sierra_compile = { path = "../starknet_sierra_compile", version = "0.0" } thiserror.workspace = true tokio.workspace = true validator.workspace = true diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 31e23292..08c8ebad 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -1,8 +1,10 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use blockifier::blockifier::stateful_validator::StatefulValidatorError; +use blockifier::execution::errors::ContractClassError; use blockifier::state::errors::StateError; use blockifier::transaction::errors::TransactionExecutionError; +use cairo_vm::types::errors::program_errors::ProgramError; use starknet_api::block::BlockNumber; use starknet_api::transaction::{Resource, ResourceBounds}; use starknet_api::StarknetApiError; @@ -14,6 +16,12 @@ use crate::compiler_version::VersionIdError; /// Errors directed towards the end-user, as a result of gateway requests. #[derive(Debug, Error)] pub enum GatewayError { + #[error(transparent)] + CompilationError(#[from] starknet_sierra_compile::compile::CompilationUtilError), + #[error(transparent)] + DeclaredContractClassError(#[from] ContractClassError), + #[error(transparent)] + DeclaredContractProgramError(#[from] ProgramError), #[error("Internal server error: {0}")] InternalServerError(#[from] JoinError), #[error("Error sending message: {0}")] diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index b95f5396..fcf7c0a1 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -1,14 +1,18 @@ use std::clone::Clone; use std::net::SocketAddr; +use std::panic; use std::sync::Arc; use axum::extract::State; use axum::routing::{get, post}; use axum::{Json, Router}; -use starknet_api::rpc_transaction::RPCTransaction; +use blockifier::execution::contract_class::{ClassInfo, ContractClass, ContractClassV1}; +use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction}; use starknet_api::transaction::TransactionHash; use starknet_mempool_types::communication::SharedMempoolClient; use starknet_mempool_types::mempool_types::{Account, MempoolInput}; +use starknet_sierra_compile::compile::{compile_sierra_to_casm, CompilationUtilError}; +use starknet_sierra_compile::utils::into_contract_class_for_compilation; use crate::config::{GatewayConfig, GatewayNetworkConfig}; use crate::errors::{GatewayError, GatewayRunError}; @@ -116,11 +120,50 @@ fn process_tx( // Perform stateless validations. stateless_tx_validator.validate(&tx)?; - // TODO(Yael, 19/5/2024): pass the relevant class_info and deploy_account_hash. - let tx_hash = stateful_tx_validator.run_validate(state_reader_factory, &tx, None, None)?; + // Compile Sierra to Casm. + let optional_class_info = match &tx { + RPCTransaction::Declare(declare_tx) => Some(compile_contract_class(declare_tx)?), + _ => None, + }; + // TODO(Yael, 19/5/2024): pass the relevant deploy_account_hash. + let tx_hash = + stateful_tx_validator.run_validate(state_reader_factory, &tx, optional_class_info, None)?; + + // TODO(Arni): Add the Sierra and the Casm to the mempool input. Ok(MempoolInput { tx: external_tx_to_thin_tx(&tx, tx_hash), account: Account { sender_address: get_sender_address(&tx), ..Default::default() }, }) } + +/// Formats the contract class for compilation, compiles it, and returns the compiled contract class +/// wrapped in a [`ClassInfo`]. +/// Assumes the contract class is of a Sierra program which is compiled to Casm. +fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResult { + let RPCDeclareTransaction::V3(tx) = declare_tx; + let starknet_api_contract_class = &tx.contract_class; + let cairo_lang_contract_class = + into_contract_class_for_compilation(starknet_api_contract_class); + + // Compile Sierra to Casm. + let catch_unwind_result = + panic::catch_unwind(|| compile_sierra_to_casm(cairo_lang_contract_class)); + let casm_contract_class = match catch_unwind_result { + Ok(compilation_result) => compilation_result?, + Err(_) => { + // TODO(Arni): Log the panic. + return Err(GatewayError::CompilationError(CompilationUtilError::CompilationPanic)); + } + }; + + // Convert Casm contract class to Starknet contract class directly. + let blockifier_contract_class = + ContractClass::V1(ContractClassV1::try_from(casm_contract_class)?); + let class_info = ClassInfo::new( + &blockifier_contract_class, + starknet_api_contract_class.sierra_program.len(), + starknet_api_contract_class.abi.len(), + )?; + Ok(class_info) +} diff --git a/crates/starknet_sierra_compile/Cargo.toml b/crates/starknet_sierra_compile/Cargo.toml index 7c21f0e4..1925ae2c 100644 --- a/crates/starknet_sierra_compile/Cargo.toml +++ b/crates/starknet_sierra_compile/Cargo.toml @@ -12,8 +12,10 @@ workspace = true cairo-lang-sierra.workspace = true cairo-lang-starknet-classes.workspace = true cairo-lang-utils.workspace = true +num-bigint.workspace = true serde_json.workspace = true serde.workspace = true +starknet_api.workspace = true thiserror.workspace = true [dev-dependencies] diff --git a/crates/starknet_sierra_compile/src/compile.rs b/crates/starknet_sierra_compile/src/compile.rs index b62b6bec..f0925265 100644 --- a/crates/starknet_sierra_compile/src/compile.rs +++ b/crates/starknet_sierra_compile/src/compile.rs @@ -20,6 +20,8 @@ pub enum CompilationUtilError { AllowedLibfuncsError(#[from] AllowedLibfuncsError), #[error(transparent)] StarknetSierraCompilationError(#[from] StarknetSierraCompilationError), + #[error("Compilation panicked")] + CompilationPanic, } /// This function may panic. diff --git a/crates/starknet_sierra_compile/src/lib.rs b/crates/starknet_sierra_compile/src/lib.rs index 796bddbf..0560307d 100644 --- a/crates/starknet_sierra_compile/src/lib.rs +++ b/crates/starknet_sierra_compile/src/lib.rs @@ -1,6 +1,7 @@ //! A lib for compiling Sierra into Casm. pub mod compile; +pub mod utils; #[cfg(any(feature = "testing", test))] pub mod test_utils; diff --git a/crates/starknet_sierra_compile/src/utils.rs b/crates/starknet_sierra_compile/src/utils.rs new file mode 100644 index 00000000..ec995ef3 --- /dev/null +++ b/crates/starknet_sierra_compile/src/utils.rs @@ -0,0 +1,69 @@ +use std::clone::Clone; + +use cairo_lang_starknet_classes::contract_class::{ + ContractClass as CairoLangContractClass, ContractEntryPoint as CairoLangContractEntryPoint, + ContractEntryPoints as CairoLangContractEntryPoints, +}; +use cairo_lang_utils::bigint::BigUintAsHex; +use num_bigint::BigUint; +use starknet_api::hash::StarkFelt; +use starknet_api::rpc_transaction::{ + ContractClass as StarknetApiContractClass, EntryPointByType as StarknetApiEntryPointByType, +}; +use starknet_api::state::EntryPoint as StarknetApiEntryPoint; + +/// Retruns a [`CairoLangContractClass`] struct ready for Sierra to Casm compilation. Note the `abi` +/// field is None as it is not relevant for the compilation. +pub fn into_contract_class_for_compilation( + starknet_api_contract_class: &StarknetApiContractClass, +) -> CairoLangContractClass { + let sierra_program = starknet_api_contract_class + .sierra_program + .iter() + .map(stark_felt_to_big_uint_as_hex) + .collect(); + let entry_points_by_type = + into_cairo_lang_contract_entry_points(&starknet_api_contract_class.entry_points_by_type); + + CairoLangContractClass { + sierra_program, + sierra_program_debug_info: None, + contract_class_version: starknet_api_contract_class.contract_class_version.clone(), + entry_points_by_type, + abi: None, + } +} + +fn into_cairo_lang_contract_entry_points( + entry_points_by_type: &StarknetApiEntryPointByType, +) -> CairoLangContractEntryPoints { + let StarknetApiEntryPointByType { constructor, external, l1handler } = entry_points_by_type; + CairoLangContractEntryPoints { + external: into_cairo_lang_contract_entry_points_vec(external), + l1_handler: into_cairo_lang_contract_entry_points_vec(l1handler), + constructor: into_cairo_lang_contract_entry_points_vec(constructor), + } +} + +fn into_cairo_lang_contract_entry_points_vec( + entry_points: &[StarknetApiEntryPoint], +) -> Vec { + entry_points.iter().map(into_cairo_lang_contract_entry_point).collect() +} + +fn into_cairo_lang_contract_entry_point( + entry_point: &StarknetApiEntryPoint, +) -> CairoLangContractEntryPoint { + CairoLangContractEntryPoint { + selector: stark_felt_to_big_uint(&entry_point.selector.0), + function_idx: entry_point.function_idx.0, + } +} + +fn stark_felt_to_big_uint_as_hex(stark_felt: &StarkFelt) -> BigUintAsHex { + BigUintAsHex { value: stark_felt_to_big_uint(stark_felt) } +} + +fn stark_felt_to_big_uint(stark_felt: &StarkFelt) -> BigUint { + BigUint::from_bytes_be(stark_felt.bytes()) +}