From 5b47eb2981eb0be4ba2a26988113017aaeca7994 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Thu, 19 Sep 2024 11:25:09 -0500 Subject: [PATCH 1/2] feat(provider): modify provider traits for new types, impl alloy provider --- Cargo.lock | 44 +- Cargo.toml | 4 +- crates/provider/Cargo.toml | 20 +- .../src/alloy/entry_point/arbitrum.rs | 55 ++ crates/provider/src/alloy/entry_point/mod.rs | 110 ++++ .../src/alloy/entry_point/optimism.rs | 47 ++ crates/provider/src/alloy/entry_point/v0_6.rs | 525 ++++++++++++++++++ crates/provider/src/alloy/entry_point/v0_7.rs | 523 +++++++++++++++++ crates/provider/src/alloy/evm.rs | 289 ++++++++++ crates/provider/src/{ethers => alloy}/mod.rs | 8 +- crates/provider/src/ethers/entry_point/mod.rs | 115 ---- .../provider/src/ethers/entry_point/v0_6.rs | 436 --------------- .../provider/src/ethers/entry_point/v0_7.rs | 483 ---------------- .../provider/src/ethers/metrics_middleware.rs | 138 ----- crates/provider/src/ethers/provider.rs | 319 ----------- crates/provider/src/lib.rs | 29 +- crates/provider/src/traits/entry_point.rs | 104 ++-- crates/provider/src/traits/error.rs | 26 +- .../src/traits/{provider.rs => evm.rs} | 117 ++-- crates/provider/src/traits/mod.rs | 9 +- crates/provider/src/traits/test_utils.rs | 215 ++++--- crates/utils/Cargo.toml | 1 - 22 files changed, 1895 insertions(+), 1722 deletions(-) create mode 100644 crates/provider/src/alloy/entry_point/arbitrum.rs create mode 100644 crates/provider/src/alloy/entry_point/mod.rs create mode 100644 crates/provider/src/alloy/entry_point/optimism.rs create mode 100644 crates/provider/src/alloy/entry_point/v0_6.rs create mode 100644 crates/provider/src/alloy/entry_point/v0_7.rs create mode 100644 crates/provider/src/alloy/evm.rs rename crates/provider/src/{ethers => alloy}/mod.rs (72%) delete mode 100644 crates/provider/src/ethers/entry_point/mod.rs delete mode 100644 crates/provider/src/ethers/entry_point/v0_6.rs delete mode 100644 crates/provider/src/ethers/entry_point/v0_7.rs delete mode 100644 crates/provider/src/ethers/metrics_middleware.rs delete mode 100644 crates/provider/src/ethers/provider.rs rename crates/provider/src/traits/{provider.rs => evm.rs} (51%) diff --git a/Cargo.lock b/Cargo.lock index 0ca3f608e..d7b7a6183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,9 @@ dependencies = [ "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-eth", + "alloy-rpc-types-trace", "alloy-transport", + "alloy-transport-http", "async-stream", "async-trait", "auto_impl", @@ -284,6 +286,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "url", ] [[package]] @@ -318,6 +321,7 @@ dependencies = [ "alloy-transport", "alloy-transport-http", "futures", + "hyper-util", "pin-project", "serde", "serde_json", @@ -325,6 +329,7 @@ dependencies = [ "tokio-stream", "tower 0.5.1", "tracing", + "url", ] [[package]] @@ -346,6 +351,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-rpc-types-trace" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54375e5a34ec5a2cf607f9ce98c0ece30dc76ad623afeb25d3953a8d7d30f20" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "alloy-serde" version = "0.3.3" @@ -469,7 +488,14 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1742b94bb814f1ca6b322a6f9dd38a0252ff45a3119e40e888fb7029afa500ce" dependencies = [ + "alloy-json-rpc", "alloy-transport", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "serde_json", + "tower 0.5.1", + "tracing", "url", ] @@ -5146,20 +5172,25 @@ dependencies = [ name = "rundler-provider" version = "0.3.0" dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-json-rpc", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-sol-types", + "alloy-transport", "anyhow", "async-trait", "auto_impl", - "ethers", - "metrics", "mockall", - "parse-display", - "reqwest 0.12.7", + "rundler-contracts", "rundler-provider", "rundler-types", "rundler-utils", - "serde", "thiserror", - "tokio", "tracing", ] @@ -5273,7 +5304,6 @@ dependencies = [ "futures", "itertools 0.13.0", "rand", - "reqwest 0.12.7", "schnellru", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index a46134986..8303b4b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ alloy-sol-types = "0.8.3" alloy-consensus = "0.3.3" alloy-contract = "0.3.3" alloy-json-rpc = "0.3.3" -alloy-provider = "0.3.3" +alloy-provider = { version = "0.3.3", default-features = false } alloy-rpc-client = "0.3.3" alloy-rpc-types-eth = "0.3.3" alloy-rpc-types-trace = "0.3.3" @@ -52,7 +52,7 @@ rand = "0.8.5" reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls"] } rustls = "0.23.12" thiserror = "1.0.63" -tokio = { version = "1.39.3", default-features = false } +tokio = { version = "1.39.3", default-features = false, features = ["rt", "sync", "time"]} tokio-util = "0.7.11" tonic = "0.12.2" tonic-build = "0.12.2" diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index c2ee665bf..5e3332d6a 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -8,20 +8,26 @@ repository.workspace = true publish = false [dependencies] +rundler-contracts = { path = "../contracts" } rundler-types = { path = "../types" } rundler-utils = { path = "../utils" } +alloy-consensus.workspace = true +alloy-contract.workspace = true +alloy-json-rpc.workspace = true +alloy-primitives = { workspace = true, features = ["rand"] } +alloy-provider = { workspace = true, features = ["debug-api", "hyper"] } +alloy-rlp.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-rpc-types-trace.workspace = true +alloy-sol-types.workspace = true +alloy-transport.workspace = true + anyhow.workspace = true async-trait.workspace = true -auto_impl = "1.2.0" -ethers.workspace = true -metrics.workspace = true -reqwest.workspace = true -serde.workspace = true -tokio.workspace = true +auto_impl.workspace = true thiserror.workspace = true tracing.workspace = true -parse-display.workspace = true mockall = {workspace = true, optional = true } diff --git a/crates/provider/src/alloy/entry_point/arbitrum.rs b/crates/provider/src/alloy/entry_point/arbitrum.rs new file mode 100644 index 000000000..a7e3164f8 --- /dev/null +++ b/crates/provider/src/alloy/entry_point/arbitrum.rs @@ -0,0 +1,55 @@ +// 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 alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider as AlloyProvider; +use alloy_sol_types::sol; +use alloy_transport::Transport; + +use crate::ProviderResult; + +// From https://github.com/OffchainLabs/nitro-contracts/blob/fbbcef09c95f69decabaced3da683f987902f3e2/src/node-interface/NodeInterface.sol#L112 +sol! { + #[sol(rpc)] + interface NodeInterface { + function gasEstimateL1Component( + address to, + bool contractCreation, + bytes calldata data + ) + external + payable + returns ( + uint64 gasEstimateForL1, + uint256 baseFee, + uint256 l1BaseFeeEstimate + ); + } +} + +pub(crate) async fn estimate_l1_gas, T: Transport + Clone>( + provider: AP, + oracle_address: Address, + to_address: Address, + data: Bytes, +) -> ProviderResult { + let inst = NodeInterface::NodeInterfaceInstance::new(oracle_address, provider); + + // assume contract creation + let ret = inst + .gasEstimateL1Component(to_address, true, data) + .call() + .await?; + + Ok(ret.gasEstimateForL1 as u128) +} diff --git a/crates/provider/src/alloy/entry_point/mod.rs b/crates/provider/src/alloy/entry_point/mod.rs new file mode 100644 index 000000000..121f75e67 --- /dev/null +++ b/crates/provider/src/alloy/entry_point/mod.rs @@ -0,0 +1,110 @@ +// 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 alloy_consensus::{transaction::SignableTransaction, TxEnvelope, TypedTransaction}; +use alloy_primitives::{address, Address, Bytes, Parity, Signature, U256}; +use alloy_provider::Provider as AlloyProvider; +use alloy_rlp::Encodable; +use alloy_rpc_types_eth::TransactionRequest; +use alloy_transport::Transport; +use rundler_types::chain::{ChainSpec, L1GasOracleContractType}; + +use crate::ProviderResult; + +pub(crate) mod v0_6; +pub(crate) mod v0_7; + +mod arbitrum; +mod optimism; + +#[derive(Debug, Default)] +enum L1GasOracle { + ArbitrumNitro(Address), + OptimismBedrock(Address), + #[default] + None, +} + +impl L1GasOracle { + fn new(chain_spec: &ChainSpec) -> L1GasOracle { + match chain_spec.l1_gas_oracle_contract_type { + L1GasOracleContractType::ArbitrumNitro => { + L1GasOracle::ArbitrumNitro(chain_spec.l1_gas_oracle_contract_address) + } + L1GasOracleContractType::OptimismBedrock => { + L1GasOracle::OptimismBedrock(chain_spec.l1_gas_oracle_contract_address) + } + L1GasOracleContractType::None => L1GasOracle::None, + } + } + + async fn estimate_l1_gas, T: Transport + Clone>( + &self, + provider: AP, + to_address: Address, + data: Bytes, + gas_price: u128, + ) -> ProviderResult { + match self { + L1GasOracle::ArbitrumNitro(oracle_address) => { + arbitrum::estimate_l1_gas(provider, *oracle_address, to_address, data).await + } + L1GasOracle::OptimismBedrock(oracle_address) => { + optimism::estimate_l1_gas(provider, *oracle_address, data, gas_price).await + } + L1GasOracle::None => Ok(0), + } + } +} + +fn max_bundle_transaction_data(to_address: Address, data: Bytes, gas_price: u128) -> Bytes { + // Fill in max values for unknown or varying fields + let gas_price_ceil = gas_price.next_power_of_two() - 1; // max out bits of gas price, assume same power of 2 + let gas_limit = 0xffffffff; // 4 bytes + let nonce = 0xffffffff; // 4 bytes + let chain_id = 0xffffffff; // 4 bytes + + let tx = TransactionRequest::default() + .from(address!("ffffffffffffffffffffffffffffffffffffffff")) + .to(to_address) + .gas_limit(gas_limit) + .max_priority_fee_per_gas(gas_price_ceil) + .max_fee_per_gas(gas_price_ceil) + .value(U256::ZERO) + .input(data.into()) + .nonce(nonce); + + // these conversions should not fail. + let ty = tx.build_typed_tx().unwrap(); + let mut tx_1559 = match ty { + TypedTransaction::Eip1559(tx) => tx, + _ => { + panic!("transaction is not eip1559"); + } + }; + + tx_1559.set_chain_id(chain_id); + + // use a max signature + let tx_envelope: TxEnvelope = tx_1559 + .into_signed(Signature::new( + U256::MAX, + U256::MAX, + Parity::Eip155(u64::MAX), + )) + .into(); + let mut encoded = vec![]; + tx_envelope.encode(&mut encoded); + + encoded.into() +} diff --git a/crates/provider/src/alloy/entry_point/optimism.rs b/crates/provider/src/alloy/entry_point/optimism.rs new file mode 100644 index 000000000..61b2bb639 --- /dev/null +++ b/crates/provider/src/alloy/entry_point/optimism.rs @@ -0,0 +1,47 @@ +// 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 alloy_primitives::{ruint::UintTryTo, Address, Bytes}; +use alloy_provider::Provider as AlloyProvider; +use alloy_sol_types::sol; +use alloy_transport::Transport; +use anyhow::Context; + +use crate::ProviderResult; + +// From https://github.com/ethereum-optimism/optimism/blob/f93f9f40adcd448168c6ea27820aeee5da65fcbd/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L54 +sol! { + #[sol(rpc)] + interface GasPriceOracle { + function getL1Fee(bytes memory _data) external view returns (uint256); + } +} + +pub(crate) async fn estimate_l1_gas, T: Transport + Clone>( + provider: AP, + oracle_address: Address, + data: Bytes, + gas_price: u128, +) -> ProviderResult { + let oracle = GasPriceOracle::GasPriceOracleInstance::new(oracle_address, provider); + + let l1_fee: u128 = oracle + .getL1Fee(data) + .call() + .await? + ._0 + .uint_try_to() + .context("failed to convert L1 fee to u128")?; + + Ok(l1_fee.checked_div(gas_price).unwrap_or(u128::MAX)) +} diff --git a/crates/provider/src/alloy/entry_point/v0_6.rs b/crates/provider/src/alloy/entry_point/v0_6.rs new file mode 100644 index 000000000..e14151ef7 --- /dev/null +++ b/crates/provider/src/alloy/entry_point/v0_6.rs @@ -0,0 +1,525 @@ +// 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 alloy_contract::Error as ContractError; +use alloy_primitives::{ruint::UintTryTo, Address, Bytes, U256}; +use alloy_provider::Provider as AlloyProvider; +use alloy_rpc_types_eth::{state::StateOverride, BlockId, TransactionRequest}; +use alloy_sol_types::{ContractError as SolContractError, SolCall, SolError, SolInterface}; +use alloy_transport::{Transport, TransportError}; +use anyhow::Context; +use rundler_contracts::v0_6::{ + DepositInfo as DepositInfoV0_6, + GetBalances::{self, GetBalancesResult}, + IAggregator, + IEntryPoint::{ + self, ExecutionResult as ExecutionResultV0_6, FailedOp, IEntryPointErrors, + IEntryPointInstance, + }, + UserOperation as ContractUserOperation, UserOpsPerAggregator as UserOpsPerAggregatorV0_6, +}; +use rundler_types::{ + chain::ChainSpec, v0_6::UserOperation, GasFees, UserOpsPerAggregator, ValidationOutput, + ValidationRevert, +}; + +use super::L1GasOracle; +use crate::{ + AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPoint, + EntryPointProvider as EntryPointProviderTrait, EvmCall, ExecutionResult, HandleOpsOut, + L1GasProvider, ProviderResult, SignatureAggregator, SimulationProvider, +}; + +/// Entry point provider for v0.6 +pub struct EntryPointProvider { + i_entry_point: IEntryPointInstance, + l1_gas_oracle: L1GasOracle, + max_aggregation_gas: u128, +} + +impl EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + /// Create a new `EntryPoint` instance for v0.6 + pub fn new( + entry_point_address: Address, + chain_spec: &ChainSpec, + max_aggregation_gas: u128, + provider: AP, + ) -> Self { + Self { + i_entry_point: IEntryPointInstance::new(entry_point_address, provider), + l1_gas_oracle: L1GasOracle::new(chain_spec), + max_aggregation_gas, + } + } +} + +#[async_trait::async_trait] +impl EntryPoint for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + fn address(&self) -> &Address { + self.i_entry_point.address() + } + + async fn balance_of( + &self, + address: Address, + block_id: Option, + ) -> ProviderResult { + let ret = block_id + .map_or(self.i_entry_point.balanceOf(address), |bid| { + self.i_entry_point.balanceOf(address).block(bid) + }) + .call() + .await?; + + Ok(ret._0) + } + + async fn get_deposit_info(&self, address: Address) -> ProviderResult { + self.i_entry_point + .getDepositInfo(address) + .call() + .await + .map_err(Into::into) + .map(|r| r.info.into()) + } + + async fn get_balances(&self, addresses: Vec
) -> ProviderResult> { + let provider = self.i_entry_point.provider(); + let call = GetBalances::deploy_builder(provider, *self.address(), addresses) + .into_transaction_request(); + let out = provider + .call(&call) + .await + .err() + .context("get balances call should revert")?; + let out = GetBalancesResult::abi_decode( + &out.as_error_resp() + .context("get balances call should revert")? + .as_revert_data() + .context("should get revert data from get balances call")?, + false, + ) + .context("should decode revert data from get balances call")?; + + Ok(out.balances) + } +} + +#[async_trait::async_trait] +impl SignatureAggregator for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn aggregate_signatures( + &self, + aggregator_address: Address, + ops: Vec, + ) -> ProviderResult> { + let aggregator = IAggregator::new(aggregator_address, self.i_entry_point.provider()); + let ops: Vec = ops.into_iter().map(Into::into).collect(); + let result = aggregator + .aggregateSignatures(ops) + .gas(self.max_aggregation_gas) + .call() + .await; + + match result { + Ok(ret) => Ok(Some(ret.aggregatedSignature)), + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + if resp.as_revert_data().is_some() { + Ok(None) + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error).context("aggregator contract should aggregate signatures")?, + } + } + + async fn validate_user_op_signature( + &self, + aggregator_address: Address, + user_op: Self::UO, + gas_cap: u128, + ) -> ProviderResult { + let aggregator = IAggregator::new(aggregator_address, self.i_entry_point.provider()); + + let result = aggregator + .validateUserOpSignature(user_op.into()) + .gas(gas_cap) + .call() + .await; + + match result { + Ok(ret) => Ok(AggregatorOut::SuccessWithInfo(AggregatorSimOut { + address: aggregator_address, + signature: ret.sigForUserOp, + })), + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + if resp.as_revert_data().is_some() { + Ok(AggregatorOut::ValidationReverted) + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error.into()), + } + } +} + +#[async_trait::async_trait] +impl BundleHandler for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn call_handle_ops( + &self, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, + ) -> ProviderResult { + let tx = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas); + let res = self.i_entry_point.provider().call(&tx).await; + + match res { + Ok(_) => return Ok(HandleOpsOut::Success), + Err(TransportError::ErrorResp(resp)) => { + if let Some(err) = + resp.as_decoded_error::>(false) + { + match err { + SolContractError::CustomError(IEntryPointErrors::FailedOp(FailedOp { + opIndex, + reason, + })) => { + match &reason[..4] { + // This revert is a bundler issue, not a user op issue, handle it differently + "AA95" => { + Err(anyhow::anyhow!("Handle ops called with insufficient gas") + .into()) + } + _ => Ok(HandleOpsOut::FailedOp( + opIndex + .uint_try_to() + .context("returned opIndex out of bounds")?, + reason, + )), + } + } + SolContractError::CustomError( + IEntryPointErrors::SignatureValidationFailed(err), + ) => Ok(HandleOpsOut::SignatureValidationFailed(err.aggregator)), + SolContractError::Revert(r) => { + // Special handling for a bug in the 0.6 entry point contract to detect the bug where + // the `returndatacopy` opcode reverts due to a postOp revert and the revert data is too short. + // See https://github.com/eth-infinitism/account-abstraction/pull/325 for more details. + // NOTE: this error message is copied directly from Geth and assumes it will not change. + if r.reason.contains("return data out of bounds") { + Ok(HandleOpsOut::PostOpRevert) + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + _ => Err(TransportError::ErrorResp(resp).into()), + } + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error.into()), + } + } + + fn get_send_bundle_transaction( + &self, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, + gas_fees: GasFees, + ) -> TransactionRequest { + let tx = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas); + tx.max_fee_per_gas(gas_fees.max_fee_per_gas) + .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) + } +} + +#[async_trait::async_trait] +impl L1GasProvider for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn calc_l1_gas( + &self, + entry_point_address: Address, + user_op: UserOperation, + gas_price: u128, + ) -> ProviderResult { + let data = self + .i_entry_point + .handleOps(vec![user_op.into()], Address::random()) + .into_transaction_request() + .input + .into_input() + .unwrap(); + + let bundle_data = super::max_bundle_transaction_data(entry_point_address, data, gas_price); + + self.l1_gas_oracle + .estimate_l1_gas( + self.i_entry_point.provider(), + entry_point_address, + bundle_data, + gas_price, + ) + .await + } +} + +#[async_trait::async_trait] +impl SimulationProvider for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + fn get_tracer_simulate_validation_call( + &self, + user_op: UserOperation, + max_validation_gas: u128, + ) -> (TransactionRequest, StateOverride) { + let pvg = user_op.pre_verification_gas; + let call = self + .i_entry_point + .simulateValidation(user_op.into()) + .gas(max_validation_gas + pvg) + .into_transaction_request(); + (call, StateOverride::default()) + } + + async fn simulate_validation( + &self, + user_op: UserOperation, + max_validation_gas: u128, + block_id: Option, + ) -> ProviderResult> { + let pvg = user_op.pre_verification_gas; + let blockless = self + .i_entry_point + .simulateValidation(user_op.into()) + .gas(max_validation_gas + pvg); + let call = match block_id { + Some(block_id) => blockless.block(block_id), + None => blockless, + }; + + match call.call().await { + Ok(_) => Err(anyhow::anyhow!("simulateValidation should always revert"))?, + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + if let Some(err) = + resp.as_decoded_error::>(false) + { + match err { + // success cases + SolContractError::CustomError(IEntryPointErrors::ValidationResult(s)) => { + Ok(Ok(s.try_into().map_err(anyhow::Error::msg)?)) + } + SolContractError::CustomError( + IEntryPointErrors::ValidationResultWithAggregation(s), + ) => Ok(Ok(s.try_into().map_err(anyhow::Error::msg)?)), + // failure cases + SolContractError::CustomError(IEntryPointErrors::FailedOp(f)) => { + Ok(Err(f.into())) + } + SolContractError::Revert(r) => Ok(Err(r.into())), + // unexpected case or unknown error + _ => Err(TransportError::ErrorResp(resp).into()), + } + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error.into()), + } + } + + fn get_simulate_handle_op_call(&self, op: Self::UO, state_override: StateOverride) -> EvmCall { + let data = IEntryPoint::simulateHandleOpCall { + op: op.into(), + target: Address::ZERO, + targetCallData: Bytes::new(), + } + .abi_encode() + .into(); + + EvmCall { + to: *self.i_entry_point.address(), + data, + value: U256::ZERO, + state_override, + } + } + + async fn simulate_handle_op( + &self, + op: Self::UO, + target: Address, + target_call_data: Bytes, + block_id: BlockId, + gas: u128, + state_override: StateOverride, + ) -> ProviderResult> { + let contract_error = self + .i_entry_point + .simulateHandleOp(op.into(), target, target_call_data) + .block(block_id) + .gas(gas) + .state(state_override) + .call() + .await + .err() + .context("simulateHandleOp succeeded, but should always revert")?; + + match contract_error { + ContractError::TransportError(TransportError::ErrorResp(resp)) => { + match resp.as_revert_data() { + Some(err_bytes) => Ok(Self::decode_simulate_handle_ops_revert(&err_bytes)?), + None => Ok(Err(ValidationRevert::Unknown(Bytes::default()))), + } + } + _ => Err(contract_error.into()), + } + } + + fn decode_simulate_handle_ops_revert( + payload: &Bytes, + ) -> ProviderResult> { + let ret = if let Ok(err) = SolContractError::::abi_decode(payload, false) + { + match err { + // success case + SolContractError::CustomError(IEntryPointErrors::ExecutionResult(e)) => { + Ok(e.try_into()?) + } + // failure cases + SolContractError::CustomError(IEntryPointErrors::FailedOp(f)) => Err(f.into()), + SolContractError::CustomError(IEntryPointErrors::SignatureValidationFailed(f)) => { + Err(ValidationRevert::EntryPoint(format!( + "Aggregator signature validation failed: {}", + f.aggregator + ))) + } + SolContractError::Revert(r) => Err(r.into()), + // unexpected cases + SolContractError::CustomError(IEntryPointErrors::ValidationResult(_)) + | SolContractError::CustomError( + IEntryPointErrors::ValidationResultWithAggregation(_), + ) => Err(ValidationRevert::EntryPoint( + "simulateHandleOp returned ValidationResult or ValidationResultWithAggregation, unexpected type".to_string() + )), + SolContractError::Panic(p) => Err(p.into()), + } + } else { + Err(ValidationRevert::Unknown(payload.clone())) + }; + + Ok(ret) + } + + fn simulation_should_revert(&self) -> bool { + true + } +} + +impl EntryPointProviderTrait for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ +} + +fn get_handle_ops_call, T: Transport + Clone>( + entry_point: &IEntryPointInstance, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, +) -> TransactionRequest { + let mut ops_per_aggregator: Vec = ops_per_aggregator + .into_iter() + .map(|uoa| UserOpsPerAggregatorV0_6 { + userOps: uoa.user_ops.into_iter().map(Into::into).collect(), + aggregator: uoa.aggregator, + signature: uoa.signature, + }) + .collect(); + if ops_per_aggregator.len() == 1 && ops_per_aggregator[0].aggregator == Address::ZERO { + entry_point + .handleOps(ops_per_aggregator.swap_remove(0).userOps, beneficiary) + .gas(gas) + .into_transaction_request() + } else { + entry_point + .handleAggregatedOps(ops_per_aggregator, beneficiary) + .gas(gas) + .into_transaction_request() + } +} + +impl TryFrom for ExecutionResult { + type Error = anyhow::Error; + + fn try_from(result: ExecutionResultV0_6) -> Result { + Ok(ExecutionResult { + pre_op_gas: UintTryTo::::uint_try_to(&result.preOpGas) + .context("preOpGas should fit in u128")?, + paid: result.paid, + valid_after: UintTryTo::::uint_try_to(&result.validAfter) + .unwrap() + .into(), + valid_until: UintTryTo::::uint_try_to(&result.validUntil) + .unwrap() + .into(), + target_success: result.targetSuccess, + target_result: result.targetResult, + }) + } +} + +impl From for DepositInfo { + fn from(deposit_info: DepositInfoV0_6) -> Self { + Self { + deposit: U256::from(deposit_info.deposit), + staked: deposit_info.staked, + stake: UintTryTo::::uint_try_to(&deposit_info.stake).unwrap(), + unstake_delay_sec: deposit_info.unstakeDelaySec, + withdraw_time: UintTryTo::::uint_try_to(&deposit_info.withdrawTime).unwrap(), + } + } +} diff --git a/crates/provider/src/alloy/entry_point/v0_7.rs b/crates/provider/src/alloy/entry_point/v0_7.rs new file mode 100644 index 000000000..807a5909d --- /dev/null +++ b/crates/provider/src/alloy/entry_point/v0_7.rs @@ -0,0 +1,523 @@ +// 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 alloy_contract::Error as ContractError; +use alloy_json_rpc::ErrorPayload; +use alloy_primitives::{ruint::UintTryTo, Address, Bytes, U256}; +use alloy_provider::Provider as AlloyProvider; +use alloy_rpc_types_eth::{ + state::{AccountOverride, StateOverride}, + BlockId, TransactionRequest, +}; +use alloy_sol_types::{ + ContractError as SolContractError, SolCall, SolError, SolInterface, SolValue, +}; +use alloy_transport::{Transport, TransportError}; +use anyhow::Context; +use rundler_contracts::v0_7::{ + DepositInfo as DepositInfoV0_7, + GetBalances::{self, GetBalancesResult}, + IAggregator, + IEntryPoint::{FailedOp, IEntryPointErrors, IEntryPointInstance}, + IEntryPointSimulations::{ + self, ExecutionResult as ExecutionResultV0_7, IEntryPointSimulationsInstance, + }, + UserOpsPerAggregator as UserOpsPerAggregatorV0_7, ValidationResult as ValidationResultV0_7, + ENTRY_POINT_SIMULATIONS_V0_7_DEPLOYED_BYTECODE, +}; +use rundler_types::{ + chain::ChainSpec, v0_7::UserOperation, GasFees, UserOpsPerAggregator, ValidationOutput, + ValidationRevert, +}; + +use super::L1GasOracle; +use crate::{ + AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPoint, + EntryPointProvider as EntryPointProviderTrait, EvmCall, ExecutionResult, HandleOpsOut, + L1GasProvider, ProviderResult, SignatureAggregator, SimulationProvider, +}; + +/// Entry point provider for v0.7 +pub struct EntryPointProvider { + i_entry_point: IEntryPointInstance, + l1_gas_oracle: L1GasOracle, + max_aggregation_gas: u128, +} + +impl EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + /// Create a new `EntryPoint` instance for v0.7 + pub fn new( + entry_point_address: Address, + chain_spec: &ChainSpec, + max_aggregation_gas: u128, + provider: AP, + ) -> Self { + Self { + i_entry_point: IEntryPointInstance::new(entry_point_address, provider), + l1_gas_oracle: L1GasOracle::new(chain_spec), + max_aggregation_gas, + } + } +} + +#[async_trait::async_trait] +impl EntryPoint for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + fn address(&self) -> &Address { + self.i_entry_point.address() + } + + async fn balance_of( + &self, + address: Address, + block_id: Option, + ) -> ProviderResult { + let ret = block_id + .map_or(self.i_entry_point.balanceOf(address), |bid| { + self.i_entry_point.balanceOf(address).block(bid) + }) + .call() + .await?; + + Ok(ret._0) + } + + async fn get_deposit_info(&self, address: Address) -> ProviderResult { + self.i_entry_point + .getDepositInfo(address) + .call() + .await + .map_err(Into::into) + .map(|r| r.info.into()) + } + + async fn get_balances(&self, addresses: Vec
) -> ProviderResult> { + let provider = self.i_entry_point.provider(); + let call = GetBalances::deploy_builder(provider, *self.address(), addresses) + .into_transaction_request(); + let out = provider + .call(&call) + .await + .err() + .context("get balances call should revert")?; + + let out = GetBalancesResult::abi_decode( + &out.as_error_resp() + .context("get balances call should revert")? + .as_revert_data() + .context("should get revert data from get balances call")?, + false, + ) + .context("should decode revert data from get balances call")?; + + Ok(out.balances) + } +} + +#[async_trait::async_trait] +impl SignatureAggregator for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn aggregate_signatures( + &self, + aggregator_address: Address, + ops: Vec, + ) -> ProviderResult> { + let aggregator = IAggregator::new(aggregator_address, self.i_entry_point.provider()); + + let packed_ops = ops.into_iter().map(|op| op.pack()).collect(); + + let result = aggregator + .aggregateSignatures(packed_ops) + .gas(self.max_aggregation_gas) + .call() + .await; + + match result { + Ok(ret) => Ok(Some(ret.aggregatedSignature)), + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + if resp.as_revert_data().is_some() { + Ok(None) + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error).context("aggregator contract should aggregate signatures")?, + } + } + + async fn validate_user_op_signature( + &self, + aggregator_address: Address, + user_op: Self::UO, + gas_cap: u128, + ) -> ProviderResult { + let aggregator = IAggregator::new(aggregator_address, self.i_entry_point.provider()); + + let result = aggregator + .validateUserOpSignature(user_op.pack()) + .gas(gas_cap) + .call() + .await; + + match result { + Ok(ret) => Ok(AggregatorOut::SuccessWithInfo(AggregatorSimOut { + address: aggregator_address, + signature: ret.sigForUserOp, + })), + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + if resp.as_revert_data().is_some() { + Ok(AggregatorOut::ValidationReverted) + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error.into()), + } + } +} + +#[async_trait::async_trait] +impl BundleHandler for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn call_handle_ops( + &self, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, + ) -> ProviderResult { + let tx = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas); + let res = self.i_entry_point.provider().call(&tx).await; + + match res { + Ok(_) => return Ok(HandleOpsOut::Success), + Err(TransportError::ErrorResp(resp)) => { + if let Some(err) = resp.as_decoded_error::(false) { + match err { + IEntryPointErrors::FailedOp(FailedOp { opIndex, reason }) => { + match &reason[..4] { + // This revert is a bundler issue, not a user op issue, handle it differently + "AA95" => { + Err(anyhow::anyhow!("Handle ops called with insufficient gas") + .into()) + } + _ => Ok(HandleOpsOut::FailedOp( + opIndex + .uint_try_to() + .context("returned opIndex out of bounds")?, + reason, + )), + } + } + IEntryPointErrors::SignatureValidationFailed(failure) => { + Ok(HandleOpsOut::SignatureValidationFailed(failure.aggregator)) + } + _ => Err(TransportError::ErrorResp(resp).into()), + } + } else { + Err(TransportError::ErrorResp(resp).into()) + } + } + Err(error) => Err(error.into()), + } + } + + fn get_send_bundle_transaction( + &self, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, + gas_fees: GasFees, + ) -> TransactionRequest { + let tx = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas); + tx.max_fee_per_gas(gas_fees.max_fee_per_gas) + .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) + } +} + +#[async_trait::async_trait] +impl L1GasProvider for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + async fn calc_l1_gas( + &self, + entry_point_address: Address, + user_op: UserOperation, + gas_price: u128, + ) -> ProviderResult { + let data = self + .i_entry_point + .handleOps(vec![user_op.pack()], Address::random()) + .into_transaction_request() + .input + .into_input() + .unwrap(); + + let bundle_data = super::max_bundle_transaction_data(entry_point_address, data, gas_price); + + self.l1_gas_oracle + .estimate_l1_gas( + self.i_entry_point.provider(), + entry_point_address, + bundle_data, + gas_price, + ) + .await + } +} + +#[async_trait::async_trait] +impl SimulationProvider for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + type UO = UserOperation; + + fn get_tracer_simulate_validation_call( + &self, + user_op: Self::UO, + max_validation_gas: u128, + ) -> (TransactionRequest, StateOverride) { + let addr = *self.i_entry_point.address(); + let pvg = user_op.pre_verification_gas; + let mut override_ep = StateOverride::default(); + add_simulations_override(&mut override_ep, addr); + + let ep_simulations = + IEntryPointSimulationsInstance::new(addr, self.i_entry_point.provider()); + + let call = ep_simulations + .simulateValidation(user_op.pack()) + .gas(max_validation_gas + pvg) + .into_transaction_request(); + + (call, override_ep) + } + + async fn simulate_validation( + &self, + user_op: Self::UO, + max_validation_gas: u128, + block_id: Option, + ) -> ProviderResult> { + let (tx, overrides) = self.get_tracer_simulate_validation_call(user_op, max_validation_gas); + let mut call = self.i_entry_point.provider().call(&tx); + if let Some(block_id) = block_id { + call = call.block(block_id); + } + + let result = call.overrides(&overrides).await; + + match result { + Ok(output) => { + let out = ValidationResultV0_7::abi_decode(&output, false) + .context("failed to decode validation result")?; + Ok(Ok(out.try_into().map_err(anyhow::Error::msg)?)) + } + Err(TransportError::ErrorResp(resp)) => Ok(Err(decode_validation_revert_payload(resp))), + Err(error) => Err(error.into()), + } + } + + fn get_simulate_handle_op_call( + &self, + op: Self::UO, + mut state_override: StateOverride, + ) -> EvmCall { + add_simulations_override(&mut state_override, *self.i_entry_point.address()); + + let data = IEntryPointSimulations::simulateHandleOpCall { + op: op.pack(), + target: Address::ZERO, + targetCallData: Bytes::new(), + } + .abi_encode() + .into(); + + EvmCall { + to: *self.i_entry_point.address(), + data, + value: U256::ZERO, + state_override, + } + } + + async fn simulate_handle_op( + &self, + op: Self::UO, + target: Address, + target_call_data: Bytes, + block_id: BlockId, + gas: u128, + mut state_override: StateOverride, + ) -> ProviderResult> { + add_simulations_override(&mut state_override, *self.i_entry_point.address()); + let ep_simulations = IEntryPointSimulations::new( + *self.i_entry_point.address(), + self.i_entry_point.provider(), + ); + let res = ep_simulations + .simulateHandleOp(op.pack(), target, target_call_data) + .block(block_id) + .gas(gas) + .state(state_override) + .call() + .await; + + match res { + Ok(output) => Ok(Ok(output._0.try_into()?)), + Err(ContractError::TransportError(TransportError::ErrorResp(resp))) => { + Ok(Err(decode_validation_revert_payload(resp))) + } + Err(error) => Err(error.into()), + } + } + + fn decode_simulate_handle_ops_revert( + revert_data: &Bytes, + ) -> ProviderResult> { + // v0.7 doesn't encode execution results in the revert data + // so we can just decode as validation revert + Ok(Err(decode_validation_revert(revert_data))) + } + + fn simulation_should_revert(&self) -> bool { + false + } +} + +impl EntryPointProviderTrait for EntryPointProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ +} + +fn add_simulations_override(state_override: &mut StateOverride, addr: Address) { + state_override.insert( + addr, + AccountOverride { + code: Some(ENTRY_POINT_SIMULATIONS_V0_7_DEPLOYED_BYTECODE.clone()), + ..Default::default() + }, + ); +} + +fn get_handle_ops_call, T: Transport + Clone>( + entry_point: &IEntryPointInstance, + ops_per_aggregator: Vec>, + beneficiary: Address, + gas: u128, +) -> TransactionRequest { + let mut ops_per_aggregator: Vec = ops_per_aggregator + .into_iter() + .map(|uoa| UserOpsPerAggregatorV0_7 { + userOps: uoa.user_ops.into_iter().map(|op| op.pack()).collect(), + aggregator: uoa.aggregator, + signature: uoa.signature, + }) + .collect(); + if ops_per_aggregator.len() == 1 && ops_per_aggregator[0].aggregator == Address::ZERO { + entry_point + .handleOps(ops_per_aggregator.swap_remove(0).userOps, beneficiary) + .gas(gas) + .into_transaction_request() + } else { + entry_point + .handleAggregatedOps(ops_per_aggregator, beneficiary) + .gas(gas) + .into_transaction_request() + } +} + +fn decode_validation_revert_payload(err: ErrorPayload) -> ValidationRevert { + match err.as_revert_data() { + Some(err_bytes) => decode_validation_revert(&err_bytes), + None => ValidationRevert::Unknown(Bytes::default()), + } +} + +/// Decodes raw validation revert bytes from a v0.7 entry point +pub fn decode_validation_revert(err_bytes: &Bytes) -> ValidationRevert { + if let Ok(rev) = SolContractError::::abi_decode(err_bytes, false) { + match rev { + SolContractError::CustomError(IEntryPointErrors::FailedOp(f)) => f.into(), + SolContractError::CustomError(IEntryPointErrors::FailedOpWithRevert(f)) => f.into(), + SolContractError::CustomError(IEntryPointErrors::SignatureValidationFailed(f)) => { + ValidationRevert::EntryPoint(format!( + "Aggregator signature validation failed: {}", + f.aggregator + )) + } + SolContractError::Revert(r) => r.into(), + SolContractError::Panic(p) => p.into(), + } + } else { + ValidationRevert::Unknown(err_bytes.clone()) + } +} + +impl From for DepositInfo { + fn from(deposit_info: DepositInfoV0_7) -> Self { + Self { + deposit: deposit_info.deposit, + staked: deposit_info.staked, + stake: UintTryTo::::uint_try_to(&deposit_info.stake).unwrap(), + unstake_delay_sec: deposit_info.unstakeDelaySec, + withdraw_time: UintTryTo::::uint_try_to(&deposit_info.withdrawTime).unwrap(), + } + } +} + +impl TryFrom for ExecutionResult { + type Error = anyhow::Error; + + fn try_from(result: ExecutionResultV0_7) -> Result { + let account = rundler_types::parse_validation_data(result.accountValidationData); + let paymaster = rundler_types::parse_validation_data(result.paymasterValidationData); + let intersect_range = account + .valid_time_range() + .intersect(paymaster.valid_time_range()); + + Ok(ExecutionResult { + pre_op_gas: UintTryTo::::uint_try_to(&result.preOpGas) + .context("preOpGas should fit in u128")?, + paid: result.paid, + valid_after: intersect_range.valid_after, + valid_until: intersect_range.valid_until, + target_success: result.targetSuccess, + target_result: result.targetResult, + }) + } +} diff --git a/crates/provider/src/alloy/evm.rs b/crates/provider/src/alloy/evm.rs new file mode 100644 index 000000000..bc2db9b98 --- /dev/null +++ b/crates/provider/src/alloy/evm.rs @@ -0,0 +1,289 @@ +// 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 std::marker::PhantomData; + +use alloy_json_rpc::{RpcParam, RpcReturn}; +use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; +use alloy_provider::{ext::DebugApi, network::TransactionBuilder, Provider as AlloyProvider}; +use alloy_rpc_types_eth::{ + state::{AccountOverride, StateOverride}, + Block, BlockId, BlockNumberOrTag, BlockTransactionsKind, FeeHistory, Filter, Log, Transaction, + TransactionReceipt, TransactionRequest, +}; +use alloy_rpc_types_trace::geth::{ + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, +}; +use alloy_transport::Transport; +use anyhow::Context; +use rundler_contracts::utils::{ + GetCodeHashes::{self, GetCodeHashesInstance}, + GetGasUsed::{self, GasUsedResult}, + StorageLoader, +}; + +use crate::{EvmCall, EvmProvider, ProviderResult}; + +/// Evm Provider implementation using [alloy-provider](https://github.com/alloy-rs/alloy-rs) +pub struct AlloyEvmProvider { + inner: AP, + _marker: PhantomData, +} + +impl AlloyEvmProvider { + /// Create a new `AlloyEvmProvider` + pub fn new(inner: AP) -> Self { + Self { + inner, + _marker: PhantomData, + } + } +} + +impl From for AlloyEvmProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + fn from(inner: AP) -> Self { + Self::new(inner) + } +} + +#[async_trait::async_trait] +impl EvmProvider for AlloyEvmProvider +where + T: Transport + Clone, + AP: AlloyProvider, +{ + async fn request(&self, method: &'static str, params: P) -> ProviderResult + where + P: RpcParam + 'static, + R: RpcReturn, + { + Ok(self.inner.raw_request(method.into(), params).await?) + } + + async fn fee_history( + &self, + block_count: u64, + block_number: BlockNumberOrTag, + reward_percentiles: &[f64], + ) -> ProviderResult { + Ok(self + .inner + .get_fee_history(block_count, block_number, reward_percentiles) + .await?) + } + + async fn call( + &self, + tx: &TransactionRequest, + block: Option, + state_overrides: &StateOverride, + ) -> ProviderResult { + let mut call = self.inner.call(tx); + if let Some(block) = block { + call = call.block(block); + } + call = call.overrides(state_overrides); + + Ok(call.await?) + } + + async fn get_block_number(&self) -> ProviderResult { + Ok(self.inner.get_block_number().await?) + } + + async fn get_block(&self, block_id: BlockId) -> ProviderResult> { + Ok(self + .inner + .get_block(block_id, BlockTransactionsKind::Hashes) + .await?) + } + + async fn get_balance(&self, address: Address, block: Option) -> ProviderResult { + let mut call = self.inner.get_balance(address); + if let Some(block) = block { + call = call.block_id(block); + } + + Ok(call.await?) + } + + async fn get_transaction_by_hash(&self, tx: TxHash) -> ProviderResult> { + Ok(self.inner.get_transaction_by_hash(tx).await?) + } + + async fn get_transaction_receipt( + &self, + tx: TxHash, + ) -> ProviderResult> { + Ok(self.inner.get_transaction_receipt(tx).await?) + } + + async fn get_latest_block_hash_and_number(&self) -> ProviderResult<(B256, u64)> { + let latest_block = EvmProvider::get_block(self, BlockId::latest()) + .await? + .context("latest block should exist")?; + Ok((latest_block.header.hash, latest_block.header.number)) + } + + async fn get_pending_base_fee(&self) -> ProviderResult { + Ok(Self::get_block(self, BlockId::pending()) + .await? + .context("pending block should exist")? + .header + .base_fee_per_gas + .context("pending block should have a nonempty base fee")?) + } + + async fn get_max_priority_fee(&self) -> ProviderResult { + Ok(self.inner.get_max_priority_fee_per_gas().await?) + } + + async fn get_code(&self, address: Address, block: Option) -> ProviderResult { + let mut call = self.inner.get_code_at(address); + if let Some(block) = block { + call = call.block_id(block); + } + + Ok(call.await?) + } + + async fn get_transaction_count(&self, address: Address) -> ProviderResult { + Ok(self.inner.get_transaction_count(address).await?) + } + + async fn get_logs(&self, filter: &Filter) -> ProviderResult> { + Ok(self.inner.get_logs(filter).await?) + } + + async fn debug_trace_transaction( + &self, + tx_hash: TxHash, + trace_options: GethDebugTracingOptions, + ) -> ProviderResult { + Ok(self + .inner + .debug_trace_transaction(tx_hash, trace_options) + .await?) + } + + async fn debug_trace_call( + &self, + tx: TransactionRequest, + block_id: Option, + trace_options: GethDebugTracingCallOptions, + ) -> ProviderResult { + let call = if let Some(block_id) = block_id { + self.inner.debug_trace_call(tx, block_id, trace_options) + } else { + self.inner + .debug_trace_call(tx, BlockNumberOrTag::Latest.into(), trace_options) + }; + + Ok(call.await?) + } + + async fn get_gas_used(&self, call: EvmCall) -> ProviderResult { + let EvmCall { + to, + value, + data, + mut state_override, + } = call; + + let helper_addr = Address::random(); + let helper = GetGasUsed::new(helper_addr, &self.inner); + + let account = AccountOverride { + code: Some(GetGasUsed::DEPLOYED_BYTECODE.clone()), + ..Default::default() + }; + state_override.insert(helper_addr, account); + + let ret = helper + .getGas(to, value, data) + .state(state_override) + .call() + .await? + ._0; + + Ok(ret) + } + + async fn batch_get_storage_at( + &self, + address: Address, + slots: Vec, + ) -> ProviderResult> { + let mut overrides = StateOverride::default(); + let account = AccountOverride { + code: Some(StorageLoader::DEPLOYED_BYTECODE.clone()), + ..Default::default() + }; + overrides.insert(address, account); + + let expected_ret_size = slots.len() * 32; + let slot_data = slots + .into_iter() + .flat_map(|slot| slot.0) + .collect::>(); + + let tx = TransactionRequest::default() + .to(address) + .with_input(slot_data); + + let result_bytes = self.inner.call(&tx).overrides(&overrides).await?; + + if result_bytes.len() != expected_ret_size { + return Err(anyhow::anyhow!( + "expected {} bytes, got {}", + expected_ret_size, + result_bytes.len() + ) + .into()); + } + + Ok(result_bytes + .chunks(32) + .map(B256::try_from) + .collect::, _>>() + .unwrap()) + } + + async fn get_code_hash( + &self, + addresses: Vec
, + block: Option, + ) -> ProviderResult { + let helper_addr = Address::random(); + let helper = GetCodeHashesInstance::new(helper_addr, &self.inner); + + let mut overrides = StateOverride::default(); + let account = AccountOverride { + code: Some(GetCodeHashes::DEPLOYED_BYTECODE.clone()), + ..Default::default() + }; + overrides.insert(helper_addr, account); + + let mut call = helper.getCodeHashes(addresses).state(overrides); + if let Some(block) = block { + call = call.block(block); + } + + let ret = call.call().await?; + Ok(ret.hash) + } +} diff --git a/crates/provider/src/ethers/mod.rs b/crates/provider/src/alloy/mod.rs similarity index 72% rename from crates/provider/src/ethers/mod.rs rename to crates/provider/src/alloy/mod.rs index ad0f669b0..cbd639dc4 100644 --- a/crates/provider/src/ethers/mod.rs +++ b/crates/provider/src/alloy/mod.rs @@ -11,9 +11,5 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -//! Provider implementations using [ethers-rs](https://github.com/gakonst/ethers-rs) - -mod entry_point; -pub use entry_point::{v0_6::EntryPoint as EntryPointV0_6, v0_7::EntryPoint as EntryPointV0_7}; -mod metrics_middleware; -pub(crate) mod provider; +pub(crate) mod entry_point; +pub(crate) mod evm; diff --git a/crates/provider/src/ethers/entry_point/mod.rs b/crates/provider/src/ethers/entry_point/mod.rs deleted file mode 100644 index 21ad52004..000000000 --- a/crates/provider/src/ethers/entry_point/mod.rs +++ /dev/null @@ -1,115 +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 std::sync::Arc; - -use ethers::{ - providers::Middleware, - types::{Address, Bytes, Eip1559TransactionRequest, U256, U64}, -}; -use rundler_types::{ - chain::{ChainSpec, L1GasOracleContractType}, - contracts::{ - arbitrum::node_interface::NodeInterface, optimism::gas_price_oracle::GasPriceOracle, - }, -}; - -pub(crate) mod v0_6; -pub(crate) mod v0_7; - -#[derive(Debug, Default)] -pub(crate) enum L1GasOracle

