diff --git a/Cargo.lock b/Cargo.lock index 0556840fbe..1e2bd0794e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9076,6 +9076,7 @@ dependencies = [ "serde_json", "starknet-types-core", "starknet_api", + "tempfile", "thiserror", "validator", ] diff --git a/crates/starknet_sierra_compile/Cargo.toml b/crates/starknet_sierra_compile/Cargo.toml index 1bcda82fe5..d2c59c4ab2 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 index adc3d73d39..2888d9bea2 100644 --- a/crates/starknet_sierra_compile/build.rs +++ b/crates/starknet_sierra_compile/build.rs @@ -1,6 +1,7 @@ -use std::path::Path; +use std::fs; use std::process::Command; -use std::{env, fs}; + +include!("src/build_utils.rs"); fn main() { println!("cargo:rerun-if-changed=../../Cargo.lock"); @@ -14,12 +15,7 @@ fn main() { /// compile Sierra to Casm. In this crate (`starknet_sierra_compile`), the binary is executed as a /// subprocess whenever Sierra compilation is required. fn download_cairo() { - let out_dir = env::var("OUT_DIR").expect("Failed to get the OUT_DIR environment variable"); - // Navigate from this crate's build folder to reach the `target/BUILD_FLAVOR` directory. - let target_dir = Path::new(&out_dir) - .ancestors() - .nth(3) - .expect("Failed to navigate up three levels from OUT_DIR"); + let target_dir = get_traget_build_flavor_dir(); let cairo_folder_dir = target_dir.join("cairo"); if cairo_folder_dir.exists() { diff --git a/crates/starknet_sierra_compile/src/build_utils.rs b/crates/starknet_sierra_compile/src/build_utils.rs new file mode 100644 index 0000000000..910f81bde5 --- /dev/null +++ b/crates/starknet_sierra_compile/src/build_utils.rs @@ -0,0 +1,13 @@ +/// Get the crate's `OUT_DIR` and navigates up to reach the `target/BUILD_FLAVOR` directory. +/// This directory is shared accross all crates in this project. +pub fn get_traget_build_flavor_dir() -> &'static std::path::Path { + let out_dir = std::env::var("OUT_DIR").expect("Failed to get the OUT_DIR environment variable"); + // Navigate from this crate's build folder to reach the `target/BUILD_FLAVOR` directory. + Box::leak( + std::path::Path::new(&out_dir) + .ancestors() + .nth(3) + .expect("Failed to navigate up three levels from OUT_DIR") + .into(), + ) +} 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 0000000000..6bbda0c7b6 --- /dev/null +++ b/crates/starknet_sierra_compile/src/command_line_compiler.rs @@ -0,0 +1,77 @@ +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::build_utils::get_traget_build_flavor_dir; +use crate::config::SierraToCasmCompilationConfig; +use crate::errors::CompilationUtilError; +use crate::SierraToCasmCompiler; + +#[derive(Clone)] +pub struct CommandLineCompiler { + pub config: SierraToCasmCompilationConfig, + path_to_starknet_sierra_compile_binary: &'static str, +} + +impl CommandLineCompiler { + pub fn new(config: SierraToCasmCompilationConfig) -> Self { + Self { config, path_to_starknet_sierra_compile_binary: get_compiler_path() } + } +} + +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::UnexpectedError("Failed to get temporary file path".to_owned()), + )?; + + // Set the parameters for the compile process. + let mut command = Command::new(self.path_to_starknet_sierra_compile_binary); + command.args([ + temp_file_path, + "--add-pythonic-hints", + "--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)?) + } +} + +static COMPILER_PATH: OnceLock<&str> = OnceLock::new(); +fn get_compiler_path() -> &'static str { + COMPILER_PATH.get_or_init(|| { + let target_dir = get_traget_build_flavor_dir().join("cairo"); + + let binary_name = "starknet-sierra-compile"; + Box::leak( + target_dir + .join("bin") + .join(binary_name) + .to_str() + .expect("Failed to get compiler path") + .into(), + ) + }) +} diff --git a/crates/starknet_sierra_compile/src/compile_test.rs b/crates/starknet_sierra_compile/src/compile_test.rs index 547b92c497..3cd29a76af 100644 --- a/crates/starknet_sierra_compile/src/compile_test.rs +++ b/crates/starknet_sierra_compile/src/compile_test.rs @@ -3,22 +3,30 @@ 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 }; + +fn commnad_line_compiler() -> CommandLineCompiler { + CommandLineCompiler::new(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(&commnad_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 +40,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(&commnad_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 c1f25719ec..85ca1823c2 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 e68efcc505..8734f4d9c2 100644 --- a/crates/starknet_sierra_compile/src/lib.rs +++ b/crates/starknet_sierra_compile/src/lib.rs @@ -4,7 +4,9 @@ use cairo_lang_starknet_classes::contract_class::ContractClass; use crate::errors::CompilationUtilError; +pub mod build_utils; pub mod cairo_lang_compiler; +pub mod command_line_compiler; pub mod config; pub mod errors; pub mod utils;