diff --git a/crates/blockifier/src/blockifier/block.rs b/crates/blockifier/src/blockifier/block.rs index 7fdf2fb30a..8aa989f0c8 100644 --- a/crates/blockifier/src/blockifier/block.rs +++ b/crates/blockifier/src/blockifier/block.rs @@ -1,3 +1,4 @@ +use std::cmp::max; use std::num::NonZeroU128; use log::warn; @@ -16,6 +17,12 @@ use crate::versioned_constants::VersionedConstants; #[path = "block_test.rs"] pub mod block_test; +// This constant is used to calculate the base gas price for the next block according to EIP-1559 +// and serves as a sensitivity parameter that limits the maximum rate of change of the gas price +// between consecutive blocks. +const GAS_PRICE_MAX_CHANGE_DENOMINATOR: u128 = 48; +const MIN_GAS_PRICE: u64 = 100000; // In fri. + #[derive(Clone, Debug)] pub struct BlockInfo { pub block_number: BlockNumber, @@ -27,6 +34,48 @@ pub struct BlockInfo { pub use_kzg_da: bool, } +impl BlockInfo { + /// Calculate the base gas price for the next block according to EIP-1559. + /// + /// # Parameters + /// - `price`: The base fee of the current block. + /// - `gas_used`: The total gas used in the current block. + /// - `gas_target`: The target gas usage per block (usually half of the gas limit). + pub fn calculate_next_base_gas_price(price: u64, gas_used: u64, gas_target: u64) -> u64 { + assert!(gas_target > 0, "Gas target must be positive"); + + // We use unsigned integers (u64 and u128) to avoid overflow issues, as the input values are + // naturally unsigned and i256 is unstable in Rust. This approach allows safe handling of + // all inputs using u128 for intermediate calculations. + + // The difference between gas_used and gas_target is always u64. + let gas_delta = gas_used.abs_diff(gas_target); + // Convert to u128 to prevent overflow, as a product of two u64 fits inside a u128. + let price_u128 = u128::from(price); + let gas_delta_u128 = u128::from(gas_delta); + let gas_target_u128 = u128::from(gas_target); + + // Calculate the gas change as u128 to handle potential overflow during multiplication. + let gas_delta_cost = + price_u128.checked_mul(gas_delta_u128).expect("Both variables originate from u64"); + // Calculate the price change, maintaining precision by dividing after scaling up. + // This avoids significant precision loss that would occur if dividing before + // multiplication. + let price_change_u128 = gas_delta_cost / gas_target_u128 / GAS_PRICE_MAX_CHANGE_DENOMINATOR; + + // Convert back to u64, as the price change should fit within the u64 range. + // Since the target is half the maximum block size (which fits within a u64), the gas delta + // is bounded by half the maximum block size. Therefore, after dividing by the gas target + // (which is half the maximum block size), the result is guaranteed to fit within a u64. + let price_change = u64::try_from(price_change_u128) + .expect("Result fits u64 after division of a bounded gas delta"); + + let new_price = + if gas_used > gas_target { price + price_change } else { price - price_change }; + max(new_price, MIN_GAS_PRICE) + } +} + #[derive(Clone, Debug)] pub struct GasPrices { eth_l1_gas_price: NonZeroU128, // In wei.