Skip to content

Commit

Permalink
chore(blockifier): create unit tests for contract_class_manager
Browse files Browse the repository at this point in the history
  • Loading branch information
avivg-starkware committed Jan 5, 2025
1 parent 5745e22 commit 93d79f0
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 6 deletions.
8 changes: 7 additions & 1 deletion crates/blockifier/src/state/contract_class_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ use crate::state::global_cache::{CachedCasm, ContractCaches};

pub const DEFAULT_COMPILATION_REQUEST_CHANNEL_SIZE: usize = 1000;

#[cfg(all(test, feature = "cairo_native"))]
#[path = "contract_class_manager_test.rs"]
mod contract_class_manager_test;
/// Represents a request to compile a sierra contract class to a native compiled class.
///
/// # Fields:
Expand Down Expand Up @@ -65,7 +68,6 @@ impl ContractClassManager {
/// 2. `config.run_cairo_native` is `false`.
/// 3. `config.wait_on_native_compilation` is `true`.
pub fn start(config: ContractClassManagerConfig) -> ContractClassManager {
// TODO(Avi, 15/12/2024): Add the size of the channel to the config.
let contract_caches = ContractCaches::new(config.contract_cache_size);
#[cfg(not(feature = "cairo_native"))]
return ContractClassManager { contract_caches };
Expand Down Expand Up @@ -205,6 +207,10 @@ fn process_compilation_request(
let (class_hash, sierra, casm) = compilation_request;
if contract_caches.get_native(&class_hash).is_some() {
// The contract class is already compiled to native - skip the compilation.
log::debug!(
"Contract class with hash {} is already compiled to native. Skipping compilation.",
class_hash
);
return;
}
let sierra_for_compilation = into_contract_class_for_compilation(sierra.as_ref());
Expand Down
262 changes: 262 additions & 0 deletions crates/blockifier/src/state/contract_class_manager_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
use std::sync::mpsc::sync_channel;
use std::sync::Arc;

use rstest::rstest;
use starknet_sierra_compile::command_line_compiler::CommandLineCompiler;
use starknet_sierra_compile::config::SierraCompilationConfig;

use crate::blockifier::config::{CairoNativeRunConfig, ContractClassManagerConfig};
// use crate::concurrency::test_utils::class_hash;
use crate::execution::contract_class::RunnableCompiledClass;
use crate::execution::native::contract_class::NativeCompiledClassV1;
use crate::state::contract_class_manager::{
process_compilation_request,
run_compilation_worker,
CompilationRequest,
ContractClassManager,
};
use crate::state::global_cache::{CachedCairoNative, ContractCaches};
use crate::test_utils::contracts::FeatureContract;
use crate::test_utils::{CairoVersion, RunnableCairo1};
// use log::LevelFilter;
// use std::sync::Once;
// use log::{debug};

type TestRequestWithNative = (CompilationRequest, NativeCompiledClassV1);

const TEST_CHANNEL_SIZE: usize = 10;

#[rstest]
fn test_start(
#[values(true, false)] run_cairo_native: bool,
#[values(true, false)] wait_on_native_compilation: bool,
) {
let native_config =
CairoNativeRunConfig { run_cairo_native, wait_on_native_compilation, ..Default::default() };
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let manager = ContractClassManager::start(config.clone());

assert_eq!(manager.cairo_native_run_config, native_config);
if !run_cairo_native | wait_on_native_compilation {
assert!(manager.sender.is_none(), "Sender should be None");
} else {
assert!(manager.sender.is_some(), "Sender should be Some");
}
if !run_cairo_native | !wait_on_native_compilation {
assert!(manager.compiler.is_none(), "Compiler should be None");
} else {
// TODO(AvivG): any constraints on initial compiler?
assert!(manager.compiler.is_some(), "Compiler should be Some");
}
// TODO(AvivG): check if the compilation worker is spawned? by waiting on log
}

#[test]
#[should_panic]
fn test_send_compilation_request_channel_disconnected() {
let native_config = CairoNativeRunConfig { run_cairo_native: true, ..Default::default() };
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let contract_caches = ContractCaches::new(config.contract_cache_size);
let (sender, receiver) = sync_channel(native_config.channel_size);
drop(receiver);
let manager = ContractClassManager {
cairo_native_run_config: native_config,
contract_caches,
sender: Some(sender),
compiler: None,
};

let request = create_test_request();
// Sending request with a disconnected channel should panic.
manager.send_compilation_request(request);
}

#[test]
fn test_send_compilation_request_wait_on_native() {
let native_config = CairoNativeRunConfig {
run_cairo_native: true,
wait_on_native_compilation: true,
..Default::default()
};
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let manager = ContractClassManager::start(config);
let (request, native) = create_test_request_with_native();
let class_hash = request.0;
manager.send_compilation_request(request);
assert_eq!(
manager.get_native(&class_hash),
Some(CachedCairoNative::Compiled(native)),
"Cached Native class should match the expected result"
);
}

// TODO (AvivG): is this test redundant?
#[test]
fn test_send_compilation_request_channel_full() {
let native_config = CairoNativeRunConfig {
run_cairo_native: true,
wait_on_native_compilation: true,
..Default::default()
};
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let manager = ContractClassManager::start(config);
let request = create_test_request();
let second_request = create_test_request();

// Fill the channel (it can only hold 1 message)
manager.send_compilation_request(request);
// Should log an error without panicking
manager.send_compilation_request(second_request);
}

#[test]
#[should_panic]
fn test_send_compilation_request_run_cairo_native_false() {
let native_config = CairoNativeRunConfig {
run_cairo_native: false,
wait_on_native_compilation: true,
..Default::default()
};
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let manager = ContractClassManager::start(config);
let request = create_test_request();
manager.send_compilation_request(request);
// TODO (AvivG): add massage: Expected panic when sending request with run_cairo_native false?
}

#[rstest]
#[case::success(create_test_request_with_native(), CachedCairoNative::Compiled(create_test_request_with_native().1))]
#[case::failure(create_faulty_request(), CachedCairoNative::CompilationFailed)]
fn test_process_compilation_request(
#[case] request_w_native: TestRequestWithNative,
#[case] expected_cached_native: CachedCairoNative,
) {
let native_config = CairoNativeRunConfig {
run_cairo_native: true,
channel_size: TEST_CHANNEL_SIZE,
wait_on_native_compilation: true,
};
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let manager = ContractClassManager::start(config);
let (request, _native) = request_w_native;
let compiler_config = SierraCompilationConfig::default();
let compiler = Arc::new(CommandLineCompiler::new(compiler_config));
process_compilation_request(manager.contract_caches.clone(), compiler.clone(), request.clone());

let cached_native = manager.get_native(&request.0);
assert_eq!(
cached_native,
Some(expected_cached_native),
"Cached Native class should match the expected."
);
}

#[rstest]
fn test_run_compilation_worker() {
let native_config = CairoNativeRunConfig { run_cairo_native: true, ..Default::default() };
let config =
ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
let contract_caches = ContractCaches::new(config.contract_cache_size);
let (sender, receiver) = sync_channel(native_config.channel_size);
let compiler_config = SierraCompilationConfig::default();
let compiler = Arc::new(CommandLineCompiler::new(compiler_config));
let (request, native) = create_test_request_with_native();
sender.try_send(request.clone()).unwrap();
drop(sender);
let manager = ContractClassManager {
cairo_native_run_config: native_config,
contract_caches: contract_caches.clone(),
sender: None,
compiler: None,
};

run_compilation_worker(contract_caches.clone(), receiver, compiler);

let cached_native = manager.get_native(&request.0);
assert_eq!(
cached_native,
Some(CachedCairoNative::Compiled(native)),
"Cached Native class should match the expected."
);
}

fn create_faulty_request() -> TestRequestWithNative {
let ((class_hash, sierra, casm), native) = create_test_request_with_native();
let mut sierra = sierra.as_ref().clone();

// Truncate the sierra program to trigger an error.
sierra.sierra_program = sierra.sierra_program[..100].to_vec();

let request = (class_hash, Arc::new(sierra), casm);

(request, native)
}

fn create_test_request_from_contract(test_contract: FeatureContract) -> CompilationRequest {
let class_hash = test_contract.get_class_hash();
let sierra = Arc::new(test_contract.get_sierra());
let casm = test_contract.get_casm();

(class_hash, sierra, casm)
}

fn create_test_request() -> CompilationRequest {
// Question (AvivG): are we interested in testing other contracts?
let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Native));
create_test_request_from_contract(test_contract)
}

fn get_native(test_contract: FeatureContract) -> NativeCompiledClassV1 {
match test_contract.get_runnable_class() {
RunnableCompiledClass::V1Native(native) => native,
_ => panic!("Expected NativeCompiledClassV1"),
}
}

fn create_test_request_with_native() -> TestRequestWithNative {
let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Native));
let request = create_test_request_from_contract(test_contract);
let native = get_native(test_contract);

