Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blockifier): support Cairo1 local recompilation #184

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/blockifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ starknet-types-core.workspace = true
starknet_api = { workspace = true, features = ["testing"] }
strum.workspace = true
strum_macros.workspace = true
tempfile.workspace = true
thiserror.workspace = true
tikv-jemallocator = { workspace = true, optional = true }
toml.workspace = true
Expand Down
47 changes: 42 additions & 5 deletions crates/blockifier/src/test_utils/cairo_compile.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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 CAIRO1_REPO_RELATIVE_PATH_OVERRIDE_ENV_VAR: &str = "CAIRO1_REPO_RELATIVE_PATH";
Expand Down Expand Up @@ -100,9 +102,42 @@ pub fn cairo0_compile(path: String, extra_arg: Option<String>, debug_info: bool)
}

/// Compiles a Cairo1 program using the compiler version set in the Cargo.toml.
pub fn cairo1_compile(_path: String) -> Vec<u8> {
verify_cairo1_compiler_deps();
todo!();
pub fn cairo1_compile(
path: String,
git_tag_override: Option<String>,
cargo_nightly_arg: Option<String>,
) -> Vec<u8> {
prepare_cairo1_compiler_deps(git_tag_override);
let cairo1_compiler_path = local_cairo1_compiler_repo_path();

// Command args common to both compilation phases.
let mut base_compile_args = vec![
"run".into(),
format!("--manifest-path={}/Cargo.toml", cairo1_compiler_path.to_string_lossy()),
"--bin".into(),
];
// Add additional cargo arg if provided. Should be first arg (base command is `cargo`).
if let Some(nightly_version) = cargo_nightly_arg {
base_compile_args.insert(0, format!("+nightly-{nightly_version}"));
}

// Cairo -> Sierra.
let mut starknet_compile_commmand = Command::new("cargo");
starknet_compile_commmand.args(base_compile_args.clone());
starknet_compile_commmand.args(["starknet-compile", "--", "--single-file", &path]);
let sierra_output = run_and_verify_output(&mut starknet_compile_commmand);

let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&sierra_output.stdout).unwrap();
let temp_path_str = temp_file.into_temp_path();

// Sierra -> CASM.
let mut sierra_compile_command = Command::new("cargo");
sierra_compile_command.args(base_compile_args);
sierra_compile_command.args(["starknet-sierra-compile", temp_path_str.to_str().unwrap()]);
let casm_output = run_and_verify_output(&mut sierra_compile_command);

casm_output.stdout
}

/// Verifies that the required dependencies are available before compiling; panics if unavailable.
Expand Down Expand Up @@ -136,13 +171,15 @@ fn verify_cairo0_compiler_deps() {
);
}

fn verify_cairo1_compiler_deps() {
fn prepare_cairo1_compiler_deps(git_tag_override: Option<String>) {
// 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",
&format!("v{}", cairo1_compiler_version()),
&tag,
]));
}
19 changes: 18 additions & 1 deletion crates/blockifier/src/test_utils/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ const ERC20_CAIRO0_CONTRACT_PATH: &str = "./ERC20/ERC20_Cairo0/ERC20_without_som
const ERC20_CAIRO1_CONTRACT_SOURCE_PATH: &str = "./ERC20/ERC20_Cairo1/ERC20.cairo";
const ERC20_CAIRO1_CONTRACT_PATH: &str = "./ERC20/ERC20_Cairo1/erc20.casm.json";

// Legacy contract is compiled with a fixed version of the compiler. This compiler version no longer
// compiles with stable rust, so the toolchain is also fixed.
const LEGACY_CONTRACT_COMPILER_TAG: &str = "v2.1.0";
const LEGACY_CONTRACT_RUST_TOOLCHAIN: &str = "2023-07-05";

/// Enum representing all feature contracts.
/// The contracts that are implemented in both Cairo versions include a version field.
#[derive(Clone, Copy, Debug, EnumIter, Hash, PartialEq, Eq)]
Expand Down Expand Up @@ -269,7 +274,19 @@ impl FeatureContract {
};
cairo0_compile(self.get_source_path(), extra_arg, false)
}
CairoVersion::Cairo1 => cairo1_compile(self.get_source_path()),
CairoVersion::Cairo1 => {
let (tag_override, cargo_nightly_arg) = match self {
Self::LegacyTestContract => (
// Legacy contract is designed to test behavior of code compiled with a
// specific (old) compiler tag. To run the (old) compiler, older rust
// version is required.
Some(LEGACY_CONTRACT_COMPILER_TAG.into()),
Some(LEGACY_CONTRACT_RUST_TOOLCHAIN.into()),
),
_ => (None, None),
};
cairo1_compile(self.get_source_path(), tag_override, cargo_nightly_arg)
}
}
}

Expand Down
16 changes: 13 additions & 3 deletions crates/blockifier/tests/feature_contracts_compatibility_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -40,6 +41,7 @@ const FIX_COMMAND: &str = "FIX_FEATURE_TEST=1 cargo test -- --ignored";
// 2. for each `X.cairo` file in `TEST_CONTRACTS` there exists an `X_compiled.json` file in
// `COMPILED_CONTRACTS_SUBDIR` which equals `starknet-compile-deprecated X.cairo --no_debug_info`.
fn verify_feature_contracts_compatibility(fix: bool, cairo_version: CairoVersion) {
// TODO(Dori, 1/10/2024): Parallelize this test.
for contract in FeatureContract::all_feature_contracts()
.filter(|contract| contract.cairo_version() == cairo_version)
{
Expand Down Expand Up @@ -123,9 +125,17 @@ 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,
) {
// TODO(Dori, 1/9/2024): Support Cairo1 contracts in the CI and remove this `if` statement.
if std::env::var("CI").unwrap_or("false".into()) == "true"
&& matches!(cairo_version, CairoVersion::Cairo1)
{
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)
}
Loading