diff --git a/Cargo.lock b/Cargo.lock index 85d9f86c..1c9a4781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5225,17 +5225,21 @@ dependencies = [ name = "rundler-sim" version = "0.3.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "anyhow", "arrayvec", "async-trait", - "ethers", + "auto_impl", "futures-util", "indexmap 2.5.0", "mockall", "parse-display", "rand", "reqwest 0.12.7", + "rundler-contracts", "rundler-provider", + "rundler-sim", "rundler-types", "rundler-utils", "serde", diff --git a/crates/sim/Cargo.toml b/crates/sim/Cargo.toml index 3904cbab..687aa162 100644 --- a/crates/sim/Cargo.toml +++ b/crates/sim/Cargo.toml @@ -8,14 +8,18 @@ repository.workspace = true publish = false [dependencies] +rundler-contracts = { path = "../contracts" } rundler-provider = { path = "../provider" } rundler-types = { path = "../types" } rundler-utils = { path = "../utils" } +alloy-primitives.workspace = true +alloy-sol-types.workspace = true + anyhow.workspace = true arrayvec = "0.7.6" async-trait.workspace = true -ethers.workspace = true +auto_impl.workspace = true futures-util.workspace = true indexmap = "2.4.0" parse-display.workspace = true @@ -35,6 +39,8 @@ mockall = {workspace = true, optional = true } [dev-dependencies] mockall.workspace = true rundler-provider = { path = "../provider", features = ["test-utils"] } +rundler-sim = { path = ".", features = ["test-utils"] } +alloy-primitives = { workspace = true, features = ["rand"]} [features] test-utils = [ "mockall" ] diff --git a/crates/sim/src/estimation/estimate_call_gas.rs b/crates/sim/src/estimation/estimate_call_gas.rs index 9ee336aa..599f1abf 100644 --- a/crates/sim/src/estimation/estimate_call_gas.rs +++ b/crates/sim/src/estimation/estimate_call_gas.rs @@ -1,21 +1,13 @@ +use alloy_primitives::{Address, Bytes, B256}; +use alloy_sol_types::{Revert, SolError, SolInterface}; use anyhow::{anyhow, Context}; use async_trait::async_trait; -use ethers::{ - abi::AbiDecode, - types::{spoof, Address, Bytes, H256, U128, U256}, +use rundler_contracts::{ + v0_6::CallGasEstimationProxy::TestCallGasResult, + v0_7::CallGasEstimationProxy::CallGasEstimationProxyErrors, }; -use rundler_provider::{EntryPoint, SimulationProvider}; -use rundler_types::{ - contracts::v0_7::call_gas_estimation_proxy::{ - // Errors are shared between v0.6 and v0.7 proxies - EstimateCallGasContinuation, - EstimateCallGasResult, - EstimateCallGasRevertAtMax, - TestCallGasResult, - }, - UserOperation, -}; -use rundler_utils::eth; +use rundler_provider::{BlockId, EntryPoint, SimulationProvider, StateOverride}; +use rundler_types::UserOperation; use super::Settings; use crate::GasEstimationError; @@ -33,7 +25,7 @@ pub(crate) const PROXY_IMPLEMENTATION_ADDRESS_MARKER: &str = /// Estimates the gas limit for a user operation #[async_trait] -pub trait CallGasEstimator: Send + Sync + 'static { +pub trait CallGasEstimator: Send + Sync { /// The user operation type estimated by this estimator type UO: UserOperation; @@ -42,17 +34,17 @@ pub trait CallGasEstimator: Send + Sync + 'static { async fn estimate_call_gas( &self, op: Self::UO, - block_hash: H256, - state_override: spoof::State, - ) -> Result; + block_hash: B256, + state_override: StateOverride, + ) -> Result; /// Calls simulate_handle_op, but captures the execution result. Returning an /// error if the operation reverts or anyhow error on any other error async fn simulate_handle_op_with_result( &self, op: Self::UO, - block_hash: H256, - state_override: spoof::State, + block_hash: B256, + state_override: StateOverride, ) -> Result<(), GasEstimationError>; } @@ -69,12 +61,12 @@ pub struct CallGasEstimatorImpl { /// specialize the `CallGasEstimatorImpl` to be able to handle that version. /// Each user operation version will need an implementation of this trait to be /// able to be used with `CallGasEstimatorImpl` -pub trait CallGasEstimatorSpecialization: Send + Sync + 'static { +pub trait CallGasEstimatorSpecialization: Send + Sync { /// The user operation type estimated by this specialization type UO: UserOperation; /// Add the required CallGasEstimation proxy to the overrides at the given entrypoint address - fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut spoof::State); + fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut StateOverride); /// Returns the input user operation, modified to have limits but zero for the call gas limits. /// The intent is that the modified operation should run its validation but do nothing during execution @@ -84,14 +76,14 @@ pub trait CallGasEstimatorSpecialization: Send + Sync + 'static { fn get_estimate_call_gas_calldata( &self, callless_op: Self::UO, - min_gas: U256, - max_gas: U256, - rounding: U256, + min_gas: u128, + max_gas: u128, + rounding: u128, is_continuation: bool, ) -> Bytes; /// Returns the calldata for the `testCallGas` function of the proxy - fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: U256) -> Bytes; + fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: u128) -> Bytes; } #[async_trait] @@ -106,19 +98,19 @@ where async fn estimate_call_gas( &self, op: Self::UO, - block_hash: H256, - mut state_override: spoof::State, - ) -> Result { + block_hash: B256, + mut state_override: StateOverride, + ) -> Result { let timer = std::time::Instant::now(); self.specialization - .add_proxy_to_overrides(self.entry_point.address(), &mut state_override); + .add_proxy_to_overrides(*self.entry_point.address(), &mut state_override); let callless_op = self.specialization.get_op_with_no_call_gas(op.clone()); - let mut min_gas = U256::zero(); - let mut max_gas = U256::from(self.settings.max_call_gas); + let mut min_gas = 0; + let mut max_gas = self.settings.max_call_gas; let mut is_continuation = false; - let mut num_rounds = U256::zero(); + let mut num_rounds = 0_u32; loop { let target_call_data = self.specialization.get_estimate_call_gas_calldata( callless_op.clone(), @@ -129,56 +121,77 @@ where ); let target_revert_data = self .entry_point - .call_spoofed_simulate_op( + .simulate_handle_op( callless_op.clone(), - self.entry_point.address(), + *self.entry_point.address(), target_call_data, - block_hash, - self.settings.max_simulate_handle_ops_gas.into(), - &state_override, + BlockId::Hash(block_hash.into()), + self.settings.max_simulate_handle_ops_gas, + state_override.clone(), ) .await? .map_err(GasEstimationError::RevertInValidation)? .target_result; - if let Ok(result) = EstimateCallGasResult::decode(&target_revert_data) { - num_rounds += result.num_rounds; - tracing::debug!( - "binary search for call gas took {num_rounds} rounds, {}ms", - timer.elapsed().as_millis() - ); - return Ok(result - .gas_estimate - .try_into() - .ok() - .context("gas estimate should fit in a 128-bit int")?); - } else if let Ok(revert) = EstimateCallGasRevertAtMax::decode(&target_revert_data) { - let error = if let Some(message) = eth::parse_revert_message(&revert.revert_data) { - GasEstimationError::RevertInCallWithMessage(message) - } else { - GasEstimationError::RevertInCallWithBytes(revert.revert_data) - }; - return Err(error); - } else if let Ok(continuation) = - EstimateCallGasContinuation::decode(&target_revert_data) - { - if is_continuation - && continuation.min_gas <= min_gas - && continuation.max_gas >= max_gas - { - // This should never happen, but if it does, bail so we - // don't end up in an infinite loop! + + let decoded = CallGasEstimationProxyErrors::abi_decode(&target_revert_data, false) + .context("should decode revert data")?; + match decoded { + CallGasEstimationProxyErrors::EstimateCallGasResult(result) => { + let ret_num_rounds: u32 = result + .numRounds + .try_into() + .context("num rounds return overflow")?; + + num_rounds += ret_num_rounds; + tracing::debug!( + "binary search for call gas took {num_rounds} rounds, {}ms", + timer.elapsed().as_millis() + ); + return Ok(result + .gasEstimate + .try_into() + .ok() + .context("gas estimate should fit in a 128-bit int")?); + } + CallGasEstimationProxyErrors::EstimateCallGasRevertAtMax(revert) => { + let error = if let Ok(revert) = Revert::abi_decode(&revert.revertData, false) { + GasEstimationError::RevertInCallWithMessage(revert.reason) + } else { + GasEstimationError::RevertInCallWithBytes(revert.revertData) + }; + return Err(error); + } + CallGasEstimationProxyErrors::EstimateCallGasContinuation(continuation) => { + let ret_min_gas = continuation + .minGas + .try_into() + .context("min gas return overflow")?; + let ret_max_gas = continuation + .maxGas + .try_into() + .context("max gas return overflow")?; + let ret_num_rounds: u32 = continuation + .numRounds + .try_into() + .context("num rounds return overflow")?; + + if is_continuation && ret_min_gas <= min_gas && ret_max_gas >= max_gas { + // This should never happen, but if it does, bail so we + // don't end up in an infinite loop! + Err(anyhow!( + "estimateCallGas should make progress each time it is called" + ))?; + } + is_continuation = true; + min_gas = min_gas.max(ret_min_gas); + max_gas = max_gas.min(ret_max_gas); + num_rounds += ret_num_rounds; + } + CallGasEstimationProxyErrors::TestCallGasResult(_) => { Err(anyhow!( - "estimateCallGas should make progress each time it is called" + "estimateCallGas revert should be a Result or a Continuation" ))?; } - is_continuation = true; - min_gas = min_gas.max(continuation.min_gas); - max_gas = max_gas.min(continuation.max_gas); - num_rounds += continuation.num_rounds; - } else { - Err(anyhow!( - "estimateCallGas revert should be a Result or a Continuation" - ))?; } } } @@ -186,11 +199,11 @@ where async fn simulate_handle_op_with_result( &self, op: Self::UO, - block_hash: H256, - mut state_override: spoof::State, + block_hash: B256, + mut state_override: StateOverride, ) -> Result<(), GasEstimationError> { self.specialization - .add_proxy_to_overrides(self.entry_point.address(), &mut state_override); + .add_proxy_to_overrides(*self.entry_point.address(), &mut state_override); let call_gas_limit = op.call_gas_limit(); let callless_op = self.specialization.get_op_with_no_call_gas(op); @@ -200,30 +213,30 @@ where let target_revert_data = self .entry_point - .call_spoofed_simulate_op( + .simulate_handle_op( callless_op, - self.entry_point.address(), + *self.entry_point.address(), target_call_data, - block_hash, - self.settings.max_simulate_handle_ops_gas.into(), - &state_override, + BlockId::Hash(block_hash.into()), + self.settings.max_simulate_handle_ops_gas, + state_override.clone(), ) .await? .map_err(GasEstimationError::RevertInValidation)? .target_result; - if let Ok(result) = TestCallGasResult::decode(&target_revert_data) { - if result.success { - Ok(()) - } else { - let error = if let Some(message) = eth::parse_revert_message(&result.revert_data) { - GasEstimationError::RevertInCallWithMessage(message) - } else { - GasEstimationError::RevertInCallWithBytes(result.revert_data) - }; - Err(error) - } + + let result = TestCallGasResult::abi_decode(&target_revert_data, false) + .context("should decode revert data as TestCallGasResult")?; + + if result.success { + Ok(()) } else { - Err(anyhow!("testCallGas revert should be a TestCallGasResult"))? + let error = if let Ok(revert) = Revert::abi_decode(&result.revertData, false) { + GasEstimationError::RevertInCallWithMessage(revert.reason) + } else { + GasEstimationError::RevertInCallWithBytes(result.revertData) + }; + Err(error) } } } diff --git a/crates/sim/src/estimation/estimate_verification_gas.rs b/crates/sim/src/estimation/estimate_verification_gas.rs index 1f6a3cae..497eaeda 100644 --- a/crates/sim/src/estimation/estimate_verification_gas.rs +++ b/crates/sim/src/estimation/estimate_verification_gas.rs @@ -1,9 +1,7 @@ -use std::sync::Arc; - +use alloy_primitives::{Address, Bytes, B256, U256}; use anyhow::{anyhow, Context}; use async_trait::async_trait; -use ethers::types::{spoof, Address, Bytes, H256, U128, U256}; -use rundler_provider::{EntryPoint, Provider, SimulateOpCallData, SimulationProvider}; +use rundler_provider::{BlockId, EntryPoint, EvmProvider, SimulationProvider, StateOverride}; use rundler_types::{chain::ChainSpec, UserOperation}; use super::Settings; @@ -22,7 +20,7 @@ const OUT_OF_GAS_ERROR_CODES: &[&str] = &[ /// estimate both verification gas and, in the v0.7 case, paymaster verification /// gas. #[async_trait] -pub trait VerificationGasEstimator: Send + Sync + 'static { +pub trait VerificationGasEstimator: Send + Sync { /// The user operation type estimated by this estimator type UO: UserOperation; @@ -37,24 +35,23 @@ pub trait VerificationGasEstimator: Send + Sync + 'static { >( &self, op: &Self::UO, - block_hash: H256, - state_override: &spoof::State, - max_guess: U128, + block_hash: B256, + state_override: StateOverride, + max_guess: u128, get_op_with_limit: F, - ) -> Result; + ) -> Result; } #[derive(Debug, Clone, Copy)] pub struct GetOpWithLimitArgs { - pub gas: U128, - pub fee: U128, + pub gas: u128, + pub fee: u128, } /// Implementation of a verification gas estimator -#[derive(Debug)] pub struct VerificationGasEstimatorImpl { chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, } @@ -63,7 +60,7 @@ pub struct VerificationGasEstimatorImpl { impl VerificationGasEstimator for VerificationGasEstimatorImpl where UO: UserOperation, - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider, { type UO = UO; @@ -71,13 +68,13 @@ where async fn estimate_verification_gas UO>( &self, op: &UO, - block_hash: H256, - state_override: &spoof::State, - max_guess: U128, + block_hash: B256, + state_override: StateOverride, + max_guess: u128, get_op_with_limit: F, - ) -> Result { + ) -> Result { let timer = std::time::Instant::now(); - let paymaster_gas_fee = U128::from(self.settings.verification_estimation_gas_fee); + let paymaster_gas_fee = self.settings.verification_estimation_gas_fee; // Fee logic for gas estimation: // @@ -88,16 +85,13 @@ where // If using a paymaster, the total cost is kept constant, and the fee is adjusted // based on the gas used in the simulation. The total cost is set by a configuration // setting. - let get_op = |gas: U128| -> UO { + let get_op = |gas: u128| -> UO { let fee = if op.paymaster().is_none() { - U128::zero() + 0 } else { - U128::try_from( - U256::from(paymaster_gas_fee) - .checked_div(U256::from(gas) + op.pre_verification_gas()) - .unwrap_or(U256::MAX), - ) - .unwrap_or(U128::MAX) + paymaster_gas_fee + .checked_div(gas + op.pre_verification_gas()) + .unwrap_or(u128::MAX) }; get_op_with_limit(op.clone(), GetOpWithLimitArgs { gas, fee }) }; @@ -105,20 +99,12 @@ where // Make one attempt at max gas, to see if success is possible. // Capture the gas usage of this attempt and use as the initial guess in the binary search let initial_op = get_op(max_guess); - let SimulateOpCallData { - call_data, - spoofed_state, - } = self + let call = self .entry_point - .get_simulate_op_call_data(initial_op, state_override); + .get_simulate_handle_op_call(initial_op, state_override.clone()); let gas_used = self .provider - .get_gas_used( - self.entry_point.address(), - U256::zero(), - call_data, - spoofed_state.clone(), - ) + .get_gas_used(call) .await .context("failed to run initial guess")?; @@ -128,24 +114,20 @@ where "simulateHandleOp succeeded but should always revert. Make sure the entry point contract is deployed and the address is correct" ))?; } - } else if let Some(revert) = self - .entry_point - .decode_simulate_handle_ops_revert(gas_used.result) - .err() - { + } else if let Some(revert) = E::decode_simulate_handle_ops_revert(&gas_used.result)?.err() { return Err(GasEstimationError::RevertInValidation(revert)); } - let run_attempt_returning_error = |gas: u64| async move { - let op = get_op(gas.into()); + let run_attempt_returning_error = |gas: u128, state_override: StateOverride| async move { + let op = get_op(gas); let revert = self .entry_point - .call_spoofed_simulate_op( + .simulate_handle_op( op, - Address::zero(), + Address::ZERO, Bytes::new(), - block_hash, - self.settings.max_simulate_handle_ops_gas.into(), + BlockId::Hash(block_hash.into()), + self.settings.max_simulate_handle_ops_gas, state_override, ) .await? @@ -169,16 +151,18 @@ where let mut max_failure_gas = 1; let mut min_success_gas = self.settings.max_verification_gas; - if gas_used.gas_used.gt(&U256::from(u64::MAX)) { + if gas_used.gasUsed.gt(&U256::from(u128::MAX)) { return Err(GasEstimationError::GasUsedTooLarge); } - let mut guess = gas_used.gas_used.as_u64().saturating_mul(2); + + let ret_gas_used: u128 = gas_used.gasUsed.try_into().unwrap(); + let mut guess = ret_gas_used.saturating_mul(2); let mut num_rounds = 0; while (min_success_gas as f64) / (max_failure_gas as f64) > (1.0 + GAS_ESTIMATION_ERROR_MARGIN) { num_rounds += 1; - if run_attempt_returning_error(guess).await? { + if run_attempt_returning_error(guess, state_override.clone()).await? { min_success_gas = guess; } else { max_failure_gas = guess; @@ -191,8 +175,6 @@ where timer.elapsed().as_millis() ); - let mut min_success_gas = U256::from(min_success_gas); - // If not using a paymaster, always add the cost of a native transfer to the verification gas. // This may cause an over estimation when the account does have enough deposit to pay for the // max cost, but it is better to overestimate than underestimate. @@ -200,25 +182,18 @@ where min_success_gas += self.chain_spec.deposit_transfer_overhead; } - Ok(U128::try_from(min_success_gas) - .ok() - .context("min success gas should fit in 128-bit int")?) + Ok(min_success_gas) } } impl VerificationGasEstimatorImpl where UO: UserOperation, - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider, { /// Create a new instance - pub fn new( - chain_spec: ChainSpec, - provider: Arc

, - entry_point: E, - settings: Settings, - ) -> Self { + pub fn new(chain_spec: ChainSpec, provider: P, entry_point: E, settings: Settings) -> Self { Self { chain_spec, provider, diff --git a/crates/sim/src/estimation/mod.rs b/crates/sim/src/estimation/mod.rs index e8ba8308..5328bb99 100644 --- a/crates/sim/src/estimation/mod.rs +++ b/crates/sim/src/estimation/mod.rs @@ -11,9 +11,10 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use ethers::types::{Bytes, U128}; +use alloy_primitives::Bytes; #[cfg(feature = "test-utils")] use mockall::automock; +use rundler_provider::{ProviderError, StateOverride}; use rundler_types::{GasEstimate, ValidationRevert}; use crate::precheck::MIN_CALL_GAS_LIMIT; @@ -32,9 +33,9 @@ mod v0_7; pub use v0_7::GasEstimator as GasEstimatorV0_7; /// Percentage by which to increase the verification gas limit after binary search -const VERIFICATION_GAS_BUFFER_PERCENT: u64 = 10; +const VERIFICATION_GAS_BUFFER_PERCENT: u32 = 10; /// Absolute value by which to increase the call gas limit after binary search -const CALL_GAS_BUFFER_VALUE: U128 = U128([3000, 0]); +const CALL_GAS_BUFFER_VALUE: u128 = 3000; /// Error type for gas estimation #[derive(Debug, thiserror::Error)] @@ -53,19 +54,25 @@ pub enum GasEstimationError { GasUsedTooLarge, /// Supplied gas was too large #[error("{0} cannot be larger than {1}")] - GasFieldTooLarge(&'static str, u64), + GasFieldTooLarge(&'static str, u128), /// 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), + GasTotalTooLarge(u128, u128), /// Other error #[error(transparent)] Other(#[from] anyhow::Error), } +impl From for GasEstimationError { + fn from(error: ProviderError) -> Self { + GasEstimationError::Other(anyhow::anyhow!("provider error: {error:?}")) + } +} + /// Gas estimator trait #[cfg_attr(feature = "test-utils", automock(type UserOperationOptionalGas = rundler_types::v0_6::UserOperationOptionalGas;))] #[async_trait::async_trait] -pub trait GasEstimator: Send + Sync + 'static { +pub trait GasEstimator: Send + Sync { /// The user operation type estimated by this gas estimator type UserOperationOptionalGas; @@ -74,7 +81,7 @@ pub trait GasEstimator: Send + Sync + 'static { async fn estimate_op_gas( &self, op: Self::UserOperationOptionalGas, - state_override: ethers::types::spoof::State, + state_override: StateOverride, ) -> Result; } @@ -82,32 +89,29 @@ pub trait GasEstimator: Send + Sync + 'static { #[derive(Clone, Copy, Debug)] pub struct Settings { /// The maximum amount of gas that can be used for the verification step of a user operation - pub max_verification_gas: u64, + pub max_verification_gas: u128, /// The maximum amount of gas that can be used for the call step of a user operation - pub max_call_gas: u64, + pub max_call_gas: u128, /// The maximum amount of gas that can be used for the paymaster verification step of a user operation - pub max_paymaster_verification_gas: u64, + pub max_paymaster_verification_gas: u128, /// 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, + pub max_paymaster_post_op_gas: u128, /// The maximum amount of total execution gas to check after estimation - pub max_total_execution_gas: u64, + pub max_total_execution_gas: u128, /// The maximum amount of gas that can be used in a call to `simulateHandleOps` - pub max_simulate_handle_ops_gas: u64, + pub max_simulate_handle_ops_gas: u128, /// The gas fee to use during verification gas estimation, required to be held by the fee-payer /// during estimation. If using a paymaster, the fee-payer must have 3x this value. /// As the gas limit is varied during estimation, the fee is held constant by varying the /// gas price. /// Clients can use state overrides to set the balance of the fee-payer to at least this value. - pub verification_estimation_gas_fee: u64, + pub verification_estimation_gas_fee: u128, } impl Settings { /// Check if the settings are valid pub fn validate(&self) -> Option { - if U128::from(self.max_call_gas) - .cmp(&MIN_CALL_GAS_LIMIT) - .is_lt() - { + if self.max_call_gas.cmp(&MIN_CALL_GAS_LIMIT).is_lt() { return Some("max_call_gas field cannot be lower than MIN_CALL_GAS_LIMIT".to_string()); } None diff --git a/crates/sim/src/estimation/v0_6.rs b/crates/sim/src/estimation/v0_6.rs index 9fc1a23d..84f1c7c1 100644 --- a/crates/sim/src/estimation/v0_6.rs +++ b/crates/sim/src/estimation/v0_6.rs @@ -11,28 +11,27 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{cmp, ops::Add, sync::Arc}; +use std::{cmp, ops::Add}; -use ethers::{ - contract::EthCall, - providers::spoof, - types::{Address, Bytes, H256, U256}, -}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_sol_types::SolInterface; use rand::Rng; -use rundler_provider::{EntryPoint, L1GasProvider, Provider, SimulationProvider}; +use rundler_contracts::v0_6::{ + CallGasEstimationProxy::{ + self, estimateCallGasCall, testCallGasCall, CallGasEstimationProxyCalls, + EstimateCallGasArgs, + }, + ENTRY_POINT_V0_6_DEPLOYED_BYTECODE, +}; +use rundler_provider::{ + AccountOverride, EntryPoint, EvmProvider, L1GasProvider, SimulationProvider, StateOverride, +}; use rundler_types::{ chain::ChainSpec, - contracts::{ - v0_6::call_gas_estimation_proxy::{ - EstimateCallGasArgs, EstimateCallGasCall, TestCallGasCall, - CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE, - }, - ENTRY_POINT_V0_6_DEPLOYED_BYTECODE, - }, v0_6::{UserOperation, UserOperationOptionalGas}, GasEstimate, }; -use rundler_utils::{eth, math}; +use rundler_utils::math; use tokio::join; use super::{ @@ -45,31 +44,31 @@ use crate::{ }; /// Gas estimator implementation -#[derive(Debug)] -pub struct GasEstimator { +pub struct GasEstimator { chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, - fee_estimator: FeeEstimator

, + fee_estimator: F, verification_gas_estimator: VGE, call_gas_estimator: CGE, } #[async_trait::async_trait] -impl GasEstimatorTrait for GasEstimator +impl GasEstimatorTrait for GasEstimator where - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider + L1GasProvider, VGE: VerificationGasEstimator, CGE: CallGasEstimator, + F: FeeEstimator, { type UserOperationOptionalGas = UserOperationOptionalGas; async fn estimate_op_gas( &self, op: UserOperationOptionalGas, - state_override: spoof::State, + state_override: StateOverride, ) -> Result { self.check_provided_limits(&op)?; @@ -84,15 +83,14 @@ where let full_op = UserOperation { pre_verification_gas, ..op.clone().into_user_operation( - self.settings.max_call_gas.into(), - self.settings.max_verification_gas.into(), + self.settings.max_call_gas, + self.settings.max_verification_gas, ) }; let verification_future = - self.estimate_verification_gas(&op, &full_op, block_hash, &state_override); - let call_future = - self.estimate_call_gas(&op, full_op.clone(), block_hash, state_override.clone()); + self.estimate_verification_gas(&op, &full_op, block_hash, state_override.clone()); + let call_future = self.estimate_call_gas(&op, full_op.clone(), block_hash, state_override); // Not try_join! because then the output is nondeterministic if both // verification and call estimation fail. @@ -109,9 +107,9 @@ where 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() { + if gas_limit > self.settings.max_total_execution_gas { return Err(GasEstimationError::GasTotalTooLarge( - gas_limit.as_u64(), + gas_limit, self.settings.max_total_execution_gas, )); } @@ -125,27 +123,29 @@ where } } -impl +impl GasEstimator< P, E, VerificationGasEstimatorImpl, CallGasEstimatorImpl, + F, > where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + SimulationProvider + L1GasProvider + Clone, + F: FeeEstimator, { /// Create a new gas estimator pub fn new( chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, - fee_estimator: FeeEstimator

, + fee_estimator: F, ) -> Self { if let Some(err) = settings.validate() { panic!("Invalid gas estimator settings: {}", err); @@ -153,7 +153,7 @@ where let verification_gas_estimator = VerificationGasEstimatorImpl::new( chain_spec.clone(), - Arc::clone(&provider), + provider.clone(), entry_point.clone(), settings, ); @@ -174,19 +174,20 @@ where } } -impl GasEstimator +impl GasEstimator where - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider + L1GasProvider, VGE: VerificationGasEstimator, CGE: CallGasEstimator, + F: FeeEstimator, { fn check_provided_limits( &self, optional_op: &UserOperationOptionalGas, ) -> Result<(), GasEstimationError> { if let Some(pvg) = optional_op.pre_verification_gas { - if pvg > self.settings.max_verification_gas.into() { + if pvg > self.settings.max_verification_gas { return Err(GasEstimationError::GasFieldTooLarge( "preVerificationGas", self.settings.max_verification_gas, @@ -194,7 +195,7 @@ where } } if let Some(vl) = optional_op.verification_gas_limit { - if vl > self.settings.max_verification_gas.into() { + if vl > self.settings.max_verification_gas { return Err(GasEstimationError::GasFieldTooLarge( "verificationGasLimit", self.settings.max_verification_gas, @@ -202,7 +203,7 @@ where } } if let Some(cl) = optional_op.call_gas_limit { - if cl > self.settings.max_call_gas.into() { + if cl > self.settings.max_call_gas { return Err(GasEstimationError::GasFieldTooLarge( "callGasLimit", self.settings.max_call_gas, @@ -217,12 +218,12 @@ where &self, optional_op: &UserOperationOptionalGas, full_op: &UserOperation, - block_hash: H256, - state_override: &spoof::State, - ) -> Result { + block_hash: B256, + state_override: StateOverride, + ) -> Result { // if set and non-zero, don't estimate if let Some(vl) = optional_op.verification_gas_limit { - if vl != U256::zero() { + if vl != 0 { // No need to do an extra simulation here, if the user provides a value that is // insufficient it will cause a revert during call gas estimation (or simulation). return Ok(vl); @@ -232,25 +233,24 @@ where fn get_op_with_limit(op: UserOperation, args: GetOpWithLimitArgs) -> UserOperation { let GetOpWithLimitArgs { gas, fee } = args; UserOperation { - verification_gas_limit: gas.into(), - max_fee_per_gas: fee.into(), - max_priority_fee_per_gas: fee.into(), - call_gas_limit: U256::zero(), + verification_gas_limit: gas, + max_fee_per_gas: fee, + max_priority_fee_per_gas: fee, + call_gas_limit: 0, ..op } } - let verification_gas_limit: U256 = self + let verification_gas_limit: u128 = self .verification_gas_estimator .estimate_verification_gas( full_op, block_hash, state_override, - self.settings.max_verification_gas.into(), + self.settings.max_verification_gas, get_op_with_limit, ) - .await - .map(|gas_u128| gas_u128.into())?; + .await?; // Add a buffer to the verification gas limit. Add 10% or 2000 gas, whichever is larger // to ensure we get at least a 2000 gas buffer. Cap at the max verification gas. @@ -261,7 +261,7 @@ where ), verification_gas_limit + simulation::v0_6::REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER, ) - .min(self.settings.max_verification_gas.into()); + .min(self.settings.max_verification_gas); Ok(verification_gas_limit) } @@ -269,24 +269,22 @@ where async fn estimate_pre_verification_gas( &self, optional_op: &UserOperationOptionalGas, - ) -> Result { + ) -> Result { if let Some(pvg) = optional_op.pre_verification_gas { - if pvg != U256::zero() { + if pvg != 0 { return Ok(pvg); } } // If not using calldata pre-verification gas, return 0 let gas_price = if !self.chain_spec.calldata_pre_verification_gas { - U256::zero() + 0 } else { // If the user provides fees, use them, otherwise use the current bundle fees let (bundle_fees, base_fee) = self.fee_estimator.required_bundle_fees(None).await?; if let (Some(max_fee), Some(prio_fee)) = ( - optional_op.max_fee_per_gas.filter(|fee| !fee.is_zero()), - optional_op - .max_priority_fee_per_gas - .filter(|fee| !fee.is_zero()), + optional_op.max_fee_per_gas.filter(|fee| *fee != 0), + optional_op.max_priority_fee_per_gas.filter(|fee| *fee != 0), ) { cmp::min(max_fee, base_fee.saturating_add(prio_fee)) } else { @@ -298,12 +296,12 @@ where &self.chain_spec, &self.entry_point, &optional_op.max_fill( - self.settings.max_call_gas.into(), - self.settings.max_verification_gas.into(), + self.settings.max_call_gas, + self.settings.max_verification_gas, ), &optional_op.random_fill( - self.settings.max_call_gas.into(), - self.settings.max_verification_gas.into(), + self.settings.max_call_gas, + self.settings.max_verification_gas, ), gas_price, ) @@ -314,12 +312,12 @@ where &self, optional_op: &UserOperationOptionalGas, full_op: UserOperation, - block_hash: H256, - state_override: spoof::State, - ) -> Result { + block_hash: B256, + state_override: StateOverride, + ) -> Result { // if set and non-zero, don't estimate if let Some(cl) = optional_op.call_gas_limit { - if cl != U256::zero() { + if cl != 0 { // The user provided a non-zero value, simulate once self.call_gas_estimator .simulate_handle_op_with_result(full_op, block_hash, state_override) @@ -328,16 +326,15 @@ where } } - let call_gas_limit: U256 = self + let call_gas_limit = self .call_gas_estimator .estimate_call_gas(full_op, block_hash, state_override) - .await? - .into(); + .await?; // Add a buffer to the call gas limit and clamp let call_gas_limit = call_gas_limit .add(super::CALL_GAS_BUFFER_VALUE) - .clamp(MIN_CALL_GAS_LIMIT.into(), self.settings.max_call_gas.into()); + .clamp(MIN_CALL_GAS_LIMIT, self.settings.max_call_gas); Ok(call_gas_limit) } @@ -351,27 +348,35 @@ pub struct CallGasEstimatorSpecializationV06; impl CallGasEstimatorSpecialization for CallGasEstimatorSpecializationV06 { type UO = UserOperation; - fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut spoof::State) { + fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut StateOverride) { // For an explanation of what's going on here, see the comment at the // top of `CallGasEstimationProxy.sol`. // Use a random address for the moved entry point so that users can't // intentionally get bad estimates by interacting with the hardcoded // address. let moved_entry_point_address: Address = rand::thread_rng().gen(); - let estimation_proxy_bytecode = - estimation_proxy_bytecode_with_target(moved_entry_point_address); - state_override - .account(moved_entry_point_address) - .code(ENTRY_POINT_V0_6_DEPLOYED_BYTECODE.clone()); - state_override - .account(ep_to_override) - .code(estimation_proxy_bytecode); + state_override.insert( + moved_entry_point_address, + AccountOverride { + code: Some(ENTRY_POINT_V0_6_DEPLOYED_BYTECODE.clone()), + ..Default::default() + }, + ); + state_override.insert( + ep_to_override, + AccountOverride { + code: Some(estimation_proxy_bytecode_with_target( + moved_entry_point_address, + )), + ..Default::default() + }, + ); } fn get_op_with_no_call_gas(&self, op: Self::UO) -> Self::UO { UserOperation { - call_gas_limit: 0.into(), - max_fee_per_gas: 0.into(), + call_gas_limit: 0, + max_fee_per_gas: 0, ..op } } @@ -379,29 +384,33 @@ impl CallGasEstimatorSpecialization for CallGasEstimatorSpecializationV06 { fn get_estimate_call_gas_calldata( &self, callless_op: Self::UO, - min_gas: U256, - max_gas: U256, - rounding: U256, + min_gas: u128, + max_gas: u128, + rounding: u128, is_continuation: bool, ) -> Bytes { - eth::call_data_of( - EstimateCallGasCall::selector(), - (EstimateCallGasArgs { - call_data: callless_op.call_data, + let call = CallGasEstimationProxyCalls::estimateCallGas(estimateCallGasCall { + args: EstimateCallGasArgs { + callData: callless_op.call_data, sender: callless_op.sender, - min_gas, - max_gas, - rounding, - is_continuation, - },), - ) + minGas: U256::from(min_gas), + maxGas: U256::from(max_gas), + rounding: U256::from(rounding), + isContinuation: is_continuation, + }, + }); + + call.abi_encode().into() } - fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: U256) -> Bytes { - eth::call_data_of( - TestCallGasCall::selector(), - (callless_op.sender, callless_op.call_data, call_gas_limit), - ) + fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: u128) -> Bytes { + let call = CallGasEstimationProxyCalls::testCallGas(testCallGasCall { + sender: callless_op.sender, + callData: callless_op.call_data, + callGasLimit: U256::from(call_gas_limit), + }); + + call.abi_encode().into() } } @@ -415,37 +424,33 @@ const PROXY_TARGET_OFFSET: usize = 163; // Replaces the address of the proxy target where it appears in the proxy // bytecode so we don't need the same fixed address every time. fn estimation_proxy_bytecode_with_target(target: Address) -> Bytes { - let mut vec = CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE.to_vec(); - vec[PROXY_TARGET_OFFSET..PROXY_TARGET_OFFSET + 20].copy_from_slice(target.as_bytes()); + let mut vec = CallGasEstimationProxy::DEPLOYED_BYTECODE.to_vec(); + let bytes: [u8; 20] = target.into(); + vec[PROXY_TARGET_OFFSET..PROXY_TARGET_OFFSET + 20].copy_from_slice(&bytes); vec.into() } #[cfg(test)] mod tests { + use std::sync::{Arc, Mutex}; + + use alloy_primitives::{hex, uint}; + use alloy_sol_types::{Revert, SolCall, SolError, SolValue}; use anyhow::anyhow; - use ethers::{ - abi::{AbiEncode, Address}, - contract::EthCall, - types::{U128, U64}, - utils::hex, + use gas::MockFeeEstimator; + use rundler_contracts::v0_6::{IEntryPoint, UserOperation as ContractUserOperation}; + use rundler_provider::{ + EvmCall, ExecutionResult, GasUsedResult, MockEntryPointV0_6, MockEvmProvider, }; - use rundler_provider::{ExecutionResult, MockEntryPointV0_6, MockProvider, SimulateOpCallData}; use rundler_types::{ chain::L1GasOracleContractType, - contracts::{ - utils::get_gas_used::GasUsedResult, - v0_6::{ - call_gas_estimation_proxy::{ - EstimateCallGasContinuation, EstimateCallGasResult, EstimateCallGasRevertAtMax, - TestCallGasResult, - }, - i_entry_point, - }, - }, v0_6::{UserOperation, UserOperationOptionalGas}, - UserOperation as UserOperationTrait, ValidationRevert, + GasFees, UserOperation as UserOperationTrait, ValidationRevert, + }; + use CallGasEstimationProxy::{ + EstimateCallGasContinuation, EstimateCallGasResult, EstimateCallGasRevertAtMax, + TestCallGasResult, }; - use rundler_utils::eth::{self, ContractRevertError}; use super::*; use crate::{ @@ -454,9 +459,13 @@ mod tests { VERIFICATION_GAS_BUFFER_PERCENT, }, simulation::v0_6::REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER, - PriorityFeeMode, VerificationGasEstimatorImpl, + VerificationGasEstimatorImpl, }; + // Due to https://github.com/asomers/mockall/blob/master/mockall/examples/synchronization.rs + // sync all tests that rely on MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + static MTX: Mutex<()> = Mutex::new(()); + // Gas overhead defaults const FIXED: u32 = 21000; const PER_USER_OP: u32 = 18300; @@ -465,53 +474,52 @@ mod tests { // Alises for complex types (which also satisfy Clippy) type VerificationGasEstimatorWithMocks = - VerificationGasEstimatorImpl>; + VerificationGasEstimatorImpl, Arc>; type CallGasEstimatorWithMocks = CallGasEstimatorImpl, CallGasEstimatorSpecializationV06>; type GasEstimatorWithMocks = GasEstimator< - MockProvider, + Arc, Arc, VerificationGasEstimatorWithMocks, CallGasEstimatorWithMocks, + MockFeeEstimator, >; - fn create_base_config() -> (MockEntryPointV0_6, MockProvider) { + fn create_base_config() -> (MockEntryPointV0_6, MockEvmProvider) { let mut entry = MockEntryPointV0_6::new(); - let provider = MockProvider::new(); + let provider = MockEvmProvider::new(); // Fill in concrete implementations of call data and // `simulation_should_revert` entry - .expect_get_simulate_op_call_data() - .returning(|op, spoofed_state| { - let call_data = eth::call_data_of( - i_entry_point::SimulateHandleOpCall::selector(), - (op.clone(), Address::zero(), Bytes::new()), - ); - SimulateOpCallData { - call_data, - spoofed_state: spoofed_state.clone(), + .expect_get_simulate_handle_op_call() + .returning(|op, state_override| { + let data = IEntryPoint::simulateHandleOpCall { + op: op.into(), + target: Address::ZERO, + targetCallData: Bytes::new(), + } + .abi_encode() + .into(); + + EvmCall { + to: Address::ZERO, + data, + value: U256::ZERO, + state_override, } }); entry.expect_simulation_should_revert().return_const(true); - entry.expect_address().return_const(Address::zero()); + entry.expect_address().return_const(Address::ZERO); (entry, provider) } - fn create_fee_estimator(provider: Arc) -> FeeEstimator { - FeeEstimator::new( - &ChainSpec::default(), - provider, - PriorityFeeMode::BaseFeePercent(0), - 0, - ) - } - fn create_custom_estimator( chain_spec: ChainSpec, - provider: MockProvider, + provider: MockEvmProvider, + fee_estimator: MockFeeEstimator, entry: MockEntryPointV0_6, settings: Settings, ) -> GasEstimatorWithMocks { @@ -521,16 +529,16 @@ mod tests { Arc::clone(&provider), Arc::new(entry), settings, - create_fee_estimator(provider), + fee_estimator, ) } - const TEST_MAX_GAS_LIMITS: u64 = 10000000000; - const TEST_FEE: U256 = U256([1000, 0, 0, 0]); + const TEST_MAX_GAS_LIMITS: u128 = 10000000000; + const TEST_FEE: u128 = 1000; fn create_estimator( entry: MockEntryPointV0_6, - provider: MockProvider, + provider: MockEvmProvider, ) -> (GasEstimatorWithMocks, Settings) { let settings = Settings { max_verification_gas: TEST_MAX_GAS_LIMITS, @@ -541,14 +549,20 @@ mod tests { max_simulate_handle_ops_gas: TEST_MAX_GAS_LIMITS, verification_estimation_gas_fee: 1_000_000_000_000, }; - let estimator = create_custom_estimator(ChainSpec::default(), provider, entry, settings); + let estimator = create_custom_estimator( + ChainSpec::default(), + provider, + MockFeeEstimator::new(), + entry, + settings, + ); (estimator, settings) } - fn demo_user_op_optional_gas(pvg: Option) -> UserOperationOptionalGas { + fn demo_user_op_optional_gas(pvg: Option) -> UserOperationOptionalGas { UserOperationOptionalGas { - sender: Address::zero(), - nonce: U256::zero(), + sender: Address::ZERO, + nonce: U256::ZERO, init_code: Bytes::new(), call_data: Bytes::new(), call_gas_limit: None, @@ -563,15 +577,15 @@ mod tests { fn demo_user_op() -> UserOperation { UserOperation { - sender: Address::zero(), - nonce: U256::zero(), + sender: Address::ZERO, + nonce: U256::ZERO, init_code: Bytes::new(), call_data: Bytes::new(), - call_gas_limit: U256::from(1000), - verification_gas_limit: U256::from(1000), - pre_verification_gas: U256::from(1000), - max_fee_per_gas: U256::from(1000), - max_priority_fee_per_gas: U256::from(1000), + call_gas_limit: 100, + verification_gas_limit: 1000, + pre_verification_gas: 1000, + max_fee_per_gas: 1000, + max_priority_fee_per_gas: 1000, paymaster_and_data: Bytes::new(), signature: Bytes::new(), } @@ -580,7 +594,9 @@ mod tests { #[tokio::test] async fn test_calc_pre_verification_input() { let (entry, mut provider) = create_base_config(); - provider.expect_get_base_fee().returning(|| Ok(TEST_FEE)); + provider + .expect_get_pending_base_fee() + .returning(|| Ok(TEST_FEE)); provider .expect_get_max_priority_fee() .returning(|| Ok(TEST_FEE)); @@ -592,39 +608,33 @@ mod tests { .await .unwrap(); - let u_o = user_op.max_fill( - settings.max_call_gas.into(), - settings.max_verification_gas.into(), - ); - - let u_o_encoded = u_o.encode(); - let length_in_words = (u_o_encoded.len() + 31) / 32; + let uo = user_op.max_fill(settings.max_call_gas, settings.max_verification_gas); - //computed by mapping through the calldata bytes - //and adding to the value either 4 or 16 depending - //if the byte is non-zero - let call_data_cost = 3936; + let cuo_bytes = ContractUserOperation::from(uo).abi_encode(); + let length_in_words = (cuo_bytes.len() + 31) / 32; + let mut call_data_cost = 0; + for b in cuo_bytes.iter() { + if *b != 0 { + call_data_cost += 16; + } else { + call_data_cost += 4; + } + } - let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + let result = FIXED / BUNDLE_SIZE + call_data_cost - + U256::from(PER_USER_OP) - + U256::from(PER_USER_OP_WORD) * length_in_words; - - let dynamic_gas = 0; + + PER_USER_OP + + PER_USER_OP_WORD * (length_in_words as u32); - assert_eq!(result + dynamic_gas, estimation); + assert_eq!(result as u128, estimation); } #[tokio::test] async fn test_calc_pre_verification_input_arbitrum() { - let (mut entry, mut provider) = create_base_config(); + let (mut entry, provider) = create_base_config(); entry .expect_calc_l1_gas() .returning(|_a, _b, _c| Ok(TEST_FEE)); - provider.expect_get_base_fee().returning(|| Ok(TEST_FEE)); - provider - .expect_get_max_priority_fee() - .returning(|| Ok(TEST_FEE)); let settings = Settings { max_verification_gas: 10000000000, @@ -644,12 +654,24 @@ mod tests { ..Default::default() }; let provider = Arc::new(provider); + + let mut fee_estimator = MockFeeEstimator::new(); + fee_estimator.expect_required_bundle_fees().returning(|_| { + Ok(( + GasFees { + max_fee_per_gas: TEST_FEE, + max_priority_fee_per_gas: TEST_FEE, + }, + TEST_FEE, + )) + }); + let estimator = GasEstimator::new( cs.clone(), Arc::clone(&provider), Arc::new(entry), settings, - create_fee_estimator(provider), + fee_estimator, ); let user_op = demo_user_op_optional_gas(None); @@ -658,41 +680,37 @@ mod tests { .await .unwrap(); - let u_o = user_op.max_fill( - settings.max_call_gas.into(), - settings.max_verification_gas.into(), - ); - - let u_o_encoded = u_o.encode(); - let length_in_words = (u_o_encoded.len() + 31) / 32; + let uo = user_op.max_fill(settings.max_call_gas, settings.max_verification_gas); - //computed by mapping through the calldata bytes - //and adding to the value either 4 or 16 depending - //if the byte is non-zero - let call_data_cost = 3936; + let cuo_bytes = ContractUserOperation::from(uo).abi_encode(); + let length_in_words = (cuo_bytes.len() + 31) / 32; + let mut call_data_cost = 0; + for b in cuo_bytes.iter() { + if *b != 0 { + call_data_cost += 16; + } else { + call_data_cost += 4; + } + } - let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + let result = FIXED / BUNDLE_SIZE + call_data_cost - + U256::from(PER_USER_OP) - + U256::from(PER_USER_OP_WORD) * length_in_words; + + PER_USER_OP + + PER_USER_OP_WORD * (length_in_words as u32); //Arbitrum dynamic gas - let dynamic_gas = 1000; + let dynamic_gas: u128 = 1000; - assert_eq!(result + dynamic_gas, estimation); + assert_eq!(result as u128 + dynamic_gas, estimation); } #[tokio::test] async fn test_calc_pre_verification_input_op() { - let (mut entry, mut provider) = create_base_config(); + let (mut entry, provider) = create_base_config(); entry .expect_calc_l1_gas() .returning(|_a, _b, _c| Ok(TEST_FEE)); - provider.expect_get_base_fee().returning(|| Ok(TEST_FEE)); - provider - .expect_get_max_priority_fee() - .returning(|| Ok(TEST_FEE)); let settings = Settings { max_verification_gas: 10000000000, @@ -711,7 +729,18 @@ mod tests { l1_gas_oracle_contract_type: L1GasOracleContractType::OptimismBedrock, ..Default::default() }; - let estimator = create_custom_estimator(cs, provider, entry, settings); + let mut fee_estimator = MockFeeEstimator::new(); + fee_estimator.expect_required_bundle_fees().returning(|_| { + Ok(( + GasFees { + max_fee_per_gas: TEST_FEE, + max_priority_fee_per_gas: TEST_FEE, + }, + TEST_FEE, + )) + }); + + let estimator = create_custom_estimator(cs, provider, fee_estimator, entry, settings); let user_op = demo_user_op_optional_gas(None); let estimation = estimator @@ -719,50 +748,50 @@ mod tests { .await .unwrap(); - let u_o = user_op.max_fill( - settings.max_call_gas.into(), - settings.max_verification_gas.into(), - ); + let uo = user_op.max_fill(settings.max_call_gas, settings.max_verification_gas); - let u_o_encoded: Bytes = u_o.encode().into(); - let length_in_words = (u_o_encoded.len() + 31) / 32; - - //computed by mapping through the calldata bytes - //and adding to the value either 4 or 16 depending - //if the byte is non-zero - let call_data_cost = 3936; + let cuo_bytes = ContractUserOperation::from(uo).abi_encode(); + let length_in_words = (cuo_bytes.len() + 31) / 32; + let mut call_data_cost = 0; + for b in cuo_bytes.iter() { + if *b != 0 { + call_data_cost += 16; + } else { + call_data_cost += 4; + } + } - let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + let result = FIXED / BUNDLE_SIZE + call_data_cost - + U256::from(PER_USER_OP) - + U256::from(PER_USER_OP_WORD) * length_in_words; + + PER_USER_OP + + PER_USER_OP_WORD * (length_in_words as u32); //OP dynamic gas let dynamic_gas = 1000; - assert_eq!(result + dynamic_gas, estimation); + assert_eq!(result + dynamic_gas, estimation as u32); } #[tokio::test] async fn test_binary_search_verification_gas() { let (mut entry, mut provider) = create_base_config(); - let gas_usage = 10_000.into(); - - entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); + let gas_usage = 10_000; + + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(move |op, _b, _c, _d, _e, _f| { if op.total_verification_gas_limit() < gas_usage { return Ok(Err(ValidationRevert::EntryPoint("AA23".to_string()))); @@ -770,31 +799,29 @@ mod tests { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: gas_usage, - num_rounds: 10.into(), + gasEstimate: U256::from(gas_usage), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() })) }); - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: gas_usage * 2, - success: false, - result: Bytes::new(), - }) - }); + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: U256::from(gas_usage * 2), + success: false, + result: Bytes::new(), + }) + }); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await .unwrap(); @@ -810,50 +837,48 @@ mod tests { async fn test_binary_search_verification_gas_should_not_overflow() { let (mut entry, mut provider) = create_base_config(); + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); - entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(10000), - num_rounds: U256::from(10), + gasEstimate: U256::from(10000), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() })) }); - // this gas used number is larger than a u64 max number so we need to + // this gas used number is larger than a u128 max number so we need to // check for this overflow - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: U256::from(18446744073709551616_u128), - success: false, - result: Bytes::new(), - }) - }); + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: uint!(1000000000000000000000000000000000000000_U256), + success: false, + result: Bytes::new(), + }) + }); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await .err(); @@ -867,27 +892,27 @@ mod tests { async fn test_binary_search_verification_gas_success_field() { let (mut entry, mut provider) = create_base_config(); + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); - entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(10000), - num_rounds: U256::from(10), + gasEstimate: U256::from(10000), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -896,21 +921,19 @@ mod tests { // the success field should not be true as the // call should always revert - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: U256::from(20000), - success: true, - result: Bytes::new(), - }) - }); + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: U256::from(20000), + success: true, + result: Bytes::new(), + }) + }); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await; assert!(estimation.is_err()); @@ -921,43 +944,42 @@ mod tests { let (mut entry, mut provider) = create_base_config(); // checking for this simulated revert + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Err(ValidationRevert::EntryPoint( + "Error with reverted message".to_string(), + ))) + }); + entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Err(ValidationRevert::EntryPoint( - "Error with reverted message".to_string(), - )) - }); - entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(100), - num_rounds: U256::from(10), + gasEstimate: U256::from(100), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() })) }); - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: U256::from(20000), - success: false, - result: Bytes::new(), - }) - }); + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: U256::from(20000), + success: false, + result: Bytes::new(), + }) + }); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await; assert!(estimation.is_err()); @@ -967,39 +989,37 @@ mod tests { async fn test_binary_search_verification_gas_invalid_spoof() { let (mut entry, mut provider) = create_base_config(); - entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); //this mocked response causes error entry - .expect_call_spoofed_simulate_op() - .returning(|_a, _b, _c, _d, _e, _f| Err(anyhow!("Invalid spoof error"))); - - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: U256::from(20000), - success: false, - result: Bytes::new(), - }) - }); + .expect_simulate_handle_op() + .returning(|_a, _b, _c, _d, _e, _f| Err(anyhow!("Invalid spoof error").into())); + + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: U256::from(20000), + success: false, + result: Bytes::new(), + }) + }); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await; assert!(estimation.is_err()); @@ -1009,29 +1029,29 @@ mod tests { async fn test_binary_search_verification_gas_success_response() { let (mut entry, mut provider) = create_base_config(); - entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); // this should always revert instead of return success entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(10000), - num_rounds: U256::from(10), + gasEstimate: U256::from(10000), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1040,15 +1060,13 @@ mod tests { provider .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Err(anyhow::anyhow!("This should always revert").into()) - }); + .returning(move |_a| Err(anyhow::anyhow!("This should always revert").into())); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let user_op = demo_user_op(); let estimation = estimator - .estimate_verification_gas(&optional_op, &user_op, H256::zero(), &spoof::state()) + .estimate_verification_gas(&optional_op, &user_op, B256::ZERO, StateOverride::default()) .await; assert!(estimation.is_err()); @@ -1058,16 +1076,16 @@ mod tests { async fn test_estimate_call_gas() { let (mut entry, mut provider) = create_base_config(); - let gas_estimate = U256::from(100_000); + let gas_estimate = 100_000; entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(move |_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate, - num_rounds: U256::from(10), + gasEstimate: U256::from(gas_estimate), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1082,7 +1100,7 @@ mod tests { let optional_op = demo_user_op_optional_gas(None); let user_op = demo_user_op(); let estimation = estimator - .estimate_call_gas(&optional_op, user_op, H256::zero(), spoof::state()) + .estimate_call_gas(&optional_op, user_op, B256::ZERO, StateOverride::default()) .await .unwrap(); @@ -1098,13 +1116,13 @@ mod tests { // return an invalid response for the ExecutionResult // for a successful gas estimation entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasRevertAtMax { - revert_data: Bytes::new(), + revertData: Bytes::new(), } - .encode() + .abi_encode() .into(), target_success: false, ..Default::default() @@ -1119,7 +1137,7 @@ mod tests { let user_op = demo_user_op(); let estimation = estimator .call_gas_estimator - .estimate_call_gas(user_op, H256::zero(), spoof::state()) + .estimate_call_gas(user_op, B256::ZERO, StateOverride::default()) .await .err() .unwrap(); @@ -1135,15 +1153,15 @@ mod tests { let (mut entry, mut provider) = create_base_config(); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasContinuation { - min_gas: U256::from(100), - max_gas: U256::from(100000), - num_rounds: U256::from(10), + minGas: U256::from(100), + maxGas: U256::from(100000), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: false, ..Default::default() @@ -1151,14 +1169,14 @@ mod tests { }) .times(1); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(|_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(200), - num_rounds: U256::from(10), + gasEstimate: U256::from(200), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1174,23 +1192,23 @@ mod tests { let user_op = demo_user_op(); let estimation = estimator .call_gas_estimator - .estimate_call_gas(user_op, H256::zero(), spoof::state()) + .estimate_call_gas(user_op, B256::ZERO, StateOverride::default()) .await .unwrap(); // on the second loop of the estimate gas continuation // I update the spoofed value to 200 - assert_eq!(estimation, U128::from(200)); + assert_eq!(estimation, 200); } #[tokio::test] async fn test_estimation_optional_gas_used() { let (mut entry, mut provider) = create_base_config(); - let gas_usage = 10_000.into(); + let gas_usage = 10_000; entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(move |op, _b, _c, _d, _e, _f| { if op.total_verification_gas_limit() < gas_usage { return Ok(Err(ValidationRevert::EntryPoint("AA23".to_string()))); @@ -1198,60 +1216,61 @@ mod tests { Ok(Ok(ExecutionResult { target_result: EstimateCallGasResult { - gas_estimate: U256::from(10000), - num_rounds: U256::from(10), + gasEstimate: U256::from(10000), + numRounds: U256::from(10), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() })) }); - entry - .expect_decode_simulate_handle_ops_revert() - .returning(|_a| { - Ok(ExecutionResult { - pre_op_gas: U256::from(10000), - paid: U256::from(100000), - valid_after: 100000000000.into(), - valid_until: 100000000001.into(), - target_success: true, - target_result: Bytes::new(), - }) - }); + + let _m = MTX.lock(); + let ctx = MockEntryPointV0_6::decode_simulate_handle_ops_revert_context(); + ctx.expect().returning(|_a| { + Ok(Ok(ExecutionResult { + pre_op_gas: 10000, + paid: U256::from(100000), + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), + target_success: true, + target_result: Bytes::new(), + })) + }); provider .expect_get_code() .returning(|_a, _b| Ok(Bytes::new())); provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); - provider - .expect_get_gas_used() - .returning(move |_a, _b, _c, _d| { - Ok(GasUsedResult { - gas_used: gas_usage, - success: false, - result: Bytes::new(), - }) - }); + .returning(|| Ok((B256::ZERO, 0))); + provider.expect_get_gas_used().returning(move |_a| { + Ok(GasUsedResult { + gasUsed: U256::from(gas_usage), + success: false, + result: Bytes::new(), + }) + }); - provider.expect_get_base_fee().returning(|| Ok(TEST_FEE)); + provider + .expect_get_pending_base_fee() + .returning(|| Ok(TEST_FEE)); provider .expect_get_max_priority_fee() .returning(|| Ok(TEST_FEE)); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); + let optional_op = demo_user_op_optional_gas(Some(10000)); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .unwrap(); // this should be a pass through - assert_eq!(estimation.pre_verification_gas, U256::from(10000)); + assert_eq!(estimation.pre_verification_gas, 10000); // gas used increased by 10% let expected = gas_usage + ChainSpec::default().deposit_transfer_overhead; @@ -1264,10 +1283,7 @@ mod tests { ); // input gas limit clamped with the set limit in settings and constant MIN - assert_eq!( - estimation.call_gas_limit, - U256::from(10000) + CALL_GAS_BUFFER_VALUE - ); + assert_eq!(estimation.call_gas_limit, 10000 + CALL_GAS_BUFFER_VALUE); } #[test] @@ -1287,7 +1303,13 @@ mod tests { verification_estimation_gas_fee: 1_000_000_000_000, }; - create_custom_estimator(ChainSpec::default(), provider, entry, settings); + create_custom_estimator( + ChainSpec::default(), + provider, + MockFeeEstimator::new(), + entry, + settings, + ); } #[tokio::test] @@ -1295,10 +1317,10 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(TEST_MAX_GAS_LIMITS + 1))); + let optional_op = demo_user_op_optional_gas(Some(TEST_MAX_GAS_LIMITS + 1)); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -1314,11 +1336,11 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let mut optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); - optional_op.verification_gas_limit = Some(U256::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.verification_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -1334,11 +1356,11 @@ mod tests { let (entry, provider) = create_base_config(); 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(U256::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -1355,18 +1377,18 @@ mod tests { provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_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(), + gasUsed: U256::ZERO, + revertData: Bytes::new(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1375,12 +1397,12 @@ mod tests { 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(U256::from(10000)); - optional_op.verification_gas_limit = Some(U256::from(10000)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(10000); + optional_op.verification_gas_limit = Some(10000); let estimation = estimator - .estimate_op_gas(optional_op.clone(), spoof::state()) + .estimate_op_gas(optional_op.clone(), StateOverride::default()) .await .unwrap(); @@ -1404,23 +1426,23 @@ mod tests { provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); let revert_msg = "test revert".to_string(); - let err = ContractRevertError { + let err = Revert { reason: revert_msg.clone(), }; entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(move |_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: TestCallGasResult { success: false, - gas_used: 0.into(), - revert_data: err.clone().encode().into(), + gasUsed: U256::ZERO, + revertData: err.clone().abi_encode().into(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1429,12 +1451,12 @@ mod tests { 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(U256::from(10000)); - optional_op.verification_gas_limit = Some(U256::from(10000)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(10000); + optional_op.verification_gas_limit = Some(10000); let estimation_error = estimator - .estimate_op_gas(optional_op.clone(), spoof::state()) + .estimate_op_gas(optional_op.clone(), StateOverride::default()) .await .err() .unwrap(); @@ -1451,18 +1473,18 @@ mod tests { provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_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(), + gasUsed: U256::ZERO, + revertData: Bytes::new(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -1471,12 +1493,12 @@ mod tests { 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 mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(TEST_MAX_GAS_LIMITS); + optional_op.verification_gas_limit = Some(TEST_MAX_GAS_LIMITS); let err = estimator - .estimate_op_gas(optional_op.clone(), spoof::state()) + .estimate_op_gas(optional_op.clone(), StateOverride::default()) .await .err() .unwrap(); @@ -1491,8 +1513,8 @@ mod tests { fn test_proxy_target_offset() { let proxy_target_bytes = hex::decode(PROXY_IMPLEMENTATION_ADDRESS_MARKER).unwrap(); let mut offsets = Vec::::new(); - for i in 0..CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE.len() - 20 { - if CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE[i..i + 20] == proxy_target_bytes { + for i in 0..CallGasEstimationProxy::DEPLOYED_BYTECODE.len() - 20 { + if CallGasEstimationProxy::DEPLOYED_BYTECODE[i..i + 20] == proxy_target_bytes { offsets.push(i); } } diff --git a/crates/sim/src/estimation/v0_7.rs b/crates/sim/src/estimation/v0_7.rs index 50ff1d22..d0d54192 100644 --- a/crates/sim/src/estimation/v0_7.rs +++ b/crates/sim/src/estimation/v0_7.rs @@ -11,27 +11,27 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{cmp, ops::Add, sync::Arc}; +use std::{cmp, ops::Add}; -use ethers::{ - contract::EthCall, - types::{spoof, Address, Bytes, H256, U128, U256}, -}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_sol_types::SolInterface; use rand::Rng; -use rundler_provider::{EntryPoint, L1GasProvider, Provider, SimulationProvider}; +use rundler_contracts::v0_7::{ + CallGasEstimationProxy::{ + estimateCallGasCall, testCallGasCall, CallGasEstimationProxyCalls, EstimateCallGasArgs, + }, + CALL_GAS_ESTIMATION_PROXY_V0_7_DEPLOYED_BYTECODE, + ENTRY_POINT_SIMULATIONS_V0_7_DEPLOYED_BYTECODE, +}; +use rundler_provider::{ + AccountOverride, EntryPoint, EvmProvider, L1GasProvider, SimulationProvider, StateOverride, +}; use rundler_types::{ chain::ChainSpec, - contracts::v0_7::{ - call_gas_estimation_proxy::{ - EstimateCallGasArgs, EstimateCallGasCall, TestCallGasCall, - CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE, - }, - entry_point_simulations::ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE, - }, v0_7::{UserOperation, UserOperationBuilder, UserOperationOptionalGas}, GasEstimate, }; -use rundler_utils::{eth, math}; +use rundler_utils::math; use tokio::join; use super::{estimate_verification_gas::GetOpWithLimitArgs, GasEstimationError, Settings}; @@ -41,24 +41,24 @@ use crate::{ }; /// Gas estimator for entry point v0.7 -#[derive(Debug)] -pub struct GasEstimator { +pub struct GasEstimator { chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, - fee_estimator: FeeEstimator

, + fee_estimator: F, verification_gas_estimator: VGE, call_gas_estimator: CGE, } #[async_trait::async_trait] -impl super::GasEstimator for GasEstimator +impl super::GasEstimator for GasEstimator where - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider + L1GasProvider, VGE: VerificationGasEstimator, CGE: CallGasEstimator, + F: FeeEstimator, { type UserOperationOptionalGas = UserOperationOptionalGas; @@ -67,7 +67,7 @@ where async fn estimate_op_gas( &self, op: UserOperationOptionalGas, - state_override: spoof::State, + state_override: StateOverride, ) -> Result { self.check_provided_limits(&op)?; @@ -86,19 +86,23 @@ where .clone() .into_user_operation_builder( &self.chain_spec, - settings.max_call_gas.into(), - settings.max_verification_gas.into(), - settings.max_paymaster_verification_gas.into(), + settings.max_call_gas, + settings.max_verification_gas, + settings.max_paymaster_verification_gas, ) .pre_verification_gas(pre_verification_gas) .build(); let verification_gas_future = - self.estimate_verification_gas(&op, &full_op, block_hash, &state_override); - let paymaster_verification_gas_future = - self.estimate_paymaster_verification_gas(&op, &full_op, block_hash, &state_override); + self.estimate_verification_gas(&op, &full_op, block_hash, state_override.clone()); + let paymaster_verification_gas_future = self.estimate_paymaster_verification_gas( + &op, + &full_op, + block_hash, + state_override.clone(), + ); let call_gas_future = - self.estimate_call_gas(&op, full_op.clone(), block_hash, state_override.clone()); + self.estimate_call_gas(&op, full_op.clone(), block_hash, state_override); // Not try_join! because then the output is nondeterministic if multiple calls fail. let timer = std::time::Instant::now(); @@ -121,45 +125,47 @@ where 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() { + if gas_limit > self.settings.max_total_execution_gas { return Err(GasEstimationError::GasTotalTooLarge( - gas_limit.as_u64(), + gas_limit, self.settings.max_total_execution_gas, )); } Ok(GasEstimate { pre_verification_gas, - call_gas_limit: call_gas_limit.into(), - verification_gas_limit: verification_gas_limit.into(), + call_gas_limit, + verification_gas_limit, paymaster_verification_gas_limit: op .paymaster - .map(|_| paymaster_verification_gas_limit.into()), + .map(|_| paymaster_verification_gas_limit), }) } } -impl +impl GasEstimator< P, E, VerificationGasEstimatorImpl, CallGasEstimatorImpl, + F, > where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + SimulationProvider + L1GasProvider + Clone, + F: FeeEstimator, { /// Create a new gas estimator pub fn new( chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, - fee_estimator: FeeEstimator

, + fee_estimator: F, ) -> Self { if let Some(err) = settings.validate() { panic!("Invalid gas estimator settings: {}", err); @@ -167,7 +173,7 @@ where let verification_gas_estimator = VerificationGasEstimatorImpl::new( chain_spec.clone(), - Arc::clone(&provider), + provider.clone(), entry_point.clone(), settings, ); @@ -190,19 +196,20 @@ where } } -impl GasEstimator +impl GasEstimator where - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider + L1GasProvider, VGE: VerificationGasEstimator, CGE: CallGasEstimator, + F: FeeEstimator, { fn check_provided_limits( &self, optional_op: &UserOperationOptionalGas, ) -> Result<(), GasEstimationError> { if let Some(pvg) = optional_op.pre_verification_gas { - if pvg > self.settings.max_verification_gas.into() { + if pvg > self.settings.max_verification_gas { return Err(GasEstimationError::GasFieldTooLarge( "preVerificationGas", self.settings.max_verification_gas, @@ -210,7 +217,7 @@ where } } if let Some(vl) = optional_op.verification_gas_limit { - if vl > self.settings.max_verification_gas.into() { + if vl > self.settings.max_verification_gas { return Err(GasEstimationError::GasFieldTooLarge( "verificationGasLimit", self.settings.max_verification_gas, @@ -218,7 +225,7 @@ where } } if let Some(vl) = optional_op.paymaster_verification_gas_limit { - if vl > self.settings.max_verification_gas.into() { + if vl > self.settings.max_verification_gas { return Err(GasEstimationError::GasFieldTooLarge( "paymasterVerificationGasLimit", self.settings.max_verification_gas, @@ -226,7 +233,7 @@ where } } if let Some(cl) = optional_op.call_gas_limit { - if cl > self.settings.max_call_gas.into() { + if cl > self.settings.max_call_gas { return Err(GasEstimationError::GasFieldTooLarge( "callGasLimit", self.settings.max_call_gas, @@ -234,7 +241,7 @@ where } } if let Some(cl) = optional_op.paymaster_post_op_gas_limit { - if cl > self.settings.max_call_gas.into() { + if cl > self.settings.max_call_gas { return Err(GasEstimationError::GasFieldTooLarge( "paymasterPostOpGasLimit", self.settings.max_call_gas, @@ -249,12 +256,12 @@ where &self, optional_op: &UserOperationOptionalGas, full_op: &UserOperation, - block_hash: H256, - state_override: &spoof::State, - ) -> Result { + block_hash: B256, + state_override: StateOverride, + ) -> Result { // if set and non-zero, don't estimate if let Some(vl) = optional_op.verification_gas_limit { - if vl != U128::zero() { + if vl != 0 { // No need to do an extra simulation here, if the user provides a value that is // insufficient it will cause a revert during call gas estimation (or simulation). return Ok(vl); @@ -267,8 +274,8 @@ where .verification_gas_limit(gas) .max_fee_per_gas(fee) .max_priority_fee_per_gas(fee) - .paymaster_post_op_gas_limit(U128::zero()) - .call_gas_limit(U128::zero()) + .paymaster_post_op_gas_limit(0) + .call_gas_limit(0) .build() }; @@ -278,7 +285,7 @@ where full_op, block_hash, state_override, - self.settings.max_verification_gas.into(), + self.settings.max_verification_gas, get_op_with_limit, ) .await?; @@ -287,7 +294,7 @@ where verification_gas_limit, super::VERIFICATION_GAS_BUFFER_PERCENT, ) - .min(self.settings.max_verification_gas.into()); + .min(self.settings.max_verification_gas); Ok(verification_gas_limit) } @@ -296,12 +303,12 @@ where &self, optional_op: &UserOperationOptionalGas, full_op: &UserOperation, - block_hash: H256, - state_override: &spoof::State, - ) -> Result { + block_hash: B256, + state_override: StateOverride, + ) -> Result { // If not using paymaster, return zero, else if set and non-zero, don't estimate and return value if let Some(pvl) = optional_op.verification_gas_limit { - if pvl != U128::zero() { + if pvl != 0 { return Ok(pvl); } } @@ -312,8 +319,8 @@ where .max_fee_per_gas(fee) .max_priority_fee_per_gas(fee) .paymaster_verification_gas_limit(gas) - .paymaster_post_op_gas_limit(U128::zero()) - .call_gas_limit(U128::zero()) + .paymaster_post_op_gas_limit(0) + .call_gas_limit(0) .build() }; @@ -323,7 +330,7 @@ where full_op, block_hash, state_override, - self.settings.max_paymaster_verification_gas.into(), + self.settings.max_paymaster_verification_gas, get_op_with_limit, ) .await?; @@ -332,7 +339,7 @@ where paymaster_verification_gas_limit, super::VERIFICATION_GAS_BUFFER_PERCENT, ) - .min(self.settings.max_verification_gas.into()); + .min(self.settings.max_verification_gas); Ok(paymaster_verification_gas_limit) } @@ -340,26 +347,24 @@ where async fn estimate_pre_verification_gas( &self, optional_op: &UserOperationOptionalGas, - ) -> Result { + ) -> Result { if let Some(pvg) = optional_op.pre_verification_gas { - if pvg != U256::zero() { + if pvg != 0 { return Ok(pvg); } } // If not using calldata pre-verification gas, return 0 let gas_price = if !self.chain_spec.calldata_pre_verification_gas { - U256::zero() + 0 } else { // If the user provides fees, use them, otherwise use the current bundle fees let (bundle_fees, base_fee) = self.fee_estimator.required_bundle_fees(None).await?; if let (Some(max_fee), Some(prio_fee)) = ( - optional_op.max_fee_per_gas.filter(|fee| !fee.is_zero()), - optional_op - .max_priority_fee_per_gas - .filter(|fee| !fee.is_zero()), + optional_op.max_fee_per_gas.filter(|fee| *fee != 0), + optional_op.max_priority_fee_per_gas.filter(|fee| *fee != 0), ) { - cmp::min(max_fee.into(), base_fee.saturating_add(prio_fee.into())) + cmp::min(max_fee, base_fee.saturating_add(prio_fee)) } else { base_fee.saturating_add(bundle_fees.max_priority_fee_per_gas) } @@ -379,12 +384,12 @@ where &self, optional_op: &UserOperationOptionalGas, full_op: UserOperation, - block_hash: H256, - state_override: spoof::State, - ) -> Result { + block_hash: B256, + state_override: StateOverride, + ) -> Result { // if set and non-zero, don't estimate if let Some(cl) = optional_op.call_gas_limit { - if cl != U128::zero() { + if cl != 0 { // The user provided a non-zero value, simulate once self.call_gas_estimator .simulate_handle_op_with_result(full_op, block_hash, state_override) @@ -401,7 +406,7 @@ where // Add a buffer to the call gas limit and clamp let call_gas_limit = call_gas_limit .add(super::CALL_GAS_BUFFER_VALUE) - .clamp(MIN_CALL_GAS_LIMIT, self.settings.max_call_gas.into()); + .clamp(MIN_CALL_GAS_LIMIT, self.settings.max_call_gas); Ok(call_gas_limit) } @@ -417,55 +422,68 @@ pub struct CallGasEstimatorSpecializationV07 { impl CallGasEstimatorSpecialization for CallGasEstimatorSpecializationV07 { type UO = UserOperation; - fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut spoof::State) { + fn add_proxy_to_overrides(&self, ep_to_override: Address, state_override: &mut StateOverride) { // For an explanation of what's going on here, see the comment at the // top of `CallGasEstimationProxy.sol`. // Use a random address for the moved entry point so that users can't // intentionally get bad estimates by interacting with the hardcoded // address. let moved_entry_point_address: Address = rand::thread_rng().gen(); - let estimation_proxy_bytecode = - estimation_proxy_bytecode_with_target(moved_entry_point_address); - state_override - .account(moved_entry_point_address) - .code(ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE.clone()); - state_override - .account(ep_to_override) - .code(estimation_proxy_bytecode); + + state_override.insert( + moved_entry_point_address, + AccountOverride { + code: Some(ENTRY_POINT_SIMULATIONS_V0_7_DEPLOYED_BYTECODE.clone()), + ..Default::default() + }, + ); + + state_override.insert( + ep_to_override, + AccountOverride { + code: Some(estimation_proxy_bytecode_with_target( + moved_entry_point_address, + )), + ..Default::default() + }, + ); } fn get_op_with_no_call_gas(&self, op: Self::UO) -> Self::UO { UserOperationBuilder::from_uo(op, &self.chain_spec) - .call_gas_limit(U128::zero()) - .max_fee_per_gas(U128::zero()) + .call_gas_limit(0) + .max_fee_per_gas(0) .build() } fn get_estimate_call_gas_calldata( &self, callless_op: Self::UO, - min_gas: U256, - max_gas: U256, - rounding: U256, + min_gas: u128, + max_gas: u128, + rounding: u128, is_continuation: bool, ) -> Bytes { - eth::call_data_of( - EstimateCallGasCall::selector(), - (EstimateCallGasArgs { - user_op: callless_op.pack(), - min_gas, - max_gas, - rounding, - is_continuation, - },), - ) + let call = CallGasEstimationProxyCalls::estimateCallGas(estimateCallGasCall { + args: EstimateCallGasArgs { + userOp: callless_op.pack(), + minGas: U256::from(min_gas), + maxGas: U256::from(max_gas), + rounding: U256::from(rounding), + isContinuation: is_continuation, + }, + }); + + call.abi_encode().into() } - fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: U256) -> Bytes { - eth::call_data_of( - TestCallGasCall::selector(), - (callless_op.pack(), call_gas_limit), - ) + fn get_test_call_gas_calldata(&self, callless_op: Self::UO, call_gas_limit: u128) -> Bytes { + let call = CallGasEstimationProxyCalls::testCallGas(testCallGasCall { + userOp: callless_op.pack(), + callGasLimit: U256::from(call_gas_limit), + }); + + call.abi_encode().into() } } @@ -479,84 +497,77 @@ const PROXY_TARGET_OFFSET: usize = 163; // Replaces the address of the proxy target where it appears in the proxy // bytecode so we don't need the same fixed address every time. fn estimation_proxy_bytecode_with_target(target: Address) -> Bytes { - let mut vec = CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE.to_vec(); - vec[PROXY_TARGET_OFFSET..PROXY_TARGET_OFFSET + 20].copy_from_slice(target.as_bytes()); + let mut vec = CALL_GAS_ESTIMATION_PROXY_V0_7_DEPLOYED_BYTECODE.to_vec(); + let bytes: [u8; 20] = target.into(); + vec[PROXY_TARGET_OFFSET..PROXY_TARGET_OFFSET + 20].copy_from_slice(&bytes); vec.into() } #[cfg(test)] mod tests { - use ethers::{ - abi::AbiEncode, - contract::EthCall, - types::{Address, U64}, - utils::hex, - }; - use rundler_provider::{ExecutionResult, MockEntryPointV0_7, MockProvider, SimulateOpCallData}; - use rundler_types::{ - contracts::v0_7::{ - call_gas_estimation_proxy::TestCallGasResult, - entry_point_simulations::SimulateHandleOpCall, - }, - v0_7::UserOperationOptionalGas, + use std::sync::Arc; + + use alloy_primitives::{hex, U256}; + use alloy_sol_types::{Revert, SolCall, SolError}; + use gas::MockFeeEstimator; + use rundler_contracts::v0_7::{ + CallGasEstimationProxy::TestCallGasResult, IEntryPointSimulations, }; - use rundler_utils::eth::{self, ContractRevertError}; + use rundler_provider::{EvmCall, ExecutionResult, MockEntryPointV0_7, MockEvmProvider}; + use rundler_types::v0_7::UserOperationOptionalGas; use super::*; use crate::{ estimation::estimate_call_gas::PROXY_IMPLEMENTATION_ADDRESS_MARKER, GasEstimator as _, - PriorityFeeMode, }; // Alises for complex types (which also satisfy Clippy) type VerificationGasEstimatorWithMocks = - VerificationGasEstimatorImpl>; + VerificationGasEstimatorImpl, Arc>; type CallGasEstimatorWithMocks = CallGasEstimatorImpl, CallGasEstimatorSpecializationV07>; type GasEstimatorWithMocks = GasEstimator< - MockProvider, + Arc, Arc, VerificationGasEstimatorWithMocks, CallGasEstimatorWithMocks, + MockFeeEstimator, >; - fn create_base_config() -> (MockEntryPointV0_7, MockProvider) { + fn create_base_config() -> (MockEntryPointV0_7, MockEvmProvider) { let mut entry = MockEntryPointV0_7::new(); - let provider = MockProvider::new(); + let provider = MockEvmProvider::new(); // Fill in concrete implementations of call data and // `simulation_should_revert` entry - .expect_get_simulate_op_call_data() - .returning(|op, spoofed_state| { - let call_data = eth::call_data_of( - SimulateHandleOpCall::selector(), - (op.packed().clone(), Address::zero(), Bytes::new()), - ); - SimulateOpCallData { - call_data, - spoofed_state: spoofed_state.clone(), + .expect_get_simulate_handle_op_call() + .returning(|op, state_override| { + let data = IEntryPointSimulations::simulateHandleOpCall { + op: op.pack(), + target: Address::ZERO, + targetCallData: Bytes::new(), + } + .abi_encode() + .into(); + + EvmCall { + to: Address::ZERO, + data, + value: U256::ZERO, + state_override, } }); entry.expect_simulation_should_revert().return_const(true); - entry.expect_address().return_const(Address::zero()); + entry.expect_address().return_const(Address::ZERO); (entry, provider) } - fn create_fee_estimator(provider: Arc) -> FeeEstimator { - FeeEstimator::new( - &ChainSpec::default(), - provider, - PriorityFeeMode::BaseFeePercent(0), - 0, - ) - } - fn create_custom_estimator( chain_spec: ChainSpec, - provider: MockProvider, + provider: MockEvmProvider, entry: MockEntryPointV0_7, settings: Settings, ) -> GasEstimatorWithMocks { @@ -566,15 +577,15 @@ mod tests { Arc::clone(&provider), Arc::new(entry), settings, - create_fee_estimator(provider), + MockFeeEstimator::new(), ) } - const TEST_MAX_GAS_LIMITS: u64 = 10000000000; + const TEST_MAX_GAS_LIMITS: u128 = 10000000000; fn create_estimator( entry: MockEntryPointV0_7, - provider: MockProvider, + provider: MockEvmProvider, ) -> (GasEstimatorWithMocks, Settings) { let settings = Settings { max_verification_gas: TEST_MAX_GAS_LIMITS, @@ -589,10 +600,10 @@ mod tests { (estimator, settings) } - fn demo_user_op_optional_gas(pvg: Option) -> UserOperationOptionalGas { + fn demo_user_op_optional_gas(pvg: Option) -> UserOperationOptionalGas { UserOperationOptionalGas { - sender: Address::zero(), - nonce: U256::zero(), + sender: Address::ZERO, + nonce: U256::ZERO, call_data: Bytes::new(), call_gas_limit: None, verification_gas_limit: None, @@ -616,10 +627,10 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let optional_op = demo_user_op_optional_gas(Some(U256::from(TEST_MAX_GAS_LIMITS + 1))); + let optional_op = demo_user_op_optional_gas(Some(TEST_MAX_GAS_LIMITS + 1)); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -635,11 +646,11 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let mut optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); - optional_op.verification_gas_limit = Some(U128::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.verification_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -655,11 +666,11 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let mut optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); - optional_op.paymaster_verification_gas_limit = Some(U128::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.paymaster_verification_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -678,11 +689,11 @@ mod tests { let (entry, provider) = create_base_config(); 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(U128::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -698,11 +709,11 @@ mod tests { let (entry, provider) = create_base_config(); let (estimator, _) = create_estimator(entry, provider); - let mut optional_op = demo_user_op_optional_gas(Some(U256::from(10000))); - optional_op.paymaster_post_op_gas_limit = Some(U128::from(TEST_MAX_GAS_LIMITS + 1)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.paymaster_post_op_gas_limit = Some(TEST_MAX_GAS_LIMITS + 1); let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -719,18 +730,18 @@ mod tests { provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_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(), + gasUsed: U256::ZERO, + revertData: Bytes::new(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -739,15 +750,15 @@ mod tests { 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(U128::from(10000)); - optional_op.verification_gas_limit = Some(U128::from(10000)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(10000); + optional_op.verification_gas_limit = Some(10000); optional_op.paymaster = Some(Address::random()); - optional_op.paymaster_verification_gas_limit = Some(U128::from(10000)); - optional_op.paymaster_post_op_gas_limit = Some(U128::from(10000)); + optional_op.paymaster_verification_gas_limit = Some(10000); + optional_op.paymaster_post_op_gas_limit = Some(10000); let estimation = estimator - .estimate_op_gas(optional_op.clone(), spoof::state()) + .estimate_op_gas(optional_op.clone(), StateOverride::default()) .await .unwrap(); @@ -757,17 +768,15 @@ mod tests { ); assert_eq!( estimation.verification_gas_limit, - optional_op.verification_gas_limit.unwrap().into() + optional_op.verification_gas_limit.unwrap() ); assert_eq!( estimation.paymaster_verification_gas_limit, - optional_op - .paymaster_verification_gas_limit - .map(|v| v.into()) + optional_op.paymaster_verification_gas_limit ); assert_eq!( estimation.call_gas_limit, - optional_op.call_gas_limit.unwrap().into() + optional_op.call_gas_limit.unwrap() ); } @@ -777,23 +786,23 @@ mod tests { provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); let revert_msg = "test revert".to_string(); - let err = ContractRevertError { + let err = Revert { reason: revert_msg.clone(), }; entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_op() .returning(move |_a, _b, _c, _d, _e, _f| { Ok(Ok(ExecutionResult { target_result: TestCallGasResult { success: false, - gas_used: 0.into(), - revert_data: err.clone().encode().into(), + gasUsed: U256::ZERO, + revertData: err.clone().abi_encode().into(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -802,12 +811,12 @@ mod tests { 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(U128::from(10000)); - optional_op.verification_gas_limit = Some(U128::from(10000)); + let mut optional_op = demo_user_op_optional_gas(Some(10000)); + optional_op.call_gas_limit = Some(10000); + optional_op.verification_gas_limit = Some(10000); let estimation_error = estimator - .estimate_op_gas(optional_op.clone(), spoof::state()) + .estimate_op_gas(optional_op.clone(), StateOverride::default()) .await .err() .unwrap(); @@ -823,15 +832,15 @@ mod tests { let (mut entry, mut provider) = create_base_config(); entry - .expect_call_spoofed_simulate_op() + .expect_simulate_handle_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(), + gasUsed: U256::from(TEST_MAX_GAS_LIMITS), + revertData: Bytes::new(), } - .encode() + .abi_encode() .into(), target_success: true, ..Default::default() @@ -839,32 +848,32 @@ mod tests { }); provider .expect_get_latest_block_hash_and_number() - .returning(|| Ok((H256::zero(), U64::zero()))); + .returning(|| Ok((B256::ZERO, 0))); let (estimator, _) = create_estimator(entry, provider); let optional_op = UserOperationOptionalGas { - sender: Address::zero(), - nonce: U256::zero(), + 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()), + call_gas_limit: Some(TEST_MAX_GAS_LIMITS), + verification_gas_limit: Some(TEST_MAX_GAS_LIMITS), + pre_verification_gas: Some(TEST_MAX_GAS_LIMITS), 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()), + paymaster_verification_gas_limit: Some(TEST_MAX_GAS_LIMITS), + paymaster_post_op_gas_limit: Some(TEST_MAX_GAS_LIMITS), factory: None, factory_data: Bytes::new(), }; let estimation = estimator - .estimate_op_gas(optional_op, spoof::state()) + .estimate_op_gas(optional_op, StateOverride::default()) .await .err() .unwrap(); @@ -879,8 +888,8 @@ mod tests { fn test_proxy_target_offset() { let proxy_target_bytes = hex::decode(PROXY_IMPLEMENTATION_ADDRESS_MARKER).unwrap(); let mut offsets = Vec::::new(); - for i in 0..CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE.len() - 20 { - if CALLGASESTIMATIONPROXY_DEPLOYED_BYTECODE[i..i + 20] == proxy_target_bytes { + for i in 0..CALL_GAS_ESTIMATION_PROXY_V0_7_DEPLOYED_BYTECODE.len() - 20 { + if CALL_GAS_ESTIMATION_PROXY_V0_7_DEPLOYED_BYTECODE[i..i + 20] == proxy_target_bytes { offsets.push(i); } } diff --git a/crates/sim/src/gas/gas.rs b/crates/sim/src/gas/gas.rs index c88cb3fe..2100f67a 100644 --- a/crates/sim/src/gas/gas.rs +++ b/crates/sim/src/gas/gas.rs @@ -11,21 +11,17 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{cmp, fmt::Debug, sync::Arc}; +use std::{cmp, fmt::Debug}; use anyhow::{bail, Context}; -use ethers::types::U256; -use rundler_provider::{EntryPoint, L1GasProvider, Provider}; -use rundler_types::{ - chain::{self, ChainSpec}, - GasFees, UserOperation, -}; +#[cfg(feature = "test-utils")] +use mockall::automock; +use rundler_provider::{EntryPoint, EvmProvider, L1GasProvider}; +use rundler_types::{chain::ChainSpec, GasFees, UserOperation}; use rundler_utils::math; use tokio::try_join; -use super::oracle::{ - ConstantOracle, FeeOracle, ProviderOracle, UsageBasedFeeOracle, UsageBasedFeeOracleConfig, -}; +use super::oracle::FeeOracle; /// Returns the required pre_verification_gas for the given user operation /// @@ -48,15 +44,15 @@ pub async fn estimate_pre_verification_gas< entry_point: &E, full_op: &UO, random_op: &UO, - gas_price: U256, -) -> anyhow::Result { + gas_price: u128, +) -> anyhow::Result { let static_gas = full_op.calc_static_pre_verification_gas(chain_spec, true); if !chain_spec.calldata_pre_verification_gas { return Ok(static_gas); } let dynamic_gas = entry_point - .calc_l1_gas(entry_point.address(), random_op.clone(), gas_price) + .calc_l1_gas(*entry_point.address(), random_op.clone(), gas_price) .await?; Ok(static_gas.saturating_add(dynamic_gas)) @@ -72,8 +68,8 @@ pub async fn calc_required_pre_verification_gas< chain_spec: &ChainSpec, entry_point: &E, op: &UO, - base_fee: U256, -) -> anyhow::Result { + base_fee: u128, +) -> anyhow::Result { let static_gas = op.calc_static_pre_verification_gas(chain_spec, true); if !chain_spec.calldata_pre_verification_gas { return Ok(static_gas); @@ -84,12 +80,12 @@ pub async fn calc_required_pre_verification_gas< op.max_fee_per_gas(), ); - if gas_price.is_zero() { + if gas_price == 0 { bail!("Gas price cannot be zero") } let dynamic_gas = entry_point - .calc_l1_gas(entry_point.address(), op.clone(), gas_price) + .calc_l1_gas(*entry_point.address(), op.clone(), gas_price) .await?; Ok(static_gas + dynamic_gas) @@ -121,7 +117,7 @@ pub fn user_operation_gas_limit( chain_spec: &ChainSpec, uo: &UO, assume_single_op_bundle: bool, -) -> U256 { +) -> u128 { user_operation_pre_verification_gas_limit(chain_spec, uo, assume_single_op_bundle) + uo.total_verification_gas_limit() + uo.required_pre_execution_buffer() @@ -137,7 +133,7 @@ pub fn user_operation_execution_gas_limit( chain_spec: &ChainSpec, uo: &UO, assume_single_op_bundle: bool, -) -> U256 { +) -> u128 { user_operation_pre_verification_execution_gas_limit(chain_spec, uo, assume_single_op_bundle) + uo.total_verification_gas_limit() + uo.required_pre_execution_buffer() @@ -151,7 +147,7 @@ pub fn user_operation_pre_verification_execution_gas_limit( chain_spec: &ChainSpec, uo: &UO, include_fixed_gas_overhead: bool, -) -> U256 { +) -> u128 { // 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. @@ -169,7 +165,7 @@ pub fn user_operation_pre_verification_gas_limit( chain_spec: &ChainSpec, uo: &UO, include_fixed_gas_overhead: bool, -) -> U256 { +) -> u128 { // On some chains (OP bedrock) the L1 gas fee is charged via pre_verification_gas // but this not part of the execution TOTAL limit of the transaction. // In such cases we only consider the static portion of the pre_verification_gas in the gas limit. @@ -185,14 +181,14 @@ pub fn user_operation_pre_verification_gas_limit( #[derive(Debug, Clone, Copy)] pub enum PriorityFeeMode { /// The priority fee is required to be a percentage of the bundle base fee. - BaseFeePercent(u64), + BaseFeePercent(u32), /// The priority fee is required to be a percentage above the bundle priority fee. - PriorityFeeIncreasePercent(u64), + PriorityFeeIncreasePercent(u32), } impl PriorityFeeMode { /// Try to create a priority fee mode from a string and value. - pub fn try_from(kind: &str, value: u64) -> anyhow::Result { + pub fn try_from(kind: &str, value: u32) -> anyhow::Result { match kind { "base_fee_percent" => Ok(Self::BaseFeePercent(value)), "priority_fee_increase_percent" => Ok(Self::PriorityFeeIncreasePercent(value)), @@ -223,10 +219,10 @@ impl PriorityFeeMode { /// settings pub fn minimum_priority_fee( &self, - base_fee: U256, - base_fee_accept_percent: u64, - min_max_priority_fee_per_gas: U256, - ) -> U256 { + base_fee: u128, + base_fee_accept_percent: u32, + min_max_priority_fee_per_gas: u128, + ) -> u128 { match *self { PriorityFeeMode::BaseFeePercent(percent) => { math::percent(math::percent(base_fee, base_fee_accept_percent), percent) @@ -238,16 +234,36 @@ impl PriorityFeeMode { } } +/// Trait for a fee estimator. +#[cfg_attr(feature = "test-utils", automock)] +#[async_trait::async_trait] +pub trait FeeEstimator: Send + Sync { + /// Returns the required fees for the given bundle fees. + /// + /// `min_fees` is used to set the minimum fees to use for the bundle. Typically used if a + /// bundle has already been submitted and its fees must at least be a certain amount above the + /// already submitted fees. + /// + /// Returns the required fees and the current base fee. + async fn required_bundle_fees( + &self, + min_fees: Option, + ) -> anyhow::Result<(GasFees, u128)>; + + /// Returns the required operation fees for the given bundle fees. + fn required_op_fees(&self, bundle_fees: GasFees) -> GasFees; +} + /// Gas fee estimator for a 4337 user operation. -#[derive(Debug, Clone)] -pub struct FeeEstimator

{ - provider: Arc

, +#[derive(Clone)] +pub struct FeeEstimatorImpl { + provider: P, priority_fee_mode: PriorityFeeMode, - bundle_priority_fee_overhead_percent: u64, - fee_oracle: Arc, + bundle_priority_fee_overhead_percent: u32, + fee_oracle: O, } -impl FeeEstimator

{ +impl FeeEstimatorImpl { /// Create a new fee estimator. /// /// `priority_fee_mode` is used to determine how the required priority fee is calculated. @@ -255,31 +271,39 @@ impl FeeEstimator

{ /// `bundle_priority_fee_overhead_percent` is used to determine the overhead percentage to add /// to the network returned priority fee to ensure the bundle priority fee is high enough. pub fn new( - chain_spec: &ChainSpec, - provider: Arc

, + provider: P, + fee_oracle: O, priority_fee_mode: PriorityFeeMode, - bundle_priority_fee_overhead_percent: u64, + bundle_priority_fee_overhead_percent: u32, ) -> Self { Self { - provider: provider.clone(), + provider, + fee_oracle, priority_fee_mode, bundle_priority_fee_overhead_percent, - fee_oracle: get_fee_oracle(chain_spec, provider), } } - /// Returns the required fees for the given bundle fees. - /// - /// `min_fees` is used to set the minimum fees to use for the bundle. Typically used if a - /// bundle has already been submitted and its fees must at least be a certain amount above the - /// already submitted fees. - /// - /// Returns the required fees and the current base fee. - pub async fn required_bundle_fees( + async fn get_pending_base_fee(&self) -> anyhow::Result { + Ok(self.provider.get_pending_base_fee().await?) + } + + async fn get_priority_fee(&self) -> anyhow::Result { + self.fee_oracle + .estimate_priority_fee() + .await + .context("should get priority fee") + } +} + +#[async_trait::async_trait] +impl FeeEstimator for FeeEstimatorImpl { + async fn required_bundle_fees( &self, min_fees: Option, - ) -> anyhow::Result<(GasFees, U256)> { - let (base_fee, priority_fee) = try_join!(self.get_base_fee(), self.get_priority_fee())?; + ) -> anyhow::Result<(GasFees, u128)> { + let (base_fee, priority_fee) = + try_join!(self.get_pending_base_fee(), self.get_priority_fee())?; let required_fees = min_fees.unwrap_or_default(); @@ -303,45 +327,7 @@ impl FeeEstimator

{ )) } - /// Returns the required operation fees for the given bundle fees. - pub fn required_op_fees(&self, bundle_fees: GasFees) -> GasFees { + fn required_op_fees(&self, bundle_fees: GasFees) -> GasFees { self.priority_fee_mode.required_fees(bundle_fees) } - - async fn get_base_fee(&self) -> anyhow::Result { - Ok(self.provider.get_base_fee().await?) - } - - async fn get_priority_fee(&self) -> anyhow::Result { - self.fee_oracle - .estimate_priority_fee() - .await - .context("should get priority fee") - } -} - -fn get_fee_oracle

(chain_spec: &ChainSpec, provider: Arc

) -> Arc -where - P: Provider + Debug, -{ - if !chain_spec.eip1559_enabled { - return Arc::new(ConstantOracle::new(U256::zero())); - } - - match chain_spec.priority_fee_oracle_type { - chain::PriorityFeeOracleType::Provider => Arc::new(ProviderOracle::new( - provider, - chain_spec.min_max_priority_fee_per_gas, - )), - chain::PriorityFeeOracleType::UsageBased => { - let config = UsageBasedFeeOracleConfig { - minimum_fee: chain_spec.min_max_priority_fee_per_gas, - maximum_fee: chain_spec.max_max_priority_fee_per_gas, - congestion_trigger_usage_ratio_threshold: chain_spec - .congestion_trigger_usage_ratio_threshold, - ..Default::default() - }; - Arc::new(UsageBasedFeeOracle::new(provider, config)) - } - } } diff --git a/crates/sim/src/gas/mod.rs b/crates/sim/src/gas/mod.rs index d293db2a..6afd5c36 100644 --- a/crates/sim/src/gas/mod.rs +++ b/crates/sim/src/gas/mod.rs @@ -18,3 +18,4 @@ mod gas; pub use gas::*; mod oracle; +pub use oracle::*; diff --git a/crates/sim/src/gas/oracle.rs b/crates/sim/src/gas/oracle.rs index 632f3a4a..e718089c 100644 --- a/crates/sim/src/gas/oracle.rs +++ b/crates/sim/src/gas/oracle.rs @@ -12,17 +12,17 @@ // If not, see https://www.gnu.org/licenses/. #![allow(dead_code)] -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; -use async_trait::async_trait; -use ethers::types::{BlockNumber, U256}; use futures_util::future::join_all; -use rundler_provider::Provider; +use rundler_provider::{BlockNumberOrTag, EvmProvider}; +use rundler_types::chain::{ChainSpec, PriorityFeeOracleType}; pub(crate) type Result = std::result::Result; +/// Error type for the fee oracle #[derive(Debug, thiserror::Error)] -pub(crate) enum FeeOracleError { +pub enum FeeOracleError { /// No oracle available, or all oracles failed #[error("No oracle available")] NoOracle, @@ -31,20 +31,49 @@ pub(crate) enum FeeOracleError { Other(#[from] anyhow::Error), } +#[async_trait::async_trait] /// FeeOracle is a trait that provides a way to estimate the priority fee -#[async_trait] -pub(crate) trait FeeOracle: Send + Sync + Debug { - async fn estimate_priority_fee(&self) -> Result; +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait FeeOracle: Send + Sync { + /// Estimate the priority fee + async fn estimate_priority_fee(&self) -> Result; +} + +/// Get a fee oracle for the given chain spec. +pub fn get_fee_oracle<'a, P>(chain_spec: &ChainSpec, provider: P) -> Box +where + P: EvmProvider + 'a, +{ + if !chain_spec.eip1559_enabled { + return Box::new(ConstantOracle::new(0)); + } + + match chain_spec.priority_fee_oracle_type { + PriorityFeeOracleType::Provider => Box::new(ProviderOracle::new( + provider, + chain_spec.min_max_priority_fee_per_gas, + )), + PriorityFeeOracleType::UsageBased => { + let config = UsageBasedFeeOracleConfig { + minimum_fee: chain_spec.min_max_priority_fee_per_gas, + maximum_fee: chain_spec.max_max_priority_fee_per_gas, + congestion_trigger_usage_ratio_threshold: chain_spec + .congestion_trigger_usage_ratio_threshold, + ..Default::default() + }; + Box::new(UsageBasedFeeOracle::new(provider, config)) + } + } } /// UsageBasedFeeOracle config #[derive(Clone, Debug)] pub(crate) struct UsageBasedFeeOracleConfig { /// Minimum fee to return, returned if the chain is not congested - pub(crate) minimum_fee: U256, + pub(crate) minimum_fee: u128, /// Maximum fee to return, clamped to this value when calculating the average fee /// during congestion - pub(crate) maximum_fee: U256, + pub(crate) maximum_fee: u128, /// Congestion trigger usage ratio threshold, a block with gas usage over this ratio /// is considered congested. pub(crate) congestion_trigger_usage_ratio_threshold: f64, @@ -58,8 +87,8 @@ pub(crate) struct UsageBasedFeeOracleConfig { impl Default for UsageBasedFeeOracleConfig { fn default() -> Self { Self { - minimum_fee: U256::zero(), - maximum_fee: U256::MAX, + minimum_fee: 0, + maximum_fee: u128::MAX, congestion_trigger_usage_ratio_threshold: 0.75, congestion_trigger_num_blocks: 5, congestion_fee_percentile: 33.0, @@ -73,32 +102,31 @@ impl Default for UsageBasedFeeOracleConfig { /// inclusion probability unless the chain is congested. This oracle uses the gas usage /// of a configurable amount of blocks to determine if the chain is congested and only /// returns a priority fee above the minimum if the chain is congested. -#[derive(Debug)] pub(crate) struct UsageBasedFeeOracle

{ - provider: Arc

, + provider: P, config: UsageBasedFeeOracleConfig, } impl

UsageBasedFeeOracle

where - P: Provider, + P: EvmProvider, { - pub(crate) fn new(provider: Arc

, config: UsageBasedFeeOracleConfig) -> Self { + pub(crate) fn new(provider: P, config: UsageBasedFeeOracleConfig) -> Self { Self { provider, config } } } -#[async_trait] +#[async_trait::async_trait] impl

FeeOracle for UsageBasedFeeOracle

where - P: Provider + Debug, + P: EvmProvider, { - async fn estimate_priority_fee(&self) -> Result { + async fn estimate_priority_fee(&self) -> Result { let fee_history = self .provider .fee_history( self.config.congestion_trigger_num_blocks, - BlockNumber::Latest, + BlockNumberOrTag::Latest, &[self.config.congestion_fee_percentile], ) .await @@ -114,20 +142,21 @@ where return Ok(self.config.minimum_fee); } + let Some(reward) = fee_history.reward else { + return Ok(0); + }; + // Chain is congested, return the average of the last N blocks at configured percentile - let values = fee_history - .reward + let values = reward .iter() - .filter(|b| !b.is_empty() && !b[0].is_zero()) + .filter(|b| !b.is_empty() && b[0] != 0) .map(|b| b[0]) .collect::>(); if values.is_empty() { Ok(self.config.minimum_fee) } else { - let fee: U256 = values - .iter() - .fold(U256::zero(), |acc, x| acc.saturating_add(*x)) - / values.len(); + let fee: u128 = values.iter().fold(0_u128, |acc, x| acc.saturating_add(*x)) + / (values.len() as u128); Ok(fee.clamp(self.config.minimum_fee, self.config.maximum_fee)) } } @@ -141,9 +170,9 @@ pub(crate) struct FeeHistoryOracleConfig { /// Percentile to use for the fee history pub(crate) percentile: f64, /// Minimum fee to return - pub(crate) minimum_fee: U256, + pub(crate) minimum_fee: u128, /// Maximum fee to return - pub(crate) maximum_fee: U256, + pub(crate) maximum_fee: u128, } impl Default for FeeHistoryOracleConfig { @@ -151,59 +180,62 @@ impl Default for FeeHistoryOracleConfig { Self { blocks_history: 15, percentile: 50.0, - minimum_fee: U256::zero(), - maximum_fee: U256::MAX, + minimum_fee: 0, + maximum_fee: u128::MAX, } } } /// Oracle that uses the fee history to estimate the priority fee -#[derive(Debug)] pub(crate) struct FeeHistoryOracle

{ - provider: Arc

, + provider: P, config: FeeHistoryOracleConfig, } impl

FeeHistoryOracle

where - P: Provider, + P: EvmProvider, { - pub(crate) fn new(provider: Arc

, config: FeeHistoryOracleConfig) -> Self { + pub(crate) fn new(provider: P, config: FeeHistoryOracleConfig) -> Self { Self { provider, config } } } -#[async_trait] +#[async_trait::async_trait] impl

FeeOracle for FeeHistoryOracle

where - P: Provider + Debug, + P: EvmProvider, { - async fn estimate_priority_fee(&self) -> Result { + async fn estimate_priority_fee(&self) -> Result { let fee_history = self .provider .fee_history( self.config.blocks_history, - BlockNumber::Latest, + BlockNumberOrTag::Latest, &[self.config.percentile], ) .await .map_err(|e| FeeOracleError::Other(e.into()))?; - let fee = calculate_estimate_from_rewards(&fee_history.reward); + let Some(reward) = fee_history.reward else { + return Ok(0); + }; + + let fee = calculate_estimate_from_rewards(&reward); Ok(fee.clamp(self.config.minimum_fee, self.config.maximum_fee)) } } // Calculates the estimate based on the index of inner vector // and skips the average if block is empty -fn calculate_estimate_from_rewards(reward: &[Vec]) -> U256 { +fn calculate_estimate_from_rewards(reward: &[Vec]) -> u128 { let mut values = reward .iter() - .filter(|b| !b.is_empty() && !b[0].is_zero()) + .filter(|b| !b.is_empty() && b[0] != 0) .map(|b| b[0]) .collect::>(); if values.is_empty() { - return U256::zero(); + return 0; } values.sort(); @@ -215,20 +247,19 @@ fn calculate_estimate_from_rewards(reward: &[Vec]) -> U256 { }; let sum = values[start..end] .iter() - .fold(U256::zero(), |acc, x| acc.saturating_add(*x)); - sum / U256::from(end - start) + .fold(0_u128, |acc, x| acc.saturating_add(*x)); + sum / ((end - start) as u128) } /// Oracle that uses the provider to estimate the priority fee /// using `eth_maxPriorityFeePerGas` -#[derive(Debug)] pub(crate) struct ProviderOracle

{ - provider: Arc

, - min_max_fee_per_gas: U256, + provider: P, + min_max_fee_per_gas: u128, } impl

ProviderOracle

{ - pub(crate) fn new(provider: Arc

, min_max_fee_per_gas: U256) -> Self { + pub(crate) fn new(provider: P, min_max_fee_per_gas: u128) -> Self { Self { provider, min_max_fee_per_gas, @@ -236,12 +267,12 @@ impl

ProviderOracle

{ } } -#[async_trait] +#[async_trait::async_trait] impl

FeeOracle for ProviderOracle

where - P: Provider + Debug, + P: EvmProvider, { - async fn estimate_priority_fee(&self) -> Result { + async fn estimate_priority_fee(&self) -> Result { Ok(self .provider .get_max_priority_fee() @@ -252,7 +283,6 @@ where } /// Oracle that returns the maximum fee from a list of oracles -#[derive(Debug)] pub(crate) struct MaxOracle { oracles: Vec>, } @@ -262,14 +292,14 @@ impl MaxOracle { Self { oracles: vec![] } } - pub(crate) fn add(&mut self, oracle: T) { - self.oracles.push(Box::new(oracle)); + pub(crate) fn add(&mut self, oracle: Box) { + self.oracles.push(oracle); } } -#[async_trait] +#[async_trait::async_trait] impl FeeOracle for MaxOracle { - async fn estimate_priority_fee(&self) -> Result { + async fn estimate_priority_fee(&self) -> Result { let futures = self .oracles .iter() @@ -287,122 +317,103 @@ impl FeeOracle for MaxOracle { /// Oracle that returns a constant fee #[derive(Clone, Debug)] pub(crate) struct ConstantOracle { - fee: U256, + fee: u128, } impl ConstantOracle { - pub(crate) fn new(fee: U256) -> Self { + pub(crate) fn new(fee: u128) -> Self { Self { fee } } } -#[async_trait] +#[async_trait::async_trait] impl FeeOracle for ConstantOracle { - async fn estimate_priority_fee(&self) -> Result { + async fn estimate_priority_fee(&self) -> Result { Ok(self.fee) } } #[cfg(test)] mod tests { - use ethers::types::FeeHistory; - use rundler_provider::MockProvider; + use std::sync::Arc; + + use rundler_provider::{FeeHistory, MockEvmProvider}; use super::*; #[tokio::test] async fn test_usage_oracle_non_congested() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_fee_history() .times(1) .returning(|_: u64, _, _| { Ok(FeeHistory { base_fee_per_gas: vec![], gas_used_ratio: vec![0.75, 0.75, 0.75, 0.75, 0.74], - oldest_block: U256::zero(), - reward: vec![ - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - vec![U256::from(400)], - vec![U256::from(500)], - ], + oldest_block: 0, + reward: Some(vec![vec![100], vec![200], vec![300], vec![400], vec![500]]), + ..Default::default() }) }); let oracle = UsageBasedFeeOracle::new( - Arc::new(mock), + mock, UsageBasedFeeOracleConfig { congestion_trigger_num_blocks: 5, congestion_trigger_usage_ratio_threshold: 0.75, - minimum_fee: U256::from(100), + minimum_fee: 100, ..Default::default() }, ); - assert_eq!( - oracle.estimate_priority_fee().await.unwrap(), - U256::from(100) - ); + assert_eq!(oracle.estimate_priority_fee().await.unwrap(), 100); } #[tokio::test] async fn test_usage_oracle_congested() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_fee_history() .times(1) .returning(|_: u64, _, _| { Ok(FeeHistory { base_fee_per_gas: vec![], gas_used_ratio: vec![0.75, 0.75, 0.75, 0.75, 0.75], - oldest_block: U256::zero(), - reward: vec![ - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - vec![U256::from(400)], - vec![U256::from(500)], - ], + oldest_block: 0, + reward: Some(vec![vec![100], vec![200], vec![300], vec![400], vec![500]]), + ..Default::default() }) }); let oracle = UsageBasedFeeOracle::new( - Arc::new(mock), + mock, UsageBasedFeeOracleConfig { congestion_trigger_num_blocks: 5, congestion_trigger_usage_ratio_threshold: 0.75, - minimum_fee: U256::from(100), + minimum_fee: 100, ..Default::default() }, ); - assert_eq!( - oracle.estimate_priority_fee().await.unwrap(), - U256::from(300) - ); + assert_eq!(oracle.estimate_priority_fee().await.unwrap(), 300); } #[tokio::test] async fn test_fee_history_oracle() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_fee_history() .times(1) .returning(|_: u64, _, _| { - let reward = vec![ - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - ]; Ok(FeeHistory { base_fee_per_gas: vec![], gas_used_ratio: vec![], - oldest_block: U256::zero(), - reward, + oldest_block: 0, + reward: Some(vec![vec![100], vec![200], vec![300]]), + ..Default::default() }) }); let oracle = FeeHistoryOracle::new( - Arc::new(mock), + mock, FeeHistoryOracleConfig { blocks_history: 3, ..Default::default() @@ -410,138 +421,126 @@ mod tests { ); let fee = oracle.estimate_priority_fee().await.unwrap(); - assert_eq!(fee, U256::from(200)); + assert_eq!(fee, 200); } #[tokio::test] async fn test_max_oracle() { let mut oracle = MaxOracle::new(); - oracle.add(ConstantOracle::new(U256::from(100))); - oracle.add(ConstantOracle::new(U256::from(200))); - oracle.add(ConstantOracle::new(U256::from(300))); + oracle.add(Box::new(ConstantOracle::new(100))); + oracle.add(Box::new(ConstantOracle::new(200))); + oracle.add(Box::new(ConstantOracle::new(300))); let fee = oracle.estimate_priority_fee().await.unwrap(); - assert_eq!(fee, U256::from(300)); + assert_eq!(fee, 300); } #[tokio::test] async fn test_provider_oracle_min() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_get_max_priority_fee() .times(1) - .returning(|| Ok(U256::from(400))); - let oracle = ProviderOracle::new(Arc::new(mock), U256::from(401)); + .returning(|| Ok(400)); + let oracle = ProviderOracle::new(mock, 401); let fee = oracle.estimate_priority_fee().await.unwrap(); - assert_eq!(fee, U256::from(401)); + assert_eq!(fee, 401); } #[tokio::test] async fn test_max_oracle_choose_provider() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_fee_history() .times(1) .returning(|_: u64, _, _| { - let reward = vec![ - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - ]; Ok(FeeHistory { base_fee_per_gas: vec![], gas_used_ratio: vec![], - oldest_block: U256::zero(), - reward, + oldest_block: 0, + reward: Some(vec![vec![100], vec![200], vec![300]]), + ..Default::default() }) }); mock.expect_get_max_priority_fee() .times(1) - .returning(|| Ok(U256::from(400))); + .returning(|| Ok(400)); let provider = Arc::new(mock); let mut oracle = MaxOracle::new(); - oracle.add(FeeHistoryOracle::new( + oracle.add(Box::new(FeeHistoryOracle::new( Arc::clone(&provider), FeeHistoryOracleConfig { blocks_history: 3, ..Default::default() }, - )); - oracle.add(ProviderOracle::new(provider, U256::from(0))); + ))); + oracle.add(Box::new(ProviderOracle::new(provider, 0))); let fee = oracle.estimate_priority_fee().await.unwrap(); - assert_eq!(fee, U256::from(400)); + assert_eq!(fee, 400); } #[tokio::test] async fn test_max_oracle_choose_fee_history() { - let mut mock = MockProvider::default(); + let mut mock = MockEvmProvider::default(); mock.expect_fee_history() .times(1) .returning(|_: u64, _, _| { - let reward = vec![ - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - ]; Ok(FeeHistory { base_fee_per_gas: vec![], gas_used_ratio: vec![], - oldest_block: U256::zero(), - reward, + oldest_block: 0, + reward: Some(vec![vec![100], vec![200], vec![300]]), + ..Default::default() }) }); mock.expect_get_max_priority_fee() .times(1) - .returning(|| Ok(U256::from(100))); + .returning(|| Ok(100)); let provider = Arc::new(mock); let mut oracle = MaxOracle::new(); - oracle.add(FeeHistoryOracle::new( + oracle.add(Box::new(FeeHistoryOracle::new( Arc::clone(&provider), FeeHistoryOracleConfig { blocks_history: 3, ..Default::default() }, - )); - oracle.add(ProviderOracle::new(provider, U256::from(0))); + ))); + oracle.add(Box::new(ProviderOracle::new(provider, 0))); let fee = oracle.estimate_priority_fee().await.unwrap(); - assert_eq!(fee, U256::from(200)); + assert_eq!(fee, 200); } #[test] fn test_calculate_estimate_from_rewards_small() { - let reward = vec![ - vec![U256::from(300)], - vec![U256::from(100)], - vec![U256::from(200)], - ]; + let reward = vec![vec![300], vec![100], vec![200]]; let fee = calculate_estimate_from_rewards(&reward); - assert_eq!(fee, U256::from(200)); + assert_eq!(fee, 200); } #[test] fn test_calculate_estimate_from_rewards_outliers() { let reward = vec![ - vec![U256::from(300)], - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(300)], - vec![U256::from(100)], - vec![U256::from(200)], - vec![U256::from(2)], - vec![U256::from(20000)], + vec![300], + vec![100], + vec![200], + vec![300], + vec![100], + vec![200], + vec![2], + vec![20000], ]; let fee = calculate_estimate_from_rewards(&reward); - assert_eq!(fee, U256::from(200)); + assert_eq!(fee, 200); } #[test] fn test_calculate_estimate_from_rewards_single() { - let reward = vec![vec![U256::from(200)]]; + let reward = vec![vec![200]]; let fee = calculate_estimate_from_rewards(&reward); - assert_eq!(fee, U256::from(200)); + assert_eq!(fee, 200); } } diff --git a/crates/sim/src/lib.rs b/crates/sim/src/lib.rs index e78986b2..1e3d3505 100644 --- a/crates/sim/src/lib.rs +++ b/crates/sim/src/lib.rs @@ -61,5 +61,3 @@ pub use simulation::{ mod types; pub use types::{ExpectedStorage, ViolationError}; - -mod utils; diff --git a/crates/sim/src/precheck.rs b/crates/sim/src/precheck.rs index 8b53afa4..f1c0d7cd 100644 --- a/crates/sim/src/precheck.rs +++ b/crates/sim/src/precheck.rs @@ -11,17 +11,14 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{ - marker::PhantomData, - sync::{Arc, RwLock}, -}; +use std::{marker::PhantomData, sync::RwLock}; +use alloy_primitives::{Address, U256}; use anyhow::Context; use arrayvec::ArrayVec; -use ethers::types::{Address, U128, U256}; #[cfg(feature = "test-utils")] use mockall::automock; -use rundler_provider::{EntryPoint, L1GasProvider, Provider}; +use rundler_provider::{EntryPoint, EvmProvider, L1GasProvider}; use rundler_types::{ chain::ChainSpec, pool::{MempoolError, PrecheckViolation}, @@ -29,23 +26,26 @@ use rundler_types::{ }; use rundler_utils::math; -use crate::{gas, types::ViolationError}; +use crate::{ + gas::{self, FeeEstimator}, + types::ViolationError, +}; /// The min cost of a `CALL` with nonzero value, as required by the spec. -pub const MIN_CALL_GAS_LIMIT: U128 = U128([9100, 0]); +pub const MIN_CALL_GAS_LIMIT: u128 = 9100; /// Trait for checking if a user operation is valid before simulation /// according to the spec rules. #[cfg_attr(feature = "test-utils", automock(type UO = rundler_types::v0_6::UserOperation;))] #[async_trait::async_trait] -pub trait Prechecker: Send + Sync + 'static { +pub trait Prechecker: Send + Sync { /// The user operation type type UO: UserOperation; /// Run the precheck on the given operation and return an error if it fails. async fn check(&self, op: &Self::UO) -> Result<(), PrecheckError>; /// Update and return the bundle fees. - async fn update_fees(&self) -> anyhow::Result<(GasFees, U256)>; + async fn update_fees(&self) -> anyhow::Result<(GasFees, u128)>; } /// Precheck error @@ -64,19 +64,18 @@ impl From for MempoolError { // extract violation and replace with dummy Self::PrecheckViolation(std::mem::replace( violation, - PrecheckViolation::SenderIsNotContractAndNoInitCode(Address::zero()), + PrecheckViolation::SenderIsNotContractAndNoInitCode(Address::ZERO), )) } } /// Prechecker implementation -#[derive(Debug)] -pub struct PrecheckerImpl { +pub struct PrecheckerImpl { chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, settings: Settings, - fee_estimator: gas::FeeEstimator

, + fee_estimator: F, cache: RwLock, _uo_type: PhantomData, } @@ -85,28 +84,28 @@ pub struct PrecheckerImpl { #[derive(Copy, Clone, Debug)] pub struct Settings { /// Maximum verification gas allowed for a user operation - pub max_verification_gas: U256, + pub max_verification_gas: u128, /// Maximum total execution gas allowed for a user operation - pub max_total_execution_gas: U256, + pub max_total_execution_gas: u128, /// If using a bundle priority fee, the percentage to add to the network/oracle /// provided value as a safety margin for fast inclusion. - pub bundle_priority_fee_overhead_percent: u64, + pub bundle_priority_fee_overhead_percent: u32, /// The priority fee mode to use for calculating required user operation priority fee. pub priority_fee_mode: gas::PriorityFeeMode, /// Percentage of the current network base fee that a user operation must have to be accepted into the mempool. - pub base_fee_accept_percent: u64, + pub base_fee_accept_percent: u32, /// Percentage of the preVerificationGas that a user operation must have to be accepted into the mempool. - pub pre_verification_gas_accept_percent: u64, + pub pre_verification_gas_accept_percent: u32, } #[cfg(any(test, feature = "test-utils"))] impl Default for Settings { fn default() -> Self { Self { - max_verification_gas: 5_000_000.into(), + max_verification_gas: 5_000_000, bundle_priority_fee_overhead_percent: 0, priority_fee_mode: gas::PriorityFeeMode::BaseFeePercent(0), - max_total_execution_gas: 10_000_000.into(), + max_total_execution_gas: 10_000_000, base_fee_accept_percent: 50, pre_verification_gas_accept_percent: 100, } @@ -119,8 +118,8 @@ struct AsyncData { sender_exists: bool, paymaster_exists: bool, payer_funds: U256, - base_fee: U256, - min_pre_verification_gas: U256, + base_fee: u128, + min_pre_verification_gas: u128, } #[derive(Copy, Clone, Debug)] @@ -131,15 +130,16 @@ struct AsyncDataCache { #[derive(Copy, Clone, Debug)] struct FeeCache { bundle_fees: GasFees, - base_fee: U256, + base_fee: u128, } #[async_trait::async_trait] -impl Prechecker for PrecheckerImpl +impl Prechecker for PrecheckerImpl where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + L1GasProvider, UO: UserOperation, + F: FeeEstimator, { type UO = UO; @@ -155,7 +155,7 @@ where Ok(()) } - async fn update_fees(&self) -> anyhow::Result<(GasFees, U256)> { + async fn update_fees(&self) -> anyhow::Result<(GasFees, u128)> { let (bundle_fees, base_fee) = self.fee_estimator.required_bundle_fees(None).await?; let mut cache = self.cache.write().unwrap(); @@ -168,26 +168,21 @@ where } } -impl PrecheckerImpl +impl PrecheckerImpl where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + L1GasProvider, UO: UserOperation, + F: FeeEstimator, { /// Create a new prechecker pub fn new( chain_spec: ChainSpec, - provider: Arc

, + provider: P, entry_point: E, + fee_estimator: F, settings: Settings, ) -> Self { - let fee_estimator = gas::FeeEstimator::new( - &chain_spec, - provider.clone(), - settings.priority_fee_mode, - settings.bundle_priority_fee_overhead_percent, - ); - Self { chain_spec, provider, @@ -291,10 +286,10 @@ where )); } - if op.call_gas_limit() < MIN_CALL_GAS_LIMIT.into() { + if op.call_gas_limit() < MIN_CALL_GAS_LIMIT { violations.push(PrecheckViolation::CallGasLimitTooLow( op.call_gas_limit(), - MIN_CALL_GAS_LIMIT.into(), + MIN_CALL_GAS_LIMIT, )); } violations @@ -386,7 +381,7 @@ where async fn get_payer_balance(&self, op: &UO) -> anyhow::Result { if op.paymaster().is_some() { // Paymasters must deposit eth, and cannot pay with their own. - return Ok(0.into()); + return Ok(U256::ZERO); } self.provider .get_balance(op.sender(), None) @@ -394,7 +389,7 @@ where .context("precheck should get sender balance") } - async fn get_fees(&self) -> anyhow::Result<(GasFees, U256)> { + async fn get_fees(&self) -> anyhow::Result<(GasFees, u128)> { if let Some(fees) = self.cache.read().unwrap().fees { return Ok((fees.bundle_fees, fees.base_fee)); } @@ -404,8 +399,8 @@ where async fn get_required_pre_verification_gas( &self, op: UO, - base_fee: U256, - ) -> anyhow::Result { + base_fee: u128, + ) -> anyhow::Result { gas::calc_required_pre_verification_gas(&self.chain_spec, &self.entry_point, &op, base_fee) .await } @@ -413,19 +408,26 @@ where #[cfg(test)] mod tests { - use std::str::FromStr; + use std::sync::Arc; - use ethers::types::Bytes; - use rundler_provider::{MockEntryPointV0_6, MockProvider}; + use alloy_primitives::{address, bytes, Bytes}; + use gas::MockFeeEstimator; + use rundler_provider::{MockEntryPointV0_6, MockEvmProvider}; use rundler_types::v0_6::UserOperation; use super::*; - fn create_base_config() -> (ChainSpec, MockProvider, MockEntryPointV0_6) { + fn create_base_config() -> ( + ChainSpec, + MockEvmProvider, + MockEntryPointV0_6, + MockFeeEstimator, + ) { ( ChainSpec::default(), - MockProvider::new(), + MockEvmProvider::new(), MockEntryPointV0_6::new(), + MockFeeEstimator::new(), ) } @@ -434,61 +436,71 @@ mod tests { factory_exists: true, sender_exists: true, paymaster_exists: true, - payer_funds: 5_000_000.into(), - base_fee: 4_000.into(), - min_pre_verification_gas: 1_000.into(), + payer_funds: U256::from(5_000_000), + base_fee: 4_000, + min_pre_verification_gas: 1_000, } } #[tokio::test] async fn test_check_init_code() { - let (cs, provider, entry_point) = create_base_config(); - let prechecker = - PrecheckerImpl::new(cs, Arc::new(provider), entry_point, Settings::default()); + let (cs, provider, entry_point, fee_estimator) = create_base_config(); + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new( + cs, + provider, + entry_point, + fee_estimator, + Settings::default(), + ); let op = UserOperation { - sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), - nonce: 100.into(), - init_code: Bytes::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), + sender: address!("3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d"), + nonce: U256::from(100), + init_code: bytes!("3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d"), call_data: Bytes::default(), - call_gas_limit: 9_000.into(), // large call gas limit high to trigger TotalGasLimitTooHigh - verification_gas_limit: 10_000_000.into(), - pre_verification_gas: 0.into(), - max_fee_per_gas: 5_000.into(), - max_priority_fee_per_gas: 2_000.into(), + call_gas_limit: 9_000, // large call gas limit high to trigger TotalGasLimitTooHigh + verification_gas_limit: 10_000_000, + pre_verification_gas: 0, + max_fee_per_gas: 5_000, + max_priority_fee_per_gas: 2_000, paymaster_and_data: Bytes::default(), signature: Bytes::default(), }; let res = prechecker.check_init_code(&op, get_test_async_data()); let mut expected = ArrayVec::new(); - expected.push(PrecheckViolation::ExistingSenderWithInitCode( - Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), - )); + expected.push(PrecheckViolation::ExistingSenderWithInitCode(address!( + "3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d" + ))); assert_eq!(res, expected); } #[tokio::test] async fn test_check_gas() { - let (cs, provider, entry_point) = create_base_config(); let test_settings = Settings { - max_verification_gas: 5_000_000.into(), - max_total_execution_gas: 10_000_000.into(), + max_verification_gas: 5_000_000, + max_total_execution_gas: 10_000_000, bundle_priority_fee_overhead_percent: 0, priority_fee_mode: gas::PriorityFeeMode::BaseFeePercent(100), base_fee_accept_percent: 100, pre_verification_gas_accept_percent: 100, }; - let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, test_settings); + + let (cs, provider, entry_point, fee_estimator) = create_base_config(); + let provider = Arc::new(provider); + let prechecker = + PrecheckerImpl::new(cs, provider, entry_point, fee_estimator, test_settings); + let op = UserOperation { - sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), - nonce: 100.into(), - init_code: Bytes::from_str("0x1000000000000000000000000000000000000000").unwrap(), + sender: address!("3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d"), + nonce: U256::from(100), + init_code: bytes!("1000000000000000000000000000000000000000"), call_data: Bytes::default(), - call_gas_limit: 9_000.into(), // large call gas limit high to trigger TotalGasLimitTooHigh - verification_gas_limit: 10_000_000.into(), - pre_verification_gas: 0.into(), - max_fee_per_gas: 5_000.into(), - max_priority_fee_per_gas: 2_000.into(), + call_gas_limit: 9_000, // large call gas limit high to trigger TotalGasLimitTooHigh + verification_gas_limit: 10_000_000, + pre_verification_gas: 0, + max_fee_per_gas: 5_000, + max_priority_fee_per_gas: 2_000, paymaster_and_data: Bytes::default(), signature: Bytes::default(), }; @@ -498,35 +510,41 @@ mod tests { assert_eq!( res, ArrayVec::::from([ - PrecheckViolation::VerificationGasLimitTooHigh(10_000_000.into(), 5_000_000.into(),), - PrecheckViolation::TotalGasLimitTooHigh(20_014_000.into(), 10_000_000.into(),), - PrecheckViolation::PreVerificationGasTooLow(0.into(), 1_000.into(),), - PrecheckViolation::MaxPriorityFeePerGasTooLow(2_000.into(), 4_000.into(),), - PrecheckViolation::MaxFeePerGasTooLow(5_000.into(), 8_000.into(),), - PrecheckViolation::CallGasLimitTooLow(9_000.into(), 9_100.into(),), + PrecheckViolation::VerificationGasLimitTooHigh(10_000_000, 5_000_000,), + PrecheckViolation::TotalGasLimitTooHigh(20_014_000, 10_000_000,), + PrecheckViolation::PreVerificationGasTooLow(0, 1_000,), + PrecheckViolation::MaxPriorityFeePerGasTooLow(2_000, 4_000,), + PrecheckViolation::MaxFeePerGasTooLow(5_000, 8_000,), + PrecheckViolation::CallGasLimitTooLow(9_000, 9_100,), ]) ); } #[tokio::test] async fn test_check_payer_paymaster_deposit_too_low() { - let (cs, provider, entry_point) = create_base_config(); - let prechecker = - PrecheckerImpl::new(cs, Arc::new(provider), entry_point, Settings::default()); + let (cs, provider, entry_point, fee_estimator) = create_base_config(); + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new( + cs, + provider, + entry_point, + fee_estimator, + Settings::default(), + ); + let op = UserOperation { - sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), - nonce: 100.into(), + sender: address!("3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d"), + nonce: U256::from(100), init_code: Bytes::default(), call_data: Bytes::default(), - call_gas_limit: 500_000.into(), - verification_gas_limit: 500_000.into(), - pre_verification_gas: 0.into(), - max_fee_per_gas: 1_000.into(), - max_priority_fee_per_gas: 0.into(), - paymaster_and_data: Bytes::from_str( - "0xa4b2c8f0351d60729e4f0a12345678d9b1c3e5f27890abcdef123456780abcdef1", - ) - .unwrap(), + call_gas_limit: 500_000, + verification_gas_limit: 500_000, + pre_verification_gas: 0, + max_fee_per_gas: 1_000, + max_priority_fee_per_gas: 0, + paymaster_and_data: bytes!( + "a4b2c8f0351d60729e4f0a12345678d9b1c3e5f27890abcdef123456780abcdef1" + ), signature: Bytes::default(), }; @@ -534,8 +552,8 @@ mod tests { assert_eq!( res, Some(PrecheckViolation::PaymasterDepositTooLow( - 5_000_000.into(), - 2_000_000_000.into(), + U256::from(5_000_000), + U256::from(2_000_000_000), )) ); } @@ -547,25 +565,26 @@ mod tests { priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (mut cs, provider, entry_point) = create_base_config(); + + let (mut cs, provider, entry_point, fee_estimator) = create_base_config(); cs.id = 10; let mintip = cs.min_max_priority_fee_per_gas; - let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); + + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new(cs, provider, entry_point, fee_estimator, settings); let mut async_data = get_test_async_data(); - async_data.base_fee = 5_000.into(); - async_data.min_pre_verification_gas = 1_000.into(); + async_data.base_fee = 5_000; + async_data.min_pre_verification_gas = 1_000; let op = UserOperation { - max_fee_per_gas: U256::from(math::percent(5000, settings.base_fee_accept_percent)) - + mintip, + max_fee_per_gas: math::percent(5000, settings.base_fee_accept_percent) + mintip, max_priority_fee_per_gas: mintip, pre_verification_gas: math::percent( 1_000, settings.pre_verification_gas_accept_percent, - ) - .into(), - call_gas_limit: MIN_CALL_GAS_LIMIT.into(), + ), + call_gas_limit: MIN_CALL_GAS_LIMIT, ..Default::default() }; @@ -580,26 +599,28 @@ mod tests { priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (cs, provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); + + let (cs, provider, entry_point, fee_estimator) = create_base_config(); + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new(cs, provider, entry_point, fee_estimator, settings); let mut async_data = get_test_async_data(); - async_data.base_fee = 5_000.into(); - async_data.min_pre_verification_gas = 1_000.into(); + async_data.base_fee = 5_000; + async_data.min_pre_verification_gas = 1_000; let op = UserOperation { - max_fee_per_gas: math::percent(5000, settings.base_fee_accept_percent - 10).into(), - max_priority_fee_per_gas: 0.into(), - pre_verification_gas: 1_000.into(), - call_gas_limit: MIN_CALL_GAS_LIMIT.into(), + max_fee_per_gas: math::percent(5000, settings.base_fee_accept_percent - 10), + max_priority_fee_per_gas: 0, + pre_verification_gas: 1_000, + call_gas_limit: MIN_CALL_GAS_LIMIT, ..Default::default() }; let res = prechecker.check_gas(&op, async_data); let mut expected = ArrayVec::::new(); expected.push(PrecheckViolation::MaxFeePerGasTooLow( - math::percent(5_000, settings.base_fee_accept_percent - 10).into(), - math::percent(5_000, settings.base_fee_accept_percent).into(), + math::percent(5_000, settings.base_fee_accept_percent - 10), + math::percent(5_000, settings.base_fee_accept_percent), )); assert_eq!(res, expected); @@ -612,30 +633,33 @@ mod tests { priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (mut cs, provider, entry_point) = create_base_config(); + + let (mut cs, provider, entry_point, fee_estimator) = create_base_config(); cs.id = 10; - cs.min_max_priority_fee_per_gas = 100_000.into(); + cs.min_max_priority_fee_per_gas = 100_000; let mintip = cs.min_max_priority_fee_per_gas; - let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); + + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new(cs, provider, entry_point, fee_estimator, settings); let mut async_data = get_test_async_data(); - async_data.base_fee = 5_000.into(); - async_data.min_pre_verification_gas = 1_000.into(); + async_data.base_fee = 5_000; + async_data.min_pre_verification_gas = 1_000; - let undertip = mintip - U256::from(1); + let undertip = mintip - 1; let op = UserOperation { - max_fee_per_gas: U256::from(5_000) + mintip, + max_fee_per_gas: 5_000 + mintip, max_priority_fee_per_gas: undertip, - pre_verification_gas: 1_000.into(), - call_gas_limit: MIN_CALL_GAS_LIMIT.into(), + pre_verification_gas: 1_000, + call_gas_limit: MIN_CALL_GAS_LIMIT, ..Default::default() }; let res = prechecker.check_gas(&op, async_data); let mut expected = ArrayVec::::new(); expected.push(PrecheckViolation::MaxPriorityFeePerGasTooLow( - mintip - U256::from(1), + mintip - 1, mintip, )); @@ -649,30 +673,31 @@ mod tests { priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (cs, provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); + + let (cs, provider, entry_point, fee_estimator) = create_base_config(); + let provider = Arc::new(provider); + let prechecker = PrecheckerImpl::new(cs, provider, entry_point, fee_estimator, settings); let mut async_data = get_test_async_data(); - async_data.base_fee = 5_000.into(); - async_data.min_pre_verification_gas = 1_000.into(); + async_data.base_fee = 5_000; + async_data.min_pre_verification_gas = 1_000; let op = UserOperation { - max_fee_per_gas: 5000.into(), - max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 5000, + max_priority_fee_per_gas: 0, pre_verification_gas: math::percent( 1_000, settings.pre_verification_gas_accept_percent - 10, - ) - .into(), - call_gas_limit: MIN_CALL_GAS_LIMIT.into(), + ), + call_gas_limit: MIN_CALL_GAS_LIMIT, ..Default::default() }; let res = prechecker.check_gas(&op, async_data); let mut expected = ArrayVec::::new(); expected.push(PrecheckViolation::PreVerificationGasTooLow( - math::percent(1_000, settings.pre_verification_gas_accept_percent - 10).into(), - math::percent(1_000, settings.pre_verification_gas_accept_percent).into(), + math::percent(1_000, settings.pre_verification_gas_accept_percent - 10), + math::percent(1_000, settings.pre_verification_gas_accept_percent), )); assert_eq!(res, expected); diff --git a/crates/sim/src/simulation/context.rs b/crates/sim/src/simulation/context.rs index 87600ef5..de8bcbda 100644 --- a/crates/sim/src/simulation/context.rs +++ b/crates/sim/src/simulation/context.rs @@ -13,8 +13,9 @@ use std::collections::{BTreeSet, HashMap, HashSet}; +use alloy_primitives::{Address, U256}; use anyhow::Context; -use ethers::types::{Address, BlockId, U256}; +use rundler_provider::BlockId; use rundler_types::{ pool::SimulationViolation, EntityInfos, EntityType, Opcode, StakeInfo, UserOperation, ValidationOutput, @@ -81,16 +82,18 @@ pub(crate) struct AssociatedSlotsByAddress(pub(crate) HashMap bool { - if slot == address.as_bytes().into() { + if slot == U256::from_be_bytes(address.into_word().into()) { return true; } let Some(associated_slots) = self.0.get(&address) else { return false; }; - let Some(&next_smallest_slot) = associated_slots.range(..(slot + 1)).next_back() else { + let Some(&next_smallest_slot) = + associated_slots.range(..(slot + U256::from(1))).next_back() + else { return false; }; - slot - next_smallest_slot < 128.into() + (slot - next_smallest_slot) < U256::from(128) } pub(crate) fn addresses(&self) -> HashSet

{ @@ -100,7 +103,7 @@ impl AssociatedSlotsByAddress { /// Trait for providing the validation context for a user operation. #[async_trait::async_trait] -pub trait ValidationContextProvider: Send + Sync + 'static { +pub trait ValidationContextProvider: Send + Sync { /// The user operation type this provider targets. type UO: UserOperation; @@ -115,7 +118,7 @@ pub trait ValidationContextProvider: Send + Sync + 'static { fn get_specific_violations( &self, _context: &ValidationContext, - ) -> Vec; + ) -> anyhow::Result>; } pub(crate) fn entity_type_from_simulation_phase(i: usize) -> Option { @@ -162,8 +165,8 @@ pub(crate) fn infos_from_validation_output( } pub(crate) fn is_staked(info: StakeInfo, sim_settings: &Settings) -> bool { - info.stake >= sim_settings.min_stake_value.into() - && info.unstake_delay_sec >= sim_settings.min_unstake_delay.into() + info.stake >= sim_settings.min_stake_value + && info.unstake_delay_sec >= sim_settings.min_unstake_delay } pub(crate) fn parse_combined_context_str(combined: &str) -> anyhow::Result<(A, B)> diff --git a/crates/sim/src/simulation/mempool.rs b/crates/sim/src/simulation/mempool.rs index d3dab56e..543533a8 100644 --- a/crates/sim/src/simulation/mempool.rs +++ b/crates/sim/src/simulation/mempool.rs @@ -13,7 +13,7 @@ use std::{collections::HashMap, str::FromStr}; -use ethers::types::{Address, H256, U256}; +use alloy_primitives::{Address, B256, U256}; use rundler_types::{Entity, EntityType, Opcode}; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; @@ -41,11 +41,11 @@ impl MempoolConfig { /// A collection of mempool configurations keyed by their ID. #[derive(Debug, Clone, Deserialize, Default)] -pub struct MempoolConfigs(HashMap); +pub struct MempoolConfigs(HashMap); impl MempoolConfigs { /// Get the mempool configs for a specific entry point address - pub fn get_for_entry_point(&self, entry_point: Address) -> HashMap { + pub fn get_for_entry_point(&self, entry_point: Address) -> HashMap { self.0 .iter() .filter(|(_, config)| config.entry_point == entry_point) @@ -196,7 +196,7 @@ impl AllowlistEntry { #[derive(Debug, PartialEq, Eq)] pub(crate) enum MempoolMatchResult { /// One or more matched mempools by ID - Matches(Vec), + Matches(Vec), /// No mempools matched, with the index of the first violation that didn't match NoMatch(usize), } @@ -205,10 +205,10 @@ pub(crate) enum MempoolMatchResult { /// mempools in which all of their violations are allowlisted. If zero violations, /// an operation will match all mempools. pub(crate) fn match_mempools( - mempools: &HashMap, + mempools: &HashMap, violations: &[SimulationViolation], ) -> MempoolMatchResult { - let mut candidate_pools: Vec = mempools.keys().cloned().collect(); + let mut candidate_pools: Vec = mempools.keys().cloned().collect(); for (i, violation) in violations.iter().enumerate() { candidate_pools.retain(|p| { mempools[p] @@ -225,7 +225,7 @@ pub(crate) fn match_mempools( #[cfg(test)] mod tests { - use ethers::types::U256; + use alloy_primitives::U256; use rundler_types::{pool::NeedsStakeInformation, StorageSlot, ViolationOpCode}; use super::*; @@ -236,7 +236,7 @@ mod tests { let account_entity = Entity { kind: EntityType::Account, - address: Address::zero(), + address: Address::ZERO, }; assert!(allow.is_allowed(&account_entity)); @@ -259,7 +259,7 @@ mod tests { let account_entity = Entity { kind: EntityType::Account, - address: Address::zero(), + address: Address::ZERO, }; assert!(!allow.is_allowed(&account_entity)); @@ -314,7 +314,7 @@ mod tests { let violation = SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Account, - address: Address::zero(), + address: Address::ZERO, }, contract, ViolationOpCode(Opcode::GAS), @@ -324,7 +324,7 @@ mod tests { let violation = SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Account, - address: Address::zero(), + address: Address::ZERO, }, contract, ViolationOpCode(Opcode::BLOCKHASH), @@ -418,7 +418,7 @@ mod tests { }, StorageSlot { address: slot_addr, - slot: U256::from(0), + slot: U256::ZERO, }, ); assert!(!entry.is_allowed(&violation)); @@ -465,9 +465,9 @@ mod tests { accessing_entity: EntityType::Paymaster, accessed_entity: Some(EntityType::Paymaster), accessed_address: entity_addr, - slot: U256::zero(), - min_stake: U256::zero(), - min_unstake_delay: U256::zero(), + slot: U256::ZERO, + min_stake: U256::ZERO, + min_unstake_delay: 0, })); assert!(entry.is_allowed(&violation)); @@ -477,9 +477,9 @@ mod tests { accessing_entity: EntityType::Paymaster, accessed_entity: Some(EntityType::Paymaster), accessed_address: entity_addr, - slot: U256::zero(), - min_stake: U256::zero(), - min_unstake_delay: U256::zero(), + slot: U256::ZERO, + min_stake: U256::ZERO, + min_unstake_delay: 0, })); assert!(!entry.is_allowed(&violation)); @@ -489,9 +489,9 @@ mod tests { fn test_match_none() { let contract = Address::random(); let mempools = HashMap::from([ - (H256::random(), MempoolConfig::default()), + (B256::random(), MempoolConfig::default()), ( - H256::random(), + B256::random(), MempoolConfig { entry_point: Address::random(), allowlist: vec![AllowlistEntry::new( @@ -522,9 +522,9 @@ mod tests { fn test_match_none_second() { let contract = Address::random(); let mempools = HashMap::from([ - (H256::random(), MempoolConfig::default()), + (B256::random(), MempoolConfig::default()), ( - H256::random(), + B256::random(), MempoolConfig { entry_point: Address::random(), allowlist: vec![AllowlistEntry::new( @@ -563,8 +563,8 @@ mod tests { #[test] fn test_match_one() { - let mempool0 = H256::random(); - let mempool1 = H256::random(); + let mempool0 = B256::random(); + let mempool1 = B256::random(); let contract = Address::random(); let mempools = HashMap::from([ (mempool0, MempoolConfig::default()), @@ -598,9 +598,9 @@ mod tests { #[test] fn test_match_multiple() { - let mempool0 = H256::random(); - let mempool1 = H256::random(); - let mempool2 = H256::random(); + let mempool0 = B256::random(); + let mempool1 = B256::random(); + let mempool2 = B256::random(); let contract = Address::random(); let mempools = HashMap::from([ (mempool0, MempoolConfig::default()), diff --git a/crates/sim/src/simulation/mod.rs b/crates/sim/src/simulation/mod.rs index eb035d48..3a7d8918 100644 --- a/crates/sim/src/simulation/mod.rs +++ b/crates/sim/src/simulation/mod.rs @@ -13,11 +13,12 @@ use std::collections::HashSet; -use anyhow::Error; -use ethers::types::{Address, H256, U256}; +#[cfg(feature = "test-utils")] +use alloy_primitives::uint; +use alloy_primitives::{Address, B256, U256}; #[cfg(feature = "test-utils")] use mockall::automock; -use rundler_provider::AggregatorSimOut; +use rundler_provider::{AggregatorSimOut, ProviderError}; use rundler_types::{ pool::{MempoolError, SimulationViolation}, EntityInfos, UserOperation, ValidTimeRange, @@ -46,21 +47,21 @@ use crate::{ExpectedStorage, ViolationError}; #[derive(Clone, Debug, Default)] pub struct SimulationResult { /// The mempool IDs that support this operation - pub mempools: Vec, + pub mempools: Vec, /// Block hash this operation was simulated against - pub block_hash: H256, + pub block_hash: B256, /// Block number this operation was simulated against pub block_number: Option, /// Gas used in the pre-op phase of simulation measured /// by the entry point - pub pre_op_gas: U256, + pub pre_op_gas: u128, /// The time range for which this operation is valid pub valid_time_range: ValidTimeRange, /// If using an aggregator, the result of the aggregation /// simulation pub aggregator: Option, /// Code hash of all accessed contracts - pub code_hash: H256, + pub code_hash: B256, /// Whether the sender account is staked pub account_is_staked: bool, /// List of all addresses accessed during validation @@ -93,8 +94,8 @@ pub struct SimulationError { pub entity_infos: Option, } -impl From for SimulationError { - fn from(error: Error) -> Self { +impl From for SimulationError { + fn from(error: anyhow::Error) -> Self { SimulationError { violation_error: ViolationError::Other(error), entity_infos: None, @@ -132,10 +133,19 @@ impl From for MempoolError { } } +impl From for SimulationError { + fn from(error: ProviderError) -> Self { + SimulationError { + violation_error: ViolationError::Other(anyhow::anyhow!("provider error: {error:?}")), + entity_infos: None, + } + } +} + /// Simulator trait for running user operation simulations #[cfg_attr(feature = "test-utils", automock(type UO = rundler_types::v0_6::UserOperation;))] #[async_trait::async_trait] -pub trait Simulator: Send + Sync + 'static { +pub trait Simulator: Send + Sync { /// The type of user operation that this simulator can handle type UO: UserOperation; @@ -144,8 +154,8 @@ pub trait Simulator: Send + Sync + 'static { async fn simulate_validation( &self, op: Self::UO, - block_hash: Option, - expected_code_hash: Option, + block_hash: Option, + expected_code_hash: Option, ) -> Result; } @@ -157,11 +167,11 @@ pub struct Settings { pub min_unstake_delay: u32, /// The minimum amount of stake that a staked entity must have on the entry point /// contract in order to be considered staked. - pub min_stake_value: u128, + pub min_stake_value: U256, /// The maximum amount of gas that can be used during the simulation call - pub max_simulate_handle_ops_gas: u64, + pub max_simulate_handle_ops_gas: u128, /// The maximum amount of verification gas that can be used during the simulation call - pub max_verification_gas: u64, + pub max_verification_gas: u128, /// The max duration of the custom javascript tracer. Must be in a format parseable by the /// ParseDuration function on an ethereum node. See Docs: https://pkg.go.dev/time#ParseDuration pub tracer_timeout: String, @@ -171,9 +181,9 @@ impl Settings { /// Create new settings pub fn new( min_unstake_delay: u32, - min_stake_value: u128, - max_simulate_handle_ops_gas: u64, - max_verification_gas: u64, + min_stake_value: U256, + max_simulate_handle_ops_gas: u128, + max_verification_gas: u128, tracer_timeout: String, ) -> Self { Self { @@ -193,7 +203,7 @@ impl Default for Settings { // one day in seconds: defined in the ERC-4337 spec min_unstake_delay: 84600, // 10^18 wei = 1 eth - min_stake_value: 1_000_000_000_000_000_000, + min_stake_value: uint!(1_000_000_000_000_000_000_U256), // 550 million gas: currently the defaults for Alchemy eth_call max_simulate_handle_ops_gas: 550_000_000, max_verification_gas: 5_000_000, diff --git a/crates/sim/src/simulation/simulator.rs b/crates/sim/src/simulation/simulator.rs index 4b1dbe82..1183d10d 100644 --- a/crates/sim/src/simulation/simulator.rs +++ b/crates/sim/src/simulation/simulator.rs @@ -14,14 +14,15 @@ use std::{ collections::{HashMap, HashSet}, marker::PhantomData, - ops::Deref, - sync::Arc, }; +use alloy_primitives::{Address, B256, U256}; +use anyhow::Context; use async_trait::async_trait; -use ethers::types::{Address, H256, U256}; +use futures_util::TryFutureExt; use rundler_provider::{ - AggregatorOut, AggregatorSimOut, EntryPoint, Provider, SignatureAggregator, SimulationProvider, + AggregatorOut, AggregatorSimOut, EntryPoint, EvmProvider, SignatureAggregator, + SimulationProvider, }; use rundler_types::{ pool::{NeedsStakeInformation, SimulationViolation}, @@ -42,18 +43,18 @@ use crate::{ Settings, Simulator, }, types::ViolationError, - utils, SimulationError, SimulationResult, + SimulationError, SimulationResult, }; /// Create a new simulator for v0.6 entry point contracts pub fn new_v0_6_simulator( - provider: Arc

, + provider: P, entry_point: E, sim_settings: Settings, - mempool_configs: HashMap, + mempool_configs: HashMap, ) -> impl Simulator where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + SignatureAggregator + SimulationProvider @@ -70,13 +71,13 @@ where /// Create a new simulator for v0.6 entry point contracts pub fn new_v0_7_simulator( - provider: Arc

, + provider: P, entry_point: E, sim_settings: Settings, - mempool_configs: HashMap, + mempool_configs: HashMap, ) -> impl Simulator where - P: Provider, + P: EvmProvider + Clone, E: EntryPoint + SignatureAggregator + SimulationProvider @@ -104,11 +105,11 @@ where /// the violations. #[derive(Debug)] pub struct SimulatorImpl { - provider: Arc

, + provider: P, entry_point: E, validation_context_provider: V, sim_settings: Settings, - mempool_configs: HashMap, + mempool_configs: HashMap, allow_unstaked_addresses: HashSet

, _uo_type: PhantomData, } @@ -116,8 +117,8 @@ pub struct SimulatorImpl { impl SimulatorImpl where UO: UserOperation, - P: Provider, - E: EntryPoint + SignatureAggregator + Clone, + P: EvmProvider, + E: EntryPoint + SignatureAggregator, V: ValidationContextProvider, { /// Create a new simulator @@ -126,11 +127,11 @@ where /// It is used during simulation to determine which mempools support /// the violations found during simulation. pub fn new( - provider: Arc

, + provider: P, entry_point: E, validation_context_provider: V, sim_settings: Settings, - mempool_configs: HashMap, + mempool_configs: HashMap, ) -> Self { // Get a list of entities that are allowed to act as staked entities despite being unstaked let mut allow_unstaked_addresses = HashSet::new(); @@ -159,16 +160,17 @@ where &self, op: UO, aggregator_address: Option

, - gas_cap: u64, - ) -> anyhow::Result { + gas_cap: u128, + ) -> Result { let Some(aggregator_address) = aggregator_address else { return Ok(AggregatorOut::NotNeeded); }; - self.entry_point - .clone() + Ok(self + .entry_point .validate_user_op_signature(aggregator_address, op, gas_cap) .await + .context("should call validate user op signature")?) } // Parse the output from tracing and return a list of violations. @@ -177,7 +179,7 @@ where fn gather_context_violations( &self, context: &mut ValidationContext, - ) -> anyhow::Result> { + ) -> Result, SimulationError> { let &mut ValidationContext { ref entity_infos, ref tracer_out, @@ -212,7 +214,7 @@ where } for (addr, opcode) in &phase.ext_code_access_info { - if *addr == self.entry_point.address() { + if addr == self.entry_point.address() { // [OP-054] // [OP-051] - If calling `EXTCODESIZE ISZERO` the tracer won't add to this list violations.push(SimulationViolation::UsedForbiddenOpcode( @@ -240,7 +242,7 @@ where slots_by_address: &tracer_out.associated_slots_by_address, address, sender: sender_address, - entrypoint: self.entry_point.address(), + entrypoint: *self.entry_point.address(), has_factory, entity: &ei.entity, }); @@ -267,11 +269,8 @@ where accessed_entity, accessed_address, slot, - min_stake: self.sim_settings.min_stake_value.into(), - min_unstake_delay: self - .sim_settings - .min_unstake_delay - .into(), + min_stake: self.sim_settings.min_stake_value, + min_unstake_delay: self.sim_settings.min_unstake_delay, }, ))); } @@ -379,7 +378,7 @@ where // weird case where CREATE2 is called > 1, but there isn't a factory // defined. This should never happen, blame the violation on the entry point. violations.push(SimulationViolation::FactoryCalledCreate2Twice( - self.entry_point.address(), + *self.entry_point.address(), )); } } @@ -388,7 +387,7 @@ where // Get violations specific to the implemented entry point from the context provider violations.extend( self.validation_context_provider - .get_specific_violations(context), + .get_specific_violations(context)?, ); Ok(violations) @@ -401,8 +400,8 @@ where &self, op: UO, context: &mut ValidationContext, - expected_code_hash: Option, - ) -> Result<(H256, Option), SimulationError> { + expected_code_hash: Option, + ) -> Result<(B256, Option), SimulationError> { let &mut ValidationContext { block_id, ref mut tracer_out, @@ -414,11 +413,13 @@ where let mut violations = vec![]; let aggregator_address = entry_point_out.aggregator_info.map(|info| info.address); - let code_hash_future = utils::get_code_hash( - self.provider.deref(), - tracer_out.accessed_contracts.keys().cloned().collect(), - Some(block_id), - ); + let code_hash_future = self + .provider + .get_code_hash( + tracer_out.accessed_contracts.keys().cloned().collect(), + Some(block_id), + ) + .map_err(|e| SimulationError::from(anyhow::anyhow!("should call get_code_hash {e:?}"))); let aggregator_signature_future = self.validate_aggregator_signature( op, aggregator_address, @@ -458,8 +459,8 @@ where impl Simulator for SimulatorImpl where UO: UserOperation, - P: Provider, - E: EntryPoint + SignatureAggregator + Clone, + P: EvmProvider, + E: EntryPoint + SignatureAggregator, V: ValidationContextProvider, { type UO = UO; @@ -467,8 +468,8 @@ where async fn simulate_validation( &self, op: UO, - block_hash: Option, - expected_code_hash: Option, + block_hash: Option, + expected_code_hash: Option, ) -> Result { let (block_hash, block_number) = match block_hash { // If we are given a block_hash, we return a None block number, avoiding an extra call @@ -479,7 +480,7 @@ where .get_latest_block_hash_and_number() .await .map_err(anyhow::Error::from)?; - (hash_and_num.0, Some(hash_and_num.1.as_u64())) + (hash_and_num.0, Some(hash_and_num.1)) } }; let block_id = block_hash.into(); @@ -680,14 +681,12 @@ fn override_infos_staked(eis: &mut EntityInfos, allow_unstaked_addresses: &HashS #[cfg(test)] mod tests { - use std::str::FromStr; - + use alloy_primitives::{address, b256, bytes, uint, Bytes}; use context::ContractInfo; - use ethers::types::{Address, BlockId, BlockNumber, Bytes, U256, U64}; - use rundler_provider::{AggregatorOut, MockEntryPointV0_6, MockProvider}; - use rundler_types::{ - contracts::utils::get_code_hashes::CodeHashesResult, v0_6::UserOperation, Opcode, StakeInfo, + use rundler_provider::{ + AggregatorOut, BlockId, BlockNumberOrTag, MockEntryPointV0_6, MockEvmProvider, }; + use rundler_types::{v0_6::UserOperation, Opcode, StakeInfo}; use self::context::{Phase, TracerOutput}; use super::*; @@ -701,22 +700,22 @@ mod tests { async fn get_context( &self, op: UserOperationV0_6, - block_id: ethers::types::BlockId, + block_id: rundler_provider::BlockId, ) -> Result, ViolationError>; fn get_specific_violations( &self, context: &ValidationContext, - ) -> Vec; + ) -> anyhow::Result>; } } fn create_base_config() -> ( - MockProvider, + MockEvmProvider, MockEntryPointV0_6, MockValidationContextProviderV0_6, ) { ( - MockProvider::new(), + MockEvmProvider::new(), MockEntryPointV0_6::new(), MockValidationContextProviderV0_6::new(), ) @@ -726,7 +725,7 @@ mod tests { let tracer_out = TracerOutput { accessed_contracts: HashMap::from([ ( - Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(), + address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -734,7 +733,7 @@ mod tests { } ), ( - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -742,7 +741,7 @@ mod tests { } ), ( - Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(), + address!("8abb13360b87be5eeb1b98647a016add927a136c"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -809,46 +808,37 @@ mod tests { ValidationContext { op: UserOperation { - verification_gas_limit: U256::from(2000), - pre_verification_gas: U256::from(1000), + verification_gas_limit: 2000, + pre_verification_gas: 1000, ..Default::default() }, has_factory: true, associated_addresses: HashSet::new(), - block_id: BlockId::Number(BlockNumber::Latest), + block_id: BlockId::Number(BlockNumberOrTag::Latest), entity_infos: context::infos_from_validation_output( - Some(Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap()), - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), - Some(Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap()), + Some(address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), + Some(address!("8abb13360b87be5eeb1b98647a016add927a136c")), &ValidationOutput { - return_info: ValidationReturnInfo::from(( - U256::default(), - U256::default(), - false, - 0, - 0, - Bytes::default(), - )), - sender_info: StakeInfo::from((U256::default(), U256::default())), - factory_info: StakeInfo::from((U256::default(), U256::default())), - paymaster_info: StakeInfo::from((U256::default(), U256::default())), + return_info: ValidationReturnInfo::default(), + sender_info: StakeInfo::default(), + factory_info: StakeInfo::default(), + paymaster_info: StakeInfo::default(), aggregator_info: None, }, &Settings::default(), ), tracer_out, entry_point_out: ValidationOutput { - return_info: ValidationReturnInfo::from(( - 3000.into(), - U256::default(), - true, - 0, - 0, - Bytes::default(), - )), - sender_info: StakeInfo::from((U256::default(), U256::default())), - factory_info: StakeInfo::from((U256::default(), U256::default())), - paymaster_info: StakeInfo::from((U256::default(), U256::default())), + return_info: ValidationReturnInfo { + pre_op_gas: 3000, + account_sig_failed: true, + paymaster_sig_failed: true, + ..Default::default() + }, + sender_info: StakeInfo::default(), + factory_info: StakeInfo::default(), + paymaster_info: StakeInfo::default(), aggregator_info: None, }, accessed_addresses: HashSet::new(), @@ -856,29 +846,21 @@ mod tests { } fn create_simulator( - provider: MockProvider, + provider: MockEvmProvider, entry_point: MockEntryPointV0_6, context: MockValidationContextProviderV0_6, ) -> SimulatorImpl< UserOperation, - MockProvider, - Arc, + MockEvmProvider, + MockEntryPointV0_6, MockValidationContextProviderV0_6, > { let settings = Settings::default(); let mut mempool_configs = HashMap::new(); - mempool_configs.insert(H256::zero(), MempoolConfig::default()); + mempool_configs.insert(B256::ZERO, MempoolConfig::default()); - let provider = Arc::new(provider); - - SimulatorImpl::new( - Arc::clone(&provider), - Arc::new(entry_point), - context, - settings, - mempool_configs, - ) + SimulatorImpl::new(provider, entry_point, context, settings, mempool_configs) } #[tokio::test] @@ -889,50 +871,40 @@ mod tests { .expect_get_latest_block_hash_and_number() .returning(|| { Ok(( - H256::from_str( - "0x38138f1cb4653ab6ab1c89ae3a6acc8705b54bd16a997d880c4421014ed66c3d", - ) - .unwrap(), - U64::zero(), + b256!("38138f1cb4653ab6ab1c89ae3a6acc8705b54bd16a997d880c4421014ed66c3d"), + 0, )) }); + provider.expect_get_code_hash().returning(|_, _| { + Ok(b256!( + "091cd005abf68e7b82c951a8619f065986132f67a0945153533cfcdd93b6895f" + )) + }); + context .expect_get_context() .returning(move |_, _| Ok(get_test_context())); context .expect_get_specific_violations() - .return_const(vec![]); - - // The underlying call constructor when getting the code hash in check_contracts - provider - .expect_call_constructor() - .returning(|_, _: Vec
, _, _| { - Ok(CodeHashesResult { - hash: H256::from_str( - "0x091cd005abf68e7b82c951a8619f065986132f67a0945153533cfcdd93b6895f", - ) - .unwrap() - .into(), - }) - }); + .returning(|_| Ok(vec![])); entry_point .expect_validate_user_op_signature() .returning(|_, _, _| Ok(AggregatorOut::NotNeeded)); let user_operation = UserOperation { - sender: Address::from_str("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + sender: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), nonce: U256::from(264), - init_code: Bytes::from_str("0x").unwrap(), - call_data: Bytes::from_str("0xb61d27f6000000000000000000000000b856dbd4fa1a79a46d426f537455e7d3e79ab7c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d087d28800000000000000000000000000000000000000000000000000000000").unwrap(), - call_gas_limit: U256::from(9100), - verification_gas_limit: U256::from(64805), - pre_verification_gas: U256::from(46128), - max_fee_per_gas: U256::from(105000100), - max_priority_fee_per_gas: U256::from(105000000), - paymaster_and_data: Bytes::from_str("0x").unwrap(), - signature: Bytes::from_str("0x98f89993ce573172635b44ef3b0741bd0c19dd06909d3539159f6d66bef8c0945550cc858b1cf5921dfce0986605097ba34c2cf3fc279154dd25e161ea7b3d0f1c").unwrap(), + init_code: Bytes::default(), + call_data: bytes!("b61d27f6000000000000000000000000b856dbd4fa1a79a46d426f537455e7d3e79ab7c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d087d28800000000000000000000000000000000000000000000000000000000"), + call_gas_limit: 9100, + verification_gas_limit: 64805, + pre_verification_gas: 46128, + max_fee_per_gas: 105000100, + max_priority_fee_per_gas: 105000000, + paymaster_and_data: Bytes::default(), + signature: bytes!("98f89993ce573172635b44ef3b0741bd0c19dd06909d3539159f6d66bef8c0945550cc858b1cf5921dfce0986605097ba34c2cf3fc279154dd25e161ea7b3d0f1c"), }; let simulator = create_simulator(provider, entry_point, context); @@ -947,10 +919,10 @@ mod tests { let (provider, mut entry_point, mut context_provider) = create_base_config(); entry_point .expect_address() - .returning(|| Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap()); + .return_const(address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")); context_provider .expect_get_specific_violations() - .return_const(vec![]); + .returning(|_| Ok(vec![])); let mut context = get_test_context(); @@ -964,18 +936,15 @@ mod tests { )]; // add a storage access for a random unrelated address - let mut writes = HashMap::new(); + let mut writes: HashMap = HashMap::new(); writes.insert( - H256::from_str("0xa3f946b7ed2f016739c6be6031c5579a53d3784a471c3b5f9c2a1f8706c65a4b") - .unwrap() - .to_fixed_bytes() - .into(), + uint!(0xa3f946b7ed2f016739c6be6031c5579a53d3784a471c3b5f9c2a1f8706c65a4b_U256), 1, ); context.tracer_out.phases[1].storage_accesses.insert( - Address::from_str("0x1c0e100fcf093c64cdaa545b425ad7ed8e8a0db6").unwrap(), + address!("1c0e100fcf093c64cdaa545b425ad7ed8e8a0db6"), AccessInfo { reads: HashMap::new(), writes, @@ -991,43 +960,37 @@ mod tests { SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Account, - address: Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4") - .unwrap() + address: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4") }, - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), ViolationOpCode(Opcode::GASPRICE), ), SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Account, - address: Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4") - .unwrap() + address: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4") }, - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), ViolationOpCode(Opcode::COINBASE), ), SimulationViolation::UsedForbiddenPrecompile( Entity { kind: EntityType::Account, - address: Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4") - .unwrap() + address: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4") }, - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), - Address::from_str("0x0000000000000000000000000000000000000019").unwrap(), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), + address!("0000000000000000000000000000000000000019"), ), SimulationViolation::InvalidStorageAccess( Entity { kind: EntityType::Account, - address: Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4") - .unwrap() + address: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4") }, StorageSlot { - address: Address::from_str("0x1c0e100fcf093c64cdaa545b425ad7ed8e8a0db6") - .unwrap(), - slot: U256::from_str( - "0xa3f946b7ed2f016739c6be6031c5579a53d3784a471c3b5f9c2a1f8706c65a4b" + address: address!("1c0e100fcf093c64cdaa545b425ad7ed8e8a0db6"), + slot: uint!( + 0xa3f946b7ed2f016739c6be6031c5579a53d3784a471c3b5f9c2a1f8706c65a4b_U256 ) - .unwrap() } ), ] @@ -1039,7 +1002,7 @@ mod tests { let (provider, ep, mut context_provider) = create_base_config(); context_provider .expect_get_specific_violations() - .return_const(vec![]); + .returning(|_| Ok(vec![])); let mut context = get_test_context(); @@ -1059,19 +1022,17 @@ mod tests { SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Paymaster, - address: Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c") - .unwrap() + address: address!("8abb13360b87be5eeb1b98647a016add927a136c"), }, - Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(), + address!("8abb13360b87be5eeb1b98647a016add927a136c"), ViolationOpCode(Opcode::SELFBALANCE) ), SimulationViolation::UsedForbiddenOpcode( Entity { kind: EntityType::Paymaster, - address: Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c") - .unwrap() + address: address!("8abb13360b87be5eeb1b98647a016add927a136c"), }, - Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(), + address!("8abb13360b87be5eeb1b98647a016add927a136c"), ViolationOpCode(Opcode::BALANCE) ) ] @@ -1087,19 +1048,18 @@ mod tests { async fn test_factory_staking() { let (provider, mut ep, mut context_provider) = create_base_config(); ep.expect_address() - .returning(|| Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap()); + .return_const(address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")); context_provider .expect_get_specific_violations() - .return_const(vec![]); + .returning(|_| Ok(vec![])); let mut writes: HashMap = HashMap::new(); - let sender_address = - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(); + let sender_address = address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"); let external_access_address = Address::random(); - let sender_bytes = sender_address.as_bytes().into(); + let sender_bytes = U256::from_be_bytes(sender_address.into_word().into()); writes.insert(sender_bytes, 1); @@ -1115,6 +1075,7 @@ mod tests { // Create the simulator using the provider and tracer let simulator = create_simulator(provider, ep, context_provider); let res = simulator.gather_context_violations(&mut context); + let sender_as_slot = U256::from_be_bytes(sender_address.into_word().into()); assert_eq!( res.unwrap(), @@ -1122,7 +1083,7 @@ mod tests { None, StorageSlot { address: external_access_address, - slot: sender_address.as_bytes().into() + slot: sender_as_slot } )] ); @@ -1137,21 +1098,19 @@ mod tests { async fn test_paymaster_access_during_deploy() { let (provider, mut ep, mut context_provider) = create_base_config(); ep.expect_address() - .returning(|| Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap()); + .return_const(address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")); context_provider .expect_get_specific_violations() - .return_const(vec![]); + .returning(|_| Ok(vec![])); let mut writes: HashMap = HashMap::new(); - let sender_address = - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(); - let paymaster_address = - Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(); + let sender_address = address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"); + let paymaster_address = address!("8abb13360b87be5eeb1b98647a016add927a136c"); let external_access_address = Address::random(); - let sender_bytes = sender_address.as_bytes().into(); + let sender_bytes = U256::from_be_bytes(sender_address.into_word().into()); writes.insert(sender_bytes, 1); @@ -1174,7 +1133,7 @@ mod tests { Some(Entity::paymaster(paymaster_address)), StorageSlot { address: external_access_address, - slot: sender_address.as_bytes().into() + slot: sender_bytes } )] ); @@ -1188,10 +1147,10 @@ mod tests { async fn test_accessed_unsupported_contract() { let (provider, mut ep, mut context_provider) = create_base_config(); ep.expect_address() - .returning(|| Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap()); + .return_const(address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")); context_provider .expect_get_specific_violations() - .return_const(vec![]); + .returning(|_| Ok(vec![])); let addr = Address::random(); let mut context = get_test_context(); diff --git a/crates/sim/src/simulation/unsafe_sim.rs b/crates/sim/src/simulation/unsafe_sim.rs index 8c70c7fa..8c7301d0 100644 --- a/crates/sim/src/simulation/unsafe_sim.rs +++ b/crates/sim/src/simulation/unsafe_sim.rs @@ -11,15 +11,13 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{marker::PhantomData, sync::Arc}; +use std::marker::PhantomData; -use ethers::types::H256; +use alloy_primitives::B256; use rundler_provider::{ - AggregatorOut, EntryPoint, Provider, SignatureAggregator, SimulationProvider, -}; -use rundler_types::{ - pool::SimulationViolation, EntityInfos, UserOperation, ValidTimeRange, ValidationError, + AggregatorOut, BlockId, EntryPoint, EvmProvider, SignatureAggregator, SimulationProvider, }; +use rundler_types::{pool::SimulationViolation, EntityInfos, UserOperation, ValidTimeRange}; use crate::{ SimulationError, SimulationResult, SimulationSettings as Settings, Simulator, ViolationError, @@ -32,7 +30,7 @@ use crate::{ /// WARNING: This is "unsafe" for a reason. None of the ERC-7562 checks are /// performed. pub struct UnsafeSimulator { - provider: Arc

, + provider: P, entry_point: E, sim_settings: Settings, _uo_type: PhantomData, @@ -40,7 +38,7 @@ pub struct UnsafeSimulator { impl UnsafeSimulator { /// Creates a new unsafe simulator - pub fn new(provider: Arc

, entry_point: E, sim_settings: Settings) -> Self { + pub fn new(provider: P, entry_point: E, sim_settings: Settings) -> Self { Self { provider, entry_point, @@ -54,7 +52,7 @@ impl UnsafeSimulator { impl Simulator for UnsafeSimulator where UO: UserOperation, - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider + SignatureAggregator + Clone, { type UO = UO; @@ -65,8 +63,8 @@ where async fn simulate_validation( &self, op: UO, - block_hash: Option, - _expected_code_hash: Option, + block_hash: Option, + _expected_code_hash: Option, ) -> Result { tracing::info!("Performing unsafe simulation"); @@ -79,36 +77,28 @@ where .get_latest_block_hash_and_number() .await .map_err(anyhow::Error::from)?; - (hash_and_num.0, Some(hash_and_num.1.as_u64())) + (hash_and_num.0, Some(hash_and_num.1)) } }; // simulate the validation let validation_result = self .entry_point - .call_simulate_validation( + .simulate_validation( op.clone(), self.sim_settings.max_verification_gas, - Some(block_hash), + Some(BlockId::Hash(block_hash.into())), ) - .await; + .await?; let validation_result = match validation_result { Ok(res) => res, - Err(err) => match err { - ValidationError::Revert(revert) => { - return Err(SimulationError { - violation_error: vec![SimulationViolation::ValidationRevert(revert)].into(), - entity_infos: None, - }) - } - ValidationError::Other(err) => { - return Err(SimulationError { - violation_error: ViolationError::Other(err), - entity_infos: None, - }) - } - }, + Err(err) => { + return Err(SimulationError { + violation_error: vec![SimulationViolation::ValidationRevert(err)].into(), + entity_infos: None, + }); + } }; let valid_until = if validation_result.return_info.valid_until == 0.into() { @@ -171,7 +161,7 @@ where })? } else { Ok(SimulationResult { - mempools: vec![H256::zero()], + mempools: vec![B256::ZERO], block_hash, block_number, pre_op_gas, diff --git a/crates/sim/src/simulation/v0_6/context.rs b/crates/sim/src/simulation/v0_6/context.rs index d74ddf55..1f2e7142 100644 --- a/crates/sim/src/simulation/v0_6/context.rs +++ b/crates/sim/src/simulation/v0_6/context.rs @@ -11,13 +11,16 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{collections::HashSet, sync::Arc}; +use std::collections::HashSet; -use ethers::{abi::AbiDecode, types::BlockId}; -use rundler_provider::{Provider, SimulationProvider}; +use alloy_primitives::hex; +use alloy_sol_types::SolError; +use anyhow::Context; +use rundler_contracts::v0_6::IEntryPoint::FailedOp; +use rundler_provider::{BlockId, EvmProvider, SimulationProvider}; use rundler_types::{ - contracts::v0_6::i_entry_point::FailedOp, pool::SimulationViolation, v0_6::UserOperation, - EntityType, UserOperation as UserOperationTrait, ValidationOutput, + pool::SimulationViolation, v0_6::UserOperation, EntityType, + UserOperation as UserOperationTrait, ValidationOutput, }; use super::{ @@ -75,8 +78,9 @@ where }; let last_entity_type = sim_context::entity_type_from_simulation_phase(tracer_out.phases.len() - 1).unwrap(); + let revert_data_bytes = hex::decode(revert_data).context("should decode revert data")?; - if let Ok(failed_op) = FailedOp::decode_hex(revert_data) { + if let Ok(failed_op) = FailedOp::abi_decode(&revert_data_bytes, false) { let entity_addr = match last_entity_type { EntityType::Factory => factory_address, EntityType::Paymaster => paymaster_address, @@ -91,7 +95,7 @@ where ), ]))? } - let Ok(entry_point_out) = ValidationOutput::decode_v0_6_hex(revert_data) else { + let Ok(entry_point_out) = ValidationOutput::decode_v0_6(revert_data_bytes) else { let entity_addr = match last_entity_type { EntityType::Factory => factory_address, EntityType::Paymaster => paymaster_address, @@ -127,7 +131,7 @@ where fn get_specific_violations( &self, context: &ValidationContext, - ) -> Vec { + ) -> anyhow::Result> { let mut violations = vec![]; let &ValidationContext { @@ -155,31 +159,27 @@ where // This is a special case to cover a bug in the 0.6 entrypoint contract where a specially // crafted UO can use extra verification gas that isn't caught during simulation, but when // it runs on chain causes the transaction to revert. - let verification_gas_used = entry_point_out - .return_info - .pre_op_gas - .saturating_sub(op.pre_verification_gas()); let verification_buffer = op .total_verification_gas_limit() - .saturating_sub(verification_gas_used); + .saturating_sub(entry_point_out.return_info.pre_op_gas); if verification_buffer < REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER { violations.push(SimulationViolation::VerificationGasLimitBufferTooLow( op.total_verification_gas_limit(), - verification_gas_used + REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER, + entry_point_out.return_info.pre_op_gas + REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER, )); } - violations + Ok(violations) } } impl ValidationContextProvider> where - P: Provider, + P: EvmProvider, E: SimulationProvider, { /// Creates a new `ValidationContextProvider` for entry point v0.6 with the given provider and entry point. - pub(crate) fn new(provider: Arc

, entry_point: E, sim_settings: SimulationSettings) -> Self { + pub(crate) fn new(provider: P, entry_point: E, sim_settings: SimulationSettings) -> Self { Self { simulate_validation_tracer: SimulateValidationTracerImpl::new( provider, @@ -194,14 +194,12 @@ where #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; + use std::collections::HashMap; - use ethers::{ - abi::AbiEncode, - types::{Address, Bytes, U256}, - utils::hex, - }; - use rundler_types::{contracts::v0_6::i_entry_point::FailedOp, v0_6::UserOperation, Opcode}; + use alloy_primitives::{address, bytes, hex, Bytes, U256}; + use alloy_sol_types::SolError; + use rundler_contracts::v0_6::IEntryPoint::FailedOp; + use rundler_types::{v0_6::UserOperation, Opcode}; use sim_context::ContractInfo; use super::*; @@ -211,7 +209,7 @@ mod tests { TracerOutput { accessed_contracts: HashMap::from([ ( - Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(), + address!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -219,7 +217,7 @@ mod tests { } ), ( - Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -227,7 +225,7 @@ mod tests { } ), ( - Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(), + address!("8abb13360b87be5eeb1b98647a016add927a136c"), ContractInfo { header: "0x608060".to_string(), opcode: Opcode::CALL, @@ -314,26 +312,26 @@ mod tests { let mut tracer_output = get_test_tracer_output(); tracer_output.revert_data = Some(hex::encode( FailedOp { - op_index: U256::from(100), + opIndex: U256::from(100), reason: "AA23 reverted (or OOG)".to_string(), } - .encode(), + .abi_encode(), )); Ok(tracer_output) }); let user_operation = UserOperation { - sender: Address::from_str("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(), + sender: address!("b856dbd4fa1a79a46d426f537455e7d3e79ab7c4"), nonce: U256::from(264), - init_code: Bytes::from_str("0x").unwrap(), - call_data: Bytes::from_str("0xb61d27f6000000000000000000000000b856dbd4fa1a79a46d426f537455e7d3e79ab7c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d087d28800000000000000000000000000000000000000000000000000000000").unwrap(), - call_gas_limit: U256::from(9100), - verification_gas_limit: U256::from(64805), - pre_verification_gas: U256::from(46128), - max_fee_per_gas: U256::from(105000100), - max_priority_fee_per_gas: U256::from(105000000), - paymaster_and_data: Bytes::from_str("0x").unwrap(), - signature: Bytes::from_str("0x98f89993ce573172635b44ef3b0741bd0c19dd06909d3539159f6d66bef8c0945550cc858b1cf5921dfce0986605097ba34c2cf3fc279154dd25e161ea7b3d0f1c").unwrap(), + init_code: Bytes::default(), + call_data: bytes!("b61d27f6000000000000000000000000b856dbd4fa1a79a46d426f537455e7d3e79ab7c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d087d28800000000000000000000000000000000000000000000000000000000"), + call_gas_limit: 9100, + verification_gas_limit: 64805, + pre_verification_gas: 46128, + max_fee_per_gas: 105000100, + max_priority_fee_per_gas: 105000000, + paymaster_and_data: Bytes::default(), + signature: bytes!("98f89993ce573172635b44ef3b0741bd0c19dd06909d3539159f6d66bef8c0945550cc858b1cf5921dfce0986605097ba34c2cf3fc279154dd25e161ea7b3d0f1c"), }; let context = ValidationContextProvider { diff --git a/crates/sim/src/simulation/v0_6/mod.rs b/crates/sim/src/simulation/v0_6/mod.rs index c0e4a868..4ea5b50e 100644 --- a/crates/sim/src/simulation/v0_6/mod.rs +++ b/crates/sim/src/simulation/v0_6/mod.rs @@ -11,12 +11,10 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use ethers::types::U256; - mod context; pub(crate) use context::ValidationContextProvider; mod tracer; /// Required buffer for verification gas limit when targeting the 0.6 entrypoint contract -pub(crate) const REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER: U256 = U256([2000, 0, 0, 0]); +pub(crate) const REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER: u128 = 2000; diff --git a/crates/sim/src/simulation/v0_6/tracer.rs b/crates/sim/src/simulation/v0_6/tracer.rs index 56633e23..d7285129 100644 --- a/crates/sim/src/simulation/v0_6/tracer.rs +++ b/crates/sim/src/simulation/v0_6/tracer.rs @@ -10,14 +10,14 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{convert::TryFrom, fmt::Debug, sync::Arc}; +use std::{convert::TryFrom, fmt::Debug}; use anyhow::bail; use async_trait::async_trait; -use ethers::types::{ - BlockId, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, +use rundler_provider::{ + BlockId, EvmProvider, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, GethTrace, SimulationProvider, }; -use rundler_provider::{Provider, SimulationProvider}; use rundler_types::v0_6::UserOperation; use serde::Deserialize; @@ -27,8 +27,8 @@ impl TryFrom for TracerOutput { type Error = anyhow::Error; fn try_from(trace: GethTrace) -> Result { match trace { - GethTrace::Unknown(value) => Ok(TracerOutput::deserialize(&value)?), - GethTrace::Known(_) => { + GethTrace::JS(value) => Ok(TracerOutput::deserialize(&value)?), + _ => { bail!("Failed to deserialize simulation trace") } } @@ -37,7 +37,7 @@ impl TryFrom for TracerOutput { /// Trait for tracing the simulation of a user operation. #[async_trait] -pub(super) trait SimulateValidationTracer: Send + Sync + 'static { +pub(super) trait SimulateValidationTracer: Send + Sync { /// Traces the simulation of a user operation. async fn trace_simulate_validation( &self, @@ -49,9 +49,9 @@ pub(super) trait SimulateValidationTracer: Send + Sync + 'static { /// Tracer implementation for the bundler's custom tracer. #[derive(Debug)] pub(crate) struct SimulateValidationTracerImpl { - provider: Arc

, + provider: P, entry_point: E, - max_validation_gas: u64, + max_validation_gas: u128, tracer_timeout: String, } @@ -61,7 +61,7 @@ pub(crate) struct SimulateValidationTracerImpl { #[async_trait] impl SimulateValidationTracer for SimulateValidationTracerImpl where - P: Provider, + P: EvmProvider, E: SimulationProvider, { async fn trace_simulate_validation( @@ -98,9 +98,9 @@ where impl SimulateValidationTracerImpl { /// Creates a new instance of the bundler's custom tracer. pub(crate) fn new( - provider: Arc

, + provider: P, entry_point: E, - max_validation_gas: u64, + max_validation_gas: u128, tracer_timeout: String, ) -> Self { Self { diff --git a/crates/sim/src/simulation/v0_7/context.rs b/crates/sim/src/simulation/v0_7/context.rs index 26ff9cef..fcff1eb2 100644 --- a/crates/sim/src/simulation/v0_7/context.rs +++ b/crates/sim/src/simulation/v0_7/context.rs @@ -11,29 +11,21 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{ - collections::{BTreeSet, HashMap, HashSet}, - sync::Arc, -}; +use std::collections::{BTreeSet, HashMap, HashSet}; -use anyhow::{bail, Context}; -use ethers::{ - abi::AbiDecode, - types::{Address, BlockId, Bytes, H160, U256}, - utils::{hex::FromHex, keccak256}, +use alloy_primitives::{ + address, + hex::{self, FromHex}, + keccak256, Address, Bytes, U256, }; -use rundler_provider::{EntryPoint, Provider, SimulationProvider}; +use alloy_sol_types::SolType; +use anyhow::{bail, Context}; +use rundler_contracts::v0_7::ValidationResult; +use rundler_provider::{BlockId, EntryPoint, EvmProvider, SimulationProvider}; use rundler_types::{ - contracts::v0_7::{ - entry_point_simulations::{FailedOpWithRevert, SimulateValidationReturn}, - i_entry_point::FailedOp, - }, - pool::SimulationViolation, - v0_7::UserOperation, - EntityInfos, EntityType, Opcode, UserOperation as UserOperationTrait, ValidationOutput, - ValidationRevert, + pool::SimulationViolation, v0_7::UserOperation, EntityInfos, EntityType, Opcode, + UserOperation as UserOperationTrait, ValidationOutput, ValidationRevert, }; -use rundler_utils::eth::ContractRevertError; use super::tracer::{ CallInfo, ExitType, MethodInfo, SimulateValidationTracer, SimulateValidationTracerImpl, @@ -77,8 +69,7 @@ const VALIDATE_USER_OP_METHOD: &str = "0x19822f7c"; const VALIDATE_PAYMASTER_USER_OP_METHOD: &str = "0x52b7512c"; const DEPOSIT_TO_METHOD: &str = "0xb760faf9"; // Max precompile address 0x10000 -const MAX_PRECOMPILE_ADDRESS: Address = - H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]); +const MAX_PRECOMPILE_ADDRESS: Address = address!("0000000000000000000000000000000000010000"); /// A provider for creating `ValidationContext` for entry point v0.7. pub(crate) struct ValidationContextProvider { @@ -141,7 +132,7 @@ where // Check the call stack for calls with value or to the entry point for (i, call) in call_stack.iter().enumerate() { if call.to == self.entry_point_address - && (call.from != self.entry_point_address && call.from != Address::zero()) + && (call.from != self.entry_point_address && call.from != Address::ZERO) { // [OP-053] - can only call fallback from sender if call.method == "0x" && call.from == op.sender() { @@ -160,7 +151,7 @@ where } // [OP-061] calls with value are banned, except for the calls above - if call.value.is_some_and(|v| v != U256::zero()) { + if call.value.is_some_and(|v| v != U256::ZERO) { let phase = Self::get_nearest_entity_phase(&call_stack[i..], &entity_infos); tracer_out.phases[phase].called_non_entry_point_with_value = true; } @@ -182,7 +173,7 @@ where fn get_specific_violations( &self, context: &ValidationContext, - ) -> Vec { + ) -> anyhow::Result> { let mut violations = vec![]; let &ValidationContext { @@ -196,7 +187,7 @@ where violations.push(SimulationViolation::InvalidPaymasterSignature); } - violations + Ok(violations) } } @@ -250,7 +241,7 @@ impl ValidationContextProvider { call_type: Opcode::CALL, method: SIMULATE_VALIDATION_METHOD.to_string(), to: self.entry_point_address, - from: Address::zero(), + from: Address::ZERO, value: None, gas: 0, gas_used: exit_info.gas_used, @@ -272,31 +263,16 @@ impl ValidationContextProvider { ) -> anyhow::Result> { match top.exit_type { ExitType::Revert => { - if let Ok(result) = FailedOpWithRevert::decode_hex(top.exit_data.clone()) { - let inner_revert_reason = ContractRevertError::decode(&result.inner) - .ok() - .map(|inner_result| inner_result.reason); - Ok(Err(ValidationRevert::Operation { - entry_point_reason: result.reason, - inner_revert_data: result.inner, - inner_revert_reason, - })) - } else if let Ok(failed_op) = FailedOp::decode_hex(top.exit_data.clone()) { - Ok(Err(ValidationRevert::EntryPoint(failed_op.reason))) - } else if let Ok(err) = ContractRevertError::decode_hex(top.exit_data.clone()) { - Ok(Err(ValidationRevert::EntryPoint(err.reason))) - } else { - Ok(Err(ValidationRevert::Unknown( - Bytes::from_hex(top.exit_data.clone()) - .context("failed to parse exit data has hex")?, - ))) - } + let bytes: Bytes = hex::decode(top.exit_data.clone()) + .context("should decode hex exit data")? + .into(); + Ok(Err(rundler_provider::decode_v0_7_validation_revert(&bytes))) } ExitType::Return => { let b = Bytes::from_hex(top.exit_data.clone()) .context("faled to parse exit data as hex")?; - if let Ok(res) = SimulateValidationReturn::decode(&b) { - Ok(Ok(res.0.into())) + if let Ok(res) = ValidationResult::abi_decode(&b, false) { + Ok(Ok(res.try_into().map_err(anyhow::Error::msg)?)) } else { bail!("Failed to decode validation output {}", top.exit_data); } @@ -482,13 +458,13 @@ fn entity_type_to_phase(entity_type: EntityType) -> usize { impl ValidationContextProvider> where - P: Provider, + P: EvmProvider, E: EntryPoint + SimulationProvider, { /// Creates a new `ValidationContextProvider` for entry point v0.7 with the given provider and entry point. - pub(crate) fn new(provider: Arc

, entry_point: E, sim_settings: SimulationSettings) -> Self { + pub(crate) fn new(provider: P, entry_point: E, sim_settings: SimulationSettings) -> Self { Self { - entry_point_address: entry_point.address(), + entry_point_address: *entry_point.address(), simulate_validation_tracer: SimulateValidationTracerImpl::new( provider, entry_point, diff --git a/crates/sim/src/simulation/v0_7/tracer.rs b/crates/sim/src/simulation/v0_7/tracer.rs index 56cde7fd..e4cbf3ec 100644 --- a/crates/sim/src/simulation/v0_7/tracer.rs +++ b/crates/sim/src/simulation/v0_7/tracer.rs @@ -10,15 +10,15 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{collections::HashMap, convert::TryFrom, fmt::Debug, sync::Arc}; +use std::{collections::HashMap, convert::TryFrom, fmt::Debug}; +use alloy_primitives::{Address, U256}; use anyhow::bail; use async_trait::async_trait; -use ethers::types::{ - Address, BlockId, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, - GethTrace, U256, +use rundler_provider::{ + BlockId, EvmProvider, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, GethTrace, SimulationProvider, }; -use rundler_provider::{Provider, SimulationProvider}; use rundler_types::{v0_7::UserOperation, Opcode}; use serde::Deserialize; @@ -101,8 +101,8 @@ impl TryFrom for TracerOutput { type Error = anyhow::Error; fn try_from(trace: GethTrace) -> Result { match trace { - GethTrace::Unknown(value) => Ok(TracerOutput::deserialize(&value)?), - GethTrace::Known(_) => { + GethTrace::JS(value) => Ok(TracerOutput::deserialize(&value)?), + _ => { bail!("Failed to deserialize simulation trace") } } @@ -111,7 +111,7 @@ impl TryFrom for TracerOutput { /// Trait for tracing the simulation of a user operation. #[async_trait] -pub(super) trait SimulateValidationTracer: Send + Sync + 'static { +pub(super) trait SimulateValidationTracer: Send + Sync { /// Traces the simulation of a user operation. async fn trace_simulate_validation( &self, @@ -123,9 +123,9 @@ pub(super) trait SimulateValidationTracer: Send + Sync + 'static { /// Tracer implementation for the bundler's custom tracer. #[derive(Debug)] pub(crate) struct SimulateValidationTracerImpl { - provider: Arc

, + provider: P, entry_point: E, - max_validation_gas: u64, + max_validation_gas: u128, tracer_timeout: String, } @@ -135,7 +135,7 @@ pub(crate) struct SimulateValidationTracerImpl { #[async_trait] impl SimulateValidationTracer for SimulateValidationTracerImpl where - P: Provider, + P: EvmProvider, E: SimulationProvider, { async fn trace_simulate_validation( @@ -173,9 +173,9 @@ where impl SimulateValidationTracerImpl { /// Creates a new instance of the bundler's custom tracer. pub(crate) fn new( - provider: Arc

, + provider: P, entry_point: E, - max_validation_gas: u64, + max_validation_gas: u128, tracer_timeout: String, ) -> Self { Self { diff --git a/crates/sim/src/types.rs b/crates/sim/src/types.rs index 3c45e139..d8b6a0ba 100644 --- a/crates/sim/src/types.rs +++ b/crates/sim/src/types.rs @@ -13,14 +13,14 @@ use std::collections::{btree_map, BTreeMap}; +use alloy_primitives::{Address, B256, U256}; use anyhow::bail; -use ethers::types::{Address, H256, U256}; use serde::{Deserialize, Serialize}; /// The expected storage values for a user operation that must /// be checked to determine if this operation is valid. #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct ExpectedStorage(pub BTreeMap>); +pub struct ExpectedStorage(pub BTreeMap>); impl ExpectedStorage { /// Merge this expected storage with another one, accounting for conflicts. @@ -49,13 +49,10 @@ impl ExpectedStorage { /// Insert a new storage slot value for a given address. pub fn insert(&mut self, address: Address, slot: U256, value: U256) { - let buf: [u8; 32] = slot.into(); - let slot = H256::from_slice(&buf); - - let buf: [u8; 32] = value.into(); - let value = H256::from_slice(&buf); - - self.0.entry(address).or_default().insert(slot, value); + self.0 + .entry(address) + .or_default() + .insert(B256::from(slot), B256::from(value)); } } diff --git a/crates/sim/src/utils.rs b/crates/sim/src/utils.rs deleted file mode 100644 index 73be29ec..00000000 --- a/crates/sim/src/utils.rs +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of Rundler. -// -// Rundler is free software: you can redistribute it and/or modify it under the -// terms of the GNU Lesser General Public License as published by the Free Software -// Foundation, either version 3 of the License, or (at your option) any later version. -// -// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License along with Rundler. -// If not, see https://www.gnu.org/licenses/. - -use anyhow::Context; -use ethers::types::{spoof, Address, BlockId, H256}; -use rundler_provider::Provider; -use rundler_types::contracts::utils::get_code_hashes::{CodeHashesResult, GETCODEHASHES_BYTECODE}; - -/// Hashes together the code from all the provided addresses. The order of the input addresses does -/// not matter. -pub(crate) async fn get_code_hash( - provider: &P, - mut addresses: Vec

, - block_id: Option, -) -> anyhow::Result { - addresses.sort(); - let out: CodeHashesResult = provider - .call_constructor( - &GETCODEHASHES_BYTECODE, - addresses, - block_id, - &spoof::state(), - ) - .await - .context("should compute code hashes")?; - Ok(H256(out.hash)) -} diff --git a/crates/types/src/pool/error.rs b/crates/types/src/pool/error.rs index 729249f2..b1533d3f 100644 --- a/crates/types/src/pool/error.rs +++ b/crates/types/src/pool/error.rs @@ -78,7 +78,7 @@ pub enum MempoolError { /// Paymaster balance too low /// Spec rule: EREP-010 #[error("Paymaster balance too low. Required balance: {0}. Current balance {1}")] - PaymasterBalanceTooLow(u128, u128), + PaymasterBalanceTooLow(U256, U256), /// Operation was rejected due to a precheck violation #[error("Operation violation during precheck {0}")] PrecheckViolation(PrecheckViolation), @@ -125,7 +125,7 @@ pub enum PrecheckViolation { PaymasterIsNotContract(Address), /// The paymaster deposit is too low to pay for the user operation's maximum cost. #[display("paymaster deposit is {0} but must be at least {1} to pay for this operation")] - PaymasterDepositTooLow(u128, u128), + PaymasterDepositTooLow(U256, U256), /// The sender balance is too low to pay for the user operation's maximum cost. /// (when not using a paymaster) #[display("sender balance and deposit together is {0} but must be at least {1} to pay for this operation")] @@ -244,7 +244,7 @@ pub struct NeedsStakeInformation { /// The accessed slot number pub slot: U256, /// Minumum stake - pub min_stake: u128, + pub min_stake: U256, /// Minumum delay after an unstake event pub min_unstake_delay: u32, } diff --git a/crates/types/src/pool/types.rs b/crates/types/src/pool/types.rs index e3fdfc71..35b7c712 100644 --- a/crates/types/src/pool/types.rs +++ b/crates/types/src/pool/types.rs @@ -11,7 +11,7 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use alloy_primitives::{Address, B256}; +use alloy_primitives::{Address, B256, U256}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use crate::{ @@ -92,10 +92,10 @@ pub struct PaymasterMetadata { /// Paymaster address pub address: Address, /// The on-chain balance of the paymaster - pub confirmed_balance: u128, + pub confirmed_balance: U256, /// The pending balance is the confirm balance subtracted by /// the max cost of all the pending user operations that use the paymaster - pub pending_balance: u128, + pub pending_balance: U256, } /// A user operation with additional metadata from validation. diff --git a/crates/types/src/validation_results.rs b/crates/types/src/validation_results.rs index e6e57bb4..59deb081 100644 --- a/crates/types/src/validation_results.rs +++ b/crates/types/src/validation_results.rs @@ -376,7 +376,7 @@ pub fn parse_validation_data(data: U256) -> ValidationData { #[derive(Clone, Copy, Debug, Default)] pub struct StakeInfo { /// The amount of stake - pub stake: u128, + pub stake: U256, /// The delay for unstaking pub unstake_delay_sec: u32, } @@ -390,7 +390,7 @@ impl TryFrom for StakeInfo { unstakeDelaySec, } = value; Ok(Self { - stake: stake.try_into().map_err(|_| "stake is larger than u128")?, + stake, unstake_delay_sec: unstakeDelaySec .try_into() .map_err(|_| "unstake delay is larger than u32")?, @@ -407,7 +407,7 @@ impl TryFrom for StakeInfo { unstakeDelaySec, } = value; Ok(Self { - stake: stake.try_into().map_err(|_| "stake is larger than u128")?, + stake, unstake_delay_sec: unstakeDelaySec .try_into() .map_err(|_| "unstake delay is larger than u32")?,