{ - ArbitrumNitro(NodeInterface

), - OptimismBedrock(GasPriceOracle

), - #[default] - None, -} - -impl

L1GasOracle

-where - P: Middleware + 'static, -{ - fn new(chain_spec: &ChainSpec, provider: Arc

) -> L1GasOracle

{ - match chain_spec.l1_gas_oracle_contract_type { - L1GasOracleContractType::ArbitrumNitro => L1GasOracle::ArbitrumNitro( - NodeInterface::new(chain_spec.l1_gas_oracle_contract_address, provider), - ), - L1GasOracleContractType::OptimismBedrock => L1GasOracle::OptimismBedrock( - GasPriceOracle::new(chain_spec.l1_gas_oracle_contract_address, provider), - ), - L1GasOracleContractType::None => L1GasOracle::None, - } - } - - async fn estimate_l1_gas( - &self, - address: Address, - data: Bytes, - gas_price: U256, - ) -> anyhow::Result { - match self { - L1GasOracle::ArbitrumNitro(arb_node) => { - estimate_arbitrum_l1_gas(arb_node, address, data).await - } - L1GasOracle::OptimismBedrock(opt_oracle) => { - estimate_optimism_l1_gas(opt_oracle, address, data, gas_price).await - } - L1GasOracle::None => Ok(U256::zero()), - } - } -} - -impl

