diff --git a/Cargo.lock b/Cargo.lock index 37f3e4bd9ad..c441e1a1832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9074,6 +9074,7 @@ dependencies = [ "serde_json", "starknet-types-core", "starknet_api", + "tempfile", "thiserror", "validator", ] diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index ac64da36e88..9beb61f4c57 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -44,3 +44,6 @@ pretty_assertions.workspace = true rstest.workspace = true starknet_mempool.workspace = true tracing-test.workspace = true + +[build-dependencies] +starknet_sierra_compile.workspace = true diff --git a/crates/starknet_sierra_compile/Cargo.toml b/crates/starknet_sierra_compile/Cargo.toml index 1bcda82fe5e..d2c59c4ab29 100644 --- a/crates/starknet_sierra_compile/Cargo.toml +++ b/crates/starknet_sierra_compile/Cargo.toml @@ -17,6 +17,7 @@ serde.workspace = true serde_json.workspace = true starknet-types-core.workspace = true starknet_api.workspace = true +tempfile.workspace = true thiserror.workspace = true validator.workspace = true diff --git a/crates/starknet_sierra_compile/build.rs b/crates/starknet_sierra_compile/build.rs new file mode 100644 index 00000000000..f1e06e167ff --- /dev/null +++ b/crates/starknet_sierra_compile/build.rs @@ -0,0 +1,51 @@ +use std::env; +use std::path::Path; +use std::process::Command; + +fn main() { + println!("cargo::rerun-if-changed=../../Cargo.lock"); + println!("cargo::rerun-if-changed=build.rs"); + + install_starknet_sierra_compile(); +} + +fn install_starknet_sierra_compile() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); + let bin_path = Path::new(&out_dir).join("bin"); + + let binary_name = "starknet-sierra-compile"; + + // Create the bin directory if it doesn't exist + std::fs::create_dir_all(bin_path.clone()).expect("Failed to create bin directory"); + + // Path to the local binary + let binary_path = bin_path.join(binary_name); + // TODO(Arni): Add the configurable parameters to the function. + let _starknet_sierra_compile_version: Option = None; + + // Check if the binary is already installed locally + if !binary_path.exists() { + // Install the binary locally + + let status = Command::new("cargo") + .args({ + let args = vec![ + "install", + "starknet-sierra-compile", + "--root", + bin_path.to_str().expect("Failed to convert bin_path to str"), + ]; + + args + }) + .status() + .expect("Failed to install starknet-sierra-compile"); + + if !status.success() { + panic!("Failed to install starknet-sierra-compile"); + } + } + + // Print the path to the installed binary so that it can be used in the main application + println!("cargo::rustc-env=STARKNET_SIERRA_COMPILE_BIN={}", binary_path.display()); +} diff --git a/crates/starknet_sierra_compile/src/command_line_compiler.rs b/crates/starknet_sierra_compile/src/command_line_compiler.rs new file mode 100644 index 00000000000..d5e94d5fe60 --- /dev/null +++ b/crates/starknet_sierra_compile/src/command_line_compiler.rs @@ -0,0 +1,64 @@ +use std::env; +use std::io::Write; +use std::process::Command; +use std::sync::OnceLock; + +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use cairo_lang_starknet_classes::contract_class::ContractClass; +use tempfile::NamedTempFile; + +use crate::config::SierraToCasmCompilationConfig; +use crate::errors::CompilationUtilError; +use crate::SierraToCasmCompiler; + +#[derive(Clone)] +pub struct CommandLineCompiler { + pub config: SierraToCasmCompilationConfig, +} + +impl SierraToCasmCompiler for CommandLineCompiler { + fn compile( + &self, + contract_class: ContractClass, + ) -> Result { + // Create a temporary file to store the Sierra contract class. + let serialized_contract_class = serde_json::to_string(&contract_class)?; + + let mut temp_file = NamedTempFile::new()?; + temp_file.write_all(serialized_contract_class.as_bytes())?; + let temp_file_path = temp_file.path().to_str().ok_or( + CompilationUtilError::CompilationError("Failed to get temporary file path".to_owned()), + )?; + + // Set the parameters for the compile process. + let mut command = Command::new(excutable_file_location()); + command.arg(temp_file_path); + + command.arg("--add-pythonic-hints"); + command.args(["--max-bytecode-size", &self.config.max_bytecode_size.to_string()]); + + // Run the compile process. + let compile_output = command.output()?; + + if !compile_output.status.success() { + let stderr_output = String::from_utf8(compile_output.stderr) + .unwrap_or("Failed to get stderr output".into()); + return Err(CompilationUtilError::CompilationError(stderr_output)); + }; + + Ok(serde_json::from_slice::(&compile_output.stdout)?) + } +} + +/// Returns the location of the "starknet-sierra-compile" executable. +/// This executable compiles Sierra contracts into Casm. +fn excutable_file_location() -> &'static str { + static COMPILER_PATH: OnceLock<&str> = OnceLock::new(); + COMPILER_PATH.get_or_init(|| { + Box::leak( + env::var("STARKNET_SIERRA_COMPILE_BIN") + .expect("STARKNET_SIERRA_COMPILE_BIN not set") + .into(), + ) + }) +} diff --git a/crates/starknet_sierra_compile/src/compile_test.rs b/crates/starknet_sierra_compile/src/compile_test.rs index 547b92c4977..aa265ca6584 100644 --- a/crates/starknet_sierra_compile/src/compile_test.rs +++ b/crates/starknet_sierra_compile/src/compile_test.rs @@ -3,22 +3,28 @@ use std::path::Path; use assert_matches::assert_matches; use mempool_test_utils::{get_absolute_path, FAULTY_ACCOUNT_CLASS_FILE, TEST_FILES_FOLDER}; -use rstest::{fixture, rstest}; +use rstest::rstest; use crate::cairo_lang_compiler::CairoLangSierraToCasmCompiler; +use crate::command_line_compiler::CommandLineCompiler; use crate::config::SierraToCasmCompilationConfig; use crate::errors::CompilationUtilError; use crate::test_utils::contract_class_from_file; use crate::SierraToCasmCompiler; -#[fixture] -fn compiler() -> impl SierraToCasmCompiler { - CairoLangSierraToCasmCompiler { config: SierraToCasmCompilationConfig::default() } -} +const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraToCasmCompilationConfig = + SierraToCasmCompilationConfig { max_bytecode_size: 81920 }; + +const CAIRO_LANG_COMPILER: CairoLangSierraToCasmCompiler = + CairoLangSierraToCasmCompiler { config: SIERRA_TO_CASM_COMPILATION_CONFIG }; +const COMMAND_LINE_COMPILER: CommandLineCompiler = + CommandLineCompiler { config: SIERRA_TO_CASM_COMPILATION_CONFIG }; // TODO: use the other compiler as well. #[rstest] -fn test_compile_sierra_to_casm(compiler: impl SierraToCasmCompiler) { +#[case::cairo_lang_compiler(&CAIRO_LANG_COMPILER)] +#[case::command_line_compiler(&COMMAND_LINE_COMPILER)] +fn test_compile_sierra_to_casm(#[case] compiler: &impl SierraToCasmCompiler) { env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); let expected_casm_contract_length = 72304; @@ -32,7 +38,9 @@ fn test_compile_sierra_to_casm(compiler: impl SierraToCasmCompiler) { // TODO(Arni, 1/5/2024): Add a test for panic result test. #[rstest] -fn test_negative_flow_compile_sierra_to_casm(compiler: impl SierraToCasmCompiler) { +#[case::cairo_lang_compiler(&CAIRO_LANG_COMPILER)] +#[case::command_line_compiler(&COMMAND_LINE_COMPILER)] +fn test_negative_flow_compile_sierra_to_casm(#[case] compiler: &impl SierraToCasmCompiler) { env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); diff --git a/crates/starknet_sierra_compile/src/errors.rs b/crates/starknet_sierra_compile/src/errors.rs index c1f25719ec0..85ca1823c25 100644 --- a/crates/starknet_sierra_compile/src/errors.rs +++ b/crates/starknet_sierra_compile/src/errors.rs @@ -21,3 +21,15 @@ impl From for CompilationUtilError { CompilationUtilError::CompilationError(error.to_string()) } } + +impl From for CompilationUtilError { + fn from(error: serde_json::Error) -> Self { + CompilationUtilError::UnexpectedError(error.to_string()) + } +} + +impl From for CompilationUtilError { + fn from(error: std::io::Error) -> Self { + CompilationUtilError::UnexpectedError(error.to_string()) + } +} diff --git a/crates/starknet_sierra_compile/src/lib.rs b/crates/starknet_sierra_compile/src/lib.rs index e68efcc505b..f3d697cefa6 100644 --- a/crates/starknet_sierra_compile/src/lib.rs +++ b/crates/starknet_sierra_compile/src/lib.rs @@ -5,6 +5,7 @@ use cairo_lang_starknet_classes::contract_class::ContractClass; use crate::errors::CompilationUtilError; pub mod cairo_lang_compiler; +pub mod command_line_compiler; pub mod config; pub mod errors; pub mod utils;