diff --git a/crates/blockifier/src/test_utils.rs b/crates/blockifier/src/test_utils.rs index 58be205ff0..30589146f9 100644 --- a/crates/blockifier/src/test_utils.rs +++ b/crates/blockifier/src/test_utils.rs @@ -1,3 +1,4 @@ +pub mod cairo_compile; pub mod contracts; pub mod declare; pub mod deploy_account; @@ -49,7 +50,7 @@ pub const TEST_ERC20_CONTRACT_CLASS_HASH: &str = "0x1010"; pub const ERC20_CONTRACT_PATH: &str = "./ERC20_without_some_syscalls/ERC20/erc20_contract_without_some_syscalls_compiled.json"; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CairoVersion { Cairo0, Cairo1, diff --git a/crates/blockifier/src/test_utils/cairo_compile.rs b/crates/blockifier/src/test_utils/cairo_compile.rs new file mode 100644 index 0000000000..ad9568da37 --- /dev/null +++ b/crates/blockifier/src/test_utils/cairo_compile.rs @@ -0,0 +1,21 @@ +use std::process::Command; + +/// Compiles a Cairo0 program using the deprecated compiler. +pub fn cairo0_compile(path: String, extra_arg: Option, debug_info: bool) -> Vec { + let mut command = Command::new("starknet-compile-deprecated"); + if let Some(extra_arg) = extra_arg { + command.arg(extra_arg); + } + if !debug_info { + command.args([&path, "--no_debug_info"]); + } + let compile_output = command.output().unwrap(); + let stderr_output = String::from_utf8(compile_output.stderr).unwrap(); + assert!(compile_output.status.success(), "{stderr_output}"); + compile_output.stdout +} + +/// Compiles a Cairo1 program using the compiler version set in the Cargo.toml. +pub fn cairo1_compile(_path: String) -> Vec { + todo!(); +} diff --git a/crates/blockifier/src/test_utils/contracts.rs b/crates/blockifier/src/test_utils/contracts.rs index dc0d21c270..46314ef877 100644 --- a/crates/blockifier/src/test_utils/contracts.rs +++ b/crates/blockifier/src/test_utils/contracts.rs @@ -10,6 +10,7 @@ use strum_macros::EnumIter; use crate::abi::abi_utils::selector_from_name; use crate::abi::constants::CONSTRUCTOR_ENTRY_POINT_NAME; use crate::execution::contract_class::{ContractClass, ContractClassV0, ContractClassV1}; +use crate::test_utils::cairo_compile::{cairo0_compile, cairo1_compile}; use crate::test_utils::{get_raw_contract_class, CairoVersion}; // This file contains featured contracts, used for tests. Use the function 'test_state' in @@ -55,8 +56,10 @@ const SECURITY_TEST_CONTRACT_NAME: &str = "security_tests_contract"; const TEST_CONTRACT_NAME: &str = "test_contract"; // ERC20 contract is in a unique location. +const ERC20_BASE_NAME: &str = "ERC20"; 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"; /// Enum representing all feature contracts. /// The contracts that are implemented in both Cairo versions include a version field. @@ -73,7 +76,7 @@ pub enum FeatureContract { } impl FeatureContract { - fn cairo_version(&self) -> CairoVersion { + pub fn cairo_version(&self) -> CairoVersion { match self { Self::AccountWithLongValidate(version) | Self::AccountWithoutValidations(version) @@ -118,9 +121,8 @@ impl FeatureContract { } } - pub fn get_compiled_path(&self) -> String { - let cairo_version = self.cairo_version(); - let contract_name = match self { + fn contract_base_name(&self) -> &str { + match self { Self::AccountWithLongValidate(_) => ACCOUNT_LONG_VALIDATE_NAME, Self::AccountWithoutValidations(_) => ACCOUNT_WITHOUT_VALIDATIONS_NAME, Self::Empty(_) => EMPTY_CONTRACT_NAME, @@ -128,8 +130,31 @@ impl FeatureContract { Self::LegacyTestContract => LEGACY_CONTRACT_NAME, Self::SecurityTests => SECURITY_TEST_CONTRACT_NAME, Self::TestContract(_) => TEST_CONTRACT_NAME, + Self::ERC20 => ERC20_BASE_NAME, + } + } + + pub fn get_source_path(&self) -> String { + match self { + // Special case: ERC20 contract in a different location. + Self::ERC20 => ERC20_CONTRACT_SOURCE_PATH.into(), + _not_erc20 => format!( + "feature_contracts/cairo{}/{}.cairo", + match self.cairo_version() { + CairoVersion::Cairo0 => "0", + CairoVersion::Cairo1 => "1", + }, + self.contract_base_name() + ), + } + } + + pub fn get_compiled_path(&self) -> String { + let cairo_version = self.cairo_version(); + let contract_name = match self { // ERC20 is a special case - not in the feature_contracts directory. Self::ERC20 => return ERC20_CONTRACT_PATH.into(), + _not_erc20 => self.contract_base_name(), }; format!( "feature_contracts/cairo{}/compiled/{}{}.json", @@ -145,6 +170,31 @@ impl FeatureContract { ) } + /// Compiles the feature contract and returns the compiled contract as a byte vector. + /// Panics if the contract is ERC20, as ERC20 contract recompilation is not supported. + pub fn compile(&self) -> Vec { + if matches!(self, Self::ERC20) { + panic!("ERC20 contract recompilation not supported."); + } + match self.cairo_version() { + CairoVersion::Cairo0 => { + let extra_arg: Option = match self { + // Account contracts require the account_contract flag. + FeatureContract::AccountWithLongValidate(_) + | FeatureContract::AccountWithoutValidations(_) + | FeatureContract::FaultyAccount(_) => Some("--account_contract".into()), + FeatureContract::SecurityTests => Some("--disable_hint_validation".into()), + FeatureContract::Empty(_) + | FeatureContract::TestContract(_) + | FeatureContract::LegacyTestContract => None, + FeatureContract::ERC20 => unreachable!(), + }; + cairo0_compile(self.get_source_path(), extra_arg, false) + } + CairoVersion::Cairo1 => cairo1_compile(self.get_source_path()), + } + } + pub fn set_cairo_version(&mut self, version: CairoVersion) { match self { Self::AccountWithLongValidate(v) @@ -255,4 +305,9 @@ impl FeatureContract { } }) } + + pub fn all_feature_contracts() -> impl Iterator { + // ERC20 is a special case - not in the feature_contracts directory. + Self::all_contracts().filter(|contract| !matches!(contract, Self::ERC20)) + } } diff --git a/crates/blockifier/tests/feature_contracts_compatibility_test.rs b/crates/blockifier/tests/feature_contracts_compatibility_test.rs index c9caf8d0cf..f72c8c0bc6 100644 --- a/crates/blockifier/tests/feature_contracts_compatibility_test.rs +++ b/crates/blockifier/tests/feature_contracts_compatibility_test.rs @@ -1,5 +1,4 @@ use std::fs; -use std::process::Command; use blockifier::test_utils::contracts::FeatureContract; use blockifier::test_utils::CairoVersion; @@ -41,20 +40,12 @@ 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) { - for (path_str, file_name, existing_compiled_path) in verify_and_get_files(cairo_version) { + for contract in FeatureContract::all_feature_contracts() + .filter(|contract| contract.cairo_version() == cairo_version) + { // Compare output of cairo-file on file with existing compiled file. - let mut command = Command::new("starknet-compile-deprecated"); - command.args([&path_str, "--no_debug_info"]); - if file_name.starts_with("account") { - command.arg("--account_contract"); - } - if file_name.starts_with("security") { - command.arg("--disable_hint_validation"); - } - let compile_output = command.output().unwrap(); - let stderr_output = String::from_utf8(compile_output.stderr).unwrap(); - assert!(compile_output.status.success(), "{stderr_output}"); - let expected_compiled_output = compile_output.stdout; + let expected_compiled_output = contract.compile(); + let existing_compiled_path = contract.get_compiled_path(); if fix { fs::write(&existing_compiled_path, &expected_compiled_output).unwrap(); @@ -64,9 +55,9 @@ fn verify_feature_contracts_compatibility(fix: bool, cairo_version: CairoVersion if String::from_utf8(expected_compiled_output).unwrap() != existing_compiled_contents { panic!( - "{path_str} does not compile to {existing_compiled_path}.\nRun `{FIX_COMMAND}` to \ - fix the expected test according to locally installed \ - `starknet-compile-deprecated`.\n" + "{} does not compile to {existing_compiled_path}.\nRun `{FIX_COMMAND}` to fix the \ + expected test according to locally installed `starknet-compile-deprecated`.\n", + contract.get_source_path() ); } } @@ -119,9 +110,7 @@ fn verify_and_get_files(cairo_version: CairoVersion) -> Vec<(String, String, Str #[test] fn verify_feature_contracts_match_enum() { - let mut compiled_paths_from_enum: Vec = FeatureContract::all_contracts() - // ERC20 is a special case - not in the feature_contracts directory. - .filter(|contract| !matches!(contract, FeatureContract::ERC20)) + let mut compiled_paths_from_enum: Vec = FeatureContract::all_feature_contracts() .map(|contract| contract.get_compiled_path()) .collect(); let mut compiled_paths_on_filesystem: Vec = verify_and_get_files(CairoVersion::Cairo0)