Clone for L1GasOracle

{ - fn clone(&self) -> Self { - match self { - L1GasOracle::ArbitrumNitro(node) => L1GasOracle::ArbitrumNitro(node.clone()), - L1GasOracle::OptimismBedrock(oracle) => L1GasOracle::OptimismBedrock(oracle.clone()), - L1GasOracle::None => L1GasOracle::None, - } - } -} - -async fn estimate_arbitrum_l1_gas( - arb_node: &NodeInterface

, - address: Address, - data: Bytes, -) -> anyhow::Result { - let gas = arb_node - .gas_estimate_l1_component(address, false, data) - .call() - .await?; - Ok(U256::from(gas.0)) -} - -async fn estimate_optimism_l1_gas( - opt_oracle: &GasPriceOracle

, - address: Address, - data: Bytes, - gas_price: U256, -) -> anyhow::Result { - // construct an unsigned transaction with default values just for L1 gas estimation - let tx = Eip1559TransactionRequest::new() - .from(Address::random()) - .to(address) - .gas(U256::from(1_000_000)) - .max_priority_fee_per_gas(U256::from(100_000_000)) - .max_fee_per_gas(U256::from(100_000_000)) - .value(U256::from(0)) - .data(data) - .nonce(U256::from(100_000)) - .chain_id(U64::from(100_000)) - .rlp(); - - let l1_fee = opt_oracle.get_l1_fee(tx).call().await?; - Ok(l1_fee.checked_div(gas_price).unwrap_or(U256::MAX)) -} diff --git a/crates/provider/src/ethers/entry_point/v0_6.rs b/crates/provider/src/ethers/entry_point/v0_6.rs deleted file mode 100644 index cdfa37cc3..000000000 --- a/crates/provider/src/ethers/entry_point/v0_6.rs +++ /dev/null @@ -1,436 +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 std::sync::Arc; - -use anyhow::Context; -use ethers::{ - abi::AbiDecode, - contract::{ContractError, EthCall, FunctionCall}, - providers::{spoof, Middleware, RawCall}, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Eip1559TransactionRequest, - H256, U256, - }, -}; -use rundler_types::{ - chain::ChainSpec, - contracts::v0_6::{ - get_balances::{GetBalancesResult, GETBALANCES_BYTECODE}, - i_aggregator::IAggregator, - i_entry_point::{ - self, DepositInfo as DepositInfoV0_6, ExecutionResult as ExecutionResultV0_6, FailedOp, - IEntryPoint, SignatureValidationFailed, - UserOpsPerAggregator as UserOpsPerAggregatorV0_6, - }, - }, - v0_6::UserOperation, - GasFees, UserOpsPerAggregator, ValidationError, ValidationOutput, ValidationRevert, -}; -use rundler_utils::eth::{self, ContractRevertError}; - -use super::L1GasOracle; -use crate::{ - traits::HandleOpsOut, AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, - EntryPoint as EntryPointTrait, EntryPointProvider, ExecutionResult, L1GasProvider, Provider, - SignatureAggregator, SimulateOpCallData, SimulationProvider, -}; - -/// Implementation of the `EntryPoint` trait for the v0.6 version of the entry point contract using ethers -#[derive(Debug)] -pub struct EntryPoint { - i_entry_point: IEntryPoint

, - provider: Arc

, - l1_gas_oracle: L1GasOracle

, - max_aggregation_gas: u64, -} - -impl