(request, native)
}

// TODO (AvivG): finish this test?
// #[test]
// fn test_compilation_request_not_sent_if_already_in_cache() {
// let native_config = CairoNativeRunConfig {
// run_cairo_native: true,
// wait_on_native_compilation: true,
// ..Default::default()
// };
// let config =
// ContractClassManagerConfig { cairo_native_run_config: native_config, ..Default::default() };
// let manager = ContractClassManager::start(config);
// let (request1, native1) = create_test_request_with_native();
// let (request2, native2) = create_test_request_with_native();
// let class_hash = request1.0;
// let compiler_config = SierraCompilationConfig::default();
// let compiler = Arc::new(CommandLineCompiler::new(compiler_config));
// // Send the first request
// process_compilation_request(manager.contract_caches.clone(), compiler.clone(), request1);
//
// // TODO (AvivG): track logs
// // Send the first request again, sould not compile again.
// process_compilation_request(manager.contract_caches.clone(), compiler.clone(), request2);

// let expected_log = format!(
// "Contract class with hash {} is already compiled to native. Skipping compilation.",
// class_hash
// );
// // TODO (AvivG): fix assert
// assert!(logs.iter().any(|log| log.contains(&expected_log)), "Expected log message not
// found."); (?)
// assert!(logger.contains(&expected_log), "Expected log message not found."); (?)

