diff --git a/Cargo.lock b/Cargo.lock index e934462c589..18077dea5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10228,6 +10228,7 @@ dependencies = [ "pretty_assertions", "primitive-types", "rstest", + "semver 1.0.23", "serde", "serde_json", "sha3", diff --git a/crates/blockifier/src/transaction/test_utils.rs b/crates/blockifier/src/transaction/test_utils.rs index 36757124a48..ca08404bbe9 100644 --- a/crates/blockifier/src/transaction/test_utils.rs +++ b/crates/blockifier/src/transaction/test_utils.rs @@ -1,7 +1,7 @@ use rstest::fixture; use starknet_api::abi::abi_utils::get_fee_token_var_address; use starknet_api::block::{FeeType, GasPrice}; -use starknet_api::contract_class::{ClassInfo, ContractClass}; +use starknet_api::contract_class::{ClassInfo, ContractClass, SierraVersion}; use starknet_api::core::{ClassHash, ContractAddress, Nonce}; use starknet_api::execution_resources::GasAmount; use starknet_api::test_utils::deploy_account::DeployAccountTxArgs; @@ -348,11 +348,11 @@ pub fn create_all_resource_bounds( } pub fn calculate_class_info_for_testing(contract_class: ContractClass) -> ClassInfo { - let sierra_program_length = match contract_class { - ContractClass::V0(_) => 0, - ContractClass::V1(_) => 100, + let (sierra_program_length, sierra_version) = match contract_class { + ContractClass::V0(_) => (0, SierraVersion::zero()), + ContractClass::V1(_) => (100, SierraVersion::latest()), }; - ClassInfo::new(&contract_class, sierra_program_length, 100).unwrap() + ClassInfo::new(&contract_class, sierra_program_length, 100, sierra_version).unwrap() } pub fn emit_n_events_tx( diff --git a/crates/blockifier_reexecution/src/state_reader/reexecution_state_reader.rs b/crates/blockifier_reexecution/src/state_reader/reexecution_state_reader.rs index 3e3c3f030d4..a1aec7138b4 100644 --- a/crates/blockifier_reexecution/src/state_reader/reexecution_state_reader.rs +++ b/crates/blockifier_reexecution/src/state_reader/reexecution_state_reader.rs @@ -6,7 +6,7 @@ use blockifier::test_utils::MAX_FEE; use blockifier::transaction::transaction_execution::Transaction as BlockifierTransaction; use papyrus_execution::DEPRECATED_CONTRACT_SIERRA_SIZE; use starknet_api::block::{BlockHash, BlockNumber}; -use starknet_api::contract_class::ClassInfo; +use starknet_api::contract_class::{ClassInfo, SierraVersion}; use starknet_api::core::ClassHash; use starknet_api::transaction::{Transaction, TransactionHash}; use starknet_core::types::ContractClass as StarknetContractClass; @@ -22,7 +22,15 @@ pub trait ReexecutionStateReader { StarknetContractClass::Sierra(sierra) => { let abi_length = sierra.abi.len(); let sierra_length = sierra.sierra_program.len(); - Ok(ClassInfo::new(&sierra_to_contact_class_v1(sierra)?, sierra_length, abi_length)?) + + let sierra_version = SierraVersion::extract_from_program(&sierra.sierra_program)?; + + Ok(ClassInfo::new( + &sierra_to_contact_class_v1(sierra)?, + sierra_length, + abi_length, + sierra_version, + )?) } StarknetContractClass::Legacy(legacy) => { let abi_length = @@ -31,6 +39,7 @@ pub trait ReexecutionStateReader { &legacy_to_contract_class_v0(legacy)?, DEPRECATED_CONTRACT_SIERRA_SIZE, abi_length, + SierraVersion::zero(), )?) } } diff --git a/crates/native_blockifier/src/py_transaction.rs b/crates/native_blockifier/src/py_transaction.rs index bdf28426b08..0f4036cacd1 100644 --- a/crates/native_blockifier/src/py_transaction.rs +++ b/crates/native_blockifier/src/py_transaction.rs @@ -164,6 +164,7 @@ pub struct PyClassInfo { raw_contract_class: String, sierra_program_length: usize, abi_length: usize, + sierra_version: String, } impl PyClassInfo { @@ -185,6 +186,7 @@ impl PyClassInfo { &contract_class, py_class_info.sierra_program_length, py_class_info.abi_length, + py_class_info.sierra_version.parse()?, )?; Ok(class_info) } diff --git a/crates/papyrus_execution/src/lib.rs b/crates/papyrus_execution/src/lib.rs index 77e5348e4a1..7521631ad1e 100644 --- a/crates/papyrus_execution/src/lib.rs +++ b/crates/papyrus_execution/src/lib.rs @@ -12,7 +12,6 @@ mod execution_test; pub mod execution_utils; mod state_reader; - #[cfg(test)] mod test_utils; #[cfg(any(feature = "testing", test))] @@ -58,7 +57,7 @@ use starknet_api::block::{ NonzeroGasPrice, StarknetVersion, }; -use starknet_api::contract_class::{ClassInfo, EntryPointType}; +use starknet_api::contract_class::{ClassInfo, EntryPointType, SierraVersion}; use starknet_api::core::{ChainId, ClassHash, ContractAddress, EntryPointSelector}; use starknet_api::data_availability::L1DataAvailabilityMode; use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; @@ -428,8 +427,22 @@ pub enum ExecutableTransactionInput { // todo(yair): Do we need to support V0? DeclareV0(DeclareTransactionV0V1, DeprecatedContractClass, AbiSize, OnlyQuery), DeclareV1(DeclareTransactionV0V1, DeprecatedContractClass, AbiSize, OnlyQuery), - DeclareV2(DeclareTransactionV2, CasmContractClass, SierraSize, AbiSize, OnlyQuery), - DeclareV3(DeclareTransactionV3, CasmContractClass, SierraSize, AbiSize, OnlyQuery), + DeclareV2( + DeclareTransactionV2, + CasmContractClass, + SierraSize, + AbiSize, + OnlyQuery, + SierraVersion, + ), + DeclareV3( + DeclareTransactionV3, + CasmContractClass, + SierraSize, + AbiSize, + OnlyQuery, + SierraVersion, + ), DeployAccount(DeployAccountTransaction, OnlyQuery), L1Handler(L1HandlerTransaction, Fee, OnlyQuery), } @@ -485,13 +498,24 @@ impl ExecutableTransactionInput { sierra_program_length, abi_length, only_query, + sierra_version, ) => { let as_transaction = Transaction::Declare(DeclareTransaction::V2(tx)); let res = func(&as_transaction, only_query); let Transaction::Declare(DeclareTransaction::V2(tx)) = as_transaction else { unreachable!("Should be declare v2 transaction.") }; - (Self::DeclareV2(tx, class, sierra_program_length, abi_length, only_query), res) + ( + Self::DeclareV2( + tx, + class, + sierra_program_length, + abi_length, + only_query, + sierra_version, + ), + res, + ) } ExecutableTransactionInput::DeclareV3( tx, @@ -499,13 +523,24 @@ impl ExecutableTransactionInput { sierra_program_length, abi_length, only_query, + sierra_version, ) => { let as_transaction = Transaction::Declare(DeclareTransaction::V3(tx)); let res = func(&as_transaction, only_query); let Transaction::Declare(DeclareTransaction::V3(tx)) = as_transaction else { unreachable!("Should be declare v3 transaction.") }; - (Self::DeclareV3(tx, class, sierra_program_length, abi_length, only_query), res) + ( + Self::DeclareV3( + tx, + class, + sierra_program_length, + abi_length, + only_query, + sierra_version, + ), + res, + ) } ExecutableTransactionInput::DeployAccount(tx, only_query) => { let as_transaction = Transaction::DeployAccount(tx); @@ -795,6 +830,7 @@ fn to_blockifier_tx( &deprecated_class.into(), DEPRECATED_CONTRACT_SIERRA_SIZE, abi_length, + SierraVersion::zero(), ) .map_err(|err| ExecutionError::BadDeclareTransaction { tx: DeclareTransaction::V0(declare_tx.clone()), @@ -821,6 +857,7 @@ fn to_blockifier_tx( &deprecated_class.into(), DEPRECATED_CONTRACT_SIERRA_SIZE, abi_length, + SierraVersion::zero(), ) .map_err(|err| ExecutionError::BadDeclareTransaction { tx: DeclareTransaction::V1(declare_tx.clone()), @@ -842,14 +879,18 @@ fn to_blockifier_tx( sierra_program_length, abi_length, only_query, + sierra_version, ) => { - let class_info = - ClassInfo::new(&compiled_class.into(), sierra_program_length, abi_length).map_err( - |err| ExecutionError::BadDeclareTransaction { - tx: DeclareTransaction::V2(declare_tx.clone()), - err, - }, - )?; + let class_info = ClassInfo::new( + &compiled_class.into(), + sierra_program_length, + abi_length, + sierra_version, + ) + .map_err(|err| ExecutionError::BadDeclareTransaction { + tx: DeclareTransaction::V2(declare_tx.clone()), + err, + })?; BlockifierTransaction::from_api( Transaction::Declare(DeclareTransaction::V2(declare_tx)), tx_hash, @@ -866,14 +907,18 @@ fn to_blockifier_tx( sierra_program_length, abi_length, only_query, + sierra_version, ) => { - let class_info = - ClassInfo::new(&compiled_class.into(), sierra_program_length, abi_length).map_err( - |err| ExecutionError::BadDeclareTransaction { - tx: DeclareTransaction::V3(declare_tx.clone()), - err, - }, - )?; + let class_info = ClassInfo::new( + &compiled_class.into(), + sierra_program_length, + abi_length, + sierra_version, + ) + .map_err(|err| ExecutionError::BadDeclareTransaction { + tx: DeclareTransaction::V3(declare_tx.clone()), + err, + })?; BlockifierTransaction::from_api( Transaction::Declare(DeclareTransaction::V3(declare_tx)), tx_hash, diff --git a/crates/papyrus_execution/src/test_utils.rs b/crates/papyrus_execution/src/test_utils.rs index 861fc4b2316..377cc957a1e 100644 --- a/crates/papyrus_execution/src/test_utils.rs +++ b/crates/papyrus_execution/src/test_utils.rs @@ -21,6 +21,7 @@ use starknet_api::block::{ GasPrice, GasPricePerToken, }; +use starknet_api::contract_class::SierraVersion; use starknet_api::core::{ ChainId, ClassHash, @@ -312,6 +313,7 @@ impl TxsScenarioBuilder { DUMMY_SIERRA_SIZE, 0, false, + SierraVersion::latest(), ); self.txs.push(tx); self diff --git a/crates/papyrus_rpc/src/v0_8/api/mod.rs b/crates/papyrus_rpc/src/v0_8/api/mod.rs index 1437933c934..5e45ff13866 100644 --- a/crates/papyrus_rpc/src/v0_8/api/mod.rs +++ b/crates/papyrus_rpc/src/v0_8/api/mod.rs @@ -18,6 +18,7 @@ use papyrus_storage::state::StateStorageReader; use papyrus_storage::StorageTxn; use serde::{Deserialize, Serialize}; use starknet_api::block::{BlockHashAndNumber, BlockNumber}; +use starknet_api::contract_class::SierraVersion; use starknet_api::core::{ClassHash, ContractAddress, Nonce}; use starknet_api::deprecated_contract_class::{ ContractClass as StarknetApiDeprecatedContractClass, @@ -379,7 +380,7 @@ pub(crate) fn stored_txn_to_executable_txn( value.class_hash )) })?; - let (sierra_program_length, abi_length) = + let (sierra_program_length, abi_length, sierra_version) = get_class_lengths(storage_txn, state_number, value.class_hash)?; Ok(ExecutableTransactionInput::DeclareV2( value, @@ -387,6 +388,7 @@ pub(crate) fn stored_txn_to_executable_txn( sierra_program_length, abi_length, false, + sierra_version, )) } starknet_api::transaction::Transaction::Declare( @@ -401,7 +403,7 @@ pub(crate) fn stored_txn_to_executable_txn( value.class_hash )) })?; - let (sierra_program_length, abi_length) = + let (sierra_program_length, abi_length, sierra_version) = get_class_lengths(storage_txn, state_number, value.class_hash)?; Ok(ExecutableTransactionInput::DeclareV3( value, @@ -409,6 +411,7 @@ pub(crate) fn stored_txn_to_executable_txn( sierra_program_length, abi_length, false, + sierra_version, )) } starknet_api::transaction::Transaction::Deploy(_) => { @@ -453,9 +456,10 @@ fn get_class_lengths( storage_txn: &StorageTxn<'_, RO>, state_number: StateNumber, class_hash: ClassHash, -) -> Result<(SierraSize, AbiSize), ErrorObjectOwned> { +) -> Result<(SierraSize, AbiSize, SierraVersion), ErrorObjectOwned> { let state_number_after_block = StateNumber::unchecked_right_after_block(state_number.block_after()); + storage_txn .get_state_reader() .map_err(internal_server_error)? @@ -464,7 +468,14 @@ fn get_class_lengths( .ok_or_else(|| { internal_server_error(format!("Missing deprecated class definition of {class_hash}.")) }) - .map(|contract_class| (contract_class.sierra_program.len(), contract_class.abi.len())) + .and_then(|contract_class| { + let sierra_program_len = contract_class.sierra_program.len(); + let abi_len = contract_class.abi.len(); + let sierra_program = + SierraVersion::extract_from_program(&contract_class.sierra_program) + .map_err(internal_server_error)?; + Ok((sierra_program_len, abi_len, sierra_program)) + }) } impl TryFrom for ExecutableTransactionInput { diff --git a/crates/starknet_api/Cargo.toml b/crates/starknet_api/Cargo.toml index 5fac49f236f..4fd905b38cc 100644 --- a/crates/starknet_api/Cargo.toml +++ b/crates/starknet_api/Cargo.toml @@ -21,6 +21,7 @@ itertools.workspace = true num-bigint.workspace = true pretty_assertions.workspace = true primitive-types = { workspace = true, features = ["serde"] } +semver.workspace = true serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true sha3.workspace = true diff --git a/crates/starknet_api/src/contract_class.rs b/crates/starknet_api/src/contract_class.rs index 3a85573a06a..86445cd26fa 100644 --- a/crates/starknet_api/src/contract_class.rs +++ b/crates/starknet_api/src/contract_class.rs @@ -1,4 +1,9 @@ +use std::fmt::Display; +use std::str::FromStr; + use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use derive_more::Deref; +use semver::Version; use serde::{Deserialize, Serialize}; use crate::core::CompiledClassHash; @@ -42,7 +47,80 @@ impl ContractClass { } } } -/// All relevant information about a declared contract class, including the compiled class + +#[derive(Deref, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct SierraVersion(Version); + +impl SierraVersion { + pub fn new(major: u64, minor: u64, patch: u64) -> Self { + Self(Version::new(major, minor, patch)) + } + + /// Version of deprecated contract class. + pub fn zero() -> Self { + Self(Version::new(0, 0, 0)) + } + + // TODO(Aviv): Implement logic to fetch the latest version dynamically from Cargo.toml and write + // tests to ensure that it matches the value returned by this function. + pub fn latest() -> Self { + Self::new(2, 8, 4) + } + + /// Converts a sierra program to a SierraVersion. + /// The sierra program is a list of felts. + /// The first 3 felts are the major, minor and patch version. + /// The rest of the felts are ignored. + pub fn extract_from_program(sierra_program: &[F]) -> Result + // TODO(Aviv): Refactor the implementation to remove generic handling once we standardize to a + // single type of Felt. + where + F: TryInto + Display + Clone, + >::Error: std::fmt::Display, + { + if sierra_program.len() < 3 { + return Err(StarknetApiError::ParseSierraVersionError( + "Sierra program length must be at least 3 Felts.".to_string(), + )); + } + + let version_components: Vec = sierra_program + .iter() + .take(3) + .enumerate() + .map(|(index, felt)| { + felt.clone().try_into().map_err(|err| { + StarknetApiError::ParseSierraVersionError(format!( + "Failed to parse Sierra program to Sierra version. Index: {}, Felt: {}, \ + Error: {}", + index, felt, err + )) + }) + }) + .collect::>()?; + + Ok(Self::new(version_components[0], version_components[1], version_components[2])) + } +} + +impl Default for SierraVersion { + fn default() -> Self { + Self::latest() + } +} + +impl FromStr for SierraVersion { + type Err = StarknetApiError; + + fn from_str(s: &str) -> Result { + Ok(Self( + Version::parse(s) + .map_err(|_| StarknetApiError::ParseSierraVersionError(s.to_string()))?, + )) + } +} + +/// All relevant information about a declared contract class, including the compiled contract class /// and other parameters derived from the original declare transaction required for billing. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] // TODO(Ayelet,10/02/2024): Change to bytes. @@ -51,6 +129,7 @@ pub struct ClassInfo { pub contract_class: ContractClass, pub sierra_program_length: usize, pub abi_length: usize, + pub sierra_version: SierraVersion, } impl ClassInfo { @@ -84,6 +163,7 @@ impl ClassInfo { contract_class: &ContractClass, sierra_program_length: usize, abi_length: usize, + sierra_version: SierraVersion, ) -> Result { let (contract_class_version, condition) = match contract_class { ContractClass::V0(_) => (0, sierra_program_length == 0), @@ -91,7 +171,12 @@ impl ClassInfo { }; if condition { - Ok(Self { contract_class: contract_class.clone(), sierra_program_length, abi_length }) + Ok(Self { + contract_class: contract_class.clone(), + sierra_program_length, + abi_length, + sierra_version, + }) } else { Err(StarknetApiError::ContractClassVersionSierraProgramLengthMismatch { contract_class_version, diff --git a/crates/starknet_api/src/lib.rs b/crates/starknet_api/src/lib.rs index 2531709e670..0ac6cb2c043 100644 --- a/crates/starknet_api/src/lib.rs +++ b/crates/starknet_api/src/lib.rs @@ -65,6 +65,8 @@ pub enum StarknetApiError { version {cairo_version:?}.", **declare_version )] ContractClassVersionMismatch { declare_version: TransactionVersion, cairo_version: u64 }, + #[error("Failed to parse Sierra version: {0}")] + ParseSierraVersionError(String), } pub type StarknetApiResult = Result; diff --git a/crates/starknet_gateway/src/compilation.rs b/crates/starknet_gateway/src/compilation.rs index 5e8a4db1714..8614928df6c 100644 --- a/crates/starknet_gateway/src/compilation.rs +++ b/crates/starknet_gateway/src/compilation.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_lang_starknet_classes::contract_class::ContractClass as CairoLangContractClass; -use starknet_api::contract_class::{ClassInfo, ContractClass}; +use starknet_api::contract_class::{ClassInfo, ContractClass, SierraVersion}; use starknet_api::rpc_transaction::RpcDeclareTransaction; use starknet_gateway_types::errors::GatewaySpecError; use starknet_sierra_compile::command_line_compiler::CommandLineCompiler; @@ -41,10 +41,15 @@ impl GatewayCompiler { let casm_contract_class = self.compile(cairo_lang_contract_class)?; + let sierra_version = + SierraVersion::extract_from_program(&rpc_contract_class.sierra_program) + .map_err(|_| GatewaySpecError::UnsupportedContractClassVersion)?; + Ok(ClassInfo { contract_class: ContractClass::V1(casm_contract_class), sierra_program_length: rpc_contract_class.sierra_program.len(), abi_length: rpc_contract_class.abi.len(), + sierra_version, }) }