Clone for EntryPoint

-where - P: Provider + Middleware, -{ - fn clone(&self) -> Self { - Self { - i_entry_point: self.i_entry_point.clone(), - provider: self.provider.clone(), - l1_gas_oracle: self.l1_gas_oracle.clone(), - max_aggregation_gas: self.max_aggregation_gas, - } - } -} - -impl

EntryPoint

-where - P: Provider + Middleware, -{ - /// Create a new `EntryPointV0_6` instance - pub fn new( - entry_point_address: Address, - chain_spec: &ChainSpec, - max_aggregation_gas: u64, - provider: Arc

, - ) -> Self { - Self { - i_entry_point: IEntryPoint::new(entry_point_address, Arc::clone(&provider)), - provider: Arc::clone(&provider), - l1_gas_oracle: L1GasOracle::new(chain_spec, provider), - max_aggregation_gas, - } - } -} - -#[async_trait::async_trait] -impl

EntryPointTrait for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - fn address(&self) -> Address { - self.i_entry_point.address() - } - - async fn balance_of( - &self, - address: Address, - block_id: Option, - ) -> anyhow::Result { - block_id - .map_or(self.i_entry_point.balance_of(address), |bid| { - self.i_entry_point.balance_of(address).block(bid) - }) - .call() - .await - .context("entry point should return balance") - } - - async fn get_deposit_info(&self, address: Address) -> anyhow::Result { - Ok(self - .i_entry_point - .get_deposit_info(address) - .await - .context("should get deposit info")? - .into()) - } - - async fn get_balances(&self, addresses: Vec

) -> anyhow::Result> { - let out: GetBalancesResult = self - .provider - .call_constructor( - &GETBALANCES_BYTECODE, - (self.address(), addresses), - None, - &spoof::state(), - ) - .await - .context("should compute balances")?; - Ok(out.balances) - } -} - -#[async_trait::async_trait] -impl

SignatureAggregator for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn aggregate_signatures( - &self, - aggregator_address: Address, - ops: Vec, - ) -> anyhow::Result> { - let aggregator = IAggregator::new(aggregator_address, Arc::clone(&self.provider)); - let result = aggregator - .aggregate_signatures(ops) - .gas(self.max_aggregation_gas) - .call() - .await; - match result { - Ok(bytes) => Ok(Some(bytes)), - Err(ContractError::Revert(_)) => Ok(None), - Err(error) => Err(error).context("aggregator contract should aggregate signatures")?, - } - } - - async fn validate_user_op_signature( - &self, - aggregator_address: Address, - user_op: UserOperation, - gas_cap: u64, - ) -> anyhow::Result { - let aggregator = IAggregator::new(aggregator_address, Arc::clone(&self.provider)); - - let result = aggregator - .validate_user_op_signature(user_op) - .gas(gas_cap) - .call() - .await; - - match result { - Ok(sig) => Ok(AggregatorOut::SuccessWithInfo(AggregatorSimOut { - address: aggregator_address, - signature: sig, - })), - Err(ContractError::Revert(_)) => Ok(AggregatorOut::ValidationReverted), - Err(error) => Err(error).context("should call aggregator to validate signature")?, - } - } -} - -#[async_trait::async_trait] -impl

BundleHandler for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn call_handle_ops( - &self, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, - ) -> anyhow::Result { - let result = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas) - .call() - .await; - let error = match result { - Ok(()) => return Ok(HandleOpsOut::Success), - Err(error) => error, - }; - if let ContractError::Revert(revert_data) = &error { - if let Ok(FailedOp { op_index, reason }) = FailedOp::decode(revert_data) { - match &reason[..4] { - "AA95" => anyhow::bail!("Handle ops called with insufficient gas"), - _ => return Ok(HandleOpsOut::FailedOp(op_index.as_usize(), reason)), - } - } - if let Ok(failure) = SignatureValidationFailed::decode(revert_data) { - return Ok(HandleOpsOut::SignatureValidationFailed(failure.aggregator)); - } - // Special handling for a bug in the 0.6 entry point contract to detect the bug where - // the `returndatacopy` opcode reverts due to a postOp revert and the revert data is too short. - // See https://github.com/eth-infinitism/account-abstraction/pull/325 for more details. - // NOTE: this error message is copied directly from Geth and assumes it will not change. - if error.to_string().contains("return data out of bounds") { - return Ok(HandleOpsOut::PostOpRevert); - } - } - Err(error)? - } - - fn get_send_bundle_transaction( - &self, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, - gas_fees: GasFees, - ) -> TypedTransaction { - let tx: Eip1559TransactionRequest = - get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas) - .tx - .into(); - tx.max_fee_per_gas(gas_fees.max_fee_per_gas) - .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) - .into() - } -} - -#[async_trait::async_trait] -impl

L1GasProvider for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn calc_l1_gas( - &self, - entry_point_address: Address, - user_op: UserOperation, - gas_price: U256, - ) -> anyhow::Result { - let data = self - .i_entry_point - .handle_ops(vec![user_op], Address::random()) - .calldata() - .context("should get calldata for entry point handle ops")?; - - self.l1_gas_oracle - .estimate_l1_gas(entry_point_address, data, gas_price) - .await - } -} - -#[async_trait::async_trait] -impl

SimulationProvider for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - fn get_tracer_simulate_validation_call( - &self, - user_op: UserOperation, - max_validation_gas: u64, - ) -> (TypedTransaction, spoof::State) { - let pvg = user_op.pre_verification_gas; - let call = self - .i_entry_point - .simulate_validation(user_op) - .gas(U256::from(max_validation_gas) + pvg) - .tx; - (call, spoof::State::default()) - } - - async fn call_simulate_validation( - &self, - user_op: UserOperation, - max_validation_gas: u64, - block_hash: Option, - ) -> Result { - let pvg = user_op.pre_verification_gas; - let blockless = self - .i_entry_point - .simulate_validation(user_op) - .gas(U256::from(max_validation_gas) + pvg); - let call = match block_hash { - Some(block_hash) => blockless.block(block_hash), - None => blockless, - }; - - match call.call().await { - Ok(()) => Err(anyhow::anyhow!("simulateValidation should always revert"))?, - Err(ContractError::Revert(revert_data)) => { - if let Ok(result) = ValidationOutput::decode_v0_6(&revert_data) { - Ok(result) - } else if let Ok(failed_op) = FailedOp::decode(&revert_data) { - Err(ValidationRevert::from(failed_op))? - } else if let Ok(err) = ContractRevertError::decode(&revert_data) { - Err(ValidationRevert::from(err))? - } else { - Err(ValidationRevert::Unknown(revert_data))? - } - } - Err(error) => Err(error).context("call simulation RPC failed")?, - } - } - - fn decode_simulate_handle_ops_revert( - &self, - revert_data: Bytes, - ) -> Result { - if let Ok(result) = ExecutionResultV0_6::decode(&revert_data) { - Ok(result.into()) - } else if let Ok(failed_op) = FailedOp::decode(&revert_data) { - Err(ValidationRevert::EntryPoint(failed_op.reason)) - } else if let Ok(err) = ContractRevertError::decode(&revert_data) { - Err(ValidationRevert::EntryPoint(err.reason)) - } else { - Err(ValidationRevert::Unknown(revert_data)) - } - } - - fn get_simulate_op_call_data( - &self, - op: UserOperation, - spoofed_state: &spoof::State, - ) -> SimulateOpCallData { - 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(), - } - } - - async fn call_spoofed_simulate_op( - &self, - user_op: UserOperation, - target: Address, - target_call_data: Bytes, - block_hash: H256, - gas: U256, - spoofed_state: &spoof::State, - ) -> anyhow::Result> { - let contract_error = self - .i_entry_point - .simulate_handle_op(user_op, target, target_call_data) - .block(block_hash) - .gas(gas) - .call_raw() - .state(spoofed_state) - .await - .err() - .context("simulateHandleOp succeeded, but should always revert")?; - let revert_data = eth::get_revert_bytes(contract_error) - .context("simulateHandleOps should return revert data")?; - return Ok(self.decode_simulate_handle_ops_revert(revert_data)); - } - - fn simulation_should_revert(&self) -> bool { - true - } -} - -impl

