diff --git a/crates/builder/Cargo.toml b/crates/builder/Cargo.toml index 872db3942..833a6be58 100644 --- a/crates/builder/Cargo.toml +++ b/crates/builder/Cargo.toml @@ -48,6 +48,7 @@ mockall = {workspace = true, optional = true } mockall.workspace = true rundler-pool = { path = "../pool", features = ["test-utils"] } rundler-provider = { path = "../provider", features = ["test-utils"] } +rundler-sim = { path = "../sim", features = ["test-utils"] } [build-dependencies] tonic-build.workspace = true diff --git a/crates/builder/src/bundle_proposer.rs b/crates/builder/src/bundle_proposer.rs index 5976e686a..c23f7db0b 100644 --- a/crates/builder/src/bundle_proposer.rs +++ b/crates/builder/src/bundle_proposer.rs @@ -41,8 +41,6 @@ use crate::emit::{BuilderEvent, OpRejectionReason, SkipReason}; /// A user op must be valid for at least this long into the future to be included. const TIME_RANGE_BUFFER: Duration = Duration::from_secs(60); -/// Entrypoint requires a buffer over the user operation gas limits in the bundle transaction -const BUNDLE_TRANSACTION_GAS_OVERHEAD_BUFFER: u64 = 5000; /// Extra buffer percent to add on the bundle transaction gas estimate to be sure it will be enough const BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT: u64 = 5; @@ -538,7 +536,7 @@ where let mut gas_left = U256::from(self.settings.max_bundle_gas); let mut ops_in_bundle = Vec::new(); for op in ops { - let gas = gas::user_operation_gas_limit(&op.uo, self.settings.chain_id); + let gas = gas::user_operation_gas_limit(&op.uo, self.settings.chain_id, false); if gas_left < gas { self.emit(BuilderEvent::skipped_op( self.builder_index, @@ -739,10 +737,7 @@ impl ProposalContext { } fn get_total_gas_limit(&self, chain_id: u64) -> U256 { - self.iter_ops() - .map(|op| gas::user_operation_gas_limit(op, chain_id)) - .fold(U256::zero(), |acc, c| acc + c) - + BUNDLE_TRANSACTION_GAS_OVERHEAD_BUFFER + gas::bundle_gas_limit(self.iter_ops(), chain_id) } fn iter_ops_with_simulations(&self) -> impl Iterator + '_ { @@ -762,7 +757,7 @@ mod tests { use ethers::{types::H160, utils::parse_units}; use rundler_pool::MockPoolServer; use rundler_provider::{AggregatorSimOut, MockEntryPoint, MockProvider}; - use rundler_sim::{MockSimulator, SimulationViolation}; + use rundler_sim::{gas::GasOverheads, MockSimulator, SimulationViolation}; use rundler_types::ValidTimeRange; use super::*; @@ -782,11 +777,13 @@ mod tests { }]) .await; + let ov = GasOverheads::default(); let expected_gas = math::increase_by_percent( op.pre_verification_gas + op.verification_gas_limit * 2 + op.call_gas_limit - + BUNDLE_TRANSACTION_GAS_OVERHEAD_BUFFER, + + ov.bundle_transaction_gas_buffer + + ov.transaction_gas_overhead, BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT, ); @@ -909,7 +906,7 @@ mod tests { let max_priority_fee_per_gas = U256::from(50); let op1 = op_with_sender_and_fees(address(1), 2054.into(), 54.into()); let op2 = op_with_sender_and_fees(address(2), 2055.into(), 55.into()); - let bundle = make_bundle( + let bundle = mock_make_bundle( vec![ MockOp { op: op1.clone(), @@ -943,7 +940,7 @@ mod tests { let max_priority_fee_per_gas = U256::from(50); let op1 = op_with_sender_and_fees(address(1), 1054.into(), 55.into()); let op2 = op_with_sender_and_fees(address(2), 1055.into(), 55.into()); - let bundle = make_bundle( + let bundle = mock_make_bundle( vec![ MockOp { op: op1.clone(), @@ -993,7 +990,7 @@ mod tests { let op_b_aggregated_sig = 21; let aggregator_a_signature = 101; let aggregator_b_signature = 102; - let bundle = make_bundle( + let bundle = mock_make_bundle( vec![ MockOp { op: unaggregated_op.clone(), @@ -1098,7 +1095,7 @@ mod tests { let op6 = op_with_sender_factory(address(6), address(4)); let deposit = parse_units("1", "ether").unwrap().into(); - let bundle = make_bundle( + let bundle = mock_make_bundle( vec![ MockOp { op: op1.clone(), @@ -1159,7 +1156,7 @@ mod tests { let op4 = op_with_sender_call_gas_limit(address(4), U256::from(10_000_000)); let deposit = parse_units("1", "ether").unwrap().into(); - let bundle = make_bundle( + let bundle = mock_make_bundle( vec![ MockOp { op: op1.clone(), @@ -1198,7 +1195,7 @@ mod tests { assert_eq!( bundle.gas_estimate, U256::from(math::increase_by_percent( - 10_000_000 + BUNDLE_TRANSACTION_GAS_OVERHEAD_BUFFER, + 10_000_000 + 5_000 + 21_000, BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT )) ); @@ -1216,7 +1213,7 @@ mod tests { } async fn simple_make_bundle(mock_ops: Vec) -> Bundle { - make_bundle( + mock_make_bundle( mock_ops, vec![], vec![HandleOpsOut::Success], @@ -1227,7 +1224,7 @@ mod tests { .await } - async fn make_bundle( + async fn mock_make_bundle( mock_ops: Vec, mock_aggregators: Vec, mock_handle_ops_call_results: Vec, diff --git a/crates/sim/src/gas/gas.rs b/crates/sim/src/gas/gas.rs index 470988075..50fcdd771 100644 --- a/crates/sim/src/gas/gas.rs +++ b/crates/sim/src/gas/gas.rs @@ -28,28 +28,28 @@ use tokio::try_join; use super::polygon::Polygon; -// Gas overheads for user operations -// used in calculating the pre-verification gas -// see: https://github.com/eth-infinitism/bundler/blob/main/packages/sdk/src/calcPreVerificationGas.ts +/// Gas overheads for user operations used in calculating the pre-verification gas. See: https://github.com/eth-infinitism/bundler/blob/main/packages/sdk/src/calcPreVerificationGas.ts #[derive(Clone, Copy, Debug)] -struct GasOverheads { - fixed: U256, +pub struct GasOverheads { + /// The Entrypoint requires a gas buffer for the bundle to account for the gas spent outside of the major steps in the processing of UOs + pub bundle_transaction_gas_buffer: U256, + /// The fixed gas overhead for any EVM transaction + pub transaction_gas_overhead: U256, per_user_op: U256, per_user_op_word: U256, zero_byte: U256, non_zero_byte: U256, - bundle_size: U256, } impl Default for GasOverheads { fn default() -> Self { Self { - fixed: 21000.into(), - per_user_op: 18300.into(), + bundle_transaction_gas_buffer: 5_000.into(), + transaction_gas_overhead: 21_000.into(), + per_user_op: 18_300.into(), per_user_op_word: 4.into(), zero_byte: 4.into(), non_zero_byte: 16.into(), - bundle_size: 1.into(), } } } @@ -74,7 +74,7 @@ pub async fn calc_pre_verification_gas( provider: Arc

