diff --git a/crates/blockifier/src/test_utils.rs b/crates/blockifier/src/test_utils.rs index 35b7de8876..58be205ff0 100644 --- a/crates/blockifier/src/test_utils.rs +++ b/crates/blockifier/src/test_utils.rs @@ -71,6 +71,13 @@ impl CairoVersion { _ => panic!("Transaction version {:?} is not supported.", tx_version), } } + + pub fn other(&self) -> Self { + match self { + Self::Cairo0 => Self::Cairo1, + Self::Cairo1 => Self::Cairo0, + } + } } // Storage keys. diff --git a/crates/blockifier/src/test_utils/contracts.rs b/crates/blockifier/src/test_utils/contracts.rs index 7a69636d74..dc0d21c270 100644 --- a/crates/blockifier/src/test_utils/contracts.rs +++ b/crates/blockifier/src/test_utils/contracts.rs @@ -4,6 +4,8 @@ use starknet_api::deprecated_contract_class::{ }; use starknet_api::hash::StarkHash; use starknet_api::{class_hash, contract_address, patricia_key}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; use crate::abi::abi_utils::selector_from_name; use crate::abi::constants::CONSTRUCTOR_ENTRY_POINT_NAME; @@ -58,7 +60,7 @@ const ERC20_CONTRACT_PATH: &str = /// Enum representing all feature contracts. /// The contracts that are implemented in both Cairo versions include a version field. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, EnumIter)] pub enum FeatureContract { AccountWithLongValidate(CairoVersion), AccountWithoutValidations(CairoVersion), @@ -83,6 +85,17 @@ impl FeatureContract { } } + fn has_two_versions(&self) -> bool { + match self { + Self::AccountWithLongValidate(_) + | Self::AccountWithoutValidations(_) + | Self::Empty(_) + | Self::FaultyAccount(_) + | Self::TestContract(_) => true, + Self::SecurityTests | Self::ERC20 | Self::LegacyTestContract => false, + } + } + fn get_cairo_version_bit(&self) -> u32 { match self.cairo_version() { CairoVersion::Cairo0 => 0, @@ -105,7 +118,7 @@ impl FeatureContract { } } - fn get_compiled_path(&self) -> String { + pub fn get_compiled_path(&self) -> String { let cairo_version = self.cairo_version(); let contract_name = match self { Self::AccountWithLongValidate(_) => ACCOUNT_LONG_VALIDATE_NAME, @@ -119,7 +132,7 @@ impl FeatureContract { Self::ERC20 => return ERC20_CONTRACT_PATH.into(), }; format!( - "./feature_contracts/cairo{}/compiled/{}{}.json", + "feature_contracts/cairo{}/compiled/{}{}.json", match cairo_version { CairoVersion::Cairo0 => "0", CairoVersion::Cairo1 => "1", @@ -228,4 +241,18 @@ impl FeatureContract { entry_point_selector.unwrap_or(selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME)); self.get_offset(selector, EntryPointType::Constructor) } + + pub fn all_contracts() -> impl Iterator { + // EnumIter iterates over all variants with Default::default() as the cairo + // version. + Self::iter().flat_map(|contract| { + if contract.has_two_versions() { + let mut other_contract = contract; + other_contract.set_cairo_version(contract.cairo_version().other()); + vec![contract, other_contract].into_iter() + } else { + vec![contract].into_iter() + } + }) + } } diff --git a/crates/blockifier/tests/feature_contracts_compatibility_test.rs b/crates/blockifier/tests/feature_contracts_compatibility_test.rs index 34d77a229b..c9caf8d0cf 100644 --- a/crates/blockifier/tests/feature_contracts_compatibility_test.rs +++ b/crates/blockifier/tests/feature_contracts_compatibility_test.rs @@ -1,7 +1,12 @@ use std::fs; use std::process::Command; -const FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo0"; +use blockifier::test_utils::contracts::FeatureContract; +use blockifier::test_utils::CairoVersion; +use pretty_assertions::assert_eq; + +const CAIRO0_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo0"; +const CAIRO1_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo1"; const COMPILED_CONTRACTS_SUBDIR: &str = "compiled"; const FIX_COMMAND: &str = "FIX_FEATURE_TEST=1 cargo test -- --ignored"; @@ -35,35 +40,9 @@ const FIX_COMMAND: &str = "FIX_FEATURE_TEST=1 cargo test -- --ignored"; // `COMPILED_CONTRACTS_SUBDIR`. // 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) { - for file in fs::read_dir(FEATURE_CONTRACTS_DIR).unwrap() { - let path = file.unwrap().path(); - - // Test `TEST_CONTRACTS` file and directory structure. - if !path.is_file() { - if let Some(dir_name) = path.file_name() { - assert_eq!( - dir_name, - COMPILED_CONTRACTS_SUBDIR, - "Found directory '{}' in `{FEATURE_CONTRACTS_DIR}`, which should contain only \ - the `{COMPILED_CONTRACTS_SUBDIR}` directory.", - dir_name.to_string_lossy() - ); - continue; - } - } - let path_str = path.to_string_lossy(); - assert_eq!( - path.extension().unwrap(), - "cairo", - "Found a non-Cairo file '{path_str}' in `{FEATURE_CONTRACTS_DIR}`" - ); - +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) { // Compare output of cairo-file on file with existing compiled file. - let file_name = path.file_stem().unwrap().to_string_lossy(); - let existing_compiled_path = format!( - "{FEATURE_CONTRACTS_DIR}/{COMPILED_CONTRACTS_SUBDIR}/{file_name}_compiled.json" - ); let mut command = Command::new("starknet-compile-deprecated"); command.args([&path_str, "--no_debug_info"]); if file_name.starts_with("account") { @@ -93,9 +72,71 @@ fn verify_feature_contracts_compatibility(fix: bool) { } } +/// Verifies that the feature contracts directory contains the expected contents, and returns a list +/// of pairs (source_path, base_filename, compiled_path) for each contract. +fn verify_and_get_files(cairo_version: CairoVersion) -> Vec<(String, String, String)> { + let mut paths = vec![]; + let directory = match cairo_version { + CairoVersion::Cairo0 => CAIRO0_FEATURE_CONTRACTS_DIR, + CairoVersion::Cairo1 => CAIRO1_FEATURE_CONTRACTS_DIR, + }; + let compiled_extension = match cairo_version { + CairoVersion::Cairo0 => "_compiled.json", + CairoVersion::Cairo1 => ".casm.json", + }; + for file in fs::read_dir(directory).unwrap() { + let path = file.unwrap().path(); + + // Verify `TEST_CONTRACTS` file and directory structure. + if !path.is_file() { + if let Some(dir_name) = path.file_name() { + assert_eq!( + dir_name, + COMPILED_CONTRACTS_SUBDIR, + "Found directory '{}' in `{directory}`, which should contain only the \ + `{COMPILED_CONTRACTS_SUBDIR}` directory.", + dir_name.to_string_lossy() + ); + continue; + } + } + let path_str = path.to_string_lossy(); + assert_eq!( + path.extension().unwrap(), + "cairo", + "Found a non-Cairo file '{path_str}' in `{directory}`" + ); + + let file_name = path.file_stem().unwrap().to_string_lossy(); + let existing_compiled_path = + format!("{directory}/{COMPILED_CONTRACTS_SUBDIR}/{file_name}{compiled_extension}"); + + paths.push((path_str.to_string(), file_name.to_string(), existing_compiled_path)); + } + + paths +} + +#[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)) + .map(|contract| contract.get_compiled_path()) + .collect(); + let mut compiled_paths_on_filesystem: Vec = verify_and_get_files(CairoVersion::Cairo0) + .into_iter() + .chain(verify_and_get_files(CairoVersion::Cairo1)) + .map(|(_, _, compiled_path)| compiled_path) + .collect(); + compiled_paths_from_enum.sort(); + compiled_paths_on_filesystem.sort(); + assert_eq!(compiled_paths_from_enum, compiled_paths_on_filesystem); +} + #[test] #[ignore] fn verify_feature_contracts() { let fix_features = std::env::var("FIX_FEATURE_TEST").is_ok(); - verify_feature_contracts_compatibility(fix_features) + verify_feature_contracts_compatibility(fix_features, CairoVersion::Cairo0) }