diff --git a/crates/apollo_infra_utils/src/cairo0_compiler.rs b/crates/apollo_infra_utils/src/cairo0_compiler.rs index 412d4428c63..f9e59658d5b 100644 --- a/crates/apollo_infra_utils/src/cairo0_compiler.rs +++ b/crates/apollo_infra_utils/src/cairo0_compiler.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; #[cfg(any(test, feature = "testing"))] use std::sync::LazyLock; @@ -67,24 +67,26 @@ pub fn cairo0_compilers_correct_version() -> Result<(), Cairo0CompilerVersionErr /// Compile a Cairo0 program. pub fn compile_cairo0_program( - path_to_main: PathBuf, - cairo_root_path: PathBuf, + path_to_main: &Path, + cairo_root_path: &Path, ) -> Result, Cairo0CompilerError> { cairo0_compilers_correct_version()?; if !path_to_main.exists() { - return Err(Cairo0CompilerError::SourceFileNotFound(path_to_main)); + return Err(Cairo0CompilerError::SourceFileNotFound(path_to_main.to_path_buf())); } if !cairo_root_path.exists() { - return Err(Cairo0CompilerError::CairoRootNotFound(cairo_root_path)); + return Err(Cairo0CompilerError::CairoRootNotFound(cairo_root_path.to_path_buf())); } let mut compile_command = Command::new(CAIRO0_COMPILE); compile_command.args([ - path_to_main.to_str().ok_or(Cairo0CompilerError::InvalidPath(path_to_main.clone()))?, + path_to_main + .to_str() + .ok_or(Cairo0CompilerError::InvalidPath(path_to_main.to_path_buf()))?, "--debug_info_with_source", "--cairo_path", cairo_root_path .to_str() - .ok_or(Cairo0CompilerError::InvalidPath(cairo_root_path.clone()))?, + .ok_or(Cairo0CompilerError::InvalidPath(cairo_root_path.to_path_buf()))?, ]); let compile_output = compile_command.output()?; diff --git a/crates/apollo_starknet_os_program/Cargo.toml b/crates/apollo_starknet_os_program/Cargo.toml index b8c93e3d1e0..c4178380c58 100644 --- a/crates/apollo_starknet_os_program/Cargo.toml +++ b/crates/apollo_starknet_os_program/Cargo.toml @@ -9,6 +9,9 @@ description = "The source (Cairo) code of the Starknet OS." [lints] workspace = true +[features] +testing = [] + [dependencies] apollo_infra_utils.workspace = true cairo-vm.workspace = true diff --git a/crates/apollo_starknet_os_program/build.rs b/crates/apollo_starknet_os_program/build.rs index 8bcf5b815de..bf135a02682 100644 --- a/crates/apollo_starknet_os_program/build.rs +++ b/crates/apollo_starknet_os_program/build.rs @@ -24,7 +24,7 @@ fn main() { } fn compile_program(path_to_main_file: PathBuf) -> Vec { - match compile_cairo0_program(path_to_main_file, cairo_root_path()) { + match compile_cairo0_program(&path_to_main_file, &cairo_root_path()) { Ok(bytes) => bytes, Err(Cairo0CompilerError::Cairo0CompilerVersion(error)) => { panic!( diff --git a/crates/apollo_starknet_os_program/src/lib.rs b/crates/apollo_starknet_os_program/src/lib.rs index 3f828c367fe..1cbb25cdfec 100644 --- a/crates/apollo_starknet_os_program/src/lib.rs +++ b/crates/apollo_starknet_os_program/src/lib.rs @@ -6,6 +6,8 @@ use cairo_vm::types::program::Program; use crate::program_hash::{ProgramHash, PROGRAM_HASH_PATH}; pub mod program_hash; +#[cfg(any(test, feature = "testing"))] +pub mod test_utils; pub const OS_PROGRAM_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/starknet_os_bytes")); pub const AGGREGATOR_PROGRAM_BYTES: &[u8] = diff --git a/crates/apollo_starknet_os_program/src/test_utils.rs b/crates/apollo_starknet_os_program/src/test_utils.rs new file mode 100644 index 00000000000..3922adca54a --- /dev/null +++ b/crates/apollo_starknet_os_program/src/test_utils.rs @@ -0,0 +1,23 @@ +use std::path::PathBuf; + +use apollo_infra_utils::cairo0_compiler::compile_cairo0_program; +use apollo_infra_utils::compile_time_cargo_manifest_dir; +use cairo_vm::types::program::Program; + +// TODO(Dori): Consider sharing this with the same function in this crate's build.rs. +fn cairo_root_path() -> PathBuf { + PathBuf::from(compile_time_cargo_manifest_dir!()).join("src/cairo") +} + +pub fn compile_os_module(path_to_module: &PathBuf) -> Vec { + compile_cairo0_program(path_to_module, &cairo_root_path()) + .unwrap_or_else(|error| panic!("Failed to compile module {path_to_module:?}: {error}.")) +} + +/// Compiles and deserializes a specific module from the OS, for unit testing. +pub fn compile_os_module_as_program( + path_to_module: &PathBuf, + main_entry_point: Option<&str>, +) -> Program { + Program::from_bytes(&compile_os_module(path_to_module), main_entry_point).unwrap() +} diff --git a/crates/starknet_committer_and_os_cli/Cargo.toml b/crates/starknet_committer_and_os_cli/Cargo.toml index 0c39a55e369..741c9718c54 100644 --- a/crates/starknet_committer_and_os_cli/Cargo.toml +++ b/crates/starknet_committer_and_os_cli/Cargo.toml @@ -18,9 +18,10 @@ tempfile.workspace = true # TODO(Amos): Add `testing` feature and move Python test dependencies under it. [dependencies] -apollo_starknet_os_program.workspace = true +# Activate `testing` feature when the feature exists in this crate. +apollo_starknet_os_program = { workspace = true, features = ["testing"] } cairo-lang-starknet-classes.workspace = true -cairo-vm.workspace = true # Should be moved under `testing` feature, when it exists. +cairo-vm.workspace = true clap = { workspace = true, features = ["cargo", "derive"] } derive_more.workspace = true ethnum.workspace = true @@ -33,7 +34,7 @@ serde_repr.workspace = true starknet-types-core.workspace = true starknet_api.workspace = true starknet_committer.workspace = true -# Should be moved under `testing` feature, when it exists. +# Activate `testing` feature when the feature exists in this crate. starknet_os = { workspace = true, features = ["deserialize", "testing"] } starknet_patricia = { workspace = true, features = ["testing"] } starknet_patricia_storage.workspace = true diff --git a/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs b/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs index f9ff371d821..c0e77de0341 100644 --- a/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs +++ b/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs @@ -1,6 +1,7 @@ use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; +use apollo_starknet_os_program::test_utils::compile_os_module; use apollo_starknet_os_program::{AGGREGATOR_PROGRAM_BYTES, OS_PROGRAM_BYTES, PROGRAM_HASH}; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_vm::types::layout_name::LayoutName; @@ -75,6 +76,15 @@ pub fn parse_and_run_os(input_path: String, output_path: String) { info!("OS program ran successfully."); } +pub(crate) fn compile_and_dump_module(path_to_module: String, output_path: String) { + // Dumping the `Program` struct won't work - it is not deserializable via cairo-lang's Program + // class. JSONify the raw bytes instead. + let module_bytes = compile_os_module(&PathBuf::from(path_to_module)); + let module_json = serde_json::from_slice::(&module_bytes) + .expect("Module bytes are JSON-serializable."); + write_to_file(&output_path, &module_json); +} + pub(crate) fn dump_os_program(output_path: String) { // Dumping the `Program` struct won't work - it is not deserializable via cairo-lang's Program // class. JSONify the raw bytes instead. diff --git a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs index ee8cc2bd8b9..ac2a9412631 100644 --- a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs +++ b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs @@ -5,6 +5,7 @@ use tracing_subscriber::reload::Handle; use tracing_subscriber::Registry; use crate::os_cli::commands::{ + compile_and_dump_module, dump_aggregator_program, dump_os_program, dump_program_hash, @@ -21,6 +22,15 @@ pub struct OsCliCommand { #[derive(Debug, Subcommand)] enum Command { + CompileAndDumpModule { + /// Path to module in the OS program crate. + #[clap(long, short = 'm')] + path_to_module: String, + + /// File path to output. + #[clap(long, short = 'o', default_value = "stdout")] + output_path: String, + }, DumpAggregatorProgram { /// File path to output. #[clap(long, short = 'o', default_value = "stdout")] @@ -49,6 +59,9 @@ pub async fn run_os_cli( ) { info!("Starting starknet-os-cli with command: \n{:?}", os_command); match os_command.command { + Command::CompileAndDumpModule { path_to_module, output_path } => { + compile_and_dump_module(path_to_module, output_path) + } Command::DumpAggregatorProgram { output_path } => dump_aggregator_program(output_path), Command::DumpOsProgram { output_path } => dump_os_program(output_path), Command::DumpProgramHash { output_path } => dump_program_hash(output_path),