, chain_id: u64, ) -> anyhow::Result { - let static_gas = calc_static_pre_verification_gas(full_op); + let static_gas = calc_static_pre_verification_gas(full_op, true); let dynamic_gas = match chain_id { _ if ARBITRUM_CHAIN_IDS.contains(&chain_id) => { provider @@ -94,13 +94,31 @@ pub async fn calc_pre_verification_gas( Ok(static_gas + dynamic_gas) } +/// Compute the gas limit for the bundle composed of the given user operations +pub fn bundle_gas_limit<'a, I>(iter_ops: I, chain_id: u64) -> U256 +where + I: Iterator, +{ + let ov = GasOverheads::default(); + iter_ops + .map(|op| user_operation_gas_limit(op, chain_id, false)) + .fold( + ov.bundle_transaction_gas_buffer + ov.transaction_gas_overhead, + |acc, c| acc + c, + ) +} + /// Returns the gas limit for the user operation that applies to bundle transaction's limit -pub fn user_operation_gas_limit(uo: &UserOperation, chain_id: u64) -> U256 { +pub fn user_operation_gas_limit( + uo: &UserOperation, + chain_id: u64, + include_fixed_gas_overhead: bool, +) -> U256 { // On some chains (OP bedrock, Arbitrum) the L1 gas fee is charged via pre_verification_gas // but this not part of the execution gas limit of the transaction. // In such cases we only consider the static portion of the pre_verification_gas in the gas limit. let pvg = if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) | ARBITRUM_CHAIN_IDS.contains(&chain_id) { - calc_static_pre_verification_gas(uo) + calc_static_pre_verification_gas(uo, include_fixed_gas_overhead) } else { uo.pre_verification_gas }; @@ -115,7 +133,7 @@ pub fn user_operation_max_gas_cost(uo: &UserOperation) -> U256 { * (uo.pre_verification_gas + uo.call_gas_limit + uo.verification_gas_limit * mul) } -fn calc_static_pre_verification_gas(op: &UserOperation) -> U256 { +fn calc_static_pre_verification_gas(op: &UserOperation, include_fixed_gas_overhead: bool) -> U256 { let ov = GasOverheads::default(); let encoded_op = op.clone().encode(); let length_in_words = encoded_op.len() / 32; // size of packed user op is always a multiple of 32 bytes @@ -131,10 +149,14 @@ fn calc_static_pre_verification_gas(op: &UserOperation) -> U256 { .reduce(|a, b| a + b) .unwrap_or_default(); - ov.fixed / ov.bundle_size - + call_data_cost + call_data_cost + ov.per_user_op + ov.per_user_op_word * length_in_words + + (if include_fixed_gas_overhead { + ov.transaction_gas_overhead + } else { + 0.into() + }) } fn verification_gas_limit_multiplier(uo: &UserOperation) -> u64 { diff --git a/crates/sim/src/precheck.rs b/crates/sim/src/precheck.rs index 95acec561..74fddf1f2 100644 --- a/crates/sim/src/precheck.rs +++ b/crates/sim/src/precheck.rs @@ -180,7 +180,7 @@ impl PrecheckerImpl { max_verification_gas, )); } - let total_gas_limit = gas::user_operation_gas_limit(op, chain_id); + let total_gas_limit = gas::user_operation_gas_limit(op, chain_id, true); if total_gas_limit > max_total_execution_gas { violations.push(PrecheckViolation::TotalGasLimitTooHigh( total_gas_limit,