Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(fee): implement methods on resource bounds #703

Closed
wants to merge 1 commit into from
Closed
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
53 changes: 24 additions & 29 deletions crates/blockifier/src/fee/fee_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ use thiserror::Error;
use crate::context::TransactionContext;
use crate::fee::actual_cost::TransactionReceipt;
use crate::fee::fee_utils::{get_balance_and_if_covers_fee, get_fee_by_gas_vector};
use crate::fee::gas_usage::compute_discounted_gas_from_gas_vector;
use crate::state::state_api::StateReader;
use crate::transaction::errors::TransactionExecutionError;
use crate::transaction::objects::{
BoundsChecker,
FeeType,
GasVector,
HasRelatedFeeType,
TransactionExecutionResult,
TransactionInfo,
};
Expand All @@ -19,6 +19,12 @@ use crate::transaction::objects::{
pub enum FeeCheckError {
#[error("Insufficient max L1 gas: max amount: {max_amount}, actual used: {actual_amount}.")]
MaxL1GasAmountExceeded { max_amount: u128, actual_amount: u128 },
#[error("Insufficient max L2 gas: max amount: {max_amount}, actual used: {actual_amount}.")]
MaxL2GasAmountExceeded { max_amount: u128, actual_amount: u128 },
#[error(
"Insufficient max L1 data gas: max amount: {max_amount}, actual used: {actual_amount}."
)]
MaxL1DataGasAmountExceeded { max_amount: u128, actual_amount: u128 },
#[error("Insufficient max fee: max fee: {}, actual fee: {}.", max_fee.0, actual_fee.0)]
MaxFeeExceeded { max_fee: Fee, actual_fee: Fee },
#[error(
Expand Down Expand Up @@ -71,16 +77,17 @@ impl FeeCheckReport {
// If the error is resource overdraft, the recommended fee is the resource bounds.
// If the transaction passed pre-validation checks (i.e. balance initially covered the
// resource bounds), the sender should be able to pay this fee.
FeeCheckError::MaxFeeExceeded { .. } | FeeCheckError::MaxL1GasAmountExceeded { .. } => {
match &tx_context.tx_info {
TransactionInfo::Current(info) => get_fee_by_gas_vector(
&tx_context.block_context.block_info,
GasVector::from_l1_gas(info.l1_resource_bounds().max_amount.into()),
&FeeType::Strk,
),
TransactionInfo::Deprecated(context) => context.max_fee,
}
}
FeeCheckError::MaxFeeExceeded { .. }
| FeeCheckError::MaxL1GasAmountExceeded { .. }
| FeeCheckError::MaxL2GasAmountExceeded { .. }
| FeeCheckError::MaxL1DataGasAmountExceeded { .. } => match &tx_context.tx_info {
TransactionInfo::Current(info) => get_fee_by_gas_vector(
&tx_context.block_context.block_info,
(&info.resource_bounds).into(),
&FeeType::Strk,
),
TransactionInfo::Deprecated(context) => context.max_fee,
},
};
Self { recommended_fee, error: Some(error) }
}
Expand All @@ -98,23 +105,11 @@ impl FeeCheckReport {
// sender.
// TODO(Aner, 21/01/24) modify for 4844 (include check for blob_gas).
match tx_info {
TransactionInfo::Current(context) => {
// Check L1 gas limit.
let max_l1_gas = context.l1_resource_bounds().max_amount.into();

// TODO(Dori, 1/7/2024): When data gas limit is added (and enforced) in resource
// bounds, check it here as well (separately, with a different error variant if
// limit exceeded).
let total_discounted_gas_used =
compute_discounted_gas_from_gas_vector(gas, tx_context);

if total_discounted_gas_used > max_l1_gas {
return Err(FeeCheckError::MaxL1GasAmountExceeded {
max_amount: max_l1_gas,
actual_amount: total_discounted_gas_used,
})?;
}
}
TransactionInfo::Current(context) => context.resource_bounds.can_cover(
gas,
&tx_context.tx_info.fee_type(),
&tx_context.block_context.block_info.gas_prices,
)?,
TransactionInfo::Deprecated(context) => {
// Check max fee.
let max_fee = context.max_fee;
Expand Down
30 changes: 23 additions & 7 deletions crates/blockifier/src/fee/gas_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ use cairo_vm::vm::runners::cairo_runner::ExecutionResources;

use super::fee_utils::calculate_l1_gas_by_vm_usage;
use crate::abi::constants;
use crate::blockifier::block::GasPrices;
use crate::context::{BlockContext, TransactionContext};
use crate::fee::eth_gas_constants;
use crate::state::cached_state::StateChangesCount;
use crate::transaction::account_transaction::AccountTransaction;
use crate::transaction::objects::{GasVector, HasRelatedFeeType, TransactionPreValidationResult};
use crate::transaction::objects::{
FeeType,
GasVector,
HasRelatedFeeType,
TransactionPreValidationResult,
};
use crate::utils::{u128_div_ceil, u128_from_usize};

#[cfg(test)]
Expand Down Expand Up @@ -190,14 +196,24 @@ pub fn estimate_minimal_gas_vector(
/// X non-data-related gas consumption and Y bytes of data, in non-blob mode, would
/// cost (X + 16*Y) units of gas. Applying the discount ratio to the data-related
/// summand, we get total_gas = (X + Y * DGP / GP).
pub fn compute_discounted_gas_from_gas_vector(
pub fn compute_discounted_gas(
gas_usage_vector: &GasVector,
tx_context: &TransactionContext,
fee_type: &FeeType,
gas_prices: &GasPrices,
) -> u128 {
let gas_prices = &tx_context.block_context.block_info.gas_prices;
let GasVector { l1_gas: gas_usage, l1_data_gas: blob_gas_usage, .. } = gas_usage_vector;
let fee_type = tx_context.tx_info.fee_type();
let gas_price = gas_prices.get_l1_gas_price_by_fee_type(&fee_type);
let data_gas_price = gas_prices.get_l1_data_gas_price_by_fee_type(&fee_type);
let gas_price = gas_prices.get_l1_gas_price_by_fee_type(fee_type);
let data_gas_price = gas_prices.get_l1_data_gas_price_by_fee_type(fee_type);
gas_usage + u128_div_ceil(blob_gas_usage * u128::from(data_gas_price), gas_price)
}

pub fn compute_discounted_gas_from_tx_context(
gas_usage_vector: &GasVector,
tx_context: &TransactionContext,
) -> u128 {
compute_discounted_gas(
gas_usage_vector,
&tx_context.tx_info.fee_type(),
&tx_context.block_context.block_info.gas_prices,
)
}
6 changes: 3 additions & 3 deletions crates/blockifier/src/fee/gas_usage_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::execution::call_info::{CallExecution, CallInfo, OrderedEvent};
use crate::fee::eth_gas_constants;
use crate::fee::fee_utils::get_fee_by_gas_vector;
use crate::fee::gas_usage::{
compute_discounted_gas_from_gas_vector,
compute_discounted_gas_from_tx_context,
get_da_gas_cost,
get_message_segment_length,
};
Expand Down Expand Up @@ -204,12 +204,12 @@ fn test_get_message_segment_length(
}

#[rstest]
fn test_compute_discounted_gas_from_gas_vector() {
fn test_compute_discounted_gas() {
let tx_context = BlockContext::create_for_testing()
.to_tx_context(&account_invoke_tx(invoke_tx_args! {}))
.unwrap();
let gas_usage = GasVector { l1_gas: 100, l1_data_gas: 2, ..Default::default() };
let actual_result = compute_discounted_gas_from_gas_vector(&gas_usage, &tx_context);
let actual_result = compute_discounted_gas_from_tx_context(&gas_usage, &tx_context);

let result_div_ceil = gas_usage.l1_gas
+ u128_div_ceil(
Expand Down
4 changes: 2 additions & 2 deletions crates/blockifier/src/transaction/account_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::fee::fee_utils::{
get_sequencer_balance_keys,
verify_can_pay_committed_bounds,
};
use crate::fee::gas_usage::{compute_discounted_gas_from_gas_vector, estimate_minimal_gas_vector};
use crate::fee::gas_usage::{compute_discounted_gas_from_tx_context, estimate_minimal_gas_vector};
use crate::retdata;
use crate::state::cached_state::{StateChanges, TransactionalState};
use crate::state::state_api::{State, StateReader, UpdatableState};
Expand Down Expand Up @@ -233,7 +233,7 @@ impl AccountTransaction {
estimate_minimal_gas_vector(&tx_context.block_context, self)?;
// TODO(Aner, 30/01/24): modify once data gas limit is enforced.
let minimal_l1_gas_amount =
compute_discounted_gas_from_gas_vector(&minimal_l1_gas_amount_vector, tx_context);
compute_discounted_gas_from_tx_context(&minimal_l1_gas_amount_vector, tx_context);

let TransactionContext { block_context, tx_info } = tx_context;
let block_info = &block_context.block_info;
Expand Down
99 changes: 87 additions & 12 deletions crates/blockifier/src/transaction/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ use starknet_types_core::felt::Felt;
use strum_macros::EnumIter;

use crate::abi::constants as abi_constants;
use crate::blockifier::block::BlockInfo;
use crate::blockifier::block::{BlockInfo, GasPrices};
use crate::execution::call_info::{CallInfo, ExecutionSummary, MessageL1CostInfo, OrderedEvent};
use crate::fee::actual_cost::TransactionReceipt;
use crate::fee::eth_gas_constants;
use crate::fee::fee_checks::FeeCheckError;
use crate::fee::fee_utils::{calculate_l1_gas_by_vm_usage, get_fee_by_gas_vector};
use crate::fee::gas_usage::{
compute_discounted_gas,
get_consumed_message_to_l2_emissions_cost,
get_da_gas_cost,
get_log_message_to_l1_emissions_cost,
Expand Down Expand Up @@ -103,11 +105,7 @@ impl TransactionInfo {

pub fn enforce_fee(&self) -> bool {
match self {
TransactionInfo::Current(context) => {
let l1_bounds = context.l1_resource_bounds();
let max_amount: u128 = l1_bounds.max_amount.into();
max_amount * l1_bounds.max_price_per_unit > 0
}
TransactionInfo::Current(context) => context.resource_bounds.willing_to_pay(),
TransactionInfo::Deprecated(context) => context.max_fee != Fee(0),
}
}
Expand Down Expand Up @@ -136,13 +134,11 @@ pub struct CurrentTransactionInfo {

impl CurrentTransactionInfo {
/// Fetch the L1 resource bounds, if they exist.
// TODO(Nimrod): Consider removing this function and add equivalent method to
// `ValidResourceBounds`.
// TODO(Nimrod): Delete this function once:
// 1. L2 gas price exists in block info.
// 2. tx resources can be calculated with l2 prices.
pub fn l1_resource_bounds(&self) -> ResourceBounds {
match self.resource_bounds {
ValidResourceBounds::L1Gas(bounds) => bounds,
ValidResourceBounds::AllResources(AllResourceBounds { l1_gas, .. }) => l1_gas,
}
self.resource_bounds.get_l1_bounds()
}
}

Expand All @@ -162,6 +158,23 @@ pub struct GasVector {
pub l2_gas: u128,
}

impl From<&ValidResourceBounds> for GasVector {
fn from(resource_bounds: &ValidResourceBounds) -> Self {
match resource_bounds {
ValidResourceBounds::L1Gas(l1_bounds) => Self::from_l1_gas(l1_bounds.max_amount.into()),
ValidResourceBounds::AllResources(AllResourceBounds {
l1_gas,
l2_gas,
l1_data_gas,
}) => Self {
l1_gas: l1_gas.max_amount.into(),
l1_data_gas: l1_data_gas.max_amount.into(),
l2_gas: l2_gas.max_amount.into(),
},
}
}
}

impl GasVector {
pub fn from_l1_gas(l1_gas: u128) -> Self {
Self { l1_gas, ..Default::default() }
Expand Down Expand Up @@ -572,3 +585,65 @@ pub enum FeeType {
pub trait TransactionInfoCreator {
fn create_tx_info(&self) -> Result<TransactionInfo, TransactionInfoCreationError>;
}

type FeeCheckResult<T> = Result<T, FeeCheckError>;
// Better name for the trait?
pub trait BoundsChecker {
/// Checks whether the given gas vector can be covered by the resource bounds.
/// Returns Ok if it can, otherwise returns an error indicates why it can't.
fn can_cover(
&self,
gas_vector: &GasVector,
fee_type: &FeeType,
gas_prices: &GasPrices,
) -> FeeCheckResult<()>;
}

impl BoundsChecker for ValidResourceBounds {
fn can_cover(
&self,
gas_vector: &GasVector,
fee_type: &FeeType,
gas_prices: &GasPrices,
) -> FeeCheckResult<()> {
match self {
ValidResourceBounds::L1Gas(l1_bounds) => {
let max_l1_gas = l1_bounds.max_amount.into();
let total_discounted_gas_used =
compute_discounted_gas(gas_vector, fee_type, gas_prices);
if total_discounted_gas_used > max_l1_gas {
return Err(FeeCheckError::MaxL1GasAmountExceeded {
max_amount: max_l1_gas,
actual_amount: total_discounted_gas_used,
});
}
}
ValidResourceBounds::AllResources(AllResourceBounds {
l1_gas,
l2_gas,
l1_data_gas,
}) => {
// Does the order matter?
if gas_vector.l1_gas > l1_gas.max_amount.into() {
return Err(FeeCheckError::MaxL2GasAmountExceeded {
max_amount: l1_gas.max_amount.into(),
actual_amount: gas_vector.l1_gas,
});
}
if gas_vector.l2_gas > l2_gas.max_amount.into() {
return Err(FeeCheckError::MaxL2GasAmountExceeded {
max_amount: l2_gas.max_amount.into(),
actual_amount: gas_vector.l2_gas,
});
}
if gas_vector.l1_data_gas > l1_data_gas.max_amount.into() {
return Err(FeeCheckError::MaxL1DataGasAmountExceeded {
max_amount: l1_data_gas.max_amount.into(),
actual_amount: gas_vector.l1_data_gas,
});
}
}
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/blockifier/src/transaction/transactions_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ fn assert_failure_if_resource_bounds_exceed_balance(
);
}
TransactionInfo::Current(context) => {
let l1_bounds = context.l1_resource_bounds();
let l1_bounds = context.resource_bounds.get_l1_bounds();
assert_matches!(
invalid_tx.execute(state, block_context, true, true).unwrap_err(),
TransactionExecutionError::TransactionPreValidationError(
Expand Down
25 changes: 25 additions & 0 deletions crates/starknet_api/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,31 @@ pub enum ValidResourceBounds {
AllResources(AllResourceBounds),
}

impl ValidResourceBounds {
/// Returns whether there is a resource the user can pay for.
pub fn willing_to_pay(&self) -> bool {
match self {
Self::L1Gas(l1_bounds) => {
l1_bounds.max_price_per_unit * u128::from(l1_bounds.max_amount) > 0
}
Self::AllResources(AllResourceBounds { l1_gas, l2_gas, l1_data_gas }) => {
let can_pay_l1_gas = l1_gas.max_price_per_unit * u128::from(l1_gas.max_amount) > 0;
let can_pay_l2_gas = l2_gas.max_price_per_unit * u128::from(l2_gas.max_amount) > 0;
let can_pay_l1_data_gas =
l1_data_gas.max_price_per_unit * u128::from(l1_data_gas.max_amount) > 0;
can_pay_l1_gas || can_pay_l2_gas || can_pay_l1_data_gas
}
}
}

pub fn get_l1_bounds(&self) -> ResourceBounds {
match self {
ValidResourceBounds::L1Gas(l1_bounds) => *l1_bounds,
ValidResourceBounds::AllResources(all_resources) => all_resources.l1_gas,
}
}
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)]
pub struct AllResourceBounds {
pub l1_gas: ResourceBounds,
Expand Down
Loading