diff --git a/Cargo.lock b/Cargo.lock index 6f4edbd788..5cf4a789bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -434,6 +434,7 @@ dependencies = [ "starknet_api", "strum", "strum_macros 0.24.3", + "tempfile", "test-case", "thiserror", "toml", diff --git a/crates/blockifier/Cargo.toml b/crates/blockifier/Cargo.toml index 8140763d96..bbd9029b2d 100644 --- a/crates/blockifier/Cargo.toml +++ b/crates/blockifier/Cargo.toml @@ -47,6 +47,7 @@ starknet-crypto.workspace = true starknet_api = { workspace = true, features = ["testing"] } strum.workspace = true strum_macros.workspace = true +tempfile.workspace = true thiserror.workspace = true toml.workspace = true diff --git a/crates/blockifier/src/test_utils/cairo_compile.rs b/crates/blockifier/src/test_utils/cairo_compile.rs index f7b398b86a..43234193b8 100644 --- a/crates/blockifier/src/test_utils/cairo_compile.rs +++ b/crates/blockifier/src/test_utils/cairo_compile.rs @@ -1,10 +1,14 @@ -use std::process::Command; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; use std::{env, fs}; use cached::proc_macro::cached; use serde::{Deserialize, Serialize}; +use tempfile::NamedTempFile; const CAIRO0_PIP_REQUIREMENTS_FILE: &str = "tests/requirements.txt"; +const LOCAL_CAIRO1_REPO_RELATIVE_PATH: &str = "../../../cairo"; /// Objects for simple deserialization of Cargo.toml to fetch the Cairo1 compiler version. /// The compiler itself isn't actually a dependency, so we compile by using the version of the @@ -52,6 +56,25 @@ pub fn cairo1_compiler_version() -> String { } } +/// Returns the path to the local Cairo1 compiler repository. +fn local_cairo1_compiler_repo_path() -> PathBuf { + // Location of blockifier's Cargo.toml. + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + // Returns /. + Path::new(&manifest_dir).join(LOCAL_CAIRO1_REPO_RELATIVE_PATH) +} + +/// Run a command, assert exit code is zero (otherwise panic with stderr output). +fn run_and_verify_output(command: &mut Command) -> Output { + let output = command.output().unwrap(); + if !output.status.success() { + let stderr_output = String::from_utf8(output.stderr).unwrap(); + panic!("{stderr_output}"); + } + output +} + /// Compiles a Cairo0 program using the deprecated compiler. pub fn cairo0_compile(path: String, extra_arg: Option, debug_info: bool) -> Vec { verify_cairo0_compiler_deps(); @@ -69,8 +92,34 @@ pub fn cairo0_compile(path: String, extra_arg: Option, debug_info: bool) } /// Compiles a Cairo1 program using the compiler version set in the Cargo.toml. -pub fn cairo1_compile(_path: String) -> Vec { - todo!(); +pub fn cairo1_compile(path: String, git_tag_override: Option) -> Vec { + verify_cairo1_compiler_deps(git_tag_override); + let cairo1_compiler_path = local_cairo1_compiler_repo_path(); + let mut cargo_command = Command::new("cargo"); + let sierra_output = run_and_verify_output(cargo_command.args([ + "run", + &format!("--manifest-path={}/Cargo.toml", cairo1_compiler_path.to_string_lossy()), + "--bin", + "starknet-compile", + "--", + "--single-file", + &path, + ])); + let mut temp_file = NamedTempFile::new().unwrap(); + + temp_file.write_all(&sierra_output.stdout).unwrap(); + let temp_path_str = temp_file.into_temp_path(); + + let mut cargo_command = Command::new("cargo"); + let casm_output = run_and_verify_output(cargo_command.args([ + "run", + &format!("--manifest-path={}/Cargo.toml", cairo1_compiler_path.to_string_lossy()), + "--bin", + "starknet-sierra-compile", + temp_path_str.to_str().unwrap(), + ])); + + casm_output.stdout } /// Verifies that the required dependencies are available before compiling. @@ -97,3 +146,16 @@ fn verify_cairo0_compiler_deps() { CAIRO0_PIP_REQUIREMENTS_FILE ); } + +fn verify_cairo1_compiler_deps(git_tag_override: Option) { + // TODO(Dori, 1/6/2024): Check repo exists. + let tag = git_tag_override.unwrap_or(format!("v{}", cairo1_compiler_version())); + // Checkout the required version in the compiler repo. + run_and_verify_output(Command::new("git").args([ + "-C", + // TODO(Dori, 1/6/2024): Handle CI case (repo path will be different). + local_cairo1_compiler_repo_path().to_str().unwrap(), + "checkout", + &tag, + ])); +} diff --git a/crates/blockifier/src/test_utils/contracts.rs b/crates/blockifier/src/test_utils/contracts.rs index 12f6c05ead..3c2e969e88 100644 --- a/crates/blockifier/src/test_utils/contracts.rs +++ b/crates/blockifier/src/test_utils/contracts.rs @@ -57,6 +57,9 @@ const ERC20_CONTRACT_PATH: &str = "./ERC20_without_some_syscalls/ERC20/erc20_contract_without_some_syscalls_compiled.json"; const ERC20_CONTRACT_SOURCE_PATH: &str = "./ERC20_without_some_syscalls/ERC20/ERC20.cairo"; +// Legacy contract is compiled with a fixed version of the compiler. +const LEGACY_CONTRACT_COMPILER_TAG: &str = "v2.1.0"; + /// Enum representing all feature contracts. /// The contracts that are implemented in both Cairo versions include a version field. #[derive(Clone, Copy, Debug, EnumIter)] @@ -187,7 +190,13 @@ impl FeatureContract { }; cairo0_compile(self.get_source_path(), extra_arg, false) } - CairoVersion::Cairo1 => cairo1_compile(self.get_source_path()), + CairoVersion::Cairo1 => { + let tag_override = match self { + Self::LegacyTestContract => Some(LEGACY_CONTRACT_COMPILER_TAG.into()), + _ => None, + }; + cairo1_compile(self.get_source_path(), tag_override) + } } } diff --git a/crates/blockifier/tests/feature_contracts_compatibility_test.rs b/crates/blockifier/tests/feature_contracts_compatibility_test.rs index f72c8c0bc6..e7d2d45bb8 100644 --- a/crates/blockifier/tests/feature_contracts_compatibility_test.rs +++ b/crates/blockifier/tests/feature_contracts_compatibility_test.rs @@ -3,6 +3,7 @@ use std::fs; use blockifier::test_utils::contracts::FeatureContract; use blockifier::test_utils::CairoVersion; use pretty_assertions::assert_eq; +use rstest::rstest; const CAIRO0_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo0"; const CAIRO1_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo1"; @@ -123,9 +124,15 @@ fn verify_feature_contracts_match_enum() { assert_eq!(compiled_paths_from_enum, compiled_paths_on_filesystem); } -#[test] +#[rstest] #[ignore] -fn verify_feature_contracts() { +fn verify_feature_contracts( + #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, +) { + if std::env::var("CI").is_ok() && matches!(cairo_version, CairoVersion::Cairo1) { + // TODO(Dori, 1/6/2024): Support Cairo1 contracts in the CI and remove this `if` statement. + return; + } let fix_features = std::env::var("FIX_FEATURE_TEST").is_ok(); - verify_feature_contracts_compatibility(fix_features, CairoVersion::Cairo0) + verify_feature_contracts_compatibility(fix_features, cairo_version) }