diff --git a/crates/blockifier/src/execution/contract_class.rs b/crates/blockifier/src/execution/contract_class.rs index 2a100bdb83..2d613234e7 100644 --- a/crates/blockifier/src/execution/contract_class.rs +++ b/crates/blockifier/src/execution/contract_class.rs @@ -26,12 +26,15 @@ use starknet_api::deprecated_contract_class::{ use crate::abi::abi_utils::selector_from_name; use crate::abi::constants::{self, CONSTRUCTOR_ENTRY_POINT_NAME}; use crate::execution::entry_point::CallEntryPoint; -use crate::execution::errors::PreExecutionError; +use crate::execution::errors::{ContractClassError, PreExecutionError}; use crate::execution::execution_utils::{felt_to_stark_felt, sn_api_to_cairo_vm_program}; /// Represents a runnable Starknet contract class (meaning, the program is runnable by the VM). /// We wrap the actual class in an Arc to avoid cloning the program when cloning the class. // Note: when deserializing from a SN API class JSON string, the ABI field is ignored // by serde, since it is not required for execution. + +pub type ContractClassResult = Result; + #[derive(Clone, Debug, Eq, PartialEq, derive_more::From)] pub enum ContractClass { V0(ContractClassV0), @@ -393,3 +396,49 @@ fn convert_entry_points_v1( }) .collect() } + +#[derive(Clone, Debug)] +// TODO(Ayelet,10/02/2024): Change to bytes. +pub struct ClassInfo { + contract_class: ContractClass, + sierra_program_length: usize, + abi_length: usize, +} + +impl ClassInfo { + pub fn bytecode_length(&self) -> usize { + self.contract_class.bytecode_length() + } + + pub fn contract_class(&self) -> ContractClass { + self.contract_class.clone() + } + + pub fn sierra_program_length(&self) -> usize { + self.sierra_program_length + } + + pub fn abi_length(&self) -> usize { + self.abi_length + } + + pub fn new( + contract_class: &ContractClass, + sierra_program_length: usize, + abi_length: usize, + ) -> ContractClassResult { + let (contract_class_version, condition) = match contract_class { + ContractClass::V0(_) => (0, sierra_program_length == 0), + ContractClass::V1(_) => (1, sierra_program_length > 0), + }; + + if condition { + Ok(Self { contract_class: contract_class.clone(), sierra_program_length, abi_length }) + } else { + Err(ContractClassError::ContractClassVersionSierraProgramLengthMismatch { + contract_class_version, + sierra_program_length, + }) + } + } +} diff --git a/crates/blockifier/src/execution/errors.rs b/crates/blockifier/src/execution/errors.rs index 68ad899f72..e5e18f4800 100644 --- a/crates/blockifier/src/execution/errors.rs +++ b/crates/blockifier/src/execution/errors.rs @@ -140,3 +140,15 @@ pub enum EntryPointExecutionError { source: CairoRunError, }, } + +#[derive(Debug, Error)] +pub enum ContractClassError { + #[error( + "Sierra program length must be > 0 for Cairo1, and == 0 for Cairo0. Got: \ + {sierra_program_length:?} for contract class version {contract_class_version:?}" + )] + ContractClassVersionSierraProgramLengthMismatch { + contract_class_version: u8, + sierra_program_length: usize, + }, +} diff --git a/crates/blockifier/src/fee/actual_cost.rs b/crates/blockifier/src/fee/actual_cost.rs index 952007d3b3..20f9604e48 100644 --- a/crates/blockifier/src/fee/actual_cost.rs +++ b/crates/blockifier/src/fee/actual_cost.rs @@ -7,6 +7,7 @@ use starknet_api::transaction::Fee; use crate::abi::constants as abi_constants; use crate::context::TransactionContext; use crate::execution::call_info::CallInfo; +use crate::execution::contract_class::ClassInfo; use crate::fee::gas_usage::{ get_calldata_and_signature_gas_cost, get_code_gas_cost, get_da_gas_cost, get_messages_gas_cost, get_tx_events_gas_cost, @@ -18,7 +19,6 @@ use crate::transaction::objects::{ }; use crate::transaction::transaction_types::TransactionType; use crate::transaction::transaction_utils::calculate_tx_resources; -use crate::transaction::transactions::ClassInfo; use crate::versioned_constants::VersionedConstants; #[cfg(test)] diff --git a/crates/blockifier/src/fee/actual_cost_test.rs b/crates/blockifier/src/fee/actual_cost_test.rs index 656af4eff8..5e95410221 100644 --- a/crates/blockifier/src/fee/actual_cost_test.rs +++ b/crates/blockifier/src/fee/actual_cost_test.rs @@ -61,11 +61,11 @@ fn test_calculate_tx_gas_usage_basic(#[values(false, true)] use_kzg_da: bool) { // Declare. for cairo_version in [CairoVersion::Cairo0, CairoVersion::Cairo1] { let empty_contract = FeatureContract::Empty(cairo_version).get_class(); - let class_info = calculate_class_info_for_testing(cairo_version, empty_contract); + let class_info = calculate_class_info_for_testing(empty_contract); let code_milligas_cost = u128_from_usize( - (class_info.bytecode_length() + class_info.sierra_program_length) + (class_info.bytecode_length() + class_info.sierra_program_length()) * eth_gas_constants::WORD_WIDTH - + class_info.abi_length, + + class_info.abi_length(), ) .unwrap() * versioned_constants.l2_resource_gas_costs.milligas_per_code_byte; diff --git a/crates/blockifier/src/fee/gas_usage.rs b/crates/blockifier/src/fee/gas_usage.rs index fe30bc1e3f..745393c3d6 100644 --- a/crates/blockifier/src/fee/gas_usage.rs +++ b/crates/blockifier/src/fee/gas_usage.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::abi::constants; use crate::context::{BlockContext, TransactionContext}; use crate::execution::call_info::{CallInfo, MessageL1CostInfo, OrderedEvent}; +use crate::execution::contract_class::ClassInfo; use crate::fee::eth_gas_constants; use crate::fee::fee_utils::calculate_tx_gas_vector; use crate::state::cached_state::StateChangesCount; @@ -11,7 +12,6 @@ use crate::transaction::objects::{ GasVector, HasRelatedFeeType, ResourcesMapping, TransactionExecutionResult, TransactionPreValidationResult, }; -use crate::transaction::transactions::ClassInfo; use crate::utils::{u128_from_usize, usize_from_u128}; use crate::versioned_constants::VersionedConstants; @@ -116,10 +116,10 @@ pub fn get_code_gas_cost( ) -> GasVector { if let Some(class_info) = class_info { let total_code_size = u128_from_usize( - (class_info.bytecode_length() + class_info.sierra_program_length) + (class_info.bytecode_length() + class_info.sierra_program_length()) // We assume each felt is a word. * eth_gas_constants::WORD_WIDTH - + class_info.abi_length, + + class_info.abi_length(), ) .expect("Failed to convert total code size from usize to u128."); let l1_milligas = diff --git a/crates/blockifier/src/test_utils/declare.rs b/crates/blockifier/src/test_utils/declare.rs index 3d22009a2f..9d51db6319 100644 --- a/crates/blockifier/src/test_utils/declare.rs +++ b/crates/blockifier/src/test_utils/declare.rs @@ -6,9 +6,10 @@ use starknet_api::transaction::{ TransactionVersion, }; +use crate::execution::contract_class::ClassInfo; use crate::test_utils::default_testing_resource_bounds; use crate::transaction::account_transaction::AccountTransaction; -use crate::transaction::transactions::{ClassInfo, DeclareTransaction}; +use crate::transaction::transactions::DeclareTransaction; #[derive(Clone)] pub struct DeclareTxArgs { diff --git a/crates/blockifier/src/transaction/account_transactions_test.rs b/crates/blockifier/src/transaction/account_transactions_test.rs index 239a8189b0..11f1157a75 100644 --- a/crates/blockifier/src/transaction/account_transactions_test.rs +++ b/crates/blockifier/src/transaction/account_transactions_test.rs @@ -262,8 +262,7 @@ fn test_max_fee_limit_validate( let grindy_validate_account = FeatureContract::AccountWithLongValidate(CairoVersion::Cairo0); let grindy_class_hash = grindy_validate_account.get_class_hash(); let block_info = &block_context.block_info; - let class_info = - calculate_class_info_for_testing(CairoVersion::Cairo0, grindy_validate_account.get_class()); + let class_info = calculate_class_info_for_testing(grindy_validate_account.get_class()); // Declare the grindy-validation account. let account_tx = declare_tx( @@ -563,7 +562,7 @@ fn test_fail_declare(block_context: BlockContext, max_fee: Fee) { }; state.set_contract_class(class_hash, contract_class.clone()).unwrap(); state.set_compiled_class_hash(class_hash, declare_tx.compiled_class_hash).unwrap(); - let class_info = calculate_class_info_for_testing(CairoVersion::Cairo1, contract_class); + let class_info = calculate_class_info_for_testing(contract_class); let declare_account_tx = AccountTransaction::Declare( DeclareTransaction::new( starknet_api::transaction::DeclareTransaction::V2(DeclareTransactionV2 { diff --git a/crates/blockifier/src/transaction/test_utils.rs b/crates/blockifier/src/transaction/test_utils.rs index 8c5b1d0974..811fc885ef 100644 --- a/crates/blockifier/src/transaction/test_utils.rs +++ b/crates/blockifier/src/transaction/test_utils.rs @@ -12,10 +12,9 @@ use starknet_api::transaction::{ use starknet_api::{calldata, class_hash, contract_address, patricia_key, stark_felt}; use strum::IntoEnumIterator; -use super::transactions::ClassInfo; use crate::abi::abi_utils::{get_fee_token_var_address, get_storage_var_address}; use crate::context::{BlockContext, ChainInfo, FeeTokenAddresses}; -use crate::execution::contract_class::{ContractClass, ContractClassV0}; +use crate::execution::contract_class::{ClassInfo, ContractClass, ContractClassV0}; use crate::state::cached_state::CachedState; use crate::state::state_api::State; use crate::test_utils::contracts::FeatureContract; @@ -252,10 +251,7 @@ pub fn create_account_tx_for_validate_test( // It does not matter which class is declared for this test. let declared_contract = FeatureContract::TestContract(CairoVersion::Cairo0); let class_hash = declared_contract.get_class_hash(); - let class_info = calculate_class_info_for_testing( - CairoVersion::Cairo0, - declared_contract.get_class(), - ); + let class_info = calculate_class_info_for_testing(declared_contract.get_class()); declare_tx( declare_tx_args! { class_hash, @@ -324,13 +320,10 @@ pub fn l1_resource_bounds(max_amount: u64, max_price: u128) -> ResourceBoundsMap .unwrap() } -pub fn calculate_class_info_for_testing( - cairo_version: CairoVersion, - contract_class: ContractClass, -) -> ClassInfo { - let sierra_program_length = match cairo_version { - CairoVersion::Cairo0 => 0, - CairoVersion::Cairo1 => 100, +pub fn calculate_class_info_for_testing(contract_class: ContractClass) -> ClassInfo { + let sierra_program_length = match contract_class { + ContractClass::V0(_) => 0, + ContractClass::V1(_) => 100, }; - ClassInfo { contract_class, sierra_program_length, abi_length: 100 } + ClassInfo::new(&contract_class, sierra_program_length, 100).unwrap() } diff --git a/crates/blockifier/src/transaction/transaction_execution.rs b/crates/blockifier/src/transaction/transaction_execution.rs index ec1df7ed0d..51b6691242 100644 --- a/crates/blockifier/src/transaction/transaction_execution.rs +++ b/crates/blockifier/src/transaction/transaction_execution.rs @@ -4,8 +4,8 @@ use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use starknet_api::core::{calculate_contract_address, ContractAddress}; use starknet_api::transaction::{Fee, Transaction as StarknetApiTransaction, TransactionHash}; -use super::transactions::ClassInfo; use crate::context::BlockContext; +use crate::execution::contract_class::ClassInfo; use crate::execution::entry_point::EntryPointExecutionContext; use crate::fee::actual_cost::ActualCost; use crate::state::cached_state::TransactionalState; diff --git a/crates/blockifier/src/transaction/transactions.rs b/crates/blockifier/src/transaction/transactions.rs index c8403119b9..1102842f72 100644 --- a/crates/blockifier/src/transaction/transactions.rs +++ b/crates/blockifier/src/transaction/transactions.rs @@ -11,7 +11,7 @@ use starknet_api::transaction::{ use crate::abi::abi_utils::selector_from_name; use crate::context::{BlockContext, TransactionContext}; use crate::execution::call_info::CallInfo; -use crate::execution::contract_class::ContractClass; +use crate::execution::contract_class::{ClassInfo, ContractClass}; use crate::execution::entry_point::{ CallEntryPoint, CallType, ConstructorContext, EntryPointExecutionContext, }; @@ -103,20 +103,6 @@ pub trait ValidatableTransaction { ) -> TransactionExecutionResult>; } -#[derive(Clone, Debug)] -// TODO(Ayelet,10/02/2024): Change to bytes. -pub struct ClassInfo { - pub contract_class: ContractClass, - pub sierra_program_length: usize, - pub abi_length: usize, -} - -impl ClassInfo { - pub fn bytecode_length(&self) -> usize { - self.contract_class.bytecode_length() - } -} - #[derive(Debug)] pub struct DeclareTransaction { pub tx: starknet_api::transaction::DeclareTransaction, @@ -134,7 +120,7 @@ impl DeclareTransaction { only_query: bool, ) -> TransactionExecutionResult { let declare_version = declare_tx.version(); - verify_contract_class_version(&class_info.contract_class, declare_version)?; + verify_contract_class_version(&class_info.contract_class(), declare_version)?; Ok(Self { tx: declare_tx, tx_hash, class_info, only_query }) } @@ -165,7 +151,7 @@ impl DeclareTransaction { } pub fn contract_class(&self) -> ContractClass { - self.class_info.contract_class.clone() + self.class_info.contract_class() } pub fn only_query(&self) -> bool { diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index 64b4af179a..a4e35850e6 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -788,8 +788,7 @@ fn test_max_fee_exceeds_balance(account_cairo_version: CairoVersion) { // Declare. let contract_to_declare = FeatureContract::Empty(CairoVersion::Cairo0); - let class_info = - calculate_class_info_for_testing(CairoVersion::Cairo0, contract_to_declare.get_class()); + let class_info = calculate_class_info_for_testing(contract_to_declare.get_class()); let invalid_tx = declare_tx( declare_tx_args! { class_hash: contract_to_declare.get_class_hash(), @@ -1079,8 +1078,7 @@ fn test_declare_tx( let chain_info = &block_context.chain_info; let state = &mut test_state(chain_info, BALANCE, &[(account, 1)]); let class_hash = empty_contract.get_class_hash(); - let class_info = - calculate_class_info_for_testing(account_cairo_version, empty_contract.get_class()); + let class_info = calculate_class_info_for_testing(empty_contract.get_class()); let sender_address = account.get_instance_address(0); let account_tx = declare_tx( @@ -1171,7 +1169,7 @@ fn test_declare_tx( // Verify class declaration. let contract_class_from_state = state.get_compiled_contract_class(class_hash).unwrap(); - assert_eq!(contract_class_from_state, class_info.contract_class); + assert_eq!(contract_class_from_state, class_info.contract_class()); } #[rstest] diff --git a/crates/native_blockifier/src/errors.rs b/crates/native_blockifier/src/errors.rs index 79383d478f..8ab8d35c2d 100644 --- a/crates/native_blockifier/src/errors.rs +++ b/crates/native_blockifier/src/errors.rs @@ -1,3 +1,4 @@ +use blockifier::execution::errors::ContractClassError; use blockifier::state::errors::StateError; use blockifier::transaction::errors::{ ParseError, TransactionExecutionError, TransactionPreValidationError, @@ -59,6 +60,7 @@ macro_rules! native_blockifier_errors { } native_blockifier_errors!( + (ContractClassError, ContractClassError, PyContractClassError), (NativeBlockifierInputError, NativeBlockifierInputError, PyNativeBlockifierInputError), (ProgramError, ProgramError, PyProgramError), (Pyo3Error, PyErr, PyPyo3Error), diff --git a/crates/native_blockifier/src/py_transaction.rs b/crates/native_blockifier/src/py_transaction.rs index a5b2e22057..085fe8c836 100644 --- a/crates/native_blockifier/src/py_transaction.rs +++ b/crates/native_blockifier/src/py_transaction.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; -use blockifier::execution::contract_class::{ContractClassV0, ContractClassV1}; +use blockifier::execution::contract_class::{ + ClassInfo, ContractClass, ContractClassV0, ContractClassV1, +}; use blockifier::transaction::account_transaction::AccountTransaction; use blockifier::transaction::transaction_execution::Transaction; use blockifier::transaction::transaction_types::TransactionType; -use blockifier::transaction::transactions::ClassInfo; -use cairo_vm::types::errors::program_errors::ProgramError; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use starknet_api::transaction::{Resource, ResourceBounds}; @@ -152,8 +152,8 @@ impl PyClassInfo { pub fn try_from( py_class_info: PyClassInfo, tx: &starknet_api::transaction::DeclareTransaction, - ) -> Result { - let contract_class = match tx { + ) -> NativeBlockifierResult { + let contract_class: ContractClass = match tx { starknet_api::transaction::DeclareTransaction::V0(_) | starknet_api::transaction::DeclareTransaction::V1(_) => { ContractClassV0::try_from_json_string(&py_class_info.raw_contract_class)?.into() @@ -163,10 +163,11 @@ impl PyClassInfo { ContractClassV1::try_from_json_string(&py_class_info.raw_contract_class)?.into() } }; - Ok(ClassInfo { - contract_class, - sierra_program_length: py_class_info.sierra_program_length, - abi_length: py_class_info.abi_length, - }) + let class_info = ClassInfo::new( + &contract_class, + py_class_info.sierra_program_length, + py_class_info.abi_length, + )?; + Ok(class_info) } }