// }
2 changes: 1 addition & 1 deletion crates/blockifier/src/state/global_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl CachedCasm {
}

#[cfg(feature = "cairo_native")]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum CachedCairoNative {
Compiled(NativeCompiledClassV1),
CompilationFailed,
Expand Down
33 changes: 29 additions & 4 deletions crates/blockifier/src/test_utils/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use starknet_types_core::felt::Felt;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;

use crate::execution::contract_class::RunnableCompiledClass;
use crate::execution::contract_class::{CompiledClassV1, RunnableCompiledClass};
use crate::execution::entry_point::CallEntryPoint;
#[cfg(feature = "cairo_native")]
use crate::execution::native::contract_class::NativeCompiledClassV1;
Expand Down Expand Up @@ -180,6 +180,28 @@ impl FeatureContract {
}
}

pub fn get_casm(&self) -> CompiledClassV1 {
// Question (AvivG) : what is the desired behaviour?
if *self == Self::ERC20(self.cairo_version()) {
todo!("ERC20 cannot be tested with Native")
};
match self.cairo_version() {
CairoVersion::Cairo0 => {
panic!("Casm contracts are only available for Cairo1.");
}
CairoVersion::Cairo1(_) => {
let compiled_path = format!(
"feature_contracts/cairo{}/{}{}.json",
"1/compiled",
self.get_non_erc20_base_name(),
".casm"
);
let contact_class = CasmContractClass::from_file(&compiled_path);
(contact_class, self.get_sierra_version()).try_into().unwrap()
}
}
}

pub fn get_runnable_class(&self) -> RunnableCompiledClass {
#[cfg(feature = "cairo_native")]
if CairoVersion::Cairo1(RunnableCairo1::Native) == self.cairo_version() {
Expand All @@ -199,10 +221,13 @@ impl FeatureContract {
get_raw_contract_class(&self.get_sierra_path())
}

pub fn get_sierra(&self) -> SierraContractClass {
pub fn get_contract_class(&self) -> CairoLangContractClass {
let raw_sierra = self.get_raw_sierra();
let cairo_contract_class: CairoLangContractClass =
serde_json::from_str(&raw_sierra).unwrap();
serde_json::from_str(&raw_sierra).unwrap()
}

pub fn get_sierra(&self) -> SierraContractClass {
let cairo_contract_class = self.get_contract_class();
SierraContractClass::from(cairo_contract_class)
}

Expand Down

0 comments on commit 93d79f0

Please sign in to comment.