From e6c9f150dba9cf45dd33e5edbd1736519255df58 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Mon, 24 Jun 2024 14:54:28 -0500 Subject: [PATCH] feat(sim): check total gas limit after estimation --- bin/rundler/src/cli/mod.rs | 1 + crates/rpc/src/eth/error.rs | 3 ++ crates/sim/src/estimation/mod.rs | 5 ++ crates/sim/src/estimation/v0_6.rs | 59 +++++++++++++++++++++ crates/sim/src/estimation/v0_7.rs | 85 ++++++++++++++++++++++++++++--- 5 files changed, 147 insertions(+), 6 deletions(-) diff --git a/bin/rundler/src/cli/mod.rs b/bin/rundler/src/cli/mod.rs index 0669c3a56..02ea790be 100644 --- a/bin/rundler/src/cli/mod.rs +++ b/bin/rundler/src/cli/mod.rs @@ -344,6 +344,7 @@ impl TryFrom<&CommonArgs> for EstimationSettings { max_call_gas, max_paymaster_verification_gas: value.max_verification_gas, max_paymaster_post_op_gas: max_call_gas, + max_total_execution_gas: value.max_bundle_gas, max_simulate_handle_ops_gas: value.max_simulate_handle_ops_gas, verification_estimation_gas_fee: value.verification_estimation_gas_fee, }) diff --git a/crates/rpc/src/eth/error.rs b/crates/rpc/src/eth/error.rs index 9bce5e653..0c58697a7 100644 --- a/crates/rpc/src/eth/error.rs +++ b/crates/rpc/src/eth/error.rs @@ -457,6 +457,9 @@ impl From for EthRpcError { error @ GasEstimationError::GasUsedTooLarge => { Self::EntryPointValidationRejected(error.to_string()) } + error @ GasEstimationError::GasTotalTooLarge(_, _) => { + Self::InvalidParams(error.to_string()) + } error @ GasEstimationError::GasFieldTooLarge(_, _) => { Self::InvalidParams(error.to_string()) } diff --git a/crates/sim/src/estimation/mod.rs b/crates/sim/src/estimation/mod.rs index 1a7bdf8fa..e8ba83089 100644 --- a/crates/sim/src/estimation/mod.rs +++ b/crates/sim/src/estimation/mod.rs @@ -54,6 +54,9 @@ pub enum GasEstimationError { /// Supplied gas was too large #[error("{0} cannot be larger than {1}")] GasFieldTooLarge(&'static str, u64), + /// The total amount of gas used by the UO is greater than allowed + #[error("total gas used by the user operation {0} is greater than the allowed limit: {1}")] + GasTotalTooLarge(u64, u64), /// Other error #[error(transparent)] Other(#[from] anyhow::Error), @@ -86,6 +89,8 @@ pub struct Settings { pub max_paymaster_verification_gas: u64, /// The maximum amount of gas that can be used for the paymaster post op step of a user operation pub max_paymaster_post_op_gas: u64, + /// The maximum amount of total execution gas to check after estimation + pub max_total_execution_gas: u64, /// The maximum amount of gas that can be used in a call to `simulateHandleOps` pub max_simulate_handle_ops_gas: u64, /// The gas fee to use during verification gas estimation, required to be held by the fee-payer diff --git a/crates/sim/src/estimation/v0_6.rs b/crates/sim/src/estimation/v0_6.rs index 56a7fd76c..9fc1a23d8 100644 --- a/crates/sim/src/estimation/v0_6.rs +++ b/crates/sim/src/estimation/v0_6.rs @@ -103,6 +103,19 @@ where let verification_gas_limit = verification_gas_limit?; let call_gas_limit = call_gas_limit?; + // Verify total gas limit + let mut op_with_gas = full_op; + op_with_gas.verification_gas_limit = verification_gas_limit; + op_with_gas.call_gas_limit = call_gas_limit; + let gas_limit = + gas::user_operation_execution_gas_limit(&self.chain_spec, &op_with_gas, true); + if gas_limit > self.settings.max_total_execution_gas.into() { + return Err(GasEstimationError::GasTotalTooLarge( + gas_limit.as_u64(), + self.settings.max_total_execution_gas, + )); + } + Ok(GasEstimate { pre_verification_gas, verification_gas_limit, @@ -524,6 +537,7 @@ mod tests { max_call_gas: TEST_MAX_GAS_LIMITS, max_paymaster_verification_gas: TEST_MAX_GAS_LIMITS, max_paymaster_post_op_gas: TEST_MAX_GAS_LIMITS, + max_total_execution_gas: TEST_MAX_GAS_LIMITS, max_simulate_handle_ops_gas: TEST_MAX_GAS_LIMITS, verification_estimation_gas_fee: 1_000_000_000_000, }; @@ -617,6 +631,7 @@ mod tests { max_call_gas: 10000000000, max_paymaster_verification_gas: 10000000000, max_paymaster_post_op_gas: 10000000000, + max_total_execution_gas: 10000000000, max_simulate_handle_ops_gas: 100000000, verification_estimation_gas_fee: 1_000_000_000_000, }; @@ -684,6 +699,7 @@ mod tests { max_call_gas: 10000000000, max_paymaster_verification_gas: 10000000000, max_paymaster_post_op_gas: 10000000000, + max_total_execution_gas: 10000000000, max_simulate_handle_ops_gas: 100000000, verification_estimation_gas_fee: 1_000_000_000_000, }; @@ -1266,6 +1282,7 @@ mod tests { max_call_gas: 10, max_paymaster_post_op_gas: 10, max_paymaster_verification_gas: 10, + max_total_execution_gas: 10, max_simulate_handle_ops_gas: 10, verification_estimation_gas_fee: 1_000_000_000_000, }; @@ -1428,6 +1445,48 @@ mod tests { )); } + #[tokio::test] + async fn test_total_limit() { + let (mut entry, mut provider) = create_base_config(); + + provider + .expect_get_latest_block_hash_and_number() + .returning(|| Ok((H256::zero(), U64::zero()))); + + entry + .expect_call_spoofed_simulate_op() + .returning(move |_a, _b, _c, _d, _e, _f| { + Ok(Ok(ExecutionResult { + target_result: TestCallGasResult { + success: true, + gas_used: 0.into(), + revert_data: Bytes::new(), + } + .encode() + .into(), + target_success: true, + ..Default::default() + })) + }); + + let (estimator, _) = create_estimator(entry, provider); + + let mut optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + optional_op.call_gas_limit = Some(TEST_MAX_GAS_LIMITS.into()); + optional_op.verification_gas_limit = Some(TEST_MAX_GAS_LIMITS.into()); + + let err = estimator + .estimate_op_gas(optional_op.clone(), spoof::state()) + .await + .err() + .unwrap(); + + assert!(matches!( + err, + GasEstimationError::GasTotalTooLarge(_, TEST_MAX_GAS_LIMITS) + )) + } + #[test] fn test_proxy_target_offset() { let proxy_target_bytes = hex::decode(PROXY_IMPLEMENTATION_ADDRESS_MARKER).unwrap(); diff --git a/crates/sim/src/estimation/v0_7.rs b/crates/sim/src/estimation/v0_7.rs index c4ebb0541..e021d1527 100644 --- a/crates/sim/src/estimation/v0_7.rs +++ b/crates/sim/src/estimation/v0_7.rs @@ -110,17 +110,32 @@ where ); tracing::debug!("gas estimation took {}ms", timer.elapsed().as_millis()); - let verification_gas_limit = verification_gas_limit?.into(); - let paymaster_verification_gas_limit = paymaster_verification_gas_limit?.into(); - let call_gas_limit = call_gas_limit?.into(); + let verification_gas_limit = verification_gas_limit?; + let paymaster_verification_gas_limit = paymaster_verification_gas_limit?; + let call_gas_limit = call_gas_limit?; + + // check the total gas limit + let mut op_with_gas = full_op; + op_with_gas.pre_verification_gas = pre_verification_gas; + op_with_gas.call_gas_limit = call_gas_limit; + op_with_gas.verification_gas_limit = verification_gas_limit; + op_with_gas.paymaster_verification_gas_limit = paymaster_verification_gas_limit; + let gas_limit = + gas::user_operation_execution_gas_limit(&self.chain_spec, &op_with_gas, true); + if gas_limit > self.settings.max_total_execution_gas.into() { + return Err(GasEstimationError::GasTotalTooLarge( + gas_limit.as_u64(), + self.settings.max_total_execution_gas, + )); + } Ok(GasEstimate { pre_verification_gas, - call_gas_limit, - verification_gas_limit, + call_gas_limit: call_gas_limit.into(), + verification_gas_limit: verification_gas_limit.into(), paymaster_verification_gas_limit: op .paymaster - .map(|_| paymaster_verification_gas_limit), + .map(|_| paymaster_verification_gas_limit.into()), }) } } @@ -563,6 +578,7 @@ mod tests { max_call_gas: TEST_MAX_GAS_LIMITS, max_paymaster_verification_gas: TEST_MAX_GAS_LIMITS, max_paymaster_post_op_gas: TEST_MAX_GAS_LIMITS, + max_total_execution_gas: TEST_MAX_GAS_LIMITS, max_simulate_handle_ops_gas: TEST_MAX_GAS_LIMITS, verification_estimation_gas_fee: 1_000_000_000_000, }; @@ -799,6 +815,63 @@ mod tests { )); } + #[tokio::test] + async fn test_total_limit() { + let (mut entry, mut provider) = create_base_config(); + + entry + .expect_call_spoofed_simulate_op() + .returning(move |_a, _b, _c, _d, _e, _f| { + Ok(Ok(ExecutionResult { + target_result: TestCallGasResult { + success: true, + gas_used: TEST_MAX_GAS_LIMITS.into(), + revert_data: Bytes::new(), + } + .encode() + .into(), + target_success: true, + ..Default::default() + })) + }); + provider + .expect_get_latest_block_hash_and_number() + .returning(|| Ok((H256::zero(), U64::zero()))); + + let (estimator, _) = create_estimator(entry, provider); + + let optional_op = UserOperationOptionalGas { + sender: Address::zero(), + nonce: U256::zero(), + call_data: Bytes::new(), + call_gas_limit: Some(TEST_MAX_GAS_LIMITS.into()), + verification_gas_limit: Some(TEST_MAX_GAS_LIMITS.into()), + pre_verification_gas: Some(TEST_MAX_GAS_LIMITS.into()), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + signature: Bytes::new(), + + paymaster: None, + paymaster_data: Bytes::new(), + paymaster_verification_gas_limit: Some(TEST_MAX_GAS_LIMITS.into()), + paymaster_post_op_gas_limit: Some(TEST_MAX_GAS_LIMITS.into()), + + factory: None, + factory_data: Bytes::new(), + }; + + let estimation = estimator + .estimate_op_gas(optional_op, spoof::state()) + .await + .err() + .unwrap(); + + assert!(matches!( + estimation, + GasEstimationError::GasTotalTooLarge(_, TEST_MAX_GAS_LIMITS) + )); + } + #[test] fn test_proxy_target_offset() { let proxy_target_bytes = hex::decode(PROXY_IMPLEMENTATION_ADDRESS_MARKER).unwrap();