Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

chore: fetch Cairo1 compiler version from Cargo.toml #1872

Open
wants to merge 3 commits into
base: dori/move-compilation-logic-to-feature-contracts-enum
Choose a base branch
from
Open
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ strum = "0.24.1"
strum_macros = "0.24.3"
tempfile = "3.7.0"
test-case = "2.2.2"
toml = "0.8"
thiserror = "1.0.37"

[workspace.lints.rust]
Expand Down
1 change: 1 addition & 0 deletions crates/blockifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ starknet_api = { workspace = true, features = ["testing"] }
strum.workspace = true
strum_macros.workspace = true
thiserror.workspace = true
toml.workspace = true

[dev-dependencies]
assert_matches.workspace = true
Expand Down
10 changes: 9 additions & 1 deletion crates/blockifier/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod cairo_compile;
pub mod contracts;
pub mod declare;
pub mod deploy_account;
Expand Down Expand Up @@ -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,
Expand All @@ -71,6 +72,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.
Expand Down
70 changes: 70 additions & 0 deletions crates/blockifier/src/test_utils/cairo_compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::process::Command;

use cached::proc_macro::cached;
use serde::{Deserialize, Serialize};

/// 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
/// cairo-lang-casm crate.
/// The choice of cairo-lang-casm is arbitrary, as all compiler crate dependencies should have the
/// same version.
/// Deserializes:
/// """
/// ...
/// [workspace.dependencies]
/// ...
/// cairo-lang-casm = VERSION
/// ...
/// """
/// where `VERSION` can be a simple "x.y.z" version string or an object with a "version" field.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum DependencyValue {
String(String),
Object { version: String },
}

#[derive(Debug, Serialize, Deserialize)]
struct CairoLangCasmDependency {
#[serde(rename = "cairo-lang-casm")]
cairo_lang_casm: DependencyValue,
}

#[derive(Debug, Serialize, Deserialize)]
struct WorkspaceFields {
dependencies: CairoLangCasmDependency,
}

#[derive(Debug, Serialize, Deserialize)]
struct CargoToml {
workspace: WorkspaceFields,
}

#[cached]
/// Returns the version of the Cairo1 compiler* defined in the root Cargo.toml.
pub fn cairo1_compiler_version() -> String {
let cargo_toml: CargoToml = toml::from_str(include_str!("../../../../Cargo.toml")).unwrap();
match cargo_toml.workspace.dependencies.cairo_lang_casm {
DependencyValue::String(version) | DependencyValue::Object { version } => version.clone(),
}
}

/// Compiles a Cairo0 program using the deprecated compiler.
pub fn cairo0_compile(path: String, extra_arg: Option<String>, debug_info: bool) -> Vec<u8> {
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<u8> {
todo!();
}
94 changes: 88 additions & 6 deletions crates/blockifier/src/test_utils/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use starknet_api::core::{ClassHash, ContractAddress, PatriciaKey};
use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass;
use starknet_api::hash::StarkHash;
use starknet_api::{class_hash, contract_address, patricia_key};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;

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
Expand Down Expand Up @@ -49,12 +52,14 @@ 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.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, EnumIter)]
pub enum FeatureContract {
AccountWithLongValidate(CairoVersion),
AccountWithoutValidations(CairoVersion),
Expand All @@ -67,7 +72,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)
Expand All @@ -79,6 +84,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,
Expand All @@ -101,21 +117,43 @@ impl FeatureContract {
}
}

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,
Self::FaultyAccount(_) => FAULTY_ACCOUNT_NAME,
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",
"feature_contracts/cairo{}/compiled/{}{}.json",
match cairo_version {
CairoVersion::Cairo0 => "0",
CairoVersion::Cairo1 => "1",
Expand All @@ -128,6 +166,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<u8> {
if matches!(self, Self::ERC20) {
panic!("ERC20 contract recompilation not supported.");
}
match self.cairo_version() {
CairoVersion::Cairo0 => {
let extra_arg: Option<String> = 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)
Expand Down Expand Up @@ -176,4 +239,23 @@ impl FeatureContract {
pub fn get_raw_class(&self) -> String {
get_raw_contract_class(&self.get_compiled_path())
}

pub fn all_contracts() -> impl Iterator<Item = Self> {
// 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()
}
})
}

pub fn all_feature_contracts() -> impl Iterator<Item = Self> {
// ERC20 is a special case - not in the feature_contracts directory.
Self::all_contracts().filter(|contract| !matches!(contract, Self::ERC20))
}
}
Loading
Loading