diff --git a/crates/blockifier/resources/versioned_constants.json b/crates/blockifier/resources/versioned_constants.json index 8af1a13efa..26d2688e17 100644 --- a/crates/blockifier/resources/versioned_constants.json +++ b/crates/blockifier/resources/versioned_constants.json @@ -3,6 +3,10 @@ "max_calldata_length": 4000, "max_contract_bytecode_size": 61440 }, + "event_gas_cost": { + "milligas_per_data_word": 2560, + "milligas_per_key": 1280 + }, "invoke_tx_max_n_steps": 3000000, "max_recursion_depth": 50, "os_constants": { diff --git a/crates/blockifier/src/fee/actual_cost.rs b/crates/blockifier/src/fee/actual_cost.rs index 0fd291eef5..f9517e0754 100644 --- a/crates/blockifier/src/fee/actual_cost.rs +++ b/crates/blockifier/src/fee/actual_cost.rs @@ -7,8 +7,8 @@ use crate::abi::constants as abi_constants; use crate::context::TransactionContext; use crate::execution::call_info::CallInfo; use crate::execution::entry_point::ExecutionResources; -use crate::fee::gas_usage::{calculate_tx_gas_usage_vector, get_da_gas_cost}; -use crate::state::cached_state::{CachedState, StateChanges}; +use crate::fee::gas_usage::{get_da_gas_cost, get_messages_gas_cost, get_tx_events_gas_cost}; +use crate::state::cached_state::{CachedState, StateChanges, StateChangesCount}; use crate::state::state_api::{StateReader, StateResult}; use crate::transaction::objects::{ GasVector, HasRelatedFeeType, ResourcesMapping, TransactionExecutionResult, @@ -16,6 +16,11 @@ 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)] +#[path = "actual_cost_test.rs"] +pub mod test; // TODO(Gilad): Use everywhere instead of passing the `actual_{fee,resources}` tuple, which often // get passed around together. @@ -140,6 +145,23 @@ impl<'a> ActualCostBuilder<'a> { self.tx_context.block_context.block_info.use_kzg_da } + /// Returns an estimation of the L1 gas amount that will be used (by Starknet's state update and + /// the Verifier) following the addition of a transaction with the given parameters to a batch; + /// e.g., a message from L2 to L1 is followed by a storage write operation in Starknet L1 + /// contract which requires gas. + fn calculate_tx_gas_usage_vector( + versioned_constants: &VersionedConstants, + call_infos: impl Iterator + Clone, + state_changes_count: StateChangesCount, + l1_handler_payload_size: Option, + use_kzg_da: bool, + ) -> TransactionExecutionResult { + // TODO(barak, 18/03/2024): Iterate over call_infos once without cloning. + Ok(get_messages_gas_cost(call_infos.clone(), l1_handler_payload_size)? + + get_da_gas_cost(state_changes_count, use_kzg_da) + + get_tx_events_gas_cost(call_infos, versioned_constants)) + } + // Construct the actual cost object using all fields that were set in the builder. fn calculate_actual_fee_and_resources( self, @@ -158,7 +180,8 @@ impl<'a> ActualCostBuilder<'a> { self.validate_call_info.into_iter().chain(self.execute_call_info); // Gas usage for SHARP costs and Starknet L1-L2 messages. Includes gas usage for data // availability. - let gas_usage_vector = calculate_tx_gas_usage_vector( + let gas_usage_vector = Self::calculate_tx_gas_usage_vector( + &self.tx_context.block_context.versioned_constants, non_optional_call_infos, state_changes_count, self.l1_payload_size, diff --git a/crates/blockifier/src/fee/actual_cost_test.rs b/crates/blockifier/src/fee/actual_cost_test.rs new file mode 100644 index 0000000000..c7e65d927f --- /dev/null +++ b/crates/blockifier/src/fee/actual_cost_test.rs @@ -0,0 +1,344 @@ +use rstest::{fixture, rstest}; +use starknet_api::hash::StarkFelt; +use starknet_api::stark_felt; +use starknet_api::transaction::L2ToL1Payload; + +use crate::execution::call_info::{CallExecution, CallInfo, MessageToL1, OrderedL2ToL1Message}; +use crate::fee::actual_cost::ActualCostBuilder; +use crate::fee::eth_gas_constants; +use crate::fee::gas_usage::{ + get_consumed_message_to_l2_emissions_cost, get_da_gas_cost, + get_log_message_to_l1_emissions_cost, get_message_segment_length, +}; +use crate::state::cached_state::StateChangesCount; +use crate::transaction::objects::GasVector; +use crate::utils::{u128_from_usize, usize_from_u128}; +use crate::versioned_constants::VersionedConstants; + +#[fixture] +fn versioned_constants() -> &'static VersionedConstants { + VersionedConstants::latest_constants() +} + +/// This test goes over six cases. In each case, we calculate the gas usage given the parameters. +/// We then perform the same calculation manually, each time using only the relevant parameters. +/// The six cases are: +/// 1. An empty transaction. +/// 2. A DeployAccount transaction. +/// 3. An L1 handler. +/// 4. A transaction with L2-to-L1 messages. +/// 5. A transaction that modifies the storage. +/// 6. A combination of cases 3. 4. and 5. +// TODO(Aner, 29/01/24) Refactor with assert on GasVector objects. +// TODO(Aner, 29/01/24) Refactor to replace match with if when formatting is nicer +#[rstest] +fn test_calculate_tx_gas_usage_basic( + #[values(false, true)] use_kzg_da: bool, + versioned_constants: &VersionedConstants, +) { + // An empty transaction (a theoretical case for sanity check). + // let versioned_constants = VersionedConstants::default(); + let empty_tx_gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + StateChangesCount::default(), + None, + use_kzg_da, + ) + .unwrap(); + assert_eq!(empty_tx_gas_usage_vector, GasVector { l1_gas: 0, l1_data_gas: 0 }); + + // DeployAccount. + + let deploy_account_state_changes_count = StateChangesCount { + n_storage_updates: 0, + n_class_hash_updates: 1, + n_compiled_class_hash_updates: 0, + n_modified_contracts: 1, + }; + + // Manual calculation. + let manual_starknet_gas_usage = 0; + let manual_gas_vector = GasVector { l1_gas: manual_starknet_gas_usage, ..Default::default() } + + get_da_gas_cost(deploy_account_state_changes_count, use_kzg_da); + + let deploy_account_gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + deploy_account_state_changes_count, + None, + use_kzg_da, + ) + .unwrap(); + assert_eq!(manual_gas_vector, deploy_account_gas_usage_vector); + + // L1 handler. + + let l1_handler_payload_size = 4; + let l1_handler_gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + StateChangesCount::default(), + Some(l1_handler_payload_size), + use_kzg_da, + ) + .unwrap(); + + // Manual calculation. + let message_segment_length = get_message_segment_length(&[], Some(l1_handler_payload_size)); + let manual_starknet_gas_usage = message_segment_length * eth_gas_constants::GAS_PER_MEMORY_WORD + + eth_gas_constants::GAS_PER_COUNTER_DECREASE + + usize_from_u128( + get_consumed_message_to_l2_emissions_cost(Some(l1_handler_payload_size)).l1_gas, + ) + .unwrap(); + let manual_sharp_gas_usage = + message_segment_length * eth_gas_constants::SHARP_GAS_PER_MEMORY_WORD; + let manual_gas_computation = GasVector { + l1_gas: u128_from_usize(manual_starknet_gas_usage + manual_sharp_gas_usage).unwrap(), + l1_data_gas: 0, + }; + + assert_eq!(l1_handler_gas_usage_vector, manual_gas_computation); + + // Any transaction with L2-to-L1 messages. + + let mut call_infos = Vec::new(); + + for i in 0..4 { + let payload_vec = vec![stark_felt!(0_u16); i]; + + let call_info = CallInfo { + execution: CallExecution { + l2_to_l1_messages: vec![OrderedL2ToL1Message { + message: MessageToL1 { + payload: L2ToL1Payload(payload_vec), + ..Default::default() + }, + ..Default::default() + }], + ..Default::default() + }, + ..Default::default() + }; + + call_infos.push(call_info); + } + + // l2_to_l1_payload_lengths is [0, 1, 2, 3] + let call_infos_iter = call_infos.iter(); + let l2_to_l1_payload_lengths: Vec = call_infos_iter + .clone() + .flat_map(|call_info| call_info.get_sorted_l2_to_l1_payload_lengths().unwrap()) + .collect(); + + let l2_to_l1_state_changes_count = StateChangesCount { + n_storage_updates: 0, + n_class_hash_updates: 0, + n_compiled_class_hash_updates: 0, + n_modified_contracts: 1, + }; + let l2_to_l1_messages_gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + call_infos_iter.clone(), + l2_to_l1_state_changes_count, + None, + use_kzg_da, + ) + .unwrap(); + + // Manual calculation. + let message_segment_length = get_message_segment_length(&l2_to_l1_payload_lengths, None); + let n_l2_to_l1_messages = l2_to_l1_payload_lengths.len(); + let manual_starknet_gas_usage = message_segment_length * eth_gas_constants::GAS_PER_MEMORY_WORD + + n_l2_to_l1_messages * eth_gas_constants::GAS_PER_ZERO_TO_NONZERO_STORAGE_SET + + usize_from_u128(get_log_message_to_l1_emissions_cost(&l2_to_l1_payload_lengths).l1_gas) + .unwrap(); + let manual_sharp_gas_usage = message_segment_length + * eth_gas_constants::SHARP_GAS_PER_MEMORY_WORD + + usize_from_u128(get_da_gas_cost(l2_to_l1_state_changes_count, use_kzg_da).l1_gas) + .unwrap(); + let manual_sharp_blob_gas_usage = + get_da_gas_cost(l2_to_l1_state_changes_count, use_kzg_da).l1_data_gas; + let manual_gas_computation = GasVector { + l1_gas: u128_from_usize(manual_starknet_gas_usage + manual_sharp_gas_usage).unwrap(), + l1_data_gas: manual_sharp_blob_gas_usage, + }; + + assert_eq!(l2_to_l1_messages_gas_usage_vector, manual_gas_computation); + + // Any calculation with storage writings. + + let n_modified_contracts = 7; + let n_storage_updates = 11; + let storage_writes_state_changes_count = StateChangesCount { + n_storage_updates, + n_class_hash_updates: 0, + n_compiled_class_hash_updates: 0, + n_modified_contracts, + }; + let storage_writings_gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + storage_writes_state_changes_count, + None, + use_kzg_da, + ) + .unwrap(); + + // Manual calculation. + let manual_gas_computation = get_da_gas_cost(storage_writes_state_changes_count, use_kzg_da); + + assert_eq!(manual_gas_computation, storage_writings_gas_usage_vector); + + // Combined case of an L1 handler, L2-to-L1 messages and storage writes. + let combined_state_changes_count = StateChangesCount { + n_storage_updates: storage_writes_state_changes_count.n_storage_updates, + n_class_hash_updates: 0, + n_compiled_class_hash_updates: 0, + n_modified_contracts: storage_writes_state_changes_count.n_modified_contracts + + l2_to_l1_state_changes_count.n_modified_contracts, + }; + let gas_usage_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + call_infos_iter, + combined_state_changes_count, + Some(l1_handler_payload_size), + use_kzg_da, + ) + .unwrap(); + + // Manual calculation. + let fee_balance_discount = match use_kzg_da { + true => 0, + false => { + eth_gas_constants::GAS_PER_MEMORY_WORD - eth_gas_constants::get_calldata_word_cost(12) + } + }; + + let expected_gas_vector = GasVector { + l1_gas: l1_handler_gas_usage_vector.l1_gas + + l2_to_l1_messages_gas_usage_vector.l1_gas + + storage_writings_gas_usage_vector.l1_gas + // l2_to_l1_messages_gas_usage and storage_writings_gas_usage got a discount each, while + // the combined calculation got it once. + + u128_from_usize(fee_balance_discount).unwrap(), + // Expected blob gas usage is from data availability only. + l1_data_gas: get_da_gas_cost(combined_state_changes_count, use_kzg_da).l1_data_gas, + }; + + assert_eq!(expected_gas_vector, gas_usage_vector); +} + +use starknet_api::core::Nonce; +use starknet_api::transaction::{Fee, TransactionVersion}; + +use crate::context::BlockContext; +use crate::invoke_tx_args; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::initial_test_state::test_state; +use crate::test_utils::{create_calldata, default_invoke_tx_args, CairoVersion, BALANCE, MAX_FEE}; +use crate::transaction::constants; +use crate::transaction::objects::HasRelatedFeeType; +use crate::transaction::test_utils::account_invoke_tx; +use crate::transaction::transactions::ExecutableTransaction; + +// Test that we exclude the fee token contract modification and adds the account’s balance change +// in the state changes. +// TODO(Aner, 21/01/24) modify for 4844 (taking blob_gas into account). +#[rstest] +fn test_calculate_tx_gas_usage(#[values(false, true)] use_kzg_da: bool) { + let account_cairo_version = CairoVersion::Cairo0; + let test_contract_cairo_version = CairoVersion::Cairo0; + let block_context = &BlockContext::create_for_account_testing_with_kzg(use_kzg_da); + let versioned_constants = &block_context.versioned_constants; + let chain_info = &block_context.chain_info; + let account_contract = FeatureContract::AccountWithoutValidations(account_cairo_version); + let test_contract = FeatureContract::TestContract(test_contract_cairo_version); + let account_contract_address = account_contract.get_instance_address(0); + let state = &mut test_state(chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); + + let account_tx = account_invoke_tx(default_invoke_tx_args( + account_contract_address, + test_contract.get_instance_address(0), + )); + let fee_token_address = chain_info.fee_token_address(&account_tx.fee_type()); + let tx_execution_info = account_tx.execute(state, block_context, true, true).unwrap(); + + let n_storage_updates = 1; // For the account balance update. + let n_modified_contracts = 1; + let state_changes_count = StateChangesCount { + n_storage_updates, + n_class_hash_updates: 0, + n_modified_contracts, + n_compiled_class_hash_updates: 0, + }; + + let gas_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + state_changes_count, + None, + use_kzg_da, + ) + .unwrap(); + let GasVector { l1_gas: l1_gas_usage, l1_data_gas: l1_blob_gas_usage } = gas_vector; + assert_eq!( + u128_from_usize(tx_execution_info.actual_resources.gas_usage()).unwrap(), + l1_gas_usage + ); + assert_eq!( + u128_from_usize(tx_execution_info.actual_resources.blob_gas_usage()).unwrap(), + l1_blob_gas_usage + ); + + // A tx that changes the account and some other balance in execute. + let some_other_account_address = account_contract.get_instance_address(17); + let execute_calldata = create_calldata( + fee_token_address, + constants::TRANSFER_ENTRY_POINT_NAME, + &[ + *some_other_account_address.0.key(), // Calldata: recipient. + stark_felt!(2_u8), // Calldata: lsb amount. + stark_felt!(0_u8), // Calldata: msb amount. + ], + ); + + let account_tx = account_invoke_tx(invoke_tx_args! { + max_fee: Fee(MAX_FEE), + sender_address: account_contract_address, + calldata: execute_calldata, + version: TransactionVersion::ONE, + nonce: Nonce(stark_felt!(1_u8)), + }); + + let tx_execution_info = account_tx.execute(state, block_context, true, true).unwrap(); + // For the balance update of the sender and the recipient. + let n_storage_updates = 2; + // Only the account contract modification (nonce update) excluding the fee token contract. + let n_modified_contracts = 1; + let state_changes_count = StateChangesCount { + n_storage_updates, + n_class_hash_updates: 0, + n_modified_contracts, + n_compiled_class_hash_updates: 0, + }; + + let gas_vector = ActualCostBuilder::calculate_tx_gas_usage_vector( + versioned_constants, + std::iter::empty(), + state_changes_count, + None, + use_kzg_da, + ) + .unwrap(); + let GasVector { l1_gas: l1_gas_usage, l1_data_gas: l1_blob_gas_usage } = gas_vector; + assert_eq!( + u128_from_usize(tx_execution_info.actual_resources.gas_usage()).unwrap(), + l1_gas_usage + ); + assert_eq!( + u128_from_usize(tx_execution_info.actual_resources.blob_gas_usage()).unwrap(), + l1_blob_gas_usage + ); +} diff --git a/crates/blockifier/src/fee/gas_usage.rs b/crates/blockifier/src/fee/gas_usage.rs index a5fc8e7f53..22165e1601 100644 --- a/crates/blockifier/src/fee/gas_usage.rs +++ b/crates/blockifier/src/fee/gas_usage.rs @@ -2,38 +2,54 @@ use std::collections::HashMap; use crate::abi::constants; use crate::context::{BlockContext, TransactionContext}; -use crate::execution::call_info::{CallInfo, MessageL1CostInfo}; +use crate::execution::call_info::{CallInfo, MessageL1CostInfo, OrderedEvent}; use crate::fee::eth_gas_constants; use crate::fee::fee_utils::calculate_tx_gas_vector; use crate::state::cached_state::StateChangesCount; use crate::transaction::account_transaction::AccountTransaction; use crate::transaction::objects::{ - GasVector, HasRelatedFeeType, ResourcesMapping, TransactionExecutionResult, + GasVector, HasRelatedFeeType, MilligasVector, ResourcesMapping, TransactionExecutionResult, TransactionPreValidationResult, }; use crate::utils::{u128_from_usize, usize_from_u128}; +use crate::versioned_constants::VersionedConstants; #[cfg(test)] #[path = "gas_usage_test.rs"] pub mod test; -/// Returns an estimation of the L1 gas amount that will be used (by Starknet's state update and -/// the Verifier) following the addition of a transaction with the given parameters to a batch; -/// e.g., a message from L2 to L1 is followed by a storage write operation in Starknet L1 contract -/// which requires gas. -pub fn calculate_tx_gas_usage_vector<'a>( - call_infos: impl Iterator, - state_changes_count: StateChangesCount, - l1_handler_payload_size: Option, - use_kzg_da: bool, -) -> TransactionExecutionResult { - Ok(calculate_messages_gas_vector(call_infos, l1_handler_payload_size)? - + get_da_gas_cost(state_changes_count, use_kzg_da)) +pub fn get_tx_events_gas_cost<'a>( + call_infos: impl Iterator + Clone, + versioned_constants: &VersionedConstants, +) -> GasVector { + let l1_milligas: u128 = call_infos + .map(|call_info| get_events_milligas_cost(&call_info.execution.events, versioned_constants)) + .sum(); + GasVector { l1_gas: l1_milligas / 1000_u128, l1_data_gas: 0_u128 } +} + +pub fn get_events_milligas_cost( + events: &[OrderedEvent], + versioned_constants: &VersionedConstants, +) -> u128 { + let (key_factor, data_word_factor) = versioned_constants.get_event_milligas_cost(); + let safe_u128_from_usize = + |x| u128_from_usize(x).expect("Could not convert starknet gas usage from usize to u128."); + events + .iter() + .map(|OrderedEvent { event, .. }| { + // TODO(barak: 18/03/2024): Once we start charging per byte change to num_bytes_keys and + // num_bytes_data. + let num_keys = safe_u128_from_usize(event.keys.len()); + let num_data_words = safe_u128_from_usize(event.data.0.len()); + key_factor * num_keys + data_word_factor * num_data_words + }) + .sum() } /// Returns an estimation of the gas usage for processing L1<>L2 messages on L1. Accounts for both /// Starknet and SHARP contracts. -pub fn calculate_messages_gas_vector<'a>( +pub fn get_messages_gas_cost<'a>( call_infos: impl Iterator, l1_handler_payload_size: Option, ) -> TransactionExecutionResult { diff --git a/crates/blockifier/src/fee/gas_usage_test.rs b/crates/blockifier/src/fee/gas_usage_test.rs index 478b6cb3b8..5b67842f9e 100644 --- a/crates/blockifier/src/fee/gas_usage_test.rs +++ b/crates/blockifier/src/fee/gas_usage_test.rs @@ -1,18 +1,108 @@ use pretty_assertions::assert_eq; -use rstest::rstest; +use rstest::{fixture, rstest}; use starknet_api::hash::StarkFelt; -use starknet_api::stark_felt; -use starknet_api::transaction::L2ToL1Payload; +use starknet_api::transaction::{EventContent, EventData, EventKey}; -use crate::execution::call_info::{CallExecution, CallInfo, MessageToL1, OrderedL2ToL1Message}; +use crate::execution::call_info::{CallExecution, CallInfo, OrderedEvent}; use crate::fee::eth_gas_constants; -use crate::fee::gas_usage::{ - calculate_tx_gas_usage_vector, get_consumed_message_to_l2_emissions_cost, get_da_gas_cost, - get_log_message_to_l1_emissions_cost, get_message_segment_length, -}; +use crate::fee::gas_usage::{get_da_gas_cost, get_tx_events_gas_cost}; use crate::state::cached_state::StateChangesCount; use crate::transaction::objects::GasVector; -use crate::utils::{u128_from_usize, usize_from_u128}; +use crate::utils::u128_from_usize; +use crate::versioned_constants::VersionedConstants; + +#[fixture] +fn versioned_constants() -> &'static VersionedConstants { + VersionedConstants::latest_constants() +} + +#[rstest] +fn test_get_event_gas_cost(versioned_constants: &VersionedConstants) { + let (key_factor, data_word_factor) = versioned_constants.get_event_milligas_cost(); + + let call_infos = vec![CallInfo::default(), CallInfo::default(), CallInfo::default()]; + assert_eq!( + GasVector::default(), + get_tx_events_gas_cost(call_infos.iter(), versioned_constants) + ); + + let call_infos = vec![ + CallInfo { + execution: CallExecution { + events: vec![ + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![EventKey(StarkFelt::ZERO)], + data: EventData(vec![StarkFelt::ZERO, StarkFelt::ONE]), + }, + }, + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![EventKey(StarkFelt::ZERO)], + data: EventData(vec![StarkFelt::ZERO, StarkFelt::ONE]), + }, + }, + ], + ..Default::default() + }, + ..Default::default() + }, + CallInfo { + execution: CallExecution { + events: vec![ + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![EventKey(StarkFelt::ZERO)], + data: EventData(vec![]), + }, + }, + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![], + data: EventData(vec![StarkFelt::ZERO]), + }, + }, + ], + ..Default::default() + }, + ..Default::default() + }, + CallInfo { + execution: CallExecution { + events: vec![ + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![], + data: EventData(vec![StarkFelt::ZERO]), + }, + }, + OrderedEvent { + order: 0, + event: EventContent { + keys: vec![EventKey(StarkFelt::ZERO)], + data: EventData(vec![]), + }, + }, + ], + ..Default::default() + }, + ..Default::default() + }, + ]; + let expected = GasVector { + // 4 keys and 6 data words overall. + l1_gas: (key_factor * 4_u128 + data_word_factor * 6_u128) / 1000, + l1_data_gas: 0_u128, + }; + let gas_vector = get_tx_events_gas_cost(call_infos.iter(), versioned_constants); + assert_eq!(expected, gas_vector); + assert_ne!(GasVector::default(), gas_vector) +} #[rstest] #[case::storage_write(StateChangesCount { @@ -59,206 +149,6 @@ fn test_get_da_gas_cost_basic(#[case] state_changes_count: StateChangesCount) { ); } -/// This test goes over six cases. In each case, we calculate the gas usage given the parameters. -/// We then perform the same calculation manually, each time using only the relevant parameters. -/// The six cases are: -/// 1. An empty transaction. -/// 2. A DeployAccount transaction. -/// 3. An L1 handler. -/// 4. A transaction with L2-to-L1 messages. -/// 5. A transaction that modifies the storage. -/// 6. A combination of cases 3. 4. and 5. -// TODO(Aner, 29/01/24) Refactor with assert on GasVector objects. -// TODO(Aner, 29/01/24) Refactor to replace match with if when formatting is nicer -#[rstest] -fn test_calculate_tx_gas_usage_basic(#[values(false, true)] use_kzg_da: bool) { - // An empty transaction (a theoretical case for sanity check). - let empty_tx_gas_usage_vector = calculate_tx_gas_usage_vector( - std::iter::empty(), - StateChangesCount::default(), - None, - use_kzg_da, - ) - .unwrap(); - assert_eq!(empty_tx_gas_usage_vector, GasVector { l1_gas: 0, l1_data_gas: 0 }); - - // DeployAccount. - - let deploy_account_state_changes_count = StateChangesCount { - n_storage_updates: 0, - n_class_hash_updates: 1, - n_compiled_class_hash_updates: 0, - n_modified_contracts: 1, - }; - - // Manual calculation. - let manual_starknet_gas_usage = 0; - let manual_gas_vector = GasVector { l1_gas: manual_starknet_gas_usage, ..Default::default() } - + get_da_gas_cost(deploy_account_state_changes_count, use_kzg_da); - - let deploy_account_gas_usage_vector = calculate_tx_gas_usage_vector( - std::iter::empty(), - deploy_account_state_changes_count, - None, - use_kzg_da, - ) - .unwrap(); - assert_eq!(manual_gas_vector, deploy_account_gas_usage_vector); - - // L1 handler. - - let l1_handler_payload_size = 4; - let l1_handler_gas_usage_vector = calculate_tx_gas_usage_vector( - std::iter::empty(), - StateChangesCount::default(), - Some(l1_handler_payload_size), - use_kzg_da, - ) - .unwrap(); - - // Manual calculation. - let message_segment_length = get_message_segment_length(&[], Some(l1_handler_payload_size)); - let manual_starknet_gas_usage = message_segment_length * eth_gas_constants::GAS_PER_MEMORY_WORD - + eth_gas_constants::GAS_PER_COUNTER_DECREASE - + usize_from_u128( - get_consumed_message_to_l2_emissions_cost(Some(l1_handler_payload_size)).l1_gas, - ) - .unwrap(); - let manual_sharp_gas_usage = - message_segment_length * eth_gas_constants::SHARP_GAS_PER_MEMORY_WORD; - let manual_gas_computation = GasVector { - l1_gas: u128_from_usize(manual_starknet_gas_usage + manual_sharp_gas_usage).unwrap(), - l1_data_gas: 0, - }; - - assert_eq!(l1_handler_gas_usage_vector, manual_gas_computation); - - // Any transaction with L2-to-L1 messages. - - let mut call_infos = Vec::new(); - - for i in 0..4 { - let payload_vec = vec![stark_felt!(0_u16); i]; - - let call_info = CallInfo { - execution: CallExecution { - l2_to_l1_messages: vec![OrderedL2ToL1Message { - message: MessageToL1 { - payload: L2ToL1Payload(payload_vec), - ..Default::default() - }, - ..Default::default() - }], - ..Default::default() - }, - ..Default::default() - }; - - call_infos.push(call_info); - } - - // l2_to_l1_payload_lengths is [0, 1, 2, 3] - let call_infos_iter = call_infos.iter(); - let l2_to_l1_payload_lengths: Vec = call_infos_iter - .clone() - .flat_map(|call_info| call_info.get_sorted_l2_to_l1_payload_lengths().unwrap()) - .collect(); - - let l2_to_l1_state_changes_count = StateChangesCount { - n_storage_updates: 0, - n_class_hash_updates: 0, - n_compiled_class_hash_updates: 0, - n_modified_contracts: 1, - }; - let l2_to_l1_messages_gas_usage_vector = calculate_tx_gas_usage_vector( - call_infos_iter.clone(), - l2_to_l1_state_changes_count, - None, - use_kzg_da, - ) - .unwrap(); - - // Manual calculation. - let message_segment_length = get_message_segment_length(&l2_to_l1_payload_lengths, None); - let n_l2_to_l1_messages = l2_to_l1_payload_lengths.len(); - let manual_starknet_gas_usage = message_segment_length * eth_gas_constants::GAS_PER_MEMORY_WORD - + n_l2_to_l1_messages * eth_gas_constants::GAS_PER_ZERO_TO_NONZERO_STORAGE_SET - + usize_from_u128(get_log_message_to_l1_emissions_cost(&l2_to_l1_payload_lengths).l1_gas) - .unwrap(); - let manual_sharp_gas_usage = message_segment_length - * eth_gas_constants::SHARP_GAS_PER_MEMORY_WORD - + usize_from_u128(get_da_gas_cost(l2_to_l1_state_changes_count, use_kzg_da).l1_gas) - .unwrap(); - let manual_sharp_blob_gas_usage = - get_da_gas_cost(l2_to_l1_state_changes_count, use_kzg_da).l1_data_gas; - let manual_gas_computation = GasVector { - l1_gas: u128_from_usize(manual_starknet_gas_usage + manual_sharp_gas_usage).unwrap(), - l1_data_gas: manual_sharp_blob_gas_usage, - }; - - assert_eq!(l2_to_l1_messages_gas_usage_vector, manual_gas_computation); - - // Any calculation with storage writings. - - let n_modified_contracts = 7; - let n_storage_updates = 11; - let storage_writes_state_changes_count = StateChangesCount { - n_storage_updates, - n_class_hash_updates: 0, - n_compiled_class_hash_updates: 0, - n_modified_contracts, - }; - let storage_writings_gas_usage_vector = calculate_tx_gas_usage_vector( - std::iter::empty(), - storage_writes_state_changes_count, - None, - use_kzg_da, - ) - .unwrap(); - - // Manual calculation. - let manual_gas_computation = get_da_gas_cost(storage_writes_state_changes_count, use_kzg_da); - - assert_eq!(manual_gas_computation, storage_writings_gas_usage_vector); - - // Combined case of an L1 handler, L2-to-L1 messages and storage writes. - let combined_state_changes_count = StateChangesCount { - n_storage_updates: storage_writes_state_changes_count.n_storage_updates, - n_class_hash_updates: 0, - n_compiled_class_hash_updates: 0, - n_modified_contracts: storage_writes_state_changes_count.n_modified_contracts - + l2_to_l1_state_changes_count.n_modified_contracts, - }; - let gas_usage_vector = calculate_tx_gas_usage_vector( - call_infos_iter, - combined_state_changes_count, - Some(l1_handler_payload_size), - use_kzg_da, - ) - .unwrap(); - - // Manual calculation. - let fee_balance_discount = match use_kzg_da { - true => 0, - false => { - eth_gas_constants::GAS_PER_MEMORY_WORD - eth_gas_constants::get_calldata_word_cost(12) - } - }; - - let expected_gas_vector = GasVector { - l1_gas: l1_handler_gas_usage_vector.l1_gas - + l2_to_l1_messages_gas_usage_vector.l1_gas - + storage_writings_gas_usage_vector.l1_gas - // l2_to_l1_messages_gas_usage and storage_writings_gas_usage got a discount each, while - // the combined calculation got it once. - + u128_from_usize(fee_balance_discount).unwrap(), - // Expected blob gas usage is from data availability only. - l1_data_gas: get_da_gas_cost(combined_state_changes_count, use_kzg_da).l1_data_gas, - }; - - assert_eq!(expected_gas_vector, gas_usage_vector); -} - #[test] fn test_onchain_data_discount() { let use_kzg_da = false; diff --git a/crates/blockifier/src/test_utils.rs b/crates/blockifier/src/test_utils.rs index fd5f7aea1d..d46b950bc3 100644 --- a/crates/blockifier/src/test_utils.rs +++ b/crates/blockifier/src/test_utils.rs @@ -21,7 +21,8 @@ use starknet_api::deprecated_contract_class::{ use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::state::StorageKey; use starknet_api::transaction::{ - Calldata, ContractAddressSalt, Resource, ResourceBounds, ResourceBoundsMapping, + Calldata, ContractAddressSalt, Fee, Resource, ResourceBounds, ResourceBoundsMapping, + TransactionSignature, }; use starknet_api::{calldata, contract_address, patricia_key, stark_felt}; @@ -29,6 +30,8 @@ use crate::abi::abi_utils::{get_fee_token_var_address, selector_from_name}; use crate::execution::contract_class::{ContractClass, ContractClassV0}; use crate::execution::entry_point::{CallEntryPoint, CallType}; use crate::execution::execution_utils::felt_to_stark_felt; +use crate::invoke_tx_args; +use crate::test_utils::invoke::InvokeTxArgs; use crate::utils::const_max; use crate::versioned_constants::VersionedConstants; @@ -371,3 +374,22 @@ pub fn create_calldata( Calldata(calldata.into()) } + +pub fn default_invoke_tx_args( + account_contract_address: ContractAddress, + test_contract_address: ContractAddress, +) -> InvokeTxArgs { + let execute_calldata = create_calldata( + test_contract_address, + "return_result", + &[stark_felt!(2_u8)], // Calldata: num. + ); + + invoke_tx_args! { + max_fee: Fee(MAX_FEE), + signature: TransactionSignature::default(), + nonce: Nonce::default(), + sender_address: account_contract_address, + calldata: execute_calldata, + } +} diff --git a/crates/blockifier/src/transaction/objects.rs b/crates/blockifier/src/transaction/objects.rs index 12f119a76d..e971c592d4 100644 --- a/crates/blockifier/src/transaction/objects.rs +++ b/crates/blockifier/src/transaction/objects.rs @@ -124,7 +124,31 @@ pub struct DeprecatedTransactionInfo { pub max_fee: Fee, } -#[derive(derive_more::Add, derive_more::Sum, Clone, Debug, Default, Eq, PartialEq, Serialize)] +#[derive(derive_more::Add, derive_more::Div, derive_more::Sum, Copy, Clone, Default)] +pub struct MilligasVector { + pub l1_gas: u128, + pub l1_data_gas: u128, +} + +impl From for GasVector { + fn from(milligas_vector: MilligasVector) -> Self { + let gas_vector = milligas_vector / 1000; + Self { l1_gas: gas_vector.l1_gas, l1_data_gas: gas_vector.l1_data_gas } + } +} + +#[derive( + derive_more::Add, + derive_more::Div, + derive_more::Sum, + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + Serialize, +)] pub struct GasVector { pub l1_gas: u128, pub l1_data_gas: u128, diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index 91e0ac0fa0..78ecd4b3f3 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -34,9 +34,7 @@ use crate::execution::entry_point::{CallEntryPoint, CallType}; use crate::execution::errors::EntryPointExecutionError; use crate::execution::execution_utils::{felt_to_stark_felt, stark_felt_to_felt}; use crate::fee::fee_utils::calculate_tx_fee; -use crate::fee::gas_usage::{ - calculate_tx_gas_usage_vector, estimate_minimal_gas_vector, get_da_gas_cost, -}; +use crate::fee::gas_usage::{estimate_minimal_gas_vector, get_da_gas_cost}; use crate::state::cached_state::{CachedState, StateChangesCount}; use crate::state::errors::StateError; use crate::state::state_api::{State, StateReader}; @@ -46,13 +44,13 @@ use crate::test_utils::declare::declare_tx; use crate::test_utils::deploy_account::deploy_account_tx; use crate::test_utils::dict_state_reader::DictStateReader; use crate::test_utils::initial_test_state::test_state; -use crate::test_utils::invoke::{invoke_tx, InvokeTxArgs}; +use crate::test_utils::invoke::invoke_tx; use crate::test_utils::prices::Prices; use crate::test_utils::{ - create_calldata, test_erc20_sequencer_balance_key, CairoVersion, NonceManager, SaltManager, - BALANCE, CHAIN_ID_NAME, CURRENT_BLOCK_NUMBER, CURRENT_BLOCK_TIMESTAMP, MAX_FEE, - MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE, TEST_CLASS_HASH, TEST_CONTRACT_ADDRESS, - TEST_SEQUENCER_ADDRESS, + create_calldata, default_invoke_tx_args, test_erc20_sequencer_balance_key, CairoVersion, + NonceManager, SaltManager, BALANCE, CHAIN_ID_NAME, CURRENT_BLOCK_NUMBER, + CURRENT_BLOCK_TIMESTAMP, MAX_FEE, MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE, TEST_CLASS_HASH, + TEST_CONTRACT_ADDRESS, TEST_SEQUENCER_ADDRESS, }; use crate::transaction::account_transaction::AccountTransaction; use crate::transaction::constants; @@ -69,7 +67,7 @@ use crate::transaction::test_utils::{ }; use crate::transaction::transaction_types::TransactionType; use crate::transaction::transactions::{ExecutableTransaction, L1HandlerTransaction}; -use crate::utils::{u128_from_usize, usize_from_u128}; +use crate::utils::usize_from_u128; use crate::versioned_constants::VersionedConstants; use crate::{ check_transaction_execution_error_for_custom_hint, @@ -284,25 +282,6 @@ fn validate_final_balances( } } -fn default_invoke_tx_args( - account_contract_address: ContractAddress, - test_contract_address: ContractAddress, -) -> InvokeTxArgs { - let execute_calldata = create_calldata( - test_contract_address, - "return_result", - &[stark_felt!(2_u8)], // Calldata: num. - ); - - invoke_tx_args! { - max_fee: Fee(MAX_FEE), - signature: TransactionSignature::default(), - nonce: Nonce::default(), - sender_address: account_contract_address, - calldata: execute_calldata, - } -} - #[rstest] #[case::with_cairo0_account( ExpectedResultTestInvokeTx{ @@ -432,7 +411,7 @@ fn test_invoke_tx( execute_call_info: expected_execute_call_info, fee_transfer_call_info: expected_fee_transfer_call_info, actual_fee: expected_actual_fee, - da_gas: da_gas.clone(), + da_gas, actual_resources: ResourcesMapping(HashMap::from([ ( abi_constants::BLOB_GAS_USAGE.to_string(), @@ -1131,7 +1110,7 @@ fn test_declare_tx( execute_call_info: None, fee_transfer_call_info: expected_fee_transfer_call_info, actual_fee: expected_actual_fee, - da_gas: da_gas.clone(), + da_gas, revert_error: None, actual_resources: ResourcesMapping(HashMap::from([ (abi_constants::L1_GAS_USAGE.to_string(), da_gas.l1_gas.try_into().unwrap()), @@ -1268,7 +1247,7 @@ fn test_deploy_account_tx( execute_call_info: expected_execute_call_info, fee_transfer_call_info: expected_fee_transfer_call_info, actual_fee: expected_actual_fee, - da_gas: da_gas.clone(), + da_gas, revert_error: None, actual_resources: ResourcesMapping(HashMap::from([ (abi_constants::L1_GAS_USAGE.to_string(), usize_from_u128(da_gas.l1_gas).unwrap()), @@ -1462,95 +1441,6 @@ fn test_validate_accounts_tx( } } -// Test that we exclude the fee token contract modification and adds the account’s balance change -// in the state changes. -// TODO(Aner, 21/01/24) modify for 4844 (taking blob_gas into account). -#[rstest] -fn test_calculate_tx_gas_usage(#[values(false, true)] use_kzg_da: bool) { - let account_cairo_version = CairoVersion::Cairo0; - let test_contract_cairo_version = CairoVersion::Cairo0; - let block_context = &BlockContext::create_for_account_testing_with_kzg(use_kzg_da); - let chain_info = &block_context.chain_info; - let account_contract = FeatureContract::AccountWithoutValidations(account_cairo_version); - let test_contract = FeatureContract::TestContract(test_contract_cairo_version); - let account_contract_address = account_contract.get_instance_address(0); - let state = &mut test_state(chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); - - let account_tx = account_invoke_tx(default_invoke_tx_args( - account_contract_address, - test_contract.get_instance_address(0), - )); - let fee_token_address = chain_info.fee_token_address(&account_tx.fee_type()); - let tx_execution_info = account_tx.execute(state, block_context, true, true).unwrap(); - - let n_storage_updates = 1; // For the account balance update. - let n_modified_contracts = 1; - let state_changes_count = StateChangesCount { - n_storage_updates, - n_class_hash_updates: 0, - n_modified_contracts, - n_compiled_class_hash_updates: 0, - }; - - let gas_vector = - calculate_tx_gas_usage_vector(std::iter::empty(), state_changes_count, None, use_kzg_da) - .unwrap(); - let GasVector { l1_gas: l1_gas_usage, l1_data_gas: l1_blob_gas_usage } = gas_vector; - assert_eq!( - u128_from_usize(tx_execution_info.actual_resources.gas_usage()).unwrap(), - l1_gas_usage - ); - assert_eq!( - u128_from_usize(tx_execution_info.actual_resources.blob_gas_usage()).unwrap(), - l1_blob_gas_usage - ); - - // A tx that changes the account and some other balance in execute. - let some_other_account_address = account_contract.get_instance_address(17); - let execute_calldata = create_calldata( - fee_token_address, - constants::TRANSFER_ENTRY_POINT_NAME, - &[ - *some_other_account_address.0.key(), // Calldata: recipient. - stark_felt!(2_u8), // Calldata: lsb amount. - stark_felt!(0_u8), // Calldata: msb amount. - ], - ); - - let account_tx = account_invoke_tx(invoke_tx_args! { - max_fee: Fee(MAX_FEE), - sender_address: account_contract_address, - calldata: execute_calldata, - version: TransactionVersion::ONE, - nonce: Nonce(stark_felt!(1_u8)), - }); - - let tx_execution_info = account_tx.execute(state, block_context, true, true).unwrap(); - // For the balance update of the sender and the recipient. - let n_storage_updates = 2; - // Only the account contract modification (nonce update) excluding the fee token contract. - let n_modified_contracts = 1; - let state_changes_count = StateChangesCount { - n_storage_updates, - n_class_hash_updates: 0, - n_modified_contracts, - n_compiled_class_hash_updates: 0, - }; - - let gas_vector = - calculate_tx_gas_usage_vector(std::iter::empty(), state_changes_count, None, use_kzg_da) - .unwrap(); - let GasVector { l1_gas: l1_gas_usage, l1_data_gas: l1_blob_gas_usage } = gas_vector; - assert_eq!( - u128_from_usize(tx_execution_info.actual_resources.gas_usage()).unwrap(), - l1_gas_usage - ); - assert_eq!( - u128_from_usize(tx_execution_info.actual_resources.blob_gas_usage()).unwrap(), - l1_blob_gas_usage - ); -} - #[rstest] fn test_valid_flag( #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] account_cairo_version: CairoVersion, diff --git a/crates/blockifier/src/versioned_constants.rs b/crates/blockifier/src/versioned_constants.rs index 7d8d5fb289..a5143ff290 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -51,6 +51,8 @@ pub struct VersionedConstants { // Note: if loaded from a json file, there are some assumptions made on its structure. // See the struct's docstring for more details. os_constants: Arc, + + event_gas_cost: Arc, } impl VersionedConstants { @@ -113,6 +115,12 @@ impl VersionedConstants { self.os_resources.get_additional_os_syscall_resources(syscall_counter) } + pub fn get_event_milligas_cost(&self) -> (u128, u128) { + // TODO(barak: 18/03/2024): Once we start charging per byte change to milligas_per_key_byte + // and milligas_per_data_byte. + (self.event_gas_cost.milligas_per_key, self.event_gas_cost.milligas_per_data_word) + } + #[cfg(any(feature = "testing", test))] pub fn create_for_account_testing() -> Self { let vm_resource_fee_cost = Arc::new(HashMap::from([ @@ -138,6 +146,14 @@ impl TryFrom<&Path> for VersionedConstants { } } +#[derive(Clone, Debug, Default, Deserialize)] +struct EventGasCost { + // TODO(barak, 18/03/2024): Once we start charging per byte change to milligas_per_data_byte + // and milligas_per_key_byte, divide the values by 32 in the JSON file. + pub milligas_per_data_word: u128, + pub milligas_per_key: u128, +} + #[derive(Clone, Debug, Default, Deserialize)] // Serde trick for adding validations via a customr deserializer, without forgoing the derive. // See: https://github.com/serde-rs/serde/issues/1220.