EntryPointProvider for EntryPoint

where - P: Provider + Middleware + Send + Sync + 'static -{ -} - -fn get_handle_ops_call( - entry_point: &IEntryPoint, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, -) -> FunctionCall, M, ()> { - let mut ops_per_aggregator: Vec = ops_per_aggregator - .into_iter() - .map(|uoa| UserOpsPerAggregatorV0_6 { - user_ops: uoa.user_ops, - aggregator: uoa.aggregator, - signature: uoa.signature, - }) - .collect(); - let call = - if ops_per_aggregator.len() == 1 && ops_per_aggregator[0].aggregator == Address::zero() { - entry_point.handle_ops(ops_per_aggregator.swap_remove(0).user_ops, beneficiary) - } else { - entry_point.handle_aggregated_ops(ops_per_aggregator, beneficiary) - }; - call.gas(gas) -} - -impl From for ExecutionResult { - fn from(result: ExecutionResultV0_6) -> Self { - ExecutionResult { - pre_op_gas: result.pre_op_gas, - paid: result.paid, - valid_after: result.valid_after.into(), - valid_until: result.valid_until.into(), - target_success: result.target_success, - target_result: result.target_result, - } - } -} - -impl From for DepositInfo { - fn from(info: DepositInfoV0_6) -> Self { - DepositInfo { - deposit: info.deposit.into(), - staked: info.staked, - stake: info.stake, - unstake_delay_sec: info.unstake_delay_sec, - withdraw_time: info.withdraw_time, - } - } -} diff --git a/crates/provider/src/ethers/entry_point/v0_7.rs b/crates/provider/src/ethers/entry_point/v0_7.rs deleted file mode 100644 index 4a95dd675..000000000 --- a/crates/provider/src/ethers/entry_point/v0_7.rs +++ /dev/null @@ -1,483 +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 std::sync::Arc; - -use anyhow::Context; -use ethers::{ - abi::AbiDecode, - contract::{ContractError, EthCall, FunctionCall}, - providers::{Middleware, RawCall}, - types::{ - spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, - Eip1559TransactionRequest, H256, U256, - }, -}; -use rundler_types::{ - chain::ChainSpec, - contracts::v0_7::{ - entry_point_simulations::{ - self, EntryPointSimulations, ExecutionResult as ExecutionResultV0_7, FailedOp, - FailedOpWithRevert, ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE, - }, - get_balances::{GetBalancesResult, GETBALANCES_BYTECODE}, - i_aggregator::IAggregator, - i_entry_point::{ - DepositInfo as DepositInfoV0_7, IEntryPoint, SignatureValidationFailed, - UserOpsPerAggregator as UserOpsPerAggregatorV0_7, - }, - }, - v0_7::UserOperation, - GasFees, UserOpsPerAggregator, ValidationError, ValidationOutput, ValidationRevert, -}; -use rundler_utils::eth::{self, ContractRevertError}; - -use super::L1GasOracle; -use crate::{ - AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPoint as EntryPointTrait, - EntryPointProvider, ExecutionResult, HandleOpsOut, L1GasProvider, Provider, - SignatureAggregator, SimulateOpCallData, SimulationProvider, -}; - -/// Entry point for the v0.7 contract. -#[derive(Debug)] -pub struct EntryPoint

{ - i_entry_point: IEntryPoint

, - provider: Arc

, - l1_gas_oracle: L1GasOracle

, - max_aggregation_gas: u64, -} - -impl

EntryPoint

-where - P: Middleware + 'static, -{ - /// Create a new `EntryPoint` instance for v0.7 - pub fn new( - entry_point_address: Address, - chain_spec: &ChainSpec, - max_aggregation_gas: u64, - provider: Arc

, - ) -> Self { - Self { - i_entry_point: IEntryPoint::new(entry_point_address, Arc::clone(&provider)), - provider: Arc::clone(&provider), - l1_gas_oracle: L1GasOracle::new(chain_spec, provider), - max_aggregation_gas, - } - } -} - -impl

Clone for EntryPoint

-where - P: Provider + Middleware, -{ - fn clone(&self) -> Self { - Self { - i_entry_point: self.i_entry_point.clone(), - provider: self.provider.clone(), - l1_gas_oracle: self.l1_gas_oracle.clone(), - max_aggregation_gas: self.max_aggregation_gas, - } - } -} - -#[async_trait::async_trait] -impl

EntryPointTrait for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - fn address(&self) -> Address { - self.i_entry_point.address() - } - - async fn balance_of( - &self, - address: Address, - block_id: Option, - ) -> anyhow::Result { - block_id - .map_or(self.i_entry_point.balance_of(address), |bid| { - self.i_entry_point.balance_of(address).block(bid) - }) - .call() - .await - .context("entry point should return balance") - } - - async fn get_deposit_info(&self, address: Address) -> anyhow::Result { - Ok(self - .i_entry_point - .get_deposit_info(address) - .await - .context("should get deposit info")? - .into()) - } - - async fn get_balances(&self, addresses: Vec

) -> anyhow::Result> { - let out: GetBalancesResult = self - .provider - .call_constructor( - &GETBALANCES_BYTECODE, - (self.address(), addresses), - None, - &spoof::state(), - ) - .await - .context("should compute balances")?; - Ok(out.balances) - } -} - -#[async_trait::async_trait] -impl

