diff --git a/Cargo.lock b/Cargo.lock index 061872a97b2..37f3879b849 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9121,6 +9121,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 6ff8e79d461..e05053e60ca 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/executable/starknet-sierra-compile b/crates/starknet_sierra_compile/executable/starknet-sierra-compile new file mode 100755 index 00000000000..2b0dc71e9f3 Binary files /dev/null and b/crates/starknet_sierra_compile/executable/starknet-sierra-compile differ diff --git a/crates/starknet_sierra_compile/src/cairo_lang_compiler.rs b/crates/starknet_sierra_compile/src/cairo_lang_compiler.rs index 7565b29afa5..96d5a4b5858 100644 --- a/crates/starknet_sierra_compile/src/cairo_lang_compiler.rs +++ b/crates/starknet_sierra_compile/src/cairo_lang_compiler.rs @@ -8,10 +8,6 @@ use crate::config::SierraToCasmCompilationConfig; use crate::errors::CompilationUtilError; use crate::SierraToCasmCompiler; -#[cfg(test)] -#[path = "compile_test.rs"] -pub mod compile_test; - /// A compiler that compiles Sierra programs to Casm. Uses the code from the /// `cairo_lang_starknet_classes` crate. #[derive(Clone)] 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..56b5b5292d1 --- /dev/null +++ b/crates/starknet_sierra_compile/src/command_line_compiler.rs @@ -0,0 +1,62 @@ +use std::io::Write; +use std::process::Command; + +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::IoError( + std::io::Error::new(std::io::ErrorKind::NotFound, "Failed to get temporary file path"), + ))?; + + // Compile the Sierra contract class to Casm. + 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()]); + + 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 absolute path from the project root. +pub fn get_absolute_path(relative_path: &str) -> std::path::PathBuf { + std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("../..") + .join(relative_path) +} + +// TODO(Arni): Get the binary in a cleaner way. +fn excutable_file_location() -> String { + const COMPILER_RELATIVE_PATH: &str = + "crates/starknet_sierra_compile/executable/starknet-sierra-compile"; + get_absolute_path(COMPILER_RELATIVE_PATH).to_str().unwrap().to_owned() +} diff --git a/crates/starknet_sierra_compile/src/compile_test.rs b/crates/starknet_sierra_compile/src/compile_test.rs index badb69f8e96..8a9cd3f6bec 100644 --- a/crates/starknet_sierra_compile/src/compile_test.rs +++ b/crates/starknet_sierra_compile/src/compile_test.rs @@ -4,20 +4,28 @@ use std::path::Path; use assert_matches::assert_matches; use cairo_lang_starknet_classes::allowed_libfuncs::AllowedLibfuncsError; 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, CompilationUtilError}; +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; @@ -31,7 +39,8 @@ 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)] +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); @@ -45,3 +54,18 @@ fn test_negative_flow_compile_sierra_to_casm(compiler: impl SierraToCasmCompiler Err(CompilationUtilError::AllowedLibfuncsError(AllowedLibfuncsError::SierraProgramError)) ); } + +// TODO(Arni, 1/5/2024): Add a test for panic result test. +#[rstest] +#[case::command_line_compiler(&COMMAND_LINE_COMPILER)] +fn test_negative_flow_compile_sierra_to_casm_2(#[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 mut contract_class = contract_class_from_file(sierra_path); + // Truncate the sierra program to trigger an error. + contract_class.sierra_program = contract_class.sierra_program[..100].to_vec(); + + let result = compiler.compile(contract_class); + assert_matches!(result, Err(CompilationUtilError::CompilationError(..))); +} diff --git a/crates/starknet_sierra_compile/src/errors.rs b/crates/starknet_sierra_compile/src/errors.rs index 2d3d2575fa2..6d056a5d7e0 100644 --- a/crates/starknet_sierra_compile/src/errors.rs +++ b/crates/starknet_sierra_compile/src/errors.rs @@ -6,8 +6,14 @@ use thiserror::Error; pub enum CompilationUtilError { #[error(transparent)] AllowedLibfuncsError(#[from] AllowedLibfuncsError), - #[error(transparent)] - StarknetSierraCompilationError(#[from] StarknetSierraCompilationError), + #[error("Starknet Sierra compilation error: {0}")] + CompilationError(String), #[error("Compilation panicked")] CompilationPanic, + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + SerdeError(#[from] serde_json::Error), + #[error(transparent)] + StarknetSierraCompilationError(#[from] StarknetSierraCompilationError), } diff --git a/crates/starknet_sierra_compile/src/lib.rs b/crates/starknet_sierra_compile/src/lib.rs index 449a69ce5ef..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; @@ -12,6 +13,10 @@ pub mod utils; #[cfg(test)] pub mod test_utils; +#[cfg(test)] +#[path = "compile_test.rs"] +pub mod compile_test; + pub trait SierraToCasmCompiler: Send + Sync { fn compile( &self,