SignatureAggregator for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn aggregate_signatures( - &self, - aggregator_address: Address, - ops: Vec, - ) -> anyhow::Result> { - let aggregator = IAggregator::new(aggregator_address, Arc::clone(&self.provider)); - - // pack the ops - let packed_ops = ops.into_iter().map(|op| op.pack()).collect(); - - let result = aggregator - .aggregate_signatures(packed_ops) - .gas(self.max_aggregation_gas) - .call() - .await; - match result { - Ok(bytes) => Ok(Some(bytes)), - Err(ContractError::Revert(_)) => Ok(None), - Err(error) => Err(error).context("aggregator contract should aggregate signatures")?, - } - } - - async fn validate_user_op_signature( - &self, - aggregator_address: Address, - user_op: UserOperation, - gas_cap: u64, - ) -> anyhow::Result { - let aggregator = IAggregator::new(aggregator_address, Arc::clone(&self.provider)); - - let result = aggregator - .validate_user_op_signature(user_op.pack()) - .gas(gas_cap) - .call() - .await; - - match result { - Ok(sig) => Ok(AggregatorOut::SuccessWithInfo(AggregatorSimOut { - address: aggregator_address, - signature: sig, - })), - Err(ContractError::Revert(_)) => Ok(AggregatorOut::ValidationReverted), - Err(error) => Err(error).context("should call aggregator to validate signature")?, - } - } -} - -#[async_trait::async_trait] -impl

BundleHandler for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn call_handle_ops( - &self, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, - ) -> anyhow::Result { - let result = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas) - .call() - .await; - let error = match result { - Ok(()) => return Ok(HandleOpsOut::Success), - Err(error) => error, - }; - if let ContractError::Revert(revert_data) = &error { - if let Ok(FailedOp { op_index, reason }) = FailedOp::decode(revert_data) { - match &reason[..4] { - // This revert is a bundler issue, not a user op issue, handle it differently - "AA95" => anyhow::bail!("Handle ops called with insufficient gas"), - _ => return Ok(HandleOpsOut::FailedOp(op_index.as_usize(), reason)), - } - } - if let Ok(failure) = SignatureValidationFailed::decode(revert_data) { - return Ok(HandleOpsOut::SignatureValidationFailed(failure.aggregator)); - } - } - Err(error)? - } - - fn get_send_bundle_transaction( - &self, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, - gas_fees: GasFees, - ) -> TypedTransaction { - let tx: Eip1559TransactionRequest = - get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas) - .tx - .into(); - tx.max_fee_per_gas(gas_fees.max_fee_per_gas) - .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) - .into() - } -} - -#[async_trait::async_trait] -impl

L1GasProvider for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - async fn calc_l1_gas( - &self, - entry_point_address: Address, - user_op: UserOperation, - gas_price: U256, - ) -> anyhow::Result { - let data = self - .i_entry_point - .handle_ops(vec![user_op.pack()], Address::random()) - .calldata() - .context("should get calldata for entry point handle ops")?; - - self.l1_gas_oracle - .estimate_l1_gas(entry_point_address, data, gas_price) - .await - } -} - -#[async_trait::async_trait] -impl

SimulationProvider for EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - type UO = UserOperation; - - fn get_tracer_simulate_validation_call( - &self, - user_op: UserOperation, - max_validation_gas: u64, - ) -> (TypedTransaction, spoof::State) { - let addr = self.i_entry_point.address(); - let pvg = user_op.pre_verification_gas; - let mut spoof_ep = spoof::State::default(); - spoof_ep - .account(addr) - .code(ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE.clone()); - let ep_simulations = EntryPointSimulations::new(addr, Arc::clone(&self.provider)); - - let call = ep_simulations - .simulate_validation(user_op.pack()) - .gas(U256::from(max_validation_gas) + pvg) - .tx; - - (call, spoof_ep) - } - - async fn call_simulate_validation( - &self, - user_op: UserOperation, - max_validation_gas: u64, - block_hash: Option, - ) -> Result { - let addr = self.i_entry_point.address(); - let pvg = user_op.pre_verification_gas; - let mut spoof_ep = spoof::State::default(); - spoof_ep - .account(addr) - .code(ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE.clone()); - - let ep_simulations = EntryPointSimulations::new(addr, Arc::clone(&self.provider)); - let blockless = ep_simulations - .simulate_validation(user_op.clone().pack()) - .gas(U256::from(max_validation_gas) + pvg); - let call = match block_hash { - Some(block_hash) => blockless.block(block_hash), - None => blockless, - }; - - match call.call_raw().state(&spoof_ep).await { - Ok(output) => Ok(output.into()), - Err(ContractError::Revert(revert_data)) => { - Err(decode_simulate_validation_revert(revert_data))? - } - Err(error) => Err(error).context("call simulation RPC failed")?, - } - } - - // Always returns `Err(ValidationRevert)`. The v0.7 entry point does not use - // reverts to indicate successful simulations. - fn decode_simulate_handle_ops_revert( - &self, - revert_data: Bytes, - ) -> Result { - Err(decode_simulate_validation_revert(revert_data)) - } - - fn get_simulate_op_call_data( - &self, - op: UserOperation, - spoofed_state: &spoof::State, - ) -> SimulateOpCallData { - let call_data = eth::call_data_of( - entry_point_simulations::SimulateHandleOpCall::selector(), - (op.pack(), Address::zero(), Bytes::new()), - ); - SimulateOpCallData { - call_data, - spoofed_state: self.get_simulate_op_spoofed_state(spoofed_state), - } - } - - // NOTE: A spoof of the entry point code will be ignored by this function. - async fn call_spoofed_simulate_op( - &self, - user_op: UserOperation, - target: Address, - target_call_data: Bytes, - block_hash: H256, - gas: U256, - spoofed_state: &spoof::State, - ) -> anyhow::Result> { - let addr = self.i_entry_point.address(); - let spoofed_state = &self.get_simulate_op_spoofed_state(spoofed_state); - let ep_simulations = EntryPointSimulations::new(addr, Arc::clone(&self.provider)); - - let contract_error = ep_simulations - .simulate_handle_op(user_op.pack(), target, target_call_data) - .block(block_hash) - .gas(gas) - .call_raw() - .state(spoofed_state) - .await; - Ok(match contract_error { - Ok(execution_result) => Ok(execution_result.into()), - Err(contract_error) => { - let revert_data = eth::get_revert_bytes(contract_error) - .context("simulateHandleOps should return revert data")?; - self.decode_simulate_handle_ops_revert(revert_data) - } - }) - } - - fn simulation_should_revert(&self) -> bool { - false - } -} - -// Private helper functions for `SimulationProvider`. -impl

EntryPoint

-where - P: Provider + Middleware + Send + Sync + 'static, -{ - fn get_simulate_op_spoofed_state(&self, base_state: &spoof::State) -> spoof::State { - let mut state_overrides = base_state.clone(); - let entry_point_overrides = state_overrides.account(self.address()); - // Do nothing if the caller has already overridden the entry point code. - // We'll trust they know what they're doing and not replace their code. - // This is needed for call gas estimation, where the entry point is - // replaced with a proxy and the simulations bytecode is elsewhere. - if entry_point_overrides.code.is_none() { - state_overrides - .account(self.address()) - .code(ENTRYPOINTSIMULATIONS_DEPLOYED_BYTECODE.clone()); - } - state_overrides - } -} - -impl

EntryPointProvider for EntryPoint

where - P: Provider + Middleware + Send + Sync + 'static -{ -} - -// Parses the revert data into a structured error -fn decode_simulate_validation_revert(revert_data: Bytes) -> ValidationRevert { - if let Ok(failed_op_with_revert) = FailedOpWithRevert::decode(&revert_data) { - failed_op_with_revert.into() - } else if let Ok(failed_op) = FailedOp::decode(&revert_data) { - failed_op.into() - } else if let Ok(err) = ContractRevertError::decode(&revert_data) { - err.into() - } else { - ValidationRevert::Unknown(revert_data) - } -} - -fn get_handle_ops_call( - entry_point: &IEntryPoint, - ops_per_aggregator: Vec>, - beneficiary: Address, - gas: U256, -) -> FunctionCall, M, ()> { - let mut ops_per_aggregator: Vec = ops_per_aggregator - .into_iter() - .map(|uoa| UserOpsPerAggregatorV0_7 { - user_ops: uoa.user_ops.into_iter().map(|op| op.pack()).collect(), - aggregator: uoa.aggregator, - signature: uoa.signature, - }) - .collect(); - let call = - if ops_per_aggregator.len() == 1 && ops_per_aggregator[0].aggregator == Address::zero() { - entry_point.handle_ops(ops_per_aggregator.swap_remove(0).user_ops, beneficiary) - } else { - entry_point.handle_aggregated_ops(ops_per_aggregator, beneficiary) - }; - call.gas(gas) -} - -impl From for ExecutionResult { - fn from(result: ExecutionResultV0_7) -> Self { - let account = rundler_types::parse_validation_data(result.account_validation_data); - let paymaster = rundler_types::parse_validation_data(result.paymaster_validation_data); - let intersect_range = account - .valid_time_range() - .intersect(paymaster.valid_time_range()); - - ExecutionResult { - pre_op_gas: result.pre_op_gas, - paid: result.paid, - valid_after: intersect_range.valid_after, - valid_until: intersect_range.valid_until, - target_success: result.target_success, - target_result: result.target_result, - } - } -} - -impl From for DepositInfo { - fn from(deposit_info: DepositInfoV0_7) -> Self { - Self { - deposit: deposit_info.deposit, - staked: deposit_info.staked, - stake: deposit_info.stake, - unstake_delay_sec: deposit_info.unstake_delay_sec, - withdraw_time: deposit_info.withdraw_time, - } - } -} diff --git a/crates/provider/src/ethers/metrics_middleware.rs b/crates/provider/src/ethers/metrics_middleware.rs deleted file mode 100644 index c2ed6ff6e..000000000 --- a/crates/provider/src/ethers/metrics_middleware.rs +++ /dev/null @@ -1,138 +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 core::fmt::Debug; -use std::time::Duration; - -use async_trait::async_trait; -use ethers::providers::{HttpClientError, JsonRpcClient}; -use metrics::{counter, histogram}; -use parse_display::Display; -use reqwest::StatusCode; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::time::Instant; - -#[derive(Display)] -#[display(style = "snake_case")] -enum RpcCode { - ServerError, - InternalError, - InvalidParams, - MethodNotFound, - InvalidRequest, - ParseError, - ExecutionFailed, - Success, - Other, -} - -#[derive(Display)] -#[display(style = "snake_case")] -enum HttpCode { - TwoHundreds, - FourHundreds, - FiveHundreds, - Other, -} - -#[derive(Debug)] -/// Metrics middleware struct to hold the inner http client -pub struct MetricsMiddleware { - inner: C, -} - -impl MetricsMiddleware -where - C: JsonRpcClient, -{ - /// Constructor for middleware - pub fn new(inner: C) -> Self { - Self { inner } - } - - fn instrument_request( - &self, - method: &str, - duration: Duration, - request: &Result, - ) { - let method_str = method.to_string(); - - let mut http_code = StatusCode::OK.as_u16() as u64; - let mut rpc_code = 0; - - if let Err(error) = request { - match error { - HttpClientError::ReqwestError(req_err) => { - http_code = req_err.status().unwrap_or_default().as_u16() as u64; - } - HttpClientError::JsonRpcError(rpc_err) => { - rpc_code = rpc_err.code; - } - _ => {} - } - } - - let http = match http_code { - x if (500..=599).contains(&x) => HttpCode::FiveHundreds, - x if (400..=499).contains(&x) => HttpCode::FourHundreds, - x if (200..=299).contains(&x) => HttpCode::TwoHundreds, - _ => HttpCode::Other, - }; - - let rpc = match rpc_code { - -32700 => RpcCode::ParseError, - -32000 => RpcCode::ExecutionFailed, - -32600 => RpcCode::InvalidRequest, - -32601 => RpcCode::MethodNotFound, - -32602 => RpcCode::InvalidParams, - -32603 => RpcCode::InternalError, - x if (-32099..=-32000).contains(&x) => RpcCode::ServerError, - x if x >= 0 => RpcCode::Success, - _ => RpcCode::Other, - }; - - counter!( - "internal_http_response_code", - &[("method", method_str.clone()), ("status", http.to_string())] - ) - .increment(1); - - counter!( - "internal_rpc_response_code", - &[("method", method_str.clone()), ("status", rpc.to_string())] - ) - .increment(1); - - histogram!("internal_rpc_method_response_time", "method" => method_str) - .record(duration.as_millis() as f64); - } -} - -#[async_trait] -impl> JsonRpcClient for MetricsMiddleware { - type Error = HttpClientError; - - async fn request(&self, method: &str, params: T) -> Result - where - T: Debug + Serialize + Send + Sync, - R: DeserializeOwned + Send, - { - let start_time = Instant::now(); - let result: Result = self.inner.request(method, params).await; - let duration = start_time.elapsed(); - self.instrument_request(method, duration, &result); - - result - } -} diff --git a/crates/provider/src/ethers/provider.rs b/crates/provider/src/ethers/provider.rs deleted file mode 100644 index baa757249..000000000 --- a/crates/provider/src/ethers/provider.rs +++ /dev/null @@ -1,319 +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 std::{fmt::Debug, sync::Arc, time::Duration}; - -use anyhow::Context; -use ethers::{ - abi::{AbiDecode, AbiEncode}, - prelude::ContractError as EthersContractError, - providers::{ - Http, HttpRateLimitRetryPolicy, JsonRpcClient, Middleware, Provider as EthersProvider, - ProviderError as EthersProviderError, RawCall, RetryClient, RetryClientBuilder, - }, - types::{ - spoof, transaction::eip2718::TypedTransaction, Address, Block, BlockId, BlockNumber, Bytes, - Eip1559TransactionRequest, FeeHistory, Filter, GethDebugTracingCallOptions, - GethDebugTracingOptions, GethTrace, Log, Transaction, TransactionReceipt, TxHash, H256, - U256, U64, - }, -}; -use reqwest::Url; -use rundler_types::contracts::utils::{ - get_gas_used::{GasUsedResult, GetGasUsed, GETGASUSED_DEPLOYED_BYTECODE}, - storage_loader::STORAGELOADER_DEPLOYED_BYTECODE, -}; -use serde::{de::DeserializeOwned, Serialize}; - -use super::metrics_middleware::MetricsMiddleware; -use crate::{Provider, ProviderError, ProviderResult}; - -#[async_trait::async_trait] -impl Provider for EthersProvider { - // We implement `ProviderLike` for `Provider` rather than for all - // `Middleware` because forming a `PendingTransaction` specifically requires - // a `Provider`. - - async fn request(&self, method: &str, params: T) -> ProviderResult - where - T: Debug + Serialize + Send + Sync + 'static, - R: Serialize + DeserializeOwned + Debug + Send + 'static, - { - Ok(EthersProvider::request(self, method, params).await?) - } - - async fn call( - &self, - tx: &TypedTransaction, - block: Option, - state_overrides: &spoof::State, - ) -> ProviderResult { - let mut call = self.call_raw(tx).state(state_overrides); - if let Some(block) = block { - call = call.block(block); - } - Ok(call.await?) - } - - async fn call_constructor( - &self, - bytecode: &Bytes, - args: A, - block_id: Option, - state_overrides: &spoof::State, - ) -> anyhow::Result - where - A: AbiEncode + Send + Sync + 'static, - R: AbiDecode + Send + Sync + 'static, - { - let mut data = bytecode.to_vec(); - data.extend(AbiEncode::encode(args)); - let tx = Eip1559TransactionRequest { - data: Some(data.into()), - ..Default::default() - }; - let error = Provider::call(self, &tx.into(), block_id, state_overrides) - .await - .err() - .context("called constructor should revert")?; - get_revert_data(error).context("should decode revert data from called constructor") - } - - async fn fee_history + Send + Sync + Serialize + 'static>( - &self, - t: T, - block_number: BlockNumber, - reward_percentiles: &[f64], - ) -> ProviderResult { - Ok(Middleware::fee_history(self, t, block_number, reward_percentiles).await?) - } - - async fn get_block_number(&self) -> ProviderResult { - Ok(Middleware::get_block_number(self) - .await - .context("should get block number from provider")? - .as_u64()) - } - - async fn get_block + Send + Sync + 'static>( - &self, - block_hash_or_number: T, - ) -> ProviderResult>> { - Ok(Middleware::get_block(self, block_hash_or_number).await?) - } - - async fn get_transaction>( - &self, - transaction_hash: T, - ) -> ProviderResult> { - Ok(Middleware::get_transaction(self, transaction_hash).await?) - } - - async fn get_transaction_receipt + 'static>( - &self, - transaction_hash: T, - ) -> ProviderResult> { - Ok(Middleware::get_transaction_receipt(self, transaction_hash).await?) - } - - async fn debug_trace_transaction( - &self, - tx_hash: TxHash, - trace_options: GethDebugTracingOptions, - ) -> ProviderResult { - Ok(Middleware::debug_trace_transaction(self, tx_hash, trace_options).await?) - } - - async fn debug_trace_call( - &self, - tx: TypedTransaction, - block_id: Option, - trace_options: GethDebugTracingCallOptions, - ) -> ProviderResult { - Ok(Middleware::debug_trace_call(self, tx, block_id, trace_options).await?) - } - - async fn get_balance(&self, address: Address, block: Option) -> ProviderResult { - Ok(Middleware::get_balance(self, address, block).await?) - } - - async fn get_latest_block_hash_and_number(&self) -> ProviderResult<(H256, U64)> { - let latest_block = Middleware::get_block(self, BlockId::Number(BlockNumber::Latest)) - .await - .context("should load block to get hash and number")? - .context("block should exist to get latest hash and number")?; - Ok(( - latest_block - .hash - .context("hash should be present on block")?, - latest_block - .number - .context("number should be present on block")?, - )) - } - - async fn get_base_fee(&self) -> ProviderResult { - Ok(Middleware::get_block(self, BlockNumber::Pending) - .await - .context("should load pending block to get base fee")? - .context("pending block should exist")? - .base_fee_per_gas - .context("pending block should have a nonempty base fee")?) - } - - async fn get_max_priority_fee(&self) -> ProviderResult { - Ok(self.request("eth_maxPriorityFeePerGas", ()).await?) - } - - async fn get_logs(&self, filter: &Filter) -> ProviderResult> { - Ok(Middleware::get_logs(self, filter).await?) - } - - async fn get_code(&self, address: Address, block_hash: Option) -> ProviderResult { - Ok(Middleware::get_code(self, address, block_hash.map(|b| b.into())).await?) - } - - async fn get_transaction_count(&self, address: Address) -> ProviderResult { - Ok(Middleware::get_transaction_count(self, address, None).await?) - } - - async fn get_gas_used( - self: &Arc, - target: Address, - value: U256, - data: Bytes, - mut state_overrides: spoof::State, - ) -> ProviderResult { - let helper_addr = Address::random(); - let helper = GetGasUsed::new(helper_addr, Arc::clone(self)); - - state_overrides - .account(helper_addr) - .code(GETGASUSED_DEPLOYED_BYTECODE.clone()); - - Ok(helper - .get_gas(target, value, data) - .call_raw() - .state(&state_overrides) - .await - .context("should get gas used")?) - } - - async fn batch_get_storage_at( - &self, - address: Address, - slots: Vec, - ) -> ProviderResult> { - let mut state_overrides = spoof::State::default(); - state_overrides - .account(address) - .code(STORAGELOADER_DEPLOYED_BYTECODE.clone()); - - let expected_ret_size = slots.len() * 32; - let slot_data = slots - .into_iter() - .flat_map(|slot| slot.to_fixed_bytes()) - .collect::>(); - - let tx: TypedTransaction = Eip1559TransactionRequest { - to: Some(address.into()), - data: Some(slot_data.into()), - ..Default::default() - } - .into(); - - let result_bytes = self - .call_raw(&tx) - .state(&state_overrides) - .await - .context("should call storage loader")?; - - if result_bytes.len() != expected_ret_size { - return Err(anyhow::anyhow!( - "expected {} bytes, got {}", - expected_ret_size, - result_bytes.len() - ) - .into()); - } - - Ok(result_bytes.chunks(32).map(H256::from_slice).collect()) - } -} - -impl From for ProviderError { - fn from(e: EthersProviderError) -> Self { - match e { - EthersProviderError::JsonRpcClientError(e) => { - if let Some(jsonrpc_error) = e.as_error_response() { - ProviderError::JsonRpcError(jsonrpc_error.clone()) - } else { - ProviderError::Other(anyhow::anyhow!(e.to_string())) - } - } - _ => ProviderError::Other(anyhow::anyhow!(e.to_string())), - } - } -} - -impl From> for ProviderError { - fn from(e: EthersContractError) -> Self { - ProviderError::ContractError(e.to_string()) - } -} - -// Gets and decodes the revert data from a provider error, if it is a revert error. -fn get_revert_data(error: ProviderError) -> Result { - let ProviderError::JsonRpcError(jsonrpc_error) = &error else { - return Err(error); - }; - if !jsonrpc_error.is_revert() { - return Err(error); - } - match jsonrpc_error.decode_revert_data() { - Some(ret) => Ok(ret), - None => Err(error), - } -} - -/// Construct a new Ethers provider from a URL and a poll interval. -/// -/// Creates a provider with a retry client that retries 10 times, with an initial backoff of 500ms. -pub fn new_provider( - url: &str, - poll_interval: Option, -) -> anyhow::Result>>>> { - let parsed_url = Url::parse(url).context("provider url should be valid")?; - - let http_client = reqwest::Client::builder() - .connect_timeout(Duration::from_secs(1)) - .build() - .context("failed to build reqwest client")?; - let http = MetricsMiddleware::new(Http::new_with_client(parsed_url, http_client)); - - let client = RetryClientBuilder::default() - // these retries are if the server returns a 429 - .rate_limit_retries(10) - // these retries are if the connection is dubious - .timeout_retries(3) - .initial_backoff(Duration::from_millis(500)) - .build(http, Box::::default()); - - let mut provider = EthersProvider::new(client); - - if let Some(poll_interval) = poll_interval { - provider = provider.interval(poll_interval); - } - - Ok(Arc::new(provider)) -} diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index 4b98ddcbd..f03761368 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -21,15 +21,32 @@ //! Rundler providers //! A provider is a type that provides access to blockchain data and functions -mod ethers; -pub use ethers::{ - provider::new_provider, EntryPointV0_6 as EthersEntryPointV0_6, - EntryPointV0_7 as EthersEntryPointV0_7, +mod alloy; +pub use alloy::{ + entry_point::{ + v0_6::EntryPointProvider as AlloyEntryPointV0_6, + v0_7::{ + decode_validation_revert as decode_v0_7_validation_revert, + EntryPointProvider as AlloyEntryPointV0_7, + }, + }, + evm::AlloyEvmProvider, }; mod traits; +// re-export alloy RPC types +pub use alloy_json_rpc::{RpcParam, RpcReturn}; +pub use alloy_rpc_types_eth::{ + state::{AccountOverride, StateOverride}, + Block, BlockId, BlockNumberOrTag, FeeHistory, Filter, Log, Transaction, TransactionReceipt, + TransactionRequest, +}; +pub use alloy_rpc_types_trace::geth::{ + GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, GethTrace, +}; +// re-export contract types +pub use rundler_contracts::utils::GetGasUsed::GasUsedResult; #[cfg(any(test, feature = "test-utils"))] pub use traits::test_utils::*; -#[cfg(any(test, feature = "test-utils"))] -pub use traits::MockProvider; pub use traits::*; diff --git a/crates/provider/src/traits/entry_point.rs b/crates/provider/src/traits/entry_point.rs index f4612315b..85459c2d4 100644 --- a/crates/provider/src/traits/entry_point.rs +++ b/crates/provider/src/traits/entry_point.rs @@ -11,14 +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 ethers::types::{ - spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, H256, U256, -}; +use alloy_primitives::{Address, Bytes, U256}; use rundler_types::{ - GasFees, Timestamp, UserOperation, UserOpsPerAggregator, ValidationError, ValidationOutput, - ValidationRevert, + GasFees, Timestamp, UserOperation, UserOpsPerAggregator, ValidationOutput, ValidationRevert, }; +use crate::{BlockId, EvmCall, ProviderResult, StateOverride, TransactionRequest}; + /// Output of a successful signature aggregator simulation call #[derive(Clone, Debug, Default)] pub struct AggregatorSimOut { @@ -72,7 +71,7 @@ pub struct DepositInfo { #[derive(Clone, Debug, Default)] pub struct ExecutionResult { /// Gas used before the operation execution - pub pre_op_gas: U256, + pub pre_op_gas: u128, /// Amount paid by the operation pub paid: U256, /// Time which the operation is valid after @@ -87,26 +86,26 @@ pub struct ExecutionResult { /// Trait for interacting with an entry point contract. #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Arc)] -pub trait EntryPoint: Send + Sync + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait EntryPoint: Send + Sync { /// Get the address of the entry point contract - fn address(&self) -> Address; + fn address(&self) -> &Address; /// Get the balance of an address async fn balance_of(&self, address: Address, block_id: Option) - -> anyhow::Result; + -> ProviderResult; /// Get the deposit info for an address - async fn get_deposit_info(&self, address: Address) -> anyhow::Result; + async fn get_deposit_info(&self, address: Address) -> ProviderResult; /// Get the balances of a list of addresses in order - async fn get_balances(&self, addresses: Vec

) -> anyhow::Result>; + async fn get_balances(&self, addresses: Vec
) -> ProviderResult>; } /// Trait for handling signature aggregators #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Arc)] -pub trait SignatureAggregator: Send + Sync + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait SignatureAggregator: Send + Sync { /// The type of user operation used by this entry point type UO: UserOperation; @@ -115,21 +114,21 @@ pub trait SignatureAggregator: Send + Sync + 'static { &self, aggregator_address: Address, ops: Vec, - ) -> anyhow::Result>; + ) -> ProviderResult>; /// Validate a user operation signature using an aggregator async fn validate_user_op_signature( &self, aggregator_address: Address, user_op: Self::UO, - gas_cap: u64, - ) -> anyhow::Result; + gas_cap: u128, + ) -> ProviderResult; } /// Trait for submitting bundles of operations to an entry point contract #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Arc)] -pub trait BundleHandler: Send + Sync + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait BundleHandler: Send + Sync { /// The type of user operation used by this entry point type UO: UserOperation; @@ -138,25 +137,25 @@ pub trait BundleHandler: Send + Sync + 'static { &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, - ) -> anyhow::Result; + gas: u128, + ) -> ProviderResult; /// Construct the transaction to send a bundle of operations to the entry point contract fn get_send_bundle_transaction( &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, + gas: u128, gas_fees: GasFees, - ) -> TypedTransaction; + ) -> TransactionRequest; } /// Trait for calculating L1 gas costs for user operations /// /// Used for L2 gas estimation #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Arc)] -pub trait L1GasProvider: Send + Sync + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait L1GasProvider: Send + Sync { /// The type of user operation used by this entry point type UO: UserOperation; @@ -167,26 +166,14 @@ pub trait L1GasProvider: Send + Sync + 'static { &self, entry_point_address: Address, op: Self::UO, - gas_price: U256, - ) -> anyhow::Result; -} - -/// Call data along with necessary state overrides for calling the entry -/// point's `simulateHandleOp` function. -#[derive(Debug)] -pub struct SimulateOpCallData { - /// Call data representing a call to `simulateHandleOp` - pub call_data: Bytes, - /// Required state override. Necessary with the v0.7 entry point, where the - /// simulation methods aren't deployed on-chain but instead must be added - /// via state overrides - pub spoofed_state: spoof::State, + gas_price: u128, + ) -> ProviderResult; } /// Trait for simulating user operations on an entry point contract #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Arc)] -pub trait SimulationProvider: Send + Sync + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait SimulationProvider: Send + Sync { /// The type of user operation used by this entry point type UO: UserOperation; @@ -194,41 +181,36 @@ pub trait SimulationProvider: Send + Sync + 'static { fn get_tracer_simulate_validation_call( &self, user_op: Self::UO, - max_validation_gas: u64, - ) -> (TypedTransaction, spoof::State); + max_validation_gas: u128, + ) -> (TransactionRequest, StateOverride); /// Call the entry point contract's `simulateValidation` function. - async fn call_simulate_validation( + async fn simulate_validation( &self, user_op: Self::UO, - max_validation_gas: u64, - block_hash: Option, - ) -> Result; + max_validation_gas: u128, + block_id: Option, + ) -> ProviderResult>; /// Get call data and state overrides needed to call `simulateHandleOp` - fn get_simulate_op_call_data( - &self, - op: Self::UO, - spoofed_state: &spoof::State, - ) -> SimulateOpCallData; + fn get_simulate_handle_op_call(&self, op: Self::UO, state_override: StateOverride) -> EvmCall; /// Call the entry point contract's `simulateHandleOp` function /// with a spoofed state - async fn call_spoofed_simulate_op( + async fn simulate_handle_op( &self, op: Self::UO, target: Address, target_call_data: Bytes, - block_hash: H256, - gas: U256, - spoofed_state: &spoof::State, - ) -> anyhow::Result>; + block_hash: BlockId, + gas: u128, + state_override: StateOverride, + ) -> ProviderResult>; /// Decode the revert data from a call to `simulateHandleOps` fn decode_simulate_handle_ops_revert( - &self, - revert_data: Bytes, - ) -> Result; + revert_data: &Bytes, + ) -> ProviderResult>; /// Returns true if this entry point uses reverts to communicate simulation /// results. @@ -236,12 +218,12 @@ pub trait SimulationProvider: Send + Sync + 'static { } /// Trait for a provider that provides all entry point functionality +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] pub trait EntryPointProvider: EntryPoint + SignatureAggregator + BundleHandler + SimulationProvider + L1GasProvider - + Clone { } diff --git a/crates/provider/src/traits/error.rs b/crates/provider/src/traits/error.rs index ac694da67..a3a13fd0c 100644 --- a/crates/provider/src/traits/error.rs +++ b/crates/provider/src/traits/error.rs @@ -11,18 +11,34 @@ // 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::providers::JsonRpcError; +use alloy_contract::Error as ContractError; +use alloy_transport::TransportError; /// Error enumeration for the Provider trait #[derive(Debug, thiserror::Error)] pub enum ProviderError { - /// JSON-RPC error - #[error(transparent)] - JsonRpcError(#[from] JsonRpcError), + /// RPC Error + #[error("RPC Error: {0}")] + RPC(TransportError), /// Contract Error #[error("Contract Error: {0}")] - ContractError(String), + ContractError(ContractError), /// Internal errors #[error(transparent)] Other(#[from] anyhow::Error), } + +impl From for ProviderError { + fn from(err: TransportError) -> Self { + ProviderError::RPC(err) + } +} + +impl From for ProviderError { + fn from(err: ContractError) -> Self { + ProviderError::ContractError(err) + } +} + +/// Result of a provider method call +pub type ProviderResult = Result; diff --git a/crates/provider/src/traits/provider.rs b/crates/provider/src/traits/evm.rs similarity index 51% rename from crates/provider/src/traits/provider.rs rename to crates/provider/src/traits/evm.rs index 0aac930b1..71e8d4016 100644 --- a/crates/provider/src/traits/provider.rs +++ b/crates/provider/src/traits/evm.rs @@ -13,87 +13,70 @@ //! Trait for interacting with chain data and contracts. -use std::{fmt::Debug, sync::Arc}; - -use ethers::{ - abi::{AbiDecode, AbiEncode}, - types::{ - spoof, transaction::eip2718::TypedTransaction, Address, Block, BlockId, BlockNumber, Bytes, - FeeHistory, Filter, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, Log, - Transaction, TransactionReceipt, TxHash, H256, U256, U64, - }, -}; -#[cfg(feature = "test-utils")] -use mockall::automock; -use rundler_types::contracts::utils::get_gas_used::GasUsedResult; -use serde::{de::DeserializeOwned, Serialize}; +use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; -use super::error::ProviderError; +use crate::{ + Block, BlockId, BlockNumberOrTag, FeeHistory, Filter, GasUsedResult, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, Log, ProviderResult, RpcParam, + RpcReturn, StateOverride, Transaction, TransactionReceipt, TransactionRequest, +}; -/// Result of a provider method call -pub type ProviderResult = Result; +/// An EVM call, a subset of a transaction that is not meant to be executed onchain, but +/// can be simulated via an eth_call, debug_traceCall, or similar. +#[derive(Debug)] +pub struct EvmCall { + /// The address to call + pub to: Address, + /// Call data + pub data: Bytes, + /// Call value + pub value: U256, + /// State overrides + pub state_override: StateOverride, +} /// Trait for interacting with chain data and contracts. -#[cfg_attr(feature = "test-utils", automock)] #[async_trait::async_trait] -pub trait Provider: Send + Sync + Debug + 'static { +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait EvmProvider: Send + Sync { /// Make an arbitrary JSON RPC request to the provider - async fn request(&self, method: &str, params: T) -> ProviderResult + async fn request(&self, method: &'static str, params: P) -> ProviderResult where - T: Debug + Serialize + Send + Sync + 'static, - R: Serialize + DeserializeOwned + Debug + Send + 'static; + P: RpcParam + 'static, + R: RpcReturn; /// Get fee history given a number of blocks and reward percentiles - async fn fee_history + Serialize + Send + Sync + 'static>( + async fn fee_history( &self, - t: T, - block_number: BlockNumber, + block_count: u64, + block_number: BlockNumberOrTag, reward_percentiles: &[f64], - ) -> Result; + ) -> ProviderResult; /// Simulate a transaction via an eth_call async fn call( &self, - tx: &TypedTransaction, + tx: &TransactionRequest, block: Option, - state_overrides: &spoof::State, + state_overrides: &StateOverride, ) -> ProviderResult; - /// Call a contract's constructor - /// The constructor is assumed to revert with the result data - async fn call_constructor( - &self, - bytecode: &Bytes, - args: A, - block_id: Option, - state_overrides: &spoof::State, - ) -> anyhow::Result - where - A: AbiEncode + Send + Sync + 'static, - R: AbiDecode + Send + Sync + 'static; - /// Get the current block number async fn get_block_number(&self) -> ProviderResult; /// Get a block by its hash or number - async fn get_block + Send + Sync + 'static>( - &self, - block_hash_or_number: T, - ) -> ProviderResult>>; + async fn get_block(&self, block_id: BlockId) -> ProviderResult>; /// Get the balance of an address async fn get_balance(&self, address: Address, block: Option) -> ProviderResult; /// Get transaction by hash - async fn get_transaction + 'static>( - &self, - tx: T, - ) -> ProviderResult>; + async fn get_transaction_by_hash(&self, tx: TxHash) -> ProviderResult>; /// Get transaction receipt by hash - async fn get_transaction_receipt + 'static>( + async fn get_transaction_receipt( &self, - transaction_hash: T, + tx: TxHash, ) -> ProviderResult>; /// Debug trace a transaction @@ -106,42 +89,44 @@ pub trait Provider: Send + Sync + Debug + 'static { /// Debug trace a call async fn debug_trace_call( &self, - tx: TypedTransaction, + tx: TransactionRequest, block_id: Option, trace_options: GethDebugTracingCallOptions, ) -> ProviderResult; /// Get the latest block hash and number - async fn get_latest_block_hash_and_number(&self) -> ProviderResult<(H256, U64)>; + async fn get_latest_block_hash_and_number(&self) -> ProviderResult<(B256, u64)>; /// Get the base fee per gas of the pending block - async fn get_base_fee(&self) -> ProviderResult; + async fn get_pending_base_fee(&self) -> ProviderResult; /// Get the max fee per gas as reported by the node's RPC - async fn get_max_priority_fee(&self) -> ProviderResult; + async fn get_max_priority_fee(&self) -> ProviderResult; /// Get the code at an address - async fn get_code(&self, address: Address, block_hash: Option) -> ProviderResult; + async fn get_code(&self, address: Address, block: Option) -> ProviderResult; /// Get the nonce/transaction count of an address - async fn get_transaction_count(&self, address: Address) -> ProviderResult; + async fn get_transaction_count(&self, address: Address) -> ProviderResult; /// Get the logs matching a filter async fn get_logs(&self, filter: &Filter) -> ProviderResult>; /// Measures the gas used by a call to target with value and data. - async fn get_gas_used( - self: &Arc, - target: Address, - value: U256, - data: Bytes, - state_overrides: spoof::State, - ) -> ProviderResult; + async fn get_gas_used(&self, call: EvmCall) -> ProviderResult; /// Get the storage values at a given address and slots async fn batch_get_storage_at( &self, address: Address, - slots: Vec, - ) -> ProviderResult>; + slots: Vec, + ) -> ProviderResult>; + + /// Hashes together the code from all the provided addresses. The order of the input addresses does + /// not matter + async fn get_code_hash( + &self, + addresses: Vec
, + block: Option, + ) -> ProviderResult; } diff --git a/crates/provider/src/traits/mod.rs b/crates/provider/src/traits/mod.rs index 73d9a54c0..ff12b2acd 100644 --- a/crates/provider/src/traits/mod.rs +++ b/crates/provider/src/traits/mod.rs @@ -14,14 +14,13 @@ //! Traits for the provider module. mod error; -pub use error::ProviderError; +pub use error::*; mod entry_point; pub use entry_point::*; -mod provider; -#[cfg(feature = "test-utils")] -pub use provider::MockProvider; -pub use provider::{Provider, ProviderResult}; +mod evm; +pub use evm::*; + #[cfg(feature = "test-utils")] pub(crate) mod test_utils; diff --git a/crates/provider/src/traits/test_utils.rs b/crates/provider/src/traits/test_utils.rs index 438c954de..3430b51fe 100644 --- a/crates/provider/src/traits/test_utils.rs +++ b/crates/provider/src/traits/test_utils.rs @@ -11,28 +11,115 @@ // 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::{ - spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, H256, U256, +use alloy_json_rpc::{RpcParam, RpcReturn}; +use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; +use alloy_rpc_types_eth::{ + state::StateOverride, Block, BlockId, BlockNumberOrTag, FeeHistory, Filter, Log, Transaction, + TransactionReceipt, TransactionRequest, }; +use alloy_rpc_types_trace::geth::{ + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, +}; +use rundler_contracts::utils::GetGasUsed::GasUsedResult; use rundler_types::{ - v0_6, v0_7, GasFees, UserOpsPerAggregator, ValidationError, ValidationOutput, ValidationRevert, + v0_6, v0_7, GasFees, UserOpsPerAggregator, ValidationOutput, ValidationRevert, }; +use super::error::ProviderResult; use crate::{ - AggregatorOut, BundleHandler, DepositInfo, EntryPoint, ExecutionResult, HandleOpsOut, - L1GasProvider, SignatureAggregator, SimulateOpCallData, SimulationProvider, + AggregatorOut, BundleHandler, DepositInfo, EntryPoint, EvmCall, + EvmProvider as EvmProviderTrait, ExecutionResult, HandleOpsOut, L1GasProvider, + SignatureAggregator, SimulationProvider, }; +mockall::mock! { + pub EvmProvider {} + + #[async_trait::async_trait] + impl EvmProviderTrait for EvmProvider { + async fn request(&self, method: &'static str, params: P) -> ProviderResult + where + P: RpcParam + 'static, + R: RpcReturn; + + async fn fee_history( + &self, + block_count: u64, + block_number: BlockNumberOrTag, + reward_percentiles: &[f64], + ) -> ProviderResult; + + async fn call( + &self, + tx: &TransactionRequest, + block: Option, + state_overrides: &StateOverride, + ) -> ProviderResult; + + async fn get_block_number(&self) -> ProviderResult; + + async fn get_block(&self, block_id: BlockId) -> ProviderResult>; + + async fn get_balance(&self, address: Address, block: Option) -> ProviderResult; + + async fn get_transaction_by_hash(&self, tx: TxHash) -> ProviderResult>; + + async fn get_transaction_receipt( + &self, + tx: TxHash, + ) -> ProviderResult>; + + async fn debug_trace_transaction( + &self, + tx_hash: TxHash, + trace_options: GethDebugTracingOptions, + ) -> ProviderResult; + + async fn debug_trace_call( + &self, + tx: TransactionRequest, + block_id: Option, + trace_options: GethDebugTracingCallOptions, + ) -> ProviderResult; + + async fn get_latest_block_hash_and_number(&self) -> ProviderResult<(B256, u64)>; + + async fn get_pending_base_fee(&self) -> ProviderResult; + + async fn get_max_priority_fee(&self) -> ProviderResult; + + async fn get_code(&self, address: Address, block: Option) -> ProviderResult; + + async fn get_transaction_count(&self, address: Address) -> ProviderResult; + + async fn get_logs(&self, filter: &Filter) -> ProviderResult>; + + async fn get_gas_used(&self, call: EvmCall) -> ProviderResult; + + async fn batch_get_storage_at( + &self, + address: Address, + slots: Vec, + ) -> ProviderResult>; + + async fn get_code_hash( + &self, + addresses: Vec
, + block: Option, + ) -> ProviderResult; + } +} + mockall::mock! { pub EntryPointV0_6 {} #[async_trait::async_trait] impl EntryPoint for EntryPointV0_6 { - fn address(&self) -> Address; + fn address(&self) -> &Address; async fn balance_of(&self, address: Address, block_id: Option) - -> anyhow::Result; - async fn get_deposit_info(&self, address: Address) -> anyhow::Result; - async fn get_balances(&self, addresses: Vec
) -> anyhow::Result>; + -> ProviderResult; + async fn get_deposit_info(&self, address: Address) -> ProviderResult; + async fn get_balances(&self, addresses: Vec
) -> ProviderResult>; } #[async_trait::async_trait] @@ -42,13 +129,13 @@ mockall::mock! { &self, aggregator_address: Address, ops: Vec, - ) -> anyhow::Result>; + ) -> ProviderResult>; async fn validate_user_op_signature( &self, aggregator_address: Address, user_op: v0_6::UserOperation, - gas_cap: u64, - ) -> anyhow::Result; + gas_cap: u128, + ) -> ProviderResult; } #[async_trait::async_trait] @@ -57,32 +144,31 @@ mockall::mock! { fn get_tracer_simulate_validation_call( &self, user_op: v0_6::UserOperation, - max_validation_gas: u64, - ) -> (TypedTransaction, spoof::State); - async fn call_simulate_validation( + max_validation_gas: u128, + ) -> (TransactionRequest, StateOverride); + async fn simulate_validation( &self, user_op: v0_6::UserOperation, - max_validation_gas: u64, - block_hash: Option - ) -> Result; - fn get_simulate_op_call_data( + max_validation_gas: u128, + block_id: Option + ) -> ProviderResult>; + fn get_simulate_handle_op_call( &self, op: v0_6::UserOperation, - spoofed_state: &spoof::State, - ) -> SimulateOpCallData; - async fn call_spoofed_simulate_op( + state_override: StateOverride, + ) -> crate::EvmCall; + async fn simulate_handle_op( &self, op: v0_6::UserOperation, target: Address, target_call_data: Bytes, - block_hash: H256, - gas: U256, - spoofed_state: &spoof::State, - ) -> anyhow::Result>; + block_id: BlockId, + gas: u128, + state_override: StateOverride, + ) -> ProviderResult>; fn decode_simulate_handle_ops_revert( - &self, - revert_data: Bytes, - ) -> Result; + revert_data: &Bytes, + ) -> ProviderResult>; fn simulation_should_revert(&self) -> bool; } @@ -93,8 +179,8 @@ mockall::mock! { &self, entry_point_address: Address, op: v0_6::UserOperation, - gas_price: U256, - ) -> anyhow::Result; + gas_price: u128, + ) -> ProviderResult; } #[async_trait::async_trait] @@ -104,15 +190,15 @@ mockall::mock! { &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, - ) -> anyhow::Result; + gas: u128, + ) -> ProviderResult; fn get_send_bundle_transaction( &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, + gas: u128, gas_fees: GasFees, - ) -> TypedTransaction; + ) -> TransactionRequest; } } @@ -121,11 +207,11 @@ mockall::mock! { #[async_trait::async_trait] impl EntryPoint for EntryPointV0_7 { - fn address(&self) -> Address; + fn address(&self) -> &Address; async fn balance_of(&self, address: Address, block_id: Option) - -> anyhow::Result; - async fn get_deposit_info(&self, address: Address) -> anyhow::Result; - async fn get_balances(&self, addresses: Vec
) -> anyhow::Result>; + -> ProviderResult; + async fn get_deposit_info(&self, address: Address) -> ProviderResult; + async fn get_balances(&self, addresses: Vec
) -> ProviderResult>; } #[async_trait::async_trait] @@ -135,13 +221,13 @@ mockall::mock! { &self, aggregator_address: Address, ops: Vec, - ) -> anyhow::Result>; + ) -> ProviderResult>; async fn validate_user_op_signature( &self, aggregator_address: Address, user_op: v0_7::UserOperation, - gas_cap: u64, - ) -> anyhow::Result; + gas_cap: u128, + ) -> ProviderResult; } #[async_trait::async_trait] @@ -150,32 +236,31 @@ mockall::mock! { fn get_tracer_simulate_validation_call( &self, user_op: v0_7::UserOperation, - max_validation_gas: u64, - ) -> (TypedTransaction, spoof::State); - async fn call_simulate_validation( + max_validation_gas: u128, + ) -> (TransactionRequest, StateOverride); + async fn simulate_validation( &self, user_op: v0_7::UserOperation, - max_validation_gas: u64, - block_hash: Option - ) -> Result; - fn get_simulate_op_call_data( + max_validation_gas: u128, + block_id: Option + ) -> ProviderResult>; + fn get_simulate_handle_op_call( &self, op: v0_7::UserOperation, - spoofed_state: &spoof::State, - ) -> SimulateOpCallData; - async fn call_spoofed_simulate_op( + state_override: StateOverride, + ) -> crate::EvmCall; + async fn simulate_handle_op( &self, op: v0_7::UserOperation, target: Address, target_call_data: Bytes, - block_hash: H256, - gas: U256, - spoofed_state: &spoof::State, - ) -> anyhow::Result>; + block_id: BlockId, + gas: u128, + state_override: StateOverride, + ) -> ProviderResult>; fn decode_simulate_handle_ops_revert( - &self, - revert_data: Bytes, - ) -> Result; + revert_data: &Bytes, + ) -> ProviderResult>; fn simulation_should_revert(&self) -> bool; } @@ -186,8 +271,8 @@ mockall::mock! { &self, entry_point_address: Address, op: v0_7::UserOperation, - gas_price: U256, - ) -> anyhow::Result; + gas_price: u128, + ) -> ProviderResult; } #[async_trait::async_trait] @@ -197,14 +282,14 @@ mockall::mock! { &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, - ) -> anyhow::Result; + gas: u128, + ) -> ProviderResult; fn get_send_bundle_transaction( &self, ops_per_aggregator: Vec>, beneficiary: Address, - gas: U256, + gas: u128, gas_fees: GasFees, - ) -> TypedTransaction; + ) -> TransactionRequest; } } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 2081f6b54..04d869d8d 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -14,7 +14,6 @@ derive_more = "0.99.18" futures.workspace = true itertools.workspace = true rand.workspace = true -reqwest.workspace = true schnellru = "0.2.1" tokio.workspace = true tracing.workspace = true From 3020990745280ff31edab57865171455a931ed6b Mon Sep 17 00:00:00 2001 From: dancoombs Date: Fri, 20 Sep 2024 11:06:57 -0500 Subject: [PATCH 2/2] feat(task): update task crate to alloy --- Cargo.lock | 2 +- crates/task/Cargo.toml | 3 ++- crates/task/src/block_watcher.rs | 22 ++++++++----------- crates/task/src/grpc/protos.rs | 37 +++++++++++++++++++++----------- crates/task/src/task.rs | 3 +++ 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7b7a6183..85d9f86c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5252,9 +5252,9 @@ dependencies = [ name = "rundler-task" version = "0.3.0" dependencies = [ + "alloy-primitives", "anyhow", "async-trait", - "ethers", "futures", "metrics", "pin-project", diff --git a/crates/task/Cargo.toml b/crates/task/Cargo.toml index 3cd2bd8b7..e965bb246 100644 --- a/crates/task/Cargo.toml +++ b/crates/task/Cargo.toml @@ -11,10 +11,11 @@ publish = false rundler-provider = { path = "../provider" } rundler-utils = { path = "../utils" } +alloy-primitives.workspace = true + anyhow.workspace = true async-trait.workspace = true futures.workspace = true -ethers.workspace = true pin-project.workspace = true metrics.workspace = true tokio.workspace = true diff --git a/crates/task/src/block_watcher.rs b/crates/task/src/block_watcher.rs index 153352f36..19a925380 100644 --- a/crates/task/src/block_watcher.rs +++ b/crates/task/src/block_watcher.rs @@ -15,8 +15,8 @@ use std::time::Duration; -use ethers::types::{Block, BlockNumber, H256}; -use rundler_provider::Provider; +use alloy_primitives::B256; +use rundler_provider::{Block, BlockId, EvmProvider}; use rundler_utils::retry::{self, UnlimitedRetryOpts}; use tokio::time; use tracing::error; @@ -26,14 +26,14 @@ use tracing::error; /// This function polls the provider for the latest block until a new block is discovered, with /// unlimited retries. pub async fn wait_for_new_block( - provider: &impl Provider, - last_block_hash: H256, + provider: &impl EvmProvider, + last_block_hash: B256, poll_interval: Duration, -) -> (H256, Block) { +) -> (B256, Block) { loop { let block = retry::with_unlimited_retries( "watch latest block", - || provider.get_block(BlockNumber::Latest), + || provider.get_block(BlockId::latest()), UnlimitedRetryOpts::default(), ) .await; @@ -41,12 +41,8 @@ pub async fn wait_for_new_block( error!("Latest block should be present when waiting for new block."); continue; }; - let Some(hash) = block.hash else { - error!("Latest block should have hash."); - continue; - }; - if last_block_hash != hash { - return (hash, block); + if last_block_hash != block.header.hash { + return (block.header.hash, block); } time::sleep(poll_interval).await; } @@ -57,7 +53,7 @@ pub async fn wait_for_new_block( /// This function polls the provider for the latest block number until a new block number is discovered, /// with unlimited retries. pub async fn wait_for_new_block_number( - provider: &impl Provider, + provider: &impl EvmProvider, last_block_number: u64, poll_interval: Duration, ) -> u64 { diff --git a/crates/task/src/grpc/protos.rs b/crates/task/src/grpc/protos.rs index bb7966f2f..f3b209447 100644 --- a/crates/task/src/grpc/protos.rs +++ b/crates/task/src/grpc/protos.rs @@ -13,7 +13,7 @@ //! Protobuf utilities -use ethers::types::{Address, Bytes, H256, U128, U256}; +use alloy_primitives::{Address, Bytes, B256, U128, U256}; /// Error type for conversions from protobuf types to Ethers/local types. #[derive(Debug, thiserror::Error)] @@ -79,7 +79,7 @@ impl FromFixedLengthProtoBytes for U128 { const LEN: usize = 16; fn from_fixed_length_bytes(bytes: &[u8]) -> Self { - Self::from_little_endian(bytes) + Self::from_le_slice(bytes) } } @@ -87,11 +87,11 @@ impl FromFixedLengthProtoBytes for U256 { const LEN: usize = 32; fn from_fixed_length_bytes(bytes: &[u8]) -> Self { - Self::from_little_endian(bytes) + Self::from_le_slice(bytes) } } -impl FromFixedLengthProtoBytes for H256 { +impl FromFixedLengthProtoBytes for B256 { const LEN: usize = 32; fn from_fixed_length_bytes(bytes: &[u8]) -> Self { @@ -99,6 +99,15 @@ impl FromFixedLengthProtoBytes for H256 { } } +impl FromFixedLengthProtoBytes for u128 { + const LEN: usize = 16; + + fn from_fixed_length_bytes(bytes: &[u8]) -> Self { + let (int_bytes, _) = bytes.split_at(std::mem::size_of::()); + u128::from_le_bytes(int_bytes.try_into().unwrap()) + } +} + /// Trait for a type that can be converted to protobuf bytes. pub trait ToProtoBytes { /// Convert to protobuf bytes. @@ -107,29 +116,25 @@ pub trait ToProtoBytes { impl ToProtoBytes for Address { fn to_proto_bytes(&self) -> Vec { - self.as_bytes().to_vec() + self.to_vec() } } impl ToProtoBytes for U128 { fn to_proto_bytes(&self) -> Vec { - let mut vec = vec![0_u8; 16]; - self.to_little_endian(&mut vec); - vec + self.to_le_bytes::<16>().into() } } impl ToProtoBytes for U256 { fn to_proto_bytes(&self) -> Vec { - let mut vec = vec![0_u8; 32]; - self.to_little_endian(&mut vec); - vec + self.to_le_bytes::<32>().into() } } -impl ToProtoBytes for H256 { +impl ToProtoBytes for B256 { fn to_proto_bytes(&self) -> Vec { - self.as_bytes().to_vec() + self.to_vec() } } @@ -138,3 +143,9 @@ impl ToProtoBytes for Bytes { self.to_vec() } } + +impl ToProtoBytes for u128 { + fn to_proto_bytes(&self) -> Vec { + self.to_le_bytes().into() + } +} diff --git a/crates/task/src/task.rs b/crates/task/src/task.rs index ab6c0342b..80b984a81 100644 --- a/crates/task/src/task.rs +++ b/crates/task/src/task.rs @@ -22,6 +22,9 @@ use tracing::{error, info}; /// Core task trait implemented by top level Rundler tasks. #[async_trait] pub trait Task: Sync + Send + 'static { + /// Convert into a boxed task. + fn boxed(self) -> Box; + /// Run the task. async fn run(self: Box, shutdown_token: CancellationToken) -> anyhow::